orquesta-agent 0.2.57 → 0.2.58
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/executor.d.ts.map +1 -1
- package/dist/executor.js +1 -3
- package/dist/executor.js.map +1 -1
- package/dist/ui/public/app.js +282 -265
- package/dist/ui/public/index.html +61 -96
- package/dist/ui/public/style.css +287 -421
- package/dist/ui/server.d.ts.map +1 -1
- package/dist/ui/server.js +0 -39
- package/dist/ui/server.js.map +1 -1
- package/package.json +4 -1
- package/scripts/postinstall.js +0 -3
package/dist/ui/public/app.js
CHANGED
|
@@ -1,322 +1,248 @@
|
|
|
1
|
-
// Orquesta Agent Manager
|
|
1
|
+
// Orquesta Agent Manager Frontend
|
|
2
2
|
|
|
3
|
-
const
|
|
3
|
+
const API_BASE = '/api'
|
|
4
4
|
let agents = []
|
|
5
5
|
let systemInfo = null
|
|
6
6
|
let editingAgentId = null
|
|
7
|
-
let
|
|
8
|
-
let
|
|
9
|
-
|
|
10
|
-
// ── Icons (inline SVG) ─────────────────────────────────────────────
|
|
11
|
-
|
|
12
|
-
const ICONS = {
|
|
13
|
-
play: '<svg viewBox="0 0 24 24" fill="currentColor"><polygon points="5 3 19 12 5 21"/></svg>',
|
|
14
|
-
stop: '<svg viewBox="0 0 24 24" fill="currentColor"><rect x="6" y="6" width="12" height="12" rx="1"/></svg>',
|
|
15
|
-
restart: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 12a9 9 0 0 0-9-9 9.75 9.75 0 0 0-6.74 2.74L3 8"/><path d="M3 3v5h5"/><path d="M3 12a9 9 0 0 0 9 9 9.75 9.75 0 0 0 6.74-2.74L21 16"/><path d="M16 16h5v5"/></svg>',
|
|
16
|
-
logs: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="16" y1="13" x2="8" y2="13"/><line x1="16" y1="17" x2="8" y2="17"/></svg>',
|
|
17
|
-
edit: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/><path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"/></svg>',
|
|
18
|
-
trash: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="3 6 5 6 21 6"/><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/></svg>',
|
|
19
|
-
link: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/><polyline points="15 3 21 3 21 9"/><line x1="10" y1="14" x2="21" y2="3"/></svg>',
|
|
20
|
-
chevDown: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="6 9 12 15 18 9"/></svg>',
|
|
21
|
-
chevUp: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="18 15 12 9 6 15"/></svg>',
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
// ── System Health ──────────────────────────────────────────────────
|
|
25
|
-
|
|
26
|
-
async function loadHealth() {
|
|
27
|
-
try {
|
|
28
|
-
const res = await fetch(`${API}/system/health`)
|
|
29
|
-
if (!res.ok) return
|
|
30
|
-
const h = await res.json()
|
|
31
|
-
|
|
32
|
-
// CPU
|
|
33
|
-
const cpuPct = h.cpu.usage
|
|
34
|
-
document.getElementById('cpu-value').innerHTML = `${cpuPct}<small>%</small>`
|
|
35
|
-
setBar('cpu-bar', cpuPct)
|
|
36
|
-
document.getElementById('cpu-detail').textContent = `${h.cpu.count} cores`
|
|
37
|
-
|
|
38
|
-
// Memory
|
|
39
|
-
const memPct = Math.round((h.memory.used / h.memory.total) * 100)
|
|
40
|
-
document.getElementById('mem-value').innerHTML = `${memPct}<small>%</small>`
|
|
41
|
-
setBar('mem-bar', memPct)
|
|
42
|
-
document.getElementById('mem-detail').textContent = `${fmtBytes(h.memory.used)} / ${fmtBytes(h.memory.total)}`
|
|
43
|
-
|
|
44
|
-
// Disk
|
|
45
|
-
if (h.disk.total > 0) {
|
|
46
|
-
const diskPct = Math.round((h.disk.used / h.disk.total) * 100)
|
|
47
|
-
document.getElementById('disk-value').innerHTML = `${diskPct}<small>%</small>`
|
|
48
|
-
setBar('disk-bar', diskPct)
|
|
49
|
-
document.getElementById('disk-detail').textContent = `${fmtBytes(h.disk.free)} free of ${fmtBytes(h.disk.total)}`
|
|
50
|
-
} else {
|
|
51
|
-
document.getElementById('disk-value').innerHTML = `N/A`
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
// Uptime
|
|
55
|
-
document.getElementById('uptime-value').textContent = fmtUptime(h.uptime * 1000)
|
|
56
|
-
document.getElementById('hostname-detail').textContent = h.hostname
|
|
57
|
-
} catch { /* ignore */ }
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
function setBar(id, pct) {
|
|
61
|
-
const el = document.getElementById(id)
|
|
62
|
-
el.style.width = `${Math.min(pct, 100)}%`
|
|
63
|
-
el.className = 'health-bar-fill ' + (pct > 90 ? 'red' : pct > 70 ? 'yellow' : 'green')
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
// ── System Info ────────────────────────────────────────────────────
|
|
7
|
+
let logsAgentId = null
|
|
8
|
+
let logsInterval = null
|
|
67
9
|
|
|
10
|
+
// Load system info
|
|
68
11
|
async function loadSystemInfo() {
|
|
69
12
|
try {
|
|
70
|
-
const res = await fetch(`${
|
|
13
|
+
const res = await fetch(`${API_BASE}/system`)
|
|
71
14
|
systemInfo = await res.json()
|
|
72
15
|
|
|
16
|
+
// Update UI
|
|
73
17
|
document.getElementById('version').textContent = `v${systemInfo.version}`
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
const
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
claudeDot.className = 'dot warn'
|
|
85
|
-
claudeLabel.textContent = 'Installed but not authenticated'
|
|
18
|
+
document.getElementById('platform').textContent = systemInfo.platform
|
|
19
|
+
|
|
20
|
+
// Claude CLI status
|
|
21
|
+
const claudeStatus = document.getElementById('claude-status')
|
|
22
|
+
if (systemInfo.hasClaudeCli) {
|
|
23
|
+
if (systemInfo.claudeAuth?.authenticated) {
|
|
24
|
+
claudeStatus.innerHTML = `<span class="badge success">✓ ${systemInfo.claudeAuth.method}</span>`
|
|
25
|
+
} else {
|
|
26
|
+
claudeStatus.innerHTML = `<span class="badge warning">⚠ Not authenticated</span>`
|
|
27
|
+
}
|
|
86
28
|
} else {
|
|
87
|
-
|
|
88
|
-
claudeLabel.textContent = 'Not installed'
|
|
29
|
+
claudeStatus.innerHTML = `<span class="badge danger">✗ Not installed</span>`
|
|
89
30
|
}
|
|
90
31
|
|
|
91
|
-
// Orquesta CLI
|
|
92
|
-
const
|
|
93
|
-
const orqDot = document.getElementById('orquesta-dot')
|
|
94
|
-
const orqLabel = document.getElementById('orquesta-label')
|
|
32
|
+
// Orquesta CLI status
|
|
33
|
+
const orquestaStatus = document.getElementById('orquesta-status')
|
|
95
34
|
if (systemInfo.hasOrquestaCli) {
|
|
96
|
-
|
|
97
|
-
orqLabel.textContent = 'Ready (Claude, GPT-4o, DeepSeek, Gemini...)'
|
|
98
|
-
orqCard.classList.add('active')
|
|
35
|
+
orquestaStatus.innerHTML = `<span class="badge success">✓ Installed</span>`
|
|
99
36
|
} else {
|
|
100
|
-
|
|
101
|
-
orqLabel.textContent = 'Not installed — npm i -g orquesta-cli'
|
|
37
|
+
orquestaStatus.innerHTML = `<span class="badge danger">✗ Not installed</span>`
|
|
102
38
|
}
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
document.getElementById('node-label').textContent = systemInfo.nodeVersion
|
|
107
|
-
} catch { /* ignore */ }
|
|
39
|
+
} catch (err) {
|
|
40
|
+
console.error('Failed to load system info:', err)
|
|
41
|
+
}
|
|
108
42
|
}
|
|
109
43
|
|
|
110
|
-
//
|
|
111
|
-
|
|
44
|
+
// Load agents
|
|
112
45
|
async function loadAgents() {
|
|
113
46
|
try {
|
|
114
|
-
const res = await fetch(`${
|
|
47
|
+
const res = await fetch(`${API_BASE}/agents`)
|
|
115
48
|
agents = await res.json()
|
|
116
49
|
renderAgents()
|
|
117
|
-
} catch {
|
|
50
|
+
} catch (err) {
|
|
51
|
+
console.error('Failed to load agents:', err)
|
|
52
|
+
}
|
|
118
53
|
}
|
|
119
54
|
|
|
55
|
+
// Render agents list
|
|
120
56
|
function renderAgents() {
|
|
121
57
|
const container = document.getElementById('agents-container')
|
|
122
58
|
const emptyState = document.getElementById('empty-state')
|
|
123
|
-
const countEl = document.getElementById('agent-count')
|
|
124
59
|
|
|
125
60
|
if (agents.length === 0) {
|
|
126
|
-
emptyState.style.display = ''
|
|
61
|
+
emptyState.style.display = 'block'
|
|
127
62
|
container.innerHTML = ''
|
|
128
63
|
container.appendChild(emptyState)
|
|
129
|
-
countEl.textContent = ''
|
|
130
64
|
return
|
|
131
65
|
}
|
|
132
66
|
|
|
133
67
|
emptyState.style.display = 'none'
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
<div class="agent-card ${a.status}" id="card-${a.id}">
|
|
141
|
-
<div class="agent-card-header">
|
|
142
|
-
<div class="agent-card-left">
|
|
143
|
-
<span class="agent-status-dot ${a.status}"></span>
|
|
144
|
-
<div>
|
|
145
|
-
<div class="agent-name">${esc(a.name)}</div>
|
|
146
|
-
<a class="agent-project-link" href="https://orquesta.live/dashboard/projects" target="_blank" rel="noopener">
|
|
147
|
-
${ICONS.link} Open in Dashboard
|
|
148
|
-
</a>
|
|
149
|
-
</div>
|
|
150
|
-
</div>
|
|
151
|
-
<div class="agent-card-right">
|
|
152
|
-
${a.status === 'running' ? `
|
|
153
|
-
<button class="btn btn-sm" onclick="stopAgent('${a.id}')" title="Stop">${ICONS.stop} <span class="btn-label">Stop</span></button>
|
|
154
|
-
<button class="btn btn-sm" onclick="restartAgent('${a.id}')" title="Restart">${ICONS.restart} <span class="btn-label">Restart</span></button>
|
|
155
|
-
` : `
|
|
156
|
-
<button class="btn btn-sm btn-success" onclick="startAgent('${a.id}')" title="Start">${ICONS.play} <span class="btn-label">Start</span></button>
|
|
157
|
-
`}
|
|
158
|
-
<button class="btn btn-sm" onclick="toggleLogs('${a.id}')" title="Logs">${isLogsOpen ? ICONS.chevUp : ICONS.logs}</button>
|
|
159
|
-
<button class="btn btn-sm" onclick="editAgent('${a.id}')" title="Edit">${ICONS.edit}</button>
|
|
160
|
-
<button class="btn btn-sm btn-danger" onclick="deleteAgent('${a.id}', '${esc(a.name)}')" title="Delete">${ICONS.trash}</button>
|
|
68
|
+
container.innerHTML = agents.map(agent => `
|
|
69
|
+
<div class="agent-card">
|
|
70
|
+
<div class="agent-header">
|
|
71
|
+
<div class="agent-title">
|
|
72
|
+
<span class="status-dot ${agent.status}"></span>
|
|
73
|
+
<h3>${escapeHtml(agent.name)}</h3>
|
|
161
74
|
</div>
|
|
75
|
+
<span class="badge ${agent.status === 'running' ? 'success' : agent.status === 'error' ? 'danger' : ''}">${agent.status.toUpperCase()}</span>
|
|
162
76
|
</div>
|
|
163
77
|
|
|
164
|
-
<div class="agent-
|
|
165
|
-
<div class="
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
<span
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
78
|
+
<div class="agent-info">
|
|
79
|
+
<div class="info-row">
|
|
80
|
+
<span class="label">Working Dir:</span>
|
|
81
|
+
<span class="value">${escapeHtml(agent.workingDir)}</span>
|
|
82
|
+
</div>
|
|
83
|
+
<div class="info-row">
|
|
84
|
+
<span class="label">CLI:</span>
|
|
85
|
+
<span class="value">${agent.cliPreference}</span>
|
|
86
|
+
</div>
|
|
87
|
+
<div class="info-row">
|
|
88
|
+
<span class="label">Permission:</span>
|
|
89
|
+
<span class="value">${agent.permissionMode}</span>
|
|
90
|
+
</div>
|
|
91
|
+
${agent.pid ? `
|
|
92
|
+
<div class="info-row">
|
|
93
|
+
<span class="label">PID:</span>
|
|
94
|
+
<span class="value">${agent.pid}</span>
|
|
95
|
+
</div>
|
|
96
|
+
` : ''}
|
|
97
|
+
${agent.uptime ? `
|
|
98
|
+
<div class="info-row">
|
|
99
|
+
<span class="label">Uptime:</span>
|
|
100
|
+
<span class="value">${formatUptime(agent.uptime)}</span>
|
|
101
|
+
</div>
|
|
102
|
+
` : ''}
|
|
103
|
+
${agent.error ? `
|
|
104
|
+
<div class="info-row">
|
|
105
|
+
<span class="label">Error:</span>
|
|
106
|
+
<span class="value" style="color: #c62828;">${escapeHtml(agent.error)}</span>
|
|
180
107
|
</div>
|
|
181
|
-
|
|
108
|
+
` : ''}
|
|
182
109
|
</div>
|
|
183
|
-
</div>`
|
|
184
|
-
}).join('')
|
|
185
110
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
111
|
+
<div class="agent-actions">
|
|
112
|
+
${agent.status === 'running' ? `
|
|
113
|
+
<button class="btn btn-sm btn-secondary" onclick="stopAgent('${agent.id}')">
|
|
114
|
+
<span class="icon">⏹</span> Stop
|
|
115
|
+
</button>
|
|
116
|
+
<button class="btn btn-sm btn-secondary" onclick="restartAgent('${agent.id}')">
|
|
117
|
+
<span class="icon">🔄</span> Restart
|
|
118
|
+
</button>
|
|
119
|
+
` : `
|
|
120
|
+
<button class="btn btn-sm btn-success" onclick="startAgent('${agent.id}')">
|
|
121
|
+
<span class="icon">▶️</span> Start
|
|
122
|
+
</button>
|
|
123
|
+
`}
|
|
124
|
+
<button class="btn btn-sm btn-secondary" onclick="viewLogs('${agent.id}', '${escapeHtml(agent.name)}')">
|
|
125
|
+
<span class="icon">📄</span> Logs
|
|
126
|
+
</button>
|
|
127
|
+
<button class="btn btn-sm btn-secondary" onclick="editAgent('${agent.id}')">
|
|
128
|
+
<span class="icon">✏️</span> Edit
|
|
129
|
+
</button>
|
|
130
|
+
<button class="btn btn-sm btn-danger" onclick="deleteAgent('${agent.id}', '${escapeHtml(agent.name)}')">
|
|
131
|
+
<span class="icon">🗑️</span> Delete
|
|
132
|
+
</button>
|
|
133
|
+
</div>
|
|
134
|
+
</div>
|
|
135
|
+
`).join('')
|
|
190
136
|
}
|
|
191
137
|
|
|
192
|
-
//
|
|
193
|
-
|
|
138
|
+
// Start agent
|
|
194
139
|
async function startAgent(id) {
|
|
195
140
|
try {
|
|
196
|
-
const res = await fetch(`${
|
|
197
|
-
|
|
141
|
+
const res = await fetch(`${API_BASE}/agents/${id}/start`, { method: 'POST' })
|
|
142
|
+
const data = await res.json()
|
|
143
|
+
|
|
144
|
+
if (!res.ok) {
|
|
145
|
+
alert(`Failed to start agent: ${data.error}`)
|
|
146
|
+
return
|
|
147
|
+
}
|
|
148
|
+
|
|
198
149
|
setTimeout(loadAgents, 1000)
|
|
199
|
-
} catch (
|
|
150
|
+
} catch (err) {
|
|
151
|
+
alert(`Failed to start agent: ${err.message}`)
|
|
152
|
+
}
|
|
200
153
|
}
|
|
201
154
|
|
|
155
|
+
// Stop agent
|
|
202
156
|
async function stopAgent(id) {
|
|
203
157
|
try {
|
|
204
|
-
const res = await fetch(`${
|
|
205
|
-
|
|
158
|
+
const res = await fetch(`${API_BASE}/agents/${id}/stop`, { method: 'POST' })
|
|
159
|
+
const data = await res.json()
|
|
160
|
+
|
|
161
|
+
if (!res.ok) {
|
|
162
|
+
alert(`Failed to stop agent: ${data.error}`)
|
|
163
|
+
return
|
|
164
|
+
}
|
|
165
|
+
|
|
206
166
|
setTimeout(loadAgents, 1000)
|
|
207
|
-
} catch (
|
|
167
|
+
} catch (err) {
|
|
168
|
+
alert(`Failed to stop agent: ${err.message}`)
|
|
169
|
+
}
|
|
208
170
|
}
|
|
209
171
|
|
|
172
|
+
// Restart agent
|
|
210
173
|
async function restartAgent(id) {
|
|
211
174
|
try {
|
|
212
|
-
const res = await fetch(`${
|
|
213
|
-
|
|
214
|
-
setTimeout(loadAgents, 2000)
|
|
215
|
-
} catch (e) { alert(e.message) }
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
async function deleteAgent(id, name) {
|
|
219
|
-
if (!confirm(`Delete agent "${name}"? This will stop it and remove all configuration.`)) return
|
|
220
|
-
try {
|
|
221
|
-
const res = await fetch(`${API}/agents/${id}`, { method: 'DELETE' })
|
|
222
|
-
if (!res.ok) { const d = await res.json(); alert(d.error); return }
|
|
223
|
-
if (openLogsId === id) openLogsId = null
|
|
224
|
-
loadAgents()
|
|
225
|
-
} catch (e) { alert(e.message) }
|
|
226
|
-
}
|
|
175
|
+
const res = await fetch(`${API_BASE}/agents/${id}/restart`, { method: 'POST' })
|
|
176
|
+
const data = await res.json()
|
|
227
177
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
if (openLogsId === id) {
|
|
232
|
-
openLogsId = null
|
|
233
|
-
const el = document.getElementById(`logs-${id}`)
|
|
234
|
-
if (el) el.classList.remove('open')
|
|
235
|
-
stopLogsPolling(id)
|
|
236
|
-
} else {
|
|
237
|
-
// Close previous
|
|
238
|
-
if (openLogsId) {
|
|
239
|
-
const prev = document.getElementById(`logs-${openLogsId}`)
|
|
240
|
-
if (prev) prev.classList.remove('open')
|
|
241
|
-
stopLogsPolling(openLogsId)
|
|
178
|
+
if (!res.ok) {
|
|
179
|
+
alert(`Failed to restart agent: ${data.error}`)
|
|
180
|
+
return
|
|
242
181
|
}
|
|
243
|
-
openLogsId = id
|
|
244
|
-
const el = document.getElementById(`logs-${id}`)
|
|
245
|
-
if (el) el.classList.add('open')
|
|
246
|
-
refreshLogs(id)
|
|
247
|
-
startLogsPolling(id)
|
|
248
|
-
}
|
|
249
|
-
// Re-render buttons (toggle icon)
|
|
250
|
-
renderAgents()
|
|
251
|
-
}
|
|
252
182
|
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
183
|
+
setTimeout(loadAgents, 2000)
|
|
184
|
+
} catch (err) {
|
|
185
|
+
alert(`Failed to restart agent: ${err.message}`)
|
|
186
|
+
}
|
|
257
187
|
}
|
|
258
188
|
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
if (
|
|
262
|
-
|
|
263
|
-
logsIntervals.delete(id)
|
|
189
|
+
// Delete agent
|
|
190
|
+
async function deleteAgent(id, name) {
|
|
191
|
+
if (!confirm(`Are you sure you want to delete agent "${name}"? This will stop the agent and remove all configuration.`)) {
|
|
192
|
+
return
|
|
264
193
|
}
|
|
265
|
-
}
|
|
266
194
|
|
|
267
|
-
async function refreshLogs(id) {
|
|
268
195
|
try {
|
|
269
|
-
const res = await fetch(`${
|
|
196
|
+
const res = await fetch(`${API_BASE}/agents/${id}`, { method: 'DELETE' })
|
|
270
197
|
const data = await res.json()
|
|
271
|
-
|
|
272
|
-
if (
|
|
273
|
-
|
|
274
|
-
|
|
198
|
+
|
|
199
|
+
if (!res.ok) {
|
|
200
|
+
alert(`Failed to delete agent: ${data.error}`)
|
|
201
|
+
return
|
|
275
202
|
}
|
|
276
|
-
} catch { /* ignore */ }
|
|
277
|
-
}
|
|
278
203
|
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
refreshLogs(id)
|
|
284
|
-
} catch { /* ignore */ }
|
|
204
|
+
loadAgents()
|
|
205
|
+
} catch (err) {
|
|
206
|
+
alert(`Failed to delete agent: ${err.message}`)
|
|
207
|
+
}
|
|
285
208
|
}
|
|
286
209
|
|
|
287
|
-
//
|
|
288
|
-
|
|
210
|
+
// Open add agent modal
|
|
289
211
|
function openAddAgentModal() {
|
|
290
212
|
editingAgentId = null
|
|
291
|
-
document.getElementById('modal-title').textContent = 'Add Agent'
|
|
213
|
+
document.getElementById('modal-title').textContent = 'Add New Agent'
|
|
292
214
|
document.getElementById('agent-form').reset()
|
|
293
|
-
document.getElementById('agent-auto-pull').checked = true
|
|
294
215
|
document.getElementById('agent-modal').classList.add('active')
|
|
295
216
|
}
|
|
296
217
|
|
|
218
|
+
// Open edit agent modal
|
|
297
219
|
function editAgent(id) {
|
|
298
|
-
const
|
|
299
|
-
if (!
|
|
220
|
+
const agent = agents.find(a => a.id === id)
|
|
221
|
+
if (!agent) return
|
|
222
|
+
|
|
300
223
|
editingAgentId = id
|
|
301
224
|
document.getElementById('modal-title').textContent = 'Edit Agent'
|
|
302
|
-
document.getElementById('agent-name').value =
|
|
303
|
-
document.getElementById('agent-working-dir').value =
|
|
304
|
-
document.getElementById('agent-token').value =
|
|
305
|
-
document.getElementById('agent-cli-preference').value =
|
|
306
|
-
document.getElementById('agent-permission-mode').value =
|
|
307
|
-
document.getElementById('agent-auto-start').checked =
|
|
308
|
-
document.getElementById('agent-auto-pull').checked =
|
|
225
|
+
document.getElementById('agent-name').value = agent.name
|
|
226
|
+
document.getElementById('agent-working-dir').value = agent.workingDir
|
|
227
|
+
document.getElementById('agent-token').value = agent.token
|
|
228
|
+
document.getElementById('agent-cli-preference').value = agent.cliPreference
|
|
229
|
+
document.getElementById('agent-permission-mode').value = agent.permissionMode
|
|
230
|
+
document.getElementById('agent-auto-start').checked = agent.autoStart
|
|
231
|
+
document.getElementById('agent-auto-pull').checked = agent.autoPull
|
|
309
232
|
document.getElementById('agent-modal').classList.add('active')
|
|
310
233
|
}
|
|
311
234
|
|
|
235
|
+
// Close agent modal
|
|
312
236
|
function closeAgentModal() {
|
|
313
237
|
document.getElementById('agent-modal').classList.remove('active')
|
|
314
238
|
editingAgentId = null
|
|
315
239
|
}
|
|
316
240
|
|
|
241
|
+
// Save agent
|
|
317
242
|
async function saveAgent(event) {
|
|
318
243
|
event.preventDefault()
|
|
319
|
-
|
|
244
|
+
|
|
245
|
+
const formData = {
|
|
320
246
|
name: document.getElementById('agent-name').value,
|
|
321
247
|
workingDir: document.getElementById('agent-working-dir').value,
|
|
322
248
|
token: document.getElementById('agent-token').value,
|
|
@@ -327,68 +253,159 @@ async function saveAgent(event) {
|
|
|
327
253
|
}
|
|
328
254
|
|
|
329
255
|
try {
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
256
|
+
let res
|
|
257
|
+
if (editingAgentId) {
|
|
258
|
+
// Update existing agent
|
|
259
|
+
res = await fetch(`${API_BASE}/agents/${editingAgentId}`, {
|
|
260
|
+
method: 'PUT',
|
|
261
|
+
headers: { 'Content-Type': 'application/json' },
|
|
262
|
+
body: JSON.stringify(formData),
|
|
263
|
+
})
|
|
264
|
+
} else {
|
|
265
|
+
// Create new agent
|
|
266
|
+
res = await fetch(`${API_BASE}/agents`, {
|
|
267
|
+
method: 'POST',
|
|
268
|
+
headers: { 'Content-Type': 'application/json' },
|
|
269
|
+
body: JSON.stringify(formData),
|
|
270
|
+
})
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
const data = await res.json()
|
|
274
|
+
|
|
275
|
+
if (!res.ok) {
|
|
276
|
+
alert(`Failed to save agent: ${data.error}`)
|
|
277
|
+
return
|
|
278
|
+
}
|
|
279
|
+
|
|
334
280
|
closeAgentModal()
|
|
335
281
|
loadAgents()
|
|
336
|
-
} catch (
|
|
282
|
+
} catch (err) {
|
|
283
|
+
alert(`Failed to save agent: ${err.message}`)
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// View logs
|
|
288
|
+
function viewLogs(id, name) {
|
|
289
|
+
logsAgentId = id
|
|
290
|
+
document.getElementById('logs-title').textContent = `Logs: ${name}`
|
|
291
|
+
document.getElementById('logs-modal').classList.add('active')
|
|
292
|
+
loadLogs()
|
|
293
|
+
|
|
294
|
+
// Auto-refresh logs every 2 seconds
|
|
295
|
+
logsInterval = setInterval(() => {
|
|
296
|
+
if (document.getElementById('logs-autoscroll').checked) {
|
|
297
|
+
loadLogs()
|
|
298
|
+
}
|
|
299
|
+
}, 2000)
|
|
337
300
|
}
|
|
338
301
|
|
|
339
|
-
//
|
|
302
|
+
// Load logs
|
|
303
|
+
async function loadLogs() {
|
|
304
|
+
if (!logsAgentId) return
|
|
340
305
|
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
306
|
+
try {
|
|
307
|
+
const res = await fetch(`${API_BASE}/agents/${logsAgentId}/logs?lines=200`)
|
|
308
|
+
const data = await res.json()
|
|
309
|
+
|
|
310
|
+
const logsContent = document.getElementById('logs-content')
|
|
311
|
+
logsContent.textContent = data.logs.join('\n')
|
|
312
|
+
|
|
313
|
+
// Auto-scroll to bottom if enabled
|
|
314
|
+
if (document.getElementById('logs-autoscroll').checked) {
|
|
315
|
+
logsContent.scrollTop = logsContent.scrollHeight
|
|
316
|
+
}
|
|
317
|
+
} catch (err) {
|
|
318
|
+
console.error('Failed to load logs:', err)
|
|
319
|
+
}
|
|
345
320
|
}
|
|
346
321
|
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
322
|
+
// Clear logs
|
|
323
|
+
async function clearLogs() {
|
|
324
|
+
if (!logsAgentId) return
|
|
325
|
+
|
|
326
|
+
if (!confirm('Are you sure you want to clear all logs for this agent?')) {
|
|
327
|
+
return
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
try {
|
|
331
|
+
const res = await fetch(`${API_BASE}/agents/${logsAgentId}/logs`, { method: 'DELETE' })
|
|
332
|
+
const data = await res.json()
|
|
333
|
+
|
|
334
|
+
if (!res.ok) {
|
|
335
|
+
alert(`Failed to clear logs: ${data.error}`)
|
|
336
|
+
return
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
loadLogs()
|
|
340
|
+
} catch (err) {
|
|
341
|
+
alert(`Failed to clear logs: ${err.message}`)
|
|
342
|
+
}
|
|
356
343
|
}
|
|
357
344
|
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
345
|
+
// Close logs modal
|
|
346
|
+
function closeLogsModal() {
|
|
347
|
+
document.getElementById('logs-modal').classList.remove('active')
|
|
348
|
+
if (logsInterval) {
|
|
349
|
+
clearInterval(logsInterval)
|
|
350
|
+
logsInterval = null
|
|
351
|
+
}
|
|
352
|
+
logsAgentId = null
|
|
364
353
|
}
|
|
365
354
|
|
|
366
|
-
//
|
|
355
|
+
// Utility: Escape HTML
|
|
356
|
+
function escapeHtml(text) {
|
|
357
|
+
const div = document.createElement('div')
|
|
358
|
+
div.textContent = text
|
|
359
|
+
return div.innerHTML
|
|
360
|
+
}
|
|
367
361
|
|
|
362
|
+
// Utility: Format uptime
|
|
363
|
+
function formatUptime(ms) {
|
|
364
|
+
const seconds = Math.floor(ms / 1000)
|
|
365
|
+
const minutes = Math.floor(seconds / 60)
|
|
366
|
+
const hours = Math.floor(minutes / 60)
|
|
367
|
+
const days = Math.floor(hours / 24)
|
|
368
|
+
|
|
369
|
+
if (days > 0) return `${days}d ${hours % 24}h`
|
|
370
|
+
if (hours > 0) return `${hours}h ${minutes % 60}m`
|
|
371
|
+
if (minutes > 0) return `${minutes}m ${seconds % 60}s`
|
|
372
|
+
return `${seconds}s`
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
// Event listeners
|
|
368
376
|
document.addEventListener('DOMContentLoaded', () => {
|
|
369
377
|
loadSystemInfo()
|
|
370
|
-
loadHealth()
|
|
371
378
|
loadAgents()
|
|
372
379
|
|
|
380
|
+
// Refresh button
|
|
373
381
|
document.getElementById('refresh-btn').addEventListener('click', () => {
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
setTimeout(() => icon.classList.remove('spinning'), 500)
|
|
382
|
+
document.getElementById('refresh-btn').querySelector('.icon').classList.add('loading')
|
|
383
|
+
Promise.all([loadSystemInfo(), loadAgents()]).then(() => {
|
|
384
|
+
document.getElementById('refresh-btn').querySelector('.icon').classList.remove('loading')
|
|
378
385
|
})
|
|
379
386
|
})
|
|
380
387
|
|
|
388
|
+
// Add agent button
|
|
381
389
|
document.getElementById('add-agent-btn').addEventListener('click', openAddAgentModal)
|
|
390
|
+
|
|
391
|
+
// Agent form submit
|
|
382
392
|
document.getElementById('agent-form').addEventListener('submit', saveAgent)
|
|
383
393
|
|
|
384
|
-
//
|
|
394
|
+
// Logs controls
|
|
395
|
+
document.getElementById('logs-refresh').addEventListener('click', loadLogs)
|
|
396
|
+
document.getElementById('logs-clear').addEventListener('click', clearLogs)
|
|
397
|
+
|
|
398
|
+
// Auto-refresh agents every 5 seconds
|
|
385
399
|
setInterval(loadAgents, 5000)
|
|
386
|
-
setInterval(loadHealth, 10000)
|
|
387
400
|
})
|
|
388
401
|
|
|
389
402
|
// Close modals on background click
|
|
390
403
|
window.addEventListener('click', (e) => {
|
|
391
404
|
if (e.target.classList.contains('modal')) {
|
|
392
405
|
e.target.classList.remove('active')
|
|
406
|
+
if (e.target.id === 'logs-modal' && logsInterval) {
|
|
407
|
+
clearInterval(logsInterval)
|
|
408
|
+
logsInterval = null
|
|
409
|
+
}
|
|
393
410
|
}
|
|
394
411
|
})
|