supervaizer 0.9.6__py3-none-any.whl
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.
- supervaizer/__init__.py +88 -0
- supervaizer/__version__.py +10 -0
- supervaizer/account.py +304 -0
- supervaizer/account_service.py +87 -0
- supervaizer/admin/routes.py +1254 -0
- supervaizer/admin/templates/agent_detail.html +145 -0
- supervaizer/admin/templates/agents.html +175 -0
- supervaizer/admin/templates/agents_grid.html +80 -0
- supervaizer/admin/templates/base.html +233 -0
- supervaizer/admin/templates/case_detail.html +230 -0
- supervaizer/admin/templates/cases_list.html +182 -0
- supervaizer/admin/templates/cases_table.html +134 -0
- supervaizer/admin/templates/console.html +389 -0
- supervaizer/admin/templates/dashboard.html +153 -0
- supervaizer/admin/templates/job_detail.html +192 -0
- supervaizer/admin/templates/jobs_list.html +180 -0
- supervaizer/admin/templates/jobs_table.html +122 -0
- supervaizer/admin/templates/navigation.html +153 -0
- supervaizer/admin/templates/recent_activity.html +81 -0
- supervaizer/admin/templates/server.html +105 -0
- supervaizer/admin/templates/server_status_cards.html +121 -0
- supervaizer/agent.py +816 -0
- supervaizer/case.py +400 -0
- supervaizer/cli.py +135 -0
- supervaizer/common.py +283 -0
- supervaizer/event.py +181 -0
- supervaizer/examples/controller-template.py +195 -0
- supervaizer/instructions.py +145 -0
- supervaizer/job.py +379 -0
- supervaizer/job_service.py +155 -0
- supervaizer/lifecycle.py +417 -0
- supervaizer/parameter.py +173 -0
- supervaizer/protocol/__init__.py +11 -0
- supervaizer/protocol/a2a/__init__.py +21 -0
- supervaizer/protocol/a2a/model.py +227 -0
- supervaizer/protocol/a2a/routes.py +99 -0
- supervaizer/protocol/acp/__init__.py +21 -0
- supervaizer/protocol/acp/model.py +198 -0
- supervaizer/protocol/acp/routes.py +74 -0
- supervaizer/py.typed +1 -0
- supervaizer/routes.py +667 -0
- supervaizer/server.py +554 -0
- supervaizer/server_utils.py +54 -0
- supervaizer/storage.py +436 -0
- supervaizer/telemetry.py +81 -0
- supervaizer-0.9.6.dist-info/METADATA +245 -0
- supervaizer-0.9.6.dist-info/RECORD +50 -0
- supervaizer-0.9.6.dist-info/WHEEL +4 -0
- supervaizer-0.9.6.dist-info/entry_points.txt +2 -0
- supervaizer-0.9.6.dist-info/licenses/LICENSE.md +346 -0
|
@@ -0,0 +1,389 @@
|
|
|
1
|
+
{% extends "base.html" %}
|
|
2
|
+
|
|
3
|
+
{% block title %}Console - Supervaizer Admin{% endblock %}
|
|
4
|
+
|
|
5
|
+
{% block content %}
|
|
6
|
+
<div class="px-4 py-6 sm:px-0">
|
|
7
|
+
<!-- Header -->
|
|
8
|
+
<div class="md:flex md:items-center md:justify-between">
|
|
9
|
+
<div class="min-w-0 flex-1">
|
|
10
|
+
<h2 class="text-2xl font-bold leading-7 text-gray-900 sm:truncate sm:text-3xl sm:tracking-tight">
|
|
11
|
+
Console
|
|
12
|
+
</h2>
|
|
13
|
+
<p class="mt-1 text-sm text-gray-500">Interactive command interface and system logs</p>
|
|
14
|
+
</div>
|
|
15
|
+
<div class="mt-4 flex md:mt-0 space-x-3">
|
|
16
|
+
<button
|
|
17
|
+
onclick="clearConsole()"
|
|
18
|
+
class="inline-flex items-center px-4 py-2 border border-gray-300 rounded-md shadow-sm text-sm font-medium text-gray-700 bg-white hover:bg-gray-50"
|
|
19
|
+
>
|
|
20
|
+
Clear
|
|
21
|
+
</button>
|
|
22
|
+
<button
|
|
23
|
+
onclick="toggleAutoScroll()"
|
|
24
|
+
id="autoscroll-btn"
|
|
25
|
+
class="inline-flex items-center px-4 py-2 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-blue-600 hover:bg-blue-700"
|
|
26
|
+
>
|
|
27
|
+
Auto-scroll: ON
|
|
28
|
+
</button>
|
|
29
|
+
</div>
|
|
30
|
+
</div>
|
|
31
|
+
|
|
32
|
+
<!-- Console Interface -->
|
|
33
|
+
<div class="mt-6 space-y-6">
|
|
34
|
+
<!-- Command Input -->
|
|
35
|
+
<div class="bg-white shadow rounded-lg">
|
|
36
|
+
<div class="px-6 py-4 border-b border-gray-200">
|
|
37
|
+
<h3 class="text-lg leading-6 font-medium text-gray-900">Command Interface</h3>
|
|
38
|
+
</div>
|
|
39
|
+
<div class="px-6 py-4">
|
|
40
|
+
<div class="flex space-x-4">
|
|
41
|
+
<input
|
|
42
|
+
type="text"
|
|
43
|
+
id="command-input"
|
|
44
|
+
placeholder="Enter command..."
|
|
45
|
+
class="flex-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm font-mono"
|
|
46
|
+
onkeypress="handleCommandKeypress(event)"
|
|
47
|
+
>
|
|
48
|
+
<button
|
|
49
|
+
onclick="executeCommand()"
|
|
50
|
+
class="inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-blue-600 hover:bg-blue-700"
|
|
51
|
+
>
|
|
52
|
+
Execute
|
|
53
|
+
</button>
|
|
54
|
+
</div>
|
|
55
|
+
</div>
|
|
56
|
+
</div>
|
|
57
|
+
|
|
58
|
+
<!-- Live Log Stream -->
|
|
59
|
+
<div class="bg-white shadow rounded-lg">
|
|
60
|
+
<div class="px-6 py-4 border-b border-gray-200">
|
|
61
|
+
<h3 class="text-lg leading-6 font-medium text-gray-900">Live Server Logs</h3>
|
|
62
|
+
<div class="mt-1 flex items-center space-x-4 text-sm text-gray-500">
|
|
63
|
+
<span id="connection-status" class="flex items-center">
|
|
64
|
+
<span class="w-2 h-2 bg-gray-400 rounded-full mr-2"></span>
|
|
65
|
+
Connecting...
|
|
66
|
+
</span>
|
|
67
|
+
<span>Auto-scroll: <span id="autoscroll-status">ON</span></span>
|
|
68
|
+
</div>
|
|
69
|
+
</div>
|
|
70
|
+
<div class="px-6 py-4">
|
|
71
|
+
<div
|
|
72
|
+
id="console-output"
|
|
73
|
+
class="bg-gray-900 rounded-lg p-4 font-mono text-sm text-green-300 h-96 overflow-y-auto"
|
|
74
|
+
>
|
|
75
|
+
<div id="console-lines" class="space-y-1">
|
|
76
|
+
<div class="flex space-x-2">
|
|
77
|
+
<span class="text-gray-500">[--:--:--]</span>
|
|
78
|
+
<span class="text-blue-400 font-semibold">SYSTEM</span>
|
|
79
|
+
<span>Connecting to live log stream...</span>
|
|
80
|
+
</div>
|
|
81
|
+
</div>
|
|
82
|
+
</div>
|
|
83
|
+
</div>
|
|
84
|
+
</div>
|
|
85
|
+
</div>
|
|
86
|
+
</div>
|
|
87
|
+
|
|
88
|
+
<script>
|
|
89
|
+
let autoScroll = true;
|
|
90
|
+
let eventSource = null;
|
|
91
|
+
let reconnectTimeout = null;
|
|
92
|
+
let lineCount = 0;
|
|
93
|
+
let maxLines = 1000; // Limit console lines for performance
|
|
94
|
+
|
|
95
|
+
// Initialize console on page load
|
|
96
|
+
document.addEventListener('DOMContentLoaded', function() {
|
|
97
|
+
connectToLogStream();
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
function connectToLogStream() {
|
|
101
|
+
if (eventSource) {
|
|
102
|
+
eventSource.close();
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
updateConnectionStatus('connecting');
|
|
106
|
+
|
|
107
|
+
// Connect to the log stream endpoint with console token
|
|
108
|
+
const consoleToken = '{{ console_token }}';
|
|
109
|
+
const cacheBuster = Date.now();
|
|
110
|
+
|
|
111
|
+
// Better validation and error handling
|
|
112
|
+
if (!consoleToken || consoleToken === 'undefined' || consoleToken === '') {
|
|
113
|
+
addLogMessage('ERROR', 'No console token available. Please refresh the page.', 'red');
|
|
114
|
+
updateConnectionStatus('error');
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const streamUrl = `/admin/log-stream?token=${encodeURIComponent(consoleToken)}&_t=${cacheBuster}`;
|
|
119
|
+
|
|
120
|
+
try {
|
|
121
|
+
eventSource = new EventSource(streamUrl);
|
|
122
|
+
|
|
123
|
+
eventSource.onopen = function(event) {
|
|
124
|
+
updateConnectionStatus('connected');
|
|
125
|
+
addLogMessage('SYSTEM', 'Connected to live log stream', 'blue');
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
eventSource.onmessage = function(event) {
|
|
129
|
+
try {
|
|
130
|
+
// Handle different event data formats
|
|
131
|
+
let logData;
|
|
132
|
+
if (event.data.startsWith('data: ')) {
|
|
133
|
+
// Remove "data: " prefix if present
|
|
134
|
+
logData = JSON.parse(event.data.substring(6));
|
|
135
|
+
} else {
|
|
136
|
+
logData = JSON.parse(event.data);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (logData && logData.level && logData.message) {
|
|
140
|
+
addLogMessage(logData.level, logData.message, getLevelColor(logData.level), logData.timestamp);
|
|
141
|
+
} else {
|
|
142
|
+
addLogMessage('WARNING', 'Received invalid log data format', 'yellow');
|
|
143
|
+
}
|
|
144
|
+
} catch (e) {
|
|
145
|
+
addLogMessage('ERROR', `Parse error: ${e.message}`, 'red');
|
|
146
|
+
}
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
eventSource.onerror = function(event) {
|
|
150
|
+
updateConnectionStatus('error');
|
|
151
|
+
|
|
152
|
+
// More specific error handling
|
|
153
|
+
if (eventSource.readyState === EventSource.CLOSED) {
|
|
154
|
+
addLogMessage('ERROR', 'Connection to log stream closed by server', 'red');
|
|
155
|
+
} else if (eventSource.readyState === EventSource.CONNECTING) {
|
|
156
|
+
addLogMessage('WARNING', 'Attempting to reconnect to log stream...', 'yellow');
|
|
157
|
+
} else {
|
|
158
|
+
addLogMessage('ERROR', 'Unknown log stream error occurred', 'red');
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Reconnect after 5 seconds
|
|
162
|
+
clearTimeout(reconnectTimeout);
|
|
163
|
+
reconnectTimeout = setTimeout(() => {
|
|
164
|
+
addLogMessage('INFO', 'Attempting to reconnect...', 'blue');
|
|
165
|
+
connectToLogStream();
|
|
166
|
+
}, 5000);
|
|
167
|
+
};
|
|
168
|
+
} catch (e) {
|
|
169
|
+
addLogMessage('ERROR', `Failed to create connection: ${e.message}`, 'red');
|
|
170
|
+
updateConnectionStatus('error');
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function updateConnectionStatus(status) {
|
|
175
|
+
const statusElement = document.getElementById('connection-status');
|
|
176
|
+
if (!statusElement) return;
|
|
177
|
+
|
|
178
|
+
const statusIndicator = statusElement.querySelector('.w-2.h-2');
|
|
179
|
+
const statusText = statusElement.querySelector('span:last-child');
|
|
180
|
+
|
|
181
|
+
if (!statusIndicator || !statusText) return;
|
|
182
|
+
|
|
183
|
+
switch (status) {
|
|
184
|
+
case 'connecting':
|
|
185
|
+
statusIndicator.className = 'w-2 h-2 bg-yellow-400 rounded-full mr-2 animate-pulse';
|
|
186
|
+
statusText.textContent = 'Connecting...';
|
|
187
|
+
break;
|
|
188
|
+
case 'connected':
|
|
189
|
+
statusIndicator.className = 'w-2 h-2 bg-green-400 rounded-full mr-2';
|
|
190
|
+
statusText.textContent = 'Connected';
|
|
191
|
+
break;
|
|
192
|
+
case 'error':
|
|
193
|
+
statusIndicator.className = 'w-2 h-2 bg-red-400 rounded-full mr-2 animate-pulse';
|
|
194
|
+
statusText.textContent = 'Disconnected';
|
|
195
|
+
break;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function getLevelColor(level) {
|
|
200
|
+
const levelColors = {
|
|
201
|
+
'TRACE': 'purple',
|
|
202
|
+
'DEBUG': 'gray',
|
|
203
|
+
'INFO': 'blue',
|
|
204
|
+
'SUCCESS': 'green',
|
|
205
|
+
'WARNING': 'yellow',
|
|
206
|
+
'ERROR': 'red',
|
|
207
|
+
'CRITICAL': 'red'
|
|
208
|
+
};
|
|
209
|
+
return levelColors[level] || 'green';
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function addLogMessage(level, message, color = 'green', timestamp = null) {
|
|
213
|
+
const lines = document.getElementById('console-lines');
|
|
214
|
+
if (!lines) return;
|
|
215
|
+
|
|
216
|
+
// Parse timestamp or use current time
|
|
217
|
+
let timeStr;
|
|
218
|
+
if (timestamp) {
|
|
219
|
+
try {
|
|
220
|
+
const date = new Date(timestamp);
|
|
221
|
+
timeStr = date.toLocaleTimeString();
|
|
222
|
+
} catch (e) {
|
|
223
|
+
timeStr = new Date().toLocaleTimeString();
|
|
224
|
+
}
|
|
225
|
+
} else {
|
|
226
|
+
timeStr = new Date().toLocaleTimeString();
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const colorMap = {
|
|
230
|
+
'red': 'text-red-400',
|
|
231
|
+
'green': 'text-green-300',
|
|
232
|
+
'blue': 'text-blue-400',
|
|
233
|
+
'yellow': 'text-yellow-400',
|
|
234
|
+
'cyan': 'text-cyan-400',
|
|
235
|
+
'purple': 'text-purple-400',
|
|
236
|
+
'gray': 'text-gray-400'
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
const div = document.createElement('div');
|
|
240
|
+
div.className = 'flex space-x-2 text-xs leading-relaxed';
|
|
241
|
+
div.innerHTML = `
|
|
242
|
+
<span class="text-gray-500 flex-shrink-0">[${timeStr}]</span>
|
|
243
|
+
<span class="font-semibold min-w-[60px] flex-shrink-0 ${colorMap[color] || 'text-green-300'}">${level}</span>
|
|
244
|
+
<span class="${colorMap[color] || 'text-green-300'} break-words">${escapeHtml(message)}</span>
|
|
245
|
+
`;
|
|
246
|
+
|
|
247
|
+
lines.appendChild(div);
|
|
248
|
+
lineCount++;
|
|
249
|
+
|
|
250
|
+
// Limit lines for performance
|
|
251
|
+
if (lineCount > maxLines) {
|
|
252
|
+
const firstChild = lines.firstElementChild;
|
|
253
|
+
if (firstChild) {
|
|
254
|
+
lines.removeChild(firstChild);
|
|
255
|
+
lineCount--;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
if (autoScroll) {
|
|
260
|
+
const output = document.getElementById('console-output');
|
|
261
|
+
if (output) {
|
|
262
|
+
output.scrollTop = output.scrollHeight;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
function escapeHtml(text) {
|
|
268
|
+
const div = document.createElement('div');
|
|
269
|
+
div.textContent = text;
|
|
270
|
+
return div.innerHTML;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
function handleCommandKeypress(event) {
|
|
274
|
+
if (event.key === 'Enter') {
|
|
275
|
+
executeCommand();
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
function executeCommand() {
|
|
280
|
+
const input = document.getElementById('command-input');
|
|
281
|
+
if (!input) return;
|
|
282
|
+
|
|
283
|
+
const command = input.value.trim();
|
|
284
|
+
|
|
285
|
+
if (!command) return;
|
|
286
|
+
|
|
287
|
+
// Show command in console
|
|
288
|
+
addLogMessage('USER', `$ ${command}`, 'cyan');
|
|
289
|
+
|
|
290
|
+
// Process command
|
|
291
|
+
processCommand(command);
|
|
292
|
+
|
|
293
|
+
// Clear input
|
|
294
|
+
input.value = '';
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
function processCommand(command) {
|
|
298
|
+
const cmd = command.toLowerCase();
|
|
299
|
+
|
|
300
|
+
// Handle local-only commands
|
|
301
|
+
if (cmd === 'clear') {
|
|
302
|
+
clearConsole();
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
if (cmd === 'reconnect') {
|
|
307
|
+
addLogMessage('INFO', 'Reconnecting to log stream...', 'blue');
|
|
308
|
+
connectToLogStream();
|
|
309
|
+
return;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// Send command to server for processing and live console display
|
|
313
|
+
sendCommandToServer(command);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
async function sendCommandToServer(command) {
|
|
317
|
+
const consoleToken = '{{ console_token }}';
|
|
318
|
+
|
|
319
|
+
if (!consoleToken || consoleToken === 'undefined' || consoleToken === '') {
|
|
320
|
+
addLogMessage('ERROR', 'No console token available. Cannot execute command.', 'red');
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
try {
|
|
325
|
+
const response = await fetch(`/admin/api/console/execute?token=${encodeURIComponent(consoleToken)}`, {
|
|
326
|
+
method: 'POST',
|
|
327
|
+
headers: {
|
|
328
|
+
'Content-Type': 'application/json',
|
|
329
|
+
},
|
|
330
|
+
body: JSON.stringify({
|
|
331
|
+
command: command
|
|
332
|
+
})
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
if (!response.ok) {
|
|
336
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
const result = await response.json();
|
|
340
|
+
|
|
341
|
+
if (result.status === 'error') {
|
|
342
|
+
addLogMessage('ERROR', `Command failed: ${result.message}`, 'red');
|
|
343
|
+
}
|
|
344
|
+
// Success messages are handled by the log stream
|
|
345
|
+
|
|
346
|
+
} catch (error) {
|
|
347
|
+
addLogMessage('ERROR', `Failed to execute command: ${error.message}`, 'red');
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
function clearConsole() {
|
|
352
|
+
const lines = document.getElementById('console-lines');
|
|
353
|
+
if (lines) {
|
|
354
|
+
lines.innerHTML = '';
|
|
355
|
+
lineCount = 0;
|
|
356
|
+
addLogMessage('SYSTEM', 'Console cleared.', 'blue');
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
function toggleAutoScroll() {
|
|
361
|
+
autoScroll = !autoScroll;
|
|
362
|
+
const btn = document.getElementById('autoscroll-btn');
|
|
363
|
+
const status = document.getElementById('autoscroll-status');
|
|
364
|
+
|
|
365
|
+
if (btn && status) {
|
|
366
|
+
const newText = autoScroll ? 'Auto-scroll: ON' : 'Auto-scroll: OFF';
|
|
367
|
+
btn.textContent = newText;
|
|
368
|
+
status.textContent = autoScroll ? 'ON' : 'OFF';
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
if (autoScroll) {
|
|
372
|
+
const output = document.getElementById('console-output');
|
|
373
|
+
if (output) {
|
|
374
|
+
output.scrollTop = output.scrollHeight;
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
// Clean up on page unload
|
|
380
|
+
window.addEventListener('beforeunload', function() {
|
|
381
|
+
if (eventSource) {
|
|
382
|
+
eventSource.close();
|
|
383
|
+
}
|
|
384
|
+
if (reconnectTimeout) {
|
|
385
|
+
clearTimeout(reconnectTimeout);
|
|
386
|
+
}
|
|
387
|
+
});
|
|
388
|
+
</script>
|
|
389
|
+
{% endblock %}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
{% extends "base.html" %}
|
|
2
|
+
|
|
3
|
+
{% block title %}Dashboard - Supervaizer Admin{% endblock %}
|
|
4
|
+
|
|
5
|
+
{% block content %}
|
|
6
|
+
<div class="px-4 py-6 sm:px-0">
|
|
7
|
+
<!-- Header -->
|
|
8
|
+
<div class="md:flex md:items-center md:justify-between">
|
|
9
|
+
<div class="min-w-0 flex-1">
|
|
10
|
+
<h2 class="text-2xl font-bold leading-7 text-gray-900 sm:truncate sm:text-3xl sm:tracking-tight">
|
|
11
|
+
Dashboard
|
|
12
|
+
</h2>
|
|
13
|
+
</div>
|
|
14
|
+
</div>
|
|
15
|
+
|
|
16
|
+
<!-- Stats Cards -->
|
|
17
|
+
<div class="mt-8 grid grid-cols-1 gap-5 sm:grid-cols-3">
|
|
18
|
+
<!-- Jobs Stats -->
|
|
19
|
+
<div class="bg-white overflow-hidden shadow rounded-lg">
|
|
20
|
+
<div class="p-5">
|
|
21
|
+
<div class="flex items-center">
|
|
22
|
+
<div class="flex-shrink-0">
|
|
23
|
+
<div class="w-8 h-8 bg-blue-500 rounded-md flex items-center justify-center">
|
|
24
|
+
<svg class="w-5 h-5 text-white" fill="currentColor" viewBox="0 0 20 20">
|
|
25
|
+
<path fill-rule="evenodd" d="M6 2a1 1 0 00-1 1v1H4a2 2 0 00-2 2v10a2 2 0 002 2h12a2 2 0 002-2V6a2 2 0 00-2-2h-1V3a1 1 0 10-2 0v1H7V3a1 1 0 00-1-1zm0 5a1 1 0 000 2h8a1 1 0 100-2H6z" clip-rule="evenodd"></path>
|
|
26
|
+
</svg>
|
|
27
|
+
</div>
|
|
28
|
+
</div>
|
|
29
|
+
<div class="ml-5 w-0 flex-1">
|
|
30
|
+
<dl>
|
|
31
|
+
<dt class="text-sm font-medium text-gray-500 truncate">Total Jobs</dt>
|
|
32
|
+
<dd class="text-lg font-medium text-gray-900">{{ stats.jobs.total }}</dd>
|
|
33
|
+
</dl>
|
|
34
|
+
</div>
|
|
35
|
+
</div>
|
|
36
|
+
</div>
|
|
37
|
+
<div class="bg-gray-50 px-5 py-3">
|
|
38
|
+
<div class="text-sm">
|
|
39
|
+
<a href="/admin/jobs" class="font-medium text-blue-700 hover:text-blue-900">
|
|
40
|
+
View all jobs
|
|
41
|
+
</a>
|
|
42
|
+
</div>
|
|
43
|
+
<div class="mt-1 text-xs text-gray-500">
|
|
44
|
+
Running: {{ stats.jobs.running }} | Completed: {{ stats.jobs.completed }} | Failed: {{ stats.jobs.failed }}
|
|
45
|
+
</div>
|
|
46
|
+
</div>
|
|
47
|
+
</div>
|
|
48
|
+
|
|
49
|
+
<!-- Cases Stats -->
|
|
50
|
+
<div class="bg-white overflow-hidden shadow rounded-lg">
|
|
51
|
+
<div class="p-5">
|
|
52
|
+
<div class="flex items-center">
|
|
53
|
+
<div class="flex-shrink-0">
|
|
54
|
+
<div class="w-8 h-8 bg-green-500 rounded-md flex items-center justify-center">
|
|
55
|
+
<svg class="w-5 h-5 text-white" fill="currentColor" viewBox="0 0 20 20">
|
|
56
|
+
<path fill-rule="evenodd" d="M3 4a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zm0 4a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zm0 4a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zm0 4a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1z" clip-rule="evenodd"></path>
|
|
57
|
+
</svg>
|
|
58
|
+
</div>
|
|
59
|
+
</div>
|
|
60
|
+
<div class="ml-5 w-0 flex-1">
|
|
61
|
+
<dl>
|
|
62
|
+
<dt class="text-sm font-medium text-gray-500 truncate">Total Cases</dt>
|
|
63
|
+
<dd class="text-lg font-medium text-gray-900">{{ stats.cases.total }}</dd>
|
|
64
|
+
</dl>
|
|
65
|
+
</div>
|
|
66
|
+
</div>
|
|
67
|
+
</div>
|
|
68
|
+
<div class="bg-gray-50 px-5 py-3">
|
|
69
|
+
<div class="text-sm">
|
|
70
|
+
<a href="/admin/cases" class="font-medium text-green-700 hover:text-green-900">
|
|
71
|
+
View all cases
|
|
72
|
+
</a>
|
|
73
|
+
</div>
|
|
74
|
+
<div class="mt-1 text-xs text-gray-500">
|
|
75
|
+
Running: {{ stats.cases.running }} | Completed: {{ stats.cases.completed }} | Failed: {{ stats.cases.failed }}
|
|
76
|
+
</div>
|
|
77
|
+
</div>
|
|
78
|
+
</div>
|
|
79
|
+
|
|
80
|
+
<!-- System Stats -->
|
|
81
|
+
<div class="bg-white overflow-hidden shadow rounded-lg">
|
|
82
|
+
<div class="p-5">
|
|
83
|
+
<div class="flex items-center">
|
|
84
|
+
<div class="flex-shrink-0">
|
|
85
|
+
<div class="w-8 h-8 bg-purple-500 rounded-md flex items-center justify-center">
|
|
86
|
+
<svg class="w-5 h-5 text-white" fill="currentColor" viewBox="0 0 20 20">
|
|
87
|
+
<path fill-rule="evenodd" d="M2 5a2 2 0 012-2h8a2 2 0 012 2v10a2 2 0 002 2H4a2 2 0 01-2-2V5zm3 1h6v4H5V6zm6 6H5v2h6v-2z" clip-rule="evenodd"></path>
|
|
88
|
+
<path d="M15 7h1a2 2 0 012 2v5.5a1.5 1.5 0 01-3 0V9a1 1 0 00-1-1h-1v-1z"></path>
|
|
89
|
+
</svg>
|
|
90
|
+
</div>
|
|
91
|
+
</div>
|
|
92
|
+
<div class="ml-5 w-0 flex-1">
|
|
93
|
+
<dl>
|
|
94
|
+
<dt class="text-sm font-medium text-gray-500 truncate">System Status</dt>
|
|
95
|
+
<dd class="text-lg font-medium text-gray-900">{{ system_status }}</dd>
|
|
96
|
+
</dl>
|
|
97
|
+
</div>
|
|
98
|
+
</div>
|
|
99
|
+
</div>
|
|
100
|
+
<div class="bg-gray-50 px-5 py-3">
|
|
101
|
+
<div class="text-sm">
|
|
102
|
+
<span class="font-medium text-purple-700">Persistent Storage: {{ data_storage_path }}</span>
|
|
103
|
+
</div>
|
|
104
|
+
<div class="mt-1 text-xs text-gray-500">
|
|
105
|
+
Database: {{ db_name }} | Collections: {{ stats.collections }}
|
|
106
|
+
</div>
|
|
107
|
+
</div>
|
|
108
|
+
</div>
|
|
109
|
+
</div>
|
|
110
|
+
|
|
111
|
+
<!-- Recent Activity -->
|
|
112
|
+
<div class="mt-8">
|
|
113
|
+
<div class="md:flex md:items-center md:justify-between">
|
|
114
|
+
<div class="min-w-0 flex-1">
|
|
115
|
+
<h3 class="text-lg font-medium leading-6 text-gray-900">Recent Activity</h3>
|
|
116
|
+
</div>
|
|
117
|
+
<div class="mt-4 flex md:mt-0">
|
|
118
|
+
<button
|
|
119
|
+
hx-get="/admin/api/recent-activity"
|
|
120
|
+
hx-target="#recent-activity-content"
|
|
121
|
+
hx-indicator="#refresh-indicator"
|
|
122
|
+
class="inline-flex items-center px-4 py-2 border border-gray-300 rounded-md shadow-sm text-sm font-medium text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
|
|
123
|
+
>
|
|
124
|
+
<svg id="refresh-indicator" class="htmx-indicator -ml-1 mr-2 h-4 w-4 animate-spin" fill="none" viewBox="0 0 24 24">
|
|
125
|
+
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
|
126
|
+
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
|
127
|
+
</svg>
|
|
128
|
+
Refresh
|
|
129
|
+
</button>
|
|
130
|
+
</div>
|
|
131
|
+
</div>
|
|
132
|
+
|
|
133
|
+
<div id="recent-activity-content" class="mt-6 bg-white shadow overflow-hidden rounded-md">
|
|
134
|
+
<div class="px-4 py-5 sm:p-6">
|
|
135
|
+
<div class="text-center">
|
|
136
|
+
<svg class="mx-auto h-12 w-12 text-gray-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
137
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5H7a2 2 0 00-2 2v10a2 2 0 002 2h8a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2" />
|
|
138
|
+
</svg>
|
|
139
|
+
<h3 class="mt-2 text-sm font-medium text-gray-900">Loading recent activity...</h3>
|
|
140
|
+
<p class="mt-1 text-sm text-gray-500">This will show the latest entity updates.</p>
|
|
141
|
+
</div>
|
|
142
|
+
</div>
|
|
143
|
+
</div>
|
|
144
|
+
</div>
|
|
145
|
+
</div>
|
|
146
|
+
|
|
147
|
+
<script>
|
|
148
|
+
// Auto-load recent activity on page load
|
|
149
|
+
document.addEventListener('DOMContentLoaded', function() {
|
|
150
|
+
htmx.trigger('#recent-activity-content', 'refresh');
|
|
151
|
+
});
|
|
152
|
+
</script>
|
|
153
|
+
{% endblock %}
|