feat: Add HTTPS/Traefik support with centralized endpoints
- Create traefik/agent-ui.yml with full routing config for domain z590.nucleoriofrio.com - Add frontend/src/config/endpoints.ts for automatic HTTP/HTTPS detection - Update all hardcoded localhost URLs to use relative paths - WebSocket connections auto-detect wss:// vs ws:// based on page protocol - Configure path-based WebSocket routing (/ws/terminal, /ws/mcp, /ws/status, /ws/whisper) - Add commented IP whitelist middleware for future security
This commit is contained in:
@@ -1,4 +1,131 @@
|
||||
{
|
||||
"hooks": {
|
||||
"UserPromptSubmit": [
|
||||
{
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "powershell -NoProfile -Command \"try { Invoke-RestMethod -Uri 'http://localhost:4101/api/claude-status' -Method POST -Body '{\\\"status\\\":\\\"processing\\\"}' -ContentType 'application/json' -TimeoutSec 2 | Out-Null } catch {}\"",
|
||||
"timeout": 5000
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"PreToolUse": [
|
||||
{
|
||||
"matcher": "Read|Glob|Grep",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "powershell -NoProfile -Command \"try { Invoke-RestMethod -Uri 'http://localhost:4101/api/claude-status' -Method POST -Body '{\\\"status\\\":\\\"reading\\\",\\\"tool\\\":\\\"$CLAUDE_TOOL_NAME\\\"}' -ContentType 'application/json' -TimeoutSec 2 | Out-Null } catch {}\"",
|
||||
"timeout": 5000
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"matcher": "Edit|Write",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "powershell -NoProfile -Command \"try { Invoke-RestMethod -Uri 'http://localhost:4101/api/claude-status' -Method POST -Body '{\\\"status\\\":\\\"writing\\\",\\\"tool\\\":\\\"$CLAUDE_TOOL_NAME\\\"}' -ContentType 'application/json' -TimeoutSec 2 | Out-Null } catch {}\"",
|
||||
"timeout": 5000
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"matcher": ".*",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "powershell -NoProfile -Command \"try { Invoke-RestMethod -Uri 'http://localhost:4101/api/claude-status' -Method POST -Body '{\\\"status\\\":\\\"toolUse\\\",\\\"tool\\\":\\\"$CLAUDE_TOOL_NAME\\\"}' -ContentType 'application/json' -TimeoutSec 2 | Out-Null } catch {}\"",
|
||||
"timeout": 5000
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"PostToolUse": [
|
||||
{
|
||||
"matcher": ".*",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "powershell -NoProfile -Command \"try { Invoke-RestMethod -Uri 'http://localhost:4101/api/claude-status' -Method POST -Body '{\\\"status\\\":\\\"toolDone\\\",\\\"tool\\\":\\\"$CLAUDE_TOOL_NAME\\\"}' -ContentType 'application/json' -TimeoutSec 2 | Out-Null } catch {}\"",
|
||||
"timeout": 5000
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"SessionStart": [
|
||||
{
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "powershell -NoProfile -Command \"try { Invoke-RestMethod -Uri 'http://localhost:4101/api/claude-status' -Method POST -Body '{\\\"status\\\":\\\"sessionStart\\\"}' -ContentType 'application/json' -TimeoutSec 2 | Out-Null } catch {}\"",
|
||||
"timeout": 5000
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"SubagentStart": [
|
||||
{
|
||||
"matcher": ".*",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "powershell -NoProfile -Command \"try { Invoke-RestMethod -Uri 'http://localhost:4101/api/claude-status' -Method POST -Body '{\\\"status\\\":\\\"subagentStart\\\"}' -ContentType 'application/json' -TimeoutSec 2 | Out-Null } catch {}\"",
|
||||
"timeout": 5000
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"SubagentStop": [
|
||||
{
|
||||
"matcher": ".*",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "powershell -NoProfile -Command \"try { Invoke-RestMethod -Uri 'http://localhost:4101/api/claude-status' -Method POST -Body '{\\\"status\\\":\\\"subagentStop\\\"}' -ContentType 'application/json' -TimeoutSec 2 | Out-Null } catch {}\"",
|
||||
"timeout": 5000
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"PermissionRequest": [
|
||||
{
|
||||
"matcher": ".*",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "powershell -NoProfile -Command \"try { Invoke-RestMethod -Uri 'http://localhost:4101/api/claude-status' -Method POST -Body '{\\\"status\\\":\\\"permissionRequest\\\",\\\"tool\\\":\\\"$CLAUDE_TOOL_NAME\\\"}' -ContentType 'application/json' -TimeoutSec 2 | Out-Null } catch {}\"",
|
||||
"timeout": 5000
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"Notification": [
|
||||
{
|
||||
"matcher": ".*",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "powershell -NoProfile -Command \"try { Invoke-RestMethod -Uri 'http://localhost:4101/api/claude-status' -Method POST -Body '{\\\"status\\\":\\\"notification\\\"}' -ContentType 'application/json' -TimeoutSec 2 | Out-Null } catch {}\"",
|
||||
"timeout": 5000
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"Stop": [
|
||||
{
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "powershell -NoProfile -Command \"try { Invoke-RestMethod -Uri 'http://localhost:4101/api/claude-status' -Method POST -Body '{\\\"status\\\":\\\"idle\\\"}' -ContentType 'application/json' -TimeoutSec 2 | Out-Null } catch {}\"",
|
||||
"timeout": 5000
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"permissions": {
|
||||
"allow": [
|
||||
"Skill(playwright-cli)",
|
||||
|
||||
@@ -11,6 +11,7 @@ import FloatingResponse from './components/FloatingResponse.vue'
|
||||
import FloatingVoice from './components/FloatingVoice.vue'
|
||||
import PwaInstallBanner from './components/PwaInstallBanner.vue'
|
||||
import { initWebMCP, getWebMCP, startTokenPolling, stopTokenPolling, connectWithToken } from './services/webmcp'
|
||||
import { endpoints } from './config/endpoints'
|
||||
import { initToolRegistry, activatePageTools, initToolsOnRefresh } from './services/toolRegistry'
|
||||
import { setTerminalControls } from './services/tools/handlers/terminalHandlers'
|
||||
import { setResponseControls } from './services/tools/handlers/responseHandlers'
|
||||
@@ -48,7 +49,7 @@ let toolFlashTimeout: number | null = null
|
||||
function connectStatusWs() {
|
||||
if (statusWs?.readyState === WebSocket.OPEN) return
|
||||
|
||||
statusWs = new WebSocket(`ws://${window.location.hostname}:4103`)
|
||||
statusWs = new WebSocket(endpoints.claudeStatus)
|
||||
|
||||
statusWs.onopen = () => {
|
||||
console.log('[App] Status WebSocket connected')
|
||||
|
||||
@@ -6,6 +6,7 @@ import { WebLinksAddon } from '@xterm/addon-web-links'
|
||||
import '@xterm/xterm/css/xterm.css'
|
||||
import { connectWithToken, stopTokenPolling } from '../services/webmcp'
|
||||
import { useCanvasStore } from '../stores/canvas'
|
||||
import { endpoints } from '../config/endpoints'
|
||||
|
||||
const props = defineProps<{
|
||||
modelValue: boolean
|
||||
@@ -47,7 +48,7 @@ let tokenBuffer = ''
|
||||
let tokenTimeout: number | null = null
|
||||
const waitingForToken = ref(false)
|
||||
|
||||
const WS_URL = `ws://${window.location.hostname}:4103`
|
||||
const WS_URL = endpoints.terminal
|
||||
|
||||
// Mouse position tracking for Ctrl+E
|
||||
const mousePos = ref({ x: 0, y: 0 })
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, onMounted, onBeforeUnmount, watch } from 'vue'
|
||||
import { useCanvasStore } from '../stores/canvas'
|
||||
import { endpoints } from '../config/endpoints'
|
||||
|
||||
const props = defineProps<{
|
||||
modelValue: boolean
|
||||
@@ -39,7 +40,7 @@ const containerRef = ref<HTMLElement | null>(null)
|
||||
let recognition: SpeechRecognition | null = null
|
||||
|
||||
// WebSocket connection to terminal
|
||||
const WS_URL = `ws://${window.location.hostname}:4103`
|
||||
const WS_URL = endpoints.terminal
|
||||
let socket: WebSocket | null = null
|
||||
const connected = ref(false)
|
||||
|
||||
@@ -53,7 +54,7 @@ let pendingWhisperSend = false // Flag to send transcript when Whisper responds
|
||||
const useWhisper = ref(false)
|
||||
const whisperReady = ref(false)
|
||||
const whisperLoading = ref(false)
|
||||
const WHISPER_WS_URL = `ws://${window.location.hostname}:4104`
|
||||
const WHISPER_WS_URL = endpoints.whisper
|
||||
let whisperSocket: WebSocket | null = null
|
||||
let mediaRecorder: MediaRecorder | null = null
|
||||
let audioChunks: Blob[] = []
|
||||
@@ -112,7 +113,7 @@ async function saveRecordingToBackend(blob: Blob) {
|
||||
reader.onloadend = async () => {
|
||||
const base64 = (reader.result as string).split(',')[1]
|
||||
|
||||
const response = await fetch(`http://${window.location.hostname}:4100/api/recordings`, {
|
||||
const response = await fetch('/api/recordings', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
@@ -254,7 +255,7 @@ function initRecognition() {
|
||||
|
||||
async function checkWhisperStatus(updateLoading = true) {
|
||||
try {
|
||||
const res = await fetch(`http://${window.location.hostname}:4100/api/whisper/status`)
|
||||
const res = await fetch('/api/whisper/status')
|
||||
const data = await res.json()
|
||||
useWhisper.value = data.enabled
|
||||
whisperReady.value = data.running
|
||||
@@ -288,7 +289,7 @@ async function toggleWhisperMode() {
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await fetch(`http://${window.location.hostname}:4100/api/whisper/toggle`, {
|
||||
const res = await fetch('/api/whisper/toggle', {
|
||||
method: 'POST'
|
||||
})
|
||||
const data = await res.json()
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { ref } from 'vue'
|
||||
import type { TableInfo, TableSchema, DbStats } from '@/types/database'
|
||||
|
||||
const API_BASE = 'http://localhost:4101/api/database'
|
||||
// Uses relative URLs - works with Vite proxy in dev and Traefik in production
|
||||
const API_BASE = '/api/database'
|
||||
|
||||
export function useDatabaseApi() {
|
||||
const tables = ref<TableInfo[]>([])
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { ref } from 'vue'
|
||||
|
||||
const API_BASE = 'http://localhost:4101/api/database'
|
||||
// Uses relative URLs - works with Vite proxy in dev and Traefik in production
|
||||
const API_BASE = '/api/database'
|
||||
|
||||
export function useQueryExecutor() {
|
||||
const queryText = ref('')
|
||||
|
||||
44
frontend/src/config/endpoints.ts
Normal file
44
frontend/src/config/endpoints.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
/**
|
||||
* Centralized endpoint configuration for Agent UI
|
||||
* Automatically detects HTTPS/Traefik and uses appropriate protocols
|
||||
*/
|
||||
|
||||
// Detect if running over HTTPS (behind Traefik)
|
||||
export const isSecure = window.location.protocol === 'https:'
|
||||
|
||||
// WebSocket protocol based on HTTP protocol
|
||||
export const wsProtocol = isSecure ? 'wss:' : 'ws:'
|
||||
|
||||
// Base hostname
|
||||
export const hostname = window.location.hostname
|
||||
|
||||
// Build WebSocket URL based on environment
|
||||
function buildWsUrl(securePath: string, devPort: number): string {
|
||||
if (isSecure) {
|
||||
// Behind Traefik - use path-based routing
|
||||
return `${wsProtocol}//${hostname}${securePath}`
|
||||
}
|
||||
// Development - direct port access
|
||||
return `${wsProtocol}//${hostname}:${devPort}`
|
||||
}
|
||||
|
||||
// Endpoint configuration
|
||||
export const endpoints = {
|
||||
// Terminal WebSocket
|
||||
terminal: buildWsUrl('/ws/terminal', 4103),
|
||||
|
||||
// Claude status WebSocket (same backend as terminal)
|
||||
claudeStatus: buildWsUrl('/ws/status', 4103),
|
||||
|
||||
// Whisper WebSocket - voice transcription
|
||||
whisper: buildWsUrl('/ws/whisper', 4104),
|
||||
|
||||
// WebMCP WebSocket
|
||||
webmcp: buildWsUrl('/ws/mcp', 4102),
|
||||
|
||||
// API base URL (Vite proxy handles /api in dev)
|
||||
api: '/api'
|
||||
}
|
||||
|
||||
// Debug logging
|
||||
console.log('[Endpoints]', isSecure ? 'HTTPS/Traefik mode' : 'Development mode', endpoints)
|
||||
@@ -82,7 +82,7 @@ async function connect() {
|
||||
|
||||
try {
|
||||
// Test connection and get repo info
|
||||
const res = await fetch('http://localhost:4101/api/gitea/repo', {
|
||||
const res = await fetch('/api/gitea/repo', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
@@ -119,7 +119,7 @@ async function connect() {
|
||||
|
||||
async function loadFileTree(path: string = '') {
|
||||
try {
|
||||
const res = await fetch('http://localhost:4101/api/gitea/tree', {
|
||||
const res = await fetch('/api/gitea/tree', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
@@ -184,7 +184,7 @@ async function selectFile(node: FileNode) {
|
||||
fileContent.value = ''
|
||||
|
||||
try {
|
||||
const res = await fetch('http://localhost:4101/api/gitea/file', {
|
||||
const res = await fetch('/api/gitea/file', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
|
||||
@@ -4,6 +4,7 @@ import { Terminal } from '@xterm/xterm'
|
||||
import { FitAddon } from '@xterm/addon-fit'
|
||||
import { WebLinksAddon } from '@xterm/addon-web-links'
|
||||
import '@xterm/xterm/css/xterm.css'
|
||||
import { endpoints } from '../config/endpoints'
|
||||
|
||||
const terminalContainer = ref<HTMLElement | null>(null)
|
||||
const connected = ref(false)
|
||||
@@ -17,7 +18,7 @@ let fitAddon: FitAddon | null = null
|
||||
let socket: WebSocket | null = null
|
||||
let resizeObserver: ResizeObserver | null = null
|
||||
|
||||
const WS_URL = `ws://${window.location.hostname}:4103`
|
||||
const WS_URL = endpoints.terminal
|
||||
|
||||
function initTerminal() {
|
||||
if (!terminalContainer.value) return
|
||||
|
||||
@@ -20,7 +20,8 @@ import { setActivePinia, type Pinia } from 'pinia'
|
||||
import { useCanvasStore } from '../stores/canvas'
|
||||
import { useThemeStore } from '../stores/theme'
|
||||
|
||||
const API_URL = 'http://localhost:4101'
|
||||
// Uses relative URLs - works with Vite proxy in dev and Traefik in production
|
||||
const API_URL = ''
|
||||
|
||||
// Tipos
|
||||
export interface VueComponentDefinition {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { ThemeVariables, Theme, DesignTokens } from '../stores/theme'
|
||||
|
||||
const API_URL = 'http://localhost:4101'
|
||||
// Uses relative URLs - works with Vite proxy in dev and Traefik in production
|
||||
const API_URL = ''
|
||||
|
||||
// =====================
|
||||
// API Client
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { ToolConfig } from './index'
|
||||
|
||||
const API_BASE = 'http://localhost:4101'
|
||||
// Uses relative URLs - works with Vite proxy in dev and Traefik in production
|
||||
const API_BASE = ''
|
||||
|
||||
export function createDatabaseHandlers(): ToolConfig[] {
|
||||
return [
|
||||
|
||||
@@ -234,7 +234,7 @@ export function createGlobalHandlers(callbacks: ToolManagementCallbacks): ToolCo
|
||||
},
|
||||
handler: async () => {
|
||||
try {
|
||||
const res = await fetch(`http://${window.location.hostname}:4100/api/whisper/status`)
|
||||
const res = await fetch('/api/whisper/status')
|
||||
const data = await res.json()
|
||||
return `Whisper GPU Status:\n` +
|
||||
` Enabled: ${data.enabled ? 'Yes' : 'No'}\n` +
|
||||
@@ -257,7 +257,7 @@ export function createGlobalHandlers(callbacks: ToolManagementCallbacks): ToolCo
|
||||
},
|
||||
handler: async () => {
|
||||
try {
|
||||
const res = await fetch(`http://${window.location.hostname}:4100/api/whisper/toggle`, {
|
||||
const res = await fetch('/api/whisper/toggle', {
|
||||
method: 'POST'
|
||||
})
|
||||
const data = await res.json()
|
||||
@@ -266,7 +266,7 @@ export function createGlobalHandlers(callbacks: ToolManagementCallbacks): ToolCo
|
||||
return `Whisper GPU ENABLED\n` +
|
||||
` Model: ${data.model}\n` +
|
||||
` Device: ${data.device}\n` +
|
||||
` Port: ws://localhost:${data.port}\n\n` +
|
||||
` Port: ${data.port}\n\n` +
|
||||
`Voice input will now use GPU-accelerated transcription.`
|
||||
} else {
|
||||
return `Whisper GPU DISABLED\n\n` +
|
||||
@@ -287,7 +287,7 @@ export function createGlobalHandlers(callbacks: ToolManagementCallbacks): ToolCo
|
||||
},
|
||||
handler: async () => {
|
||||
try {
|
||||
const res = await fetch(`http://${window.location.hostname}:4100/api/whisper/start`, {
|
||||
const res = await fetch('/api/whisper/start', {
|
||||
method: 'POST'
|
||||
})
|
||||
const data = await res.json()
|
||||
@@ -315,7 +315,7 @@ export function createGlobalHandlers(callbacks: ToolManagementCallbacks): ToolCo
|
||||
},
|
||||
handler: async () => {
|
||||
try {
|
||||
const res = await fetch(`http://${window.location.hostname}:4100/api/whisper/stop`, {
|
||||
const res = await fetch('/api/whisper/stop', {
|
||||
method: 'POST'
|
||||
})
|
||||
const data = await res.json()
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { ToolConfig } from './index'
|
||||
|
||||
const API_BASE = 'http://localhost:4101'
|
||||
// Uses relative URLs - works with Vite proxy in dev and Traefik in production
|
||||
const API_BASE = ''
|
||||
|
||||
// Store credentials in memory
|
||||
let giteaCredentials: {
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { useCanvasStore } from '../stores/canvas'
|
||||
import { endpoints, isSecure, wsProtocol, hostname } from '../config/endpoints'
|
||||
|
||||
let webmcpInstance: any = null
|
||||
const registeredTools = new Set<string>()
|
||||
const eventUnsubscribers: Array<() => void> = []
|
||||
|
||||
const API_BASE = 'http://localhost:4101'
|
||||
const API_BASE = endpoints.api
|
||||
let tokenPollingInterval: number | null = null
|
||||
|
||||
export async function initWebMCP() {
|
||||
@@ -286,9 +287,22 @@ export async function connectWithToken(token: string): Promise<boolean> {
|
||||
// Clear the pending token from server
|
||||
await clearToken()
|
||||
|
||||
// If behind HTTPS/Traefik, modify token to use secure WebSocket
|
||||
let finalToken = token
|
||||
if (isSecure) {
|
||||
const parsed = parseToken(token)
|
||||
if (parsed) {
|
||||
// Replace ws://localhost:4102 with wss://hostname/ws/mcp
|
||||
const secureServer = `${wsProtocol}//${hostname}/ws/mcp`
|
||||
const modifiedToken = { server: secureServer, token: parsed.token }
|
||||
finalToken = btoa(JSON.stringify(modifiedToken))
|
||||
console.log('[WebMCP] Modified token for HTTPS:', secureServer)
|
||||
}
|
||||
}
|
||||
|
||||
// Connect passing the token directly
|
||||
try {
|
||||
await webmcpInstance.connect(token)
|
||||
await webmcpInstance.connect(finalToken)
|
||||
return true
|
||||
} catch (e) {
|
||||
console.error('[WebMCP] Failed to connect:', e)
|
||||
|
||||
@@ -2,7 +2,8 @@ import { defineStore } from 'pinia'
|
||||
import { ref, computed } from 'vue'
|
||||
import type { ProjectCanvas, CanvasComponent, ComponentUsage } from '../types/canvas'
|
||||
|
||||
const API_URL = 'http://localhost:4101'
|
||||
// Uses relative URLs - works with Vite proxy in dev and Traefik in production
|
||||
const API_URL = ''
|
||||
|
||||
export const useProjectCanvasStore = defineStore('projectCanvas', () => {
|
||||
// State
|
||||
|
||||
@@ -44,7 +44,8 @@ export interface DesignTokens {
|
||||
examples: Record<string, string>
|
||||
}
|
||||
|
||||
const API_URL = 'http://localhost:4101'
|
||||
// Uses relative URLs - works with Vite proxy in dev and Traefik in production
|
||||
const API_URL = ''
|
||||
|
||||
// =====================
|
||||
// Store
|
||||
|
||||
248
traefik/agent-ui.yml
Normal file
248
traefik/agent-ui.yml
Normal file
@@ -0,0 +1,248 @@
|
||||
## ============================================
|
||||
## Agent UI - Traefik Dynamic Configuration
|
||||
## ============================================
|
||||
##
|
||||
## INSTALACIÓN:
|
||||
## 1. Copia este archivo a tu directorio de config dinámica de Traefik
|
||||
## Ejemplo: /etc/traefik/dynamic/agent-ui.yml
|
||||
##
|
||||
## 2. Asegúrate de tener el file provider habilitado en traefik.yml:
|
||||
## providers:
|
||||
## file:
|
||||
## directory: /etc/traefik/dynamic
|
||||
## watch: true
|
||||
##
|
||||
## 3. Asegúrate de tener el certResolver configurado (letsencrypt)
|
||||
##
|
||||
## ARQUITECTURA:
|
||||
## ┌─────────────────────────────────────────────────────┐
|
||||
## │ https://z590.nucleoriofrio.com │
|
||||
## │ / → Frontend (4100) │
|
||||
## │ /api/* → API Server (4101) │
|
||||
## │ /ws/terminal→ Terminal WebSocket (4103) │
|
||||
## │ /ws/status → Claude Status WebSocket (4103) │
|
||||
## │ /ws/mcp → WebMCP WebSocket (4102) │
|
||||
## │ /ws/whisper → Whisper WebSocket (4104) │
|
||||
## └─────────────────────────────────────────────────────┘
|
||||
##
|
||||
|
||||
http:
|
||||
## ============================================
|
||||
## MIDDLEWARES
|
||||
## ============================================
|
||||
middlewares:
|
||||
## ----------------------------------------
|
||||
## IP Whitelist (DESCOMENTEAR PARA ACTIVAR)
|
||||
## ----------------------------------------
|
||||
## Para activar: descomenta este bloque y agrega
|
||||
## "agentui-ipwhitelist" a la lista de middlewares
|
||||
## de cada router.
|
||||
##
|
||||
# agentui-ipwhitelist:
|
||||
# ipWhiteList:
|
||||
# sourceRange:
|
||||
# - "192.168.87.0/24" # Red local del servidor
|
||||
# - "192.168.1.0/24" # Otra red local
|
||||
# - "10.0.0.0/8" # Redes internas
|
||||
# - "YOUR_PUBLIC_IP/32" # Tu IP pública específica
|
||||
# # Si hay proxies intermedios, usa ipStrategy:
|
||||
# # ipStrategy:
|
||||
# # depth: 1
|
||||
|
||||
## ----------------------------------------
|
||||
## Strip Prefix para WebSockets
|
||||
## ----------------------------------------
|
||||
## Traefik recibe /ws/terminal pero el backend espera /
|
||||
strip-ws-terminal:
|
||||
stripPrefix:
|
||||
prefixes:
|
||||
- "/ws/terminal"
|
||||
|
||||
strip-ws-status:
|
||||
stripPrefix:
|
||||
prefixes:
|
||||
- "/ws/status"
|
||||
|
||||
strip-ws-mcp:
|
||||
stripPrefix:
|
||||
prefixes:
|
||||
- "/ws/mcp"
|
||||
|
||||
strip-ws-whisper:
|
||||
stripPrefix:
|
||||
prefixes:
|
||||
- "/ws/whisper"
|
||||
|
||||
## ----------------------------------------
|
||||
## Headers de seguridad
|
||||
## ----------------------------------------
|
||||
agentui-headers:
|
||||
headers:
|
||||
customRequestHeaders:
|
||||
X-Forwarded-Proto: "https"
|
||||
# Headers de seguridad opcionales:
|
||||
# stsSeconds: 31536000
|
||||
# stsIncludeSubdomains: true
|
||||
# contentTypeNosniff: true
|
||||
# browserXssFilter: true
|
||||
|
||||
## ============================================
|
||||
## ROUTERS
|
||||
## ============================================
|
||||
routers:
|
||||
## ----------------------------------------
|
||||
## Frontend (/) - Vue App
|
||||
## ----------------------------------------
|
||||
## Sirve la aplicación Vue, assets, PWA manifest, etc.
|
||||
## Excluye /api y /ws para que los otros routers los manejen.
|
||||
##
|
||||
agentui-frontend:
|
||||
rule: "Host(`z590.nucleoriofrio.com`) && !PathPrefix(`/api`) && !PathPrefix(`/ws`)"
|
||||
entryPoints:
|
||||
- websecure
|
||||
service: agentui-frontend
|
||||
middlewares:
|
||||
- agentui-headers
|
||||
# - agentui-ipwhitelist # Descomentar para activar
|
||||
tls:
|
||||
certResolver: letsencrypt
|
||||
priority: 1
|
||||
|
||||
## ----------------------------------------
|
||||
## API Backend (/api/*) - REST API
|
||||
## ----------------------------------------
|
||||
## Maneja todas las llamadas REST:
|
||||
## - /api/health - Health check
|
||||
## - /api/themes/* - Sistema de temas
|
||||
## - /api/components/* - Componentes Vue guardados
|
||||
## - /api/database/* - Explorador de base de datos
|
||||
## - /api/claude-status - Status de Claude (desde hooks)
|
||||
## - /api/whisper/* - Control de Whisper
|
||||
## - etc.
|
||||
##
|
||||
agentui-api:
|
||||
rule: "Host(`z590.nucleoriofrio.com`) && PathPrefix(`/api`)"
|
||||
entryPoints:
|
||||
- websecure
|
||||
service: agentui-api
|
||||
middlewares:
|
||||
- agentui-headers
|
||||
# - agentui-ipwhitelist
|
||||
tls:
|
||||
certResolver: letsencrypt
|
||||
priority: 10
|
||||
|
||||
## ----------------------------------------
|
||||
## Terminal WebSocket (/ws/terminal)
|
||||
## ----------------------------------------
|
||||
## Conexión WebSocket para xterm.js
|
||||
## Permite ejecutar comandos en el servidor.
|
||||
##
|
||||
agentui-ws-terminal:
|
||||
rule: "Host(`z590.nucleoriofrio.com`) && PathPrefix(`/ws/terminal`)"
|
||||
entryPoints:
|
||||
- websecure
|
||||
service: agentui-terminal
|
||||
middlewares:
|
||||
- strip-ws-terminal
|
||||
# - agentui-ipwhitelist
|
||||
tls:
|
||||
certResolver: letsencrypt
|
||||
priority: 20
|
||||
|
||||
## ----------------------------------------
|
||||
## Claude Status WebSocket (/ws/status)
|
||||
## ----------------------------------------
|
||||
## Recibe actualizaciones de estado de Claude/Nucleo
|
||||
## para las animaciones del FAB.
|
||||
## Usa el mismo backend que terminal (4103).
|
||||
##
|
||||
agentui-ws-status:
|
||||
rule: "Host(`z590.nucleoriofrio.com`) && PathPrefix(`/ws/status`)"
|
||||
entryPoints:
|
||||
- websecure
|
||||
service: agentui-terminal
|
||||
middlewares:
|
||||
- strip-ws-status
|
||||
# - agentui-ipwhitelist
|
||||
tls:
|
||||
certResolver: letsencrypt
|
||||
priority: 20
|
||||
|
||||
## ----------------------------------------
|
||||
## WebMCP WebSocket (/ws/mcp)
|
||||
## ----------------------------------------
|
||||
## Bridge entre Claude Code MCP y el browser.
|
||||
## Permite que Claude ejecute herramientas en la UI:
|
||||
## - render_vue_component
|
||||
## - navigate_to
|
||||
## - set_theme_variable
|
||||
## - etc.
|
||||
##
|
||||
agentui-ws-mcp:
|
||||
rule: "Host(`z590.nucleoriofrio.com`) && PathPrefix(`/ws/mcp`)"
|
||||
entryPoints:
|
||||
- websecure
|
||||
service: agentui-mcp
|
||||
middlewares:
|
||||
- strip-ws-mcp
|
||||
# - agentui-ipwhitelist
|
||||
tls:
|
||||
certResolver: letsencrypt
|
||||
priority: 20
|
||||
|
||||
## ----------------------------------------
|
||||
## Whisper WebSocket (/ws/whisper)
|
||||
## ----------------------------------------
|
||||
## Transcripción de voz con Whisper.
|
||||
## Opcional - solo si usas la función de voz.
|
||||
##
|
||||
agentui-ws-whisper:
|
||||
rule: "Host(`z590.nucleoriofrio.com`) && PathPrefix(`/ws/whisper`)"
|
||||
entryPoints:
|
||||
- websecure
|
||||
service: agentui-whisper
|
||||
middlewares:
|
||||
- strip-ws-whisper
|
||||
# - agentui-ipwhitelist
|
||||
tls:
|
||||
certResolver: letsencrypt
|
||||
priority: 20
|
||||
|
||||
## ============================================
|
||||
## SERVICES (Backends)
|
||||
## ============================================
|
||||
## Cambia 192.168.87.135 por la IP de tu servidor
|
||||
## si es diferente.
|
||||
##
|
||||
services:
|
||||
## Frontend - Vite dev server o build estático
|
||||
agentui-frontend:
|
||||
loadBalancer:
|
||||
servers:
|
||||
- url: "http://192.168.87.135:4100"
|
||||
|
||||
## API Server - Bun HTTP
|
||||
agentui-api:
|
||||
loadBalancer:
|
||||
servers:
|
||||
- url: "http://192.168.87.135:4101"
|
||||
|
||||
## Terminal WebSocket Server
|
||||
## También maneja Claude status broadcast
|
||||
agentui-terminal:
|
||||
loadBalancer:
|
||||
servers:
|
||||
- url: "http://192.168.87.135:4103"
|
||||
|
||||
## WebMCP WebSocket Server
|
||||
agentui-mcp:
|
||||
loadBalancer:
|
||||
servers:
|
||||
- url: "http://192.168.87.135:4102"
|
||||
|
||||
## Whisper WebSocket Server (opcional)
|
||||
agentui-whisper:
|
||||
loadBalancer:
|
||||
servers:
|
||||
- url: "http://192.168.87.135:4104"
|
||||
Reference in New Issue
Block a user