supervaizer 0.10.5__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.
Files changed (76) hide show
  1. supervaizer/__init__.py +97 -0
  2. supervaizer/__version__.py +10 -0
  3. supervaizer/account.py +308 -0
  4. supervaizer/account_service.py +93 -0
  5. supervaizer/admin/routes.py +1293 -0
  6. supervaizer/admin/static/js/job-start-form.js +373 -0
  7. supervaizer/admin/templates/agent_detail.html +145 -0
  8. supervaizer/admin/templates/agents.html +249 -0
  9. supervaizer/admin/templates/agents_grid.html +82 -0
  10. supervaizer/admin/templates/base.html +233 -0
  11. supervaizer/admin/templates/case_detail.html +230 -0
  12. supervaizer/admin/templates/cases_list.html +182 -0
  13. supervaizer/admin/templates/cases_table.html +134 -0
  14. supervaizer/admin/templates/console.html +389 -0
  15. supervaizer/admin/templates/dashboard.html +153 -0
  16. supervaizer/admin/templates/job_detail.html +192 -0
  17. supervaizer/admin/templates/job_start_test.html +109 -0
  18. supervaizer/admin/templates/jobs_list.html +180 -0
  19. supervaizer/admin/templates/jobs_table.html +122 -0
  20. supervaizer/admin/templates/navigation.html +163 -0
  21. supervaizer/admin/templates/recent_activity.html +81 -0
  22. supervaizer/admin/templates/server.html +105 -0
  23. supervaizer/admin/templates/server_status_cards.html +121 -0
  24. supervaizer/admin/templates/supervaize_instructions.html +212 -0
  25. supervaizer/agent.py +956 -0
  26. supervaizer/case.py +432 -0
  27. supervaizer/cli.py +395 -0
  28. supervaizer/common.py +324 -0
  29. supervaizer/deploy/__init__.py +16 -0
  30. supervaizer/deploy/cli.py +305 -0
  31. supervaizer/deploy/commands/__init__.py +9 -0
  32. supervaizer/deploy/commands/clean.py +294 -0
  33. supervaizer/deploy/commands/down.py +119 -0
  34. supervaizer/deploy/commands/local.py +460 -0
  35. supervaizer/deploy/commands/plan.py +167 -0
  36. supervaizer/deploy/commands/status.py +169 -0
  37. supervaizer/deploy/commands/up.py +281 -0
  38. supervaizer/deploy/docker.py +377 -0
  39. supervaizer/deploy/driver_factory.py +42 -0
  40. supervaizer/deploy/drivers/__init__.py +39 -0
  41. supervaizer/deploy/drivers/aws_app_runner.py +607 -0
  42. supervaizer/deploy/drivers/base.py +196 -0
  43. supervaizer/deploy/drivers/cloud_run.py +570 -0
  44. supervaizer/deploy/drivers/do_app_platform.py +504 -0
  45. supervaizer/deploy/health.py +404 -0
  46. supervaizer/deploy/state.py +210 -0
  47. supervaizer/deploy/templates/Dockerfile.template +44 -0
  48. supervaizer/deploy/templates/debug_env.py +69 -0
  49. supervaizer/deploy/templates/docker-compose.yml.template +37 -0
  50. supervaizer/deploy/templates/dockerignore.template +66 -0
  51. supervaizer/deploy/templates/entrypoint.sh +20 -0
  52. supervaizer/deploy/utils.py +52 -0
  53. supervaizer/event.py +181 -0
  54. supervaizer/examples/controller_template.py +196 -0
  55. supervaizer/instructions.py +145 -0
  56. supervaizer/job.py +392 -0
  57. supervaizer/job_service.py +156 -0
  58. supervaizer/lifecycle.py +417 -0
  59. supervaizer/parameter.py +233 -0
  60. supervaizer/protocol/__init__.py +11 -0
  61. supervaizer/protocol/a2a/__init__.py +21 -0
  62. supervaizer/protocol/a2a/model.py +227 -0
  63. supervaizer/protocol/a2a/routes.py +99 -0
  64. supervaizer/py.typed +1 -0
  65. supervaizer/routes.py +917 -0
  66. supervaizer/server.py +553 -0
  67. supervaizer/server_utils.py +54 -0
  68. supervaizer/storage.py +462 -0
  69. supervaizer/telemetry.py +81 -0
  70. supervaizer/utils/__init__.py +16 -0
  71. supervaizer/utils/version_check.py +56 -0
  72. supervaizer-0.10.5.dist-info/METADATA +317 -0
  73. supervaizer-0.10.5.dist-info/RECORD +76 -0
  74. supervaizer-0.10.5.dist-info/WHEEL +4 -0
  75. supervaizer-0.10.5.dist-info/entry_points.txt +2 -0
  76. supervaizer-0.10.5.dist-info/licenses/LICENSE.md +346 -0
@@ -0,0 +1,249 @@
1
+ {% extends "base.html" %}
2
+
3
+ {% block title %}Agents - 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
+ Agents
12
+ </h2>
13
+ <p class="mt-1 text-sm text-gray-500">Manage AI agents and their configurations</p>
14
+ </div>
15
+ <div class="mt-4 flex md:mt-0 space-x-3">
16
+ <button
17
+ hx-get="/admin/api/agents"
18
+ hx-target="#agents-table-container"
19
+ hx-indicator="#refresh-indicator"
20
+ 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"
21
+ >
22
+ <svg id="refresh-indicator" class="htmx-indicator -ml-1 mr-2 h-4 w-4 animate-spin" fill="none" viewBox="0 0 24 24">
23
+ <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
24
+ <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>
25
+ </svg>
26
+ Refresh
27
+ </button>
28
+ <button
29
+ onclick="showCreateAgentModal()"
30
+ 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 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
31
+ >
32
+ <svg class="-ml-1 mr-2 h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
33
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6v6m0 0v6m0-6h6m-6 0H6"></path>
34
+ </svg>
35
+ New Agent
36
+ </button>
37
+ </div>
38
+ </div>
39
+
40
+ <!-- Filters -->
41
+ <div class="mt-6 bg-white shadow rounded-lg" x-data="{ filtersOpen: false }">
42
+ <div class="px-6 py-4 border-b border-gray-200">
43
+ <button
44
+ @click="filtersOpen = !filtersOpen"
45
+ class="flex items-center text-sm font-medium text-gray-700 hover:text-gray-900"
46
+ >
47
+ <svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
48
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 4a1 1 0 011-1h16a1 1 0 011 1v2.586a1 1 0 01-.293.707l-6.414 6.414a1 1 0 00-.293.707V17l-4 4v-6.586a1 1 0 00-.293-.707L3.293 7.414A1 1 0 013 6.707V4z"></path>
49
+ </svg>
50
+ Filters
51
+ <svg class="w-4 h-4 ml-2 transition-transform" :class="filtersOpen ? 'rotate-180' : 'rotate-0'" fill="none" stroke="currentColor" viewBox="0 0 24 24">
52
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"></path>
53
+ </svg>
54
+ </button>
55
+ </div>
56
+
57
+ <div x-show="filtersOpen" x-cloak class="px-6 py-4 border-b border-gray-200">
58
+ <form
59
+ hx-get="/admin/api/agents"
60
+ hx-target="#agents-table-container"
61
+ hx-trigger="change, submit"
62
+ class="grid grid-cols-1 gap-4 sm:grid-cols-4"
63
+ >
64
+ <!-- Status Filter -->
65
+ <div>
66
+ <label for="status" class="block text-sm font-medium text-gray-700">Status</label>
67
+ <select name="status" id="status" class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm">
68
+ <option value="">All statuses</option>
69
+ <option value="active">Active</option>
70
+ <option value="inactive">Inactive</option>
71
+ <option value="error">Error</option>
72
+ <option value="training">Training</option>
73
+ </select>
74
+ </div>
75
+
76
+ <!-- Agent Type Filter -->
77
+ <div>
78
+ <label for="agent_type" class="block text-sm font-medium text-gray-700">Type</label>
79
+ <select name="agent_type" id="agent_type" class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm">
80
+ <option value="">All types</option>
81
+ <option value="conversational">Conversational</option>
82
+ <option value="analytical">Analytical</option>
83
+ <option value="specialist">Specialist</option>
84
+ <option value="workflow">Workflow</option>
85
+ </select>
86
+ </div>
87
+
88
+ <!-- Search -->
89
+ <div>
90
+ <label for="search" class="block text-sm font-medium text-gray-700">Search</label>
91
+ <input type="text" name="search" id="search" placeholder="Agent name or description..." class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm">
92
+ </div>
93
+
94
+ <!-- Sort -->
95
+ <div>
96
+ <label for="sort" class="block text-sm font-medium text-gray-700">Sort by</label>
97
+ <select name="sort" id="sort" class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm">
98
+ <option value="-created_at">Newest first</option>
99
+ <option value="created_at">Oldest first</option>
100
+ <option value="name">Name A-Z</option>
101
+ <option value="-name">Name Z-A</option>
102
+ <option value="status">Status</option>
103
+ <option value="-last_used">Last used</option>
104
+ </select>
105
+ </div>
106
+ </form>
107
+ </div>
108
+ </div>
109
+
110
+ <!-- Agents Grid -->
111
+ <div id="agents-table-container" class="mt-6">
112
+ {% include "agents_grid.html" %}
113
+ </div>
114
+ </div>
115
+
116
+ <!-- Agent Detail Modal -->
117
+ <div id="agent-modal" class="fixed inset-0 z-50" x-data="{ open: false }" x-show="open" x-cloak>
118
+ <div class="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0">
119
+ <div class="fixed inset-0 transition-opacity bg-gray-500 bg-opacity-75" @click="open = false"></div>
120
+
121
+ <div class="inline-block align-bottom bg-white rounded-lg text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-4xl sm:w-full">
122
+ <div id="agent-modal-content">
123
+ <!-- Content will be loaded here via HTMX -->
124
+ </div>
125
+ </div>
126
+ </div>
127
+ </div>
128
+
129
+ <!-- Instructions Modal -->
130
+ <div id="instructions-modal"
131
+ class="fixed inset-0 z-50"
132
+ x-data="{ open: false, agentSlug: '', instructionsPath: 'supervaize_instructions.html' }"
133
+ x-show="open"
134
+ x-cloak
135
+ @open-instructions.window="agentSlug = $event.detail.slug; instructionsPath = $event.detail.instructionsPath || 'supervaize_instructions.html'; open = true">
136
+ <div class="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0">
137
+ <div class="fixed inset-0 transition-opacity bg-gray-500 bg-opacity-75" @click="open = false"></div>
138
+
139
+ <div class="inline-block align-bottom bg-white rounded-lg text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-6xl sm:w-full">
140
+ <div class="bg-white px-4 pt-5 pb-4 sm:p-6">
141
+ <div class="flex items-center justify-between mb-4">
142
+ <h3 class="text-lg leading-6 font-medium text-gray-900">Supervaize Instructions</h3>
143
+ <button
144
+ @click="open = false"
145
+ class="bg-white rounded-md text-gray-400 hover:text-gray-600 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
146
+ >
147
+ <span class="sr-only">Close</span>
148
+ <svg class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
149
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
150
+ </svg>
151
+ </button>
152
+ </div>
153
+ <div class="mt-4" style="height: 80vh; overflow-y: auto;">
154
+ <iframe
155
+ id="instructions-iframe"
156
+ x-bind:src="agentSlug && instructionsPath ? `/supervaizer/agents/${agentSlug}/${instructionsPath}` : ''"
157
+ class="w-full h-full border-0"
158
+ style="min-height: 600px;"
159
+ ></iframe>
160
+ </div>
161
+ </div>
162
+ </div>
163
+ </div>
164
+ </div>
165
+
166
+ <script>
167
+ // Handle agent modal
168
+ document.body.addEventListener('htmx:afterRequest', function(e) {
169
+ if (e.detail.target.id === 'agent-modal-content') {
170
+ console.log('Agent modal content loaded, showing modal');
171
+ // Show modal after content loads
172
+ const modal = document.getElementById('agent-modal');
173
+ if (modal && modal._x_dataStack && modal._x_dataStack[0]) {
174
+ modal._x_dataStack[0].open = true;
175
+ console.log('Modal opened via Alpine.js');
176
+ } else {
177
+ console.log('Alpine.js not available, using fallback');
178
+ // Fallback: remove x-cloak and set open state manually
179
+ modal.classList.remove('x-cloak');
180
+ modal.style.display = 'block';
181
+ }
182
+ }
183
+ });
184
+
185
+ // Global functions for agent actions
186
+ window.showAgentDetails = function(agentSlug) {
187
+ console.log('Showing agent details for slug:', agentSlug);
188
+ htmx.ajax('GET', `/admin/api/agents/${encodeURIComponent(agentSlug)}`, {target: '#agent-modal-content'});
189
+ };
190
+
191
+ window.showAgentInstructions = function(agentSlug, instructionsPath) {
192
+ console.log('showAgentInstructions called with slug:', agentSlug, 'path:', instructionsPath);
193
+
194
+ if (!agentSlug || agentSlug.trim() === '') {
195
+ console.error('Agent slug is missing or empty');
196
+ return;
197
+ }
198
+
199
+ // Default instructions path if not provided
200
+ instructionsPath = instructionsPath || 'supervaize_instructions.html';
201
+
202
+ const modal = document.getElementById('instructions-modal');
203
+ if (!modal) {
204
+ console.error('Instructions modal not found');
205
+ return;
206
+ }
207
+
208
+ // Dispatch Alpine.js event to open modal (Alpine will handle iframe src reactively)
209
+ const event = new CustomEvent('open-instructions', {
210
+ detail: { slug: agentSlug, instructionsPath: instructionsPath },
211
+ bubbles: true
212
+ });
213
+ modal.dispatchEvent(event);
214
+ console.log('Dispatched open-instructions event with slug:', agentSlug, 'path:', instructionsPath);
215
+
216
+ // Also try direct Alpine.js access as fallback
217
+ if (modal._x_dataStack && modal._x_dataStack[0]) {
218
+ const alpineData = modal._x_dataStack[0];
219
+ alpineData.agentSlug = agentSlug;
220
+ alpineData.instructionsPath = instructionsPath;
221
+ alpineData.open = true;
222
+ console.log('Modal opened via direct Alpine.js access');
223
+ } else {
224
+ console.warn('Alpine.js data not available, modal may not open');
225
+ }
226
+ };
227
+
228
+ window.editAgent = function(agentId) {
229
+ console.log('Edit agent:', agentId);
230
+ // Future: htmx.ajax('GET', `/admin/api/agents/${agentId}/edit`, {target: '#agent-modal-content'});
231
+ };
232
+
233
+ window.showCreateAgentModal = function() {
234
+ console.log('Create new agent');
235
+ // Future: htmx.ajax('GET', '/admin/api/agents/new', {target: '#agent-modal-content'});
236
+ };
237
+
238
+ // Auto-load and refresh agents
239
+ document.addEventListener('DOMContentLoaded', function() {
240
+ // Load agents immediately
241
+ htmx.ajax('GET', '/admin/api/agents', {target: '#agents-table-container'});
242
+
243
+ // Set up auto-refresh interval
244
+ setInterval(function() {
245
+ htmx.ajax('GET', '/admin/api/agents', {target: '#agents-table-container'});
246
+ }, 30000); // Refresh every 30 seconds
247
+ });
248
+ </script>
249
+ {% endblock %}
@@ -0,0 +1,82 @@
1
+ <div class="bg-white shadow overflow-hidden rounded-md">
2
+ <div class="px-4 py-5 sm:p-6">
3
+ {% if agents %}
4
+ <div class="grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-3">
5
+ {% for agent in agents %}
6
+ <div class="bg-white border border-gray-200 rounded-lg shadow-sm hover:shadow-md transition-shadow">
7
+ <div class="p-6">
8
+ <div class="flex items-center justify-between">
9
+ <div class="flex items-center">
10
+ {% if agent.type == "analytical" %}
11
+ <div class="w-10 h-10 bg-purple-100 rounded-full flex items-center justify-center">
12
+ <svg class="w-6 h-6 text-purple-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
13
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z"></path>
14
+ </svg>
15
+ </div>
16
+ {% elif agent.type == "workflow" %}
17
+ <div class="w-10 h-10 bg-yellow-100 rounded-full flex items-center justify-center">
18
+ <svg class="w-6 h-6 text-yellow-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
19
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z"></path>
20
+ </svg>
21
+ </div>
22
+ {% elif agent.type == "specialist" %}
23
+ <div class="w-10 h-10 bg-green-100 rounded-full flex items-center justify-center">
24
+ <svg class="w-6 h-6 text-green-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
25
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.031 9-11.622 0-1.042-.133-2.052-.382-3.016z"></path>
26
+ </svg>
27
+ </div>
28
+ {% else %}
29
+ <div class="w-10 h-10 bg-blue-100 rounded-full flex items-center justify-center">
30
+ <svg class="w-6 h-6 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
31
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9.75 17L9 20l-1 1h8l-1-1-.75-3M3 13h18M5 17h14a2 2 0 002-2V5a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"></path>
32
+ </svg>
33
+ </div>
34
+ {% endif %}
35
+ <div class="ml-3">
36
+ <h3 class="text-lg font-medium text-gray-900">{{ agent.name }} </h3>
37
+ <p class="text-sm text-gray-500 capitalize">{{ agent.type or "conversational" }}</p>
38
+ </div>
39
+ </div>
40
+ <!-- For now, assume all agents are active since we don't have real status -->
41
+ <span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-green-100 text-green-800">
42
+ Active
43
+ </span>
44
+ </div>
45
+ <p class="mt-4 text-sm text-gray-600">
46
+ {{ agent.description or "AI agent ready to assist with various tasks." }}
47
+ </p>
48
+ <div class="mt-4 grid grid-cols-2 gap-4 text-sm">
49
+ <div>
50
+ <span class="text-gray-500">Version:</span>
51
+ <span class="font-medium text-gray-900">{{ agent.version or "1.0.0" }}</span>
52
+ </div>
53
+ <div>
54
+ <span class="text-gray-500">Status:</span>
55
+ <span class="font-medium text-green-600">Ready</span>
56
+ </div>
57
+ </div>
58
+ <div class="mt-6 flex space-x-3">
59
+ {% set agent_slug = agent.slug if agent.slug else (agent.api_path.replace('/agents/', '').rstrip('/') if agent.api_path and agent.api_path.startswith('/agents/') else '') %}
60
+ {% set instructions_path = agent.instructions_path if agent.instructions_path else 'supervaize_instructions.html' %}
61
+ <button onclick="showAgentInstructions('{{ agent_slug }}', '{{ instructions_path }}')" class="flex-1 bg-blue-50 text-blue-700 hover:bg-blue-100 px-3 py-2 rounded-md text-sm font-medium transition-colors">
62
+ View
63
+ </button>
64
+ <button onclick="showAgentDetails('{{ agent_slug }}')" class="flex-1 bg-gray-50 text-gray-700 hover:bg-gray-100 px-3 py-2 rounded-md text-sm font-medium transition-colors">
65
+ Configure
66
+ </button>
67
+ </div>
68
+ </div>
69
+ </div>
70
+ {% endfor %}
71
+ </div>
72
+ {% else %}
73
+ <div class="text-center py-12">
74
+ <svg class="mx-auto h-12 w-12 text-gray-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
75
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9.75 17L9 20l-1 1h8l-1-1-.75-3M3 13h18M5 17h14a2 2 0 002-2V5a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"></path>
76
+ </svg>
77
+ <h3 class="mt-2 text-sm font-medium text-gray-900">No agents available</h3>
78
+ <p class="mt-1 text-sm text-gray-500">No agents are currently configured in the server.</p>
79
+ </div>
80
+ {% endif %}
81
+ </div>
82
+ </div>
@@ -0,0 +1,233 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>{% block title %}Supervaizer Admin{% endblock %}</title>
7
+
8
+ <!-- Tailwind CSS -->
9
+ <script src="https://cdn.tailwindcss.com"></script>
10
+ <script>
11
+ // Suppress Tailwind CDN warning in development
12
+ if (typeof console !== 'undefined' && console.warn) {
13
+ const originalWarn = console.warn;
14
+ console.warn = function(...args) {
15
+ if (args[0] && args[0].includes && args[0].includes('cdn.tailwindcss.com should not be used in production')) {
16
+ return; // Suppress this specific warning
17
+ }
18
+ originalWarn.apply(console, args);
19
+ };
20
+ }
21
+ </script>
22
+
23
+ <!-- HTMX -->
24
+ <script src="https://unpkg.com/htmx.org@1.9.12"></script>
25
+
26
+ <!-- Alpine.js -->
27
+ <script defer src="https://unpkg.com/alpinejs@3.x.x/dist/cdn.min.js"></script>
28
+
29
+ <style>
30
+ [x-cloak] { display: none !important; }
31
+ .htmx-request { opacity: 0.5; }
32
+ .htmx-request.htmx-swapping { opacity: 1; }
33
+ </style>
34
+ </head>
35
+ <body class="bg-gray-50 min-h-screen">
36
+ <!-- Navigation -->
37
+ {% include "navigation.html" %}
38
+
39
+ <!-- Main Content -->
40
+ <main class="max-w-7xl mx-auto py-6 sm:px-6 lg:px-8">
41
+ {% block content %}{% endblock %}
42
+ </main>
43
+
44
+ <!-- Live Console -->
45
+ <div id="live-console" class="fixed bottom-0 left-0 right-0 bg-black text-green-300 font-mono text-xs z-40 border-t border-gray-700" x-data="{ expanded: false, logs: [] }" x-cloak>
46
+ <!-- Console Header -->
47
+ <div class="flex items-center justify-between px-4 py-2 bg-gray-900 border-b border-gray-700">
48
+ <span class="text-green-400 font-semibold">Live Console</span>
49
+ <div class="flex items-center space-x-2">
50
+ <button @click="logs = []" class="text-gray-400 hover:text-white text-xs px-2 py-1 rounded bg-gray-800 hover:bg-gray-700">Clear</button>
51
+ <button @click="expanded = !expanded" class="text-gray-400 hover:text-white">
52
+ <svg class="w-4 h-4 transform transition-transform" :class="expanded ? 'rotate-180' : ''" fill="currentColor" viewBox="0 0 20 20">
53
+ <path fill-rule="evenodd" d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" clip-rule="evenodd"></path>
54
+ </svg>
55
+ </button>
56
+ </div>
57
+ </div>
58
+
59
+ <!-- Console Body -->
60
+ <div id="console-body" x-show="expanded" x-transition:enter="transition ease-out duration-200" x-transition:enter-start="opacity-0 transform scale-95" x-transition:enter-end="opacity-100 transform scale-100" x-transition:leave="transition ease-in duration-150" x-transition:leave-start="opacity-100 transform scale-100" x-transition:leave-end="opacity-0 transform scale-95" class="h-64 overflow-y-auto p-4 space-y-1">
61
+ <template x-for="log in logs" :key="log.id">
62
+ <div class="flex space-x-2" :class="{
63
+ 'text-red-400': log.level === 'ERROR' || log.level === 'CRITICAL',
64
+ 'text-yellow-400': log.level === 'WARNING',
65
+ 'text-blue-400': log.level === 'INFO',
66
+ 'text-green-300': log.level === 'SUCCESS',
67
+ 'text-gray-400': log.level === 'DEBUG',
68
+ 'text-cyan-400': log.level === 'USER',
69
+ 'text-purple-400': log.level === 'SYSTEM'
70
+ }">
71
+ <span class="text-gray-500 whitespace-nowrap" x-text="log.time"></span>
72
+ <span class="font-semibold min-w-[60px]" x-text="log.level"></span>
73
+ <span x-text="log.message"></span>
74
+ </div>
75
+ </template>
76
+ <div x-show="logs.length === 0" class="text-gray-500 text-center py-8">
77
+ No logs yet. Start using the application to see live logs here.
78
+ </div>
79
+ </div>
80
+ </div>
81
+
82
+ <!-- Toast Notifications -->
83
+ <div id="toast-container" class="fixed top-4 right-4 z-50 space-y-2"></div>
84
+
85
+ <script>
86
+ // HTMX Event Handlers
87
+ document.body.addEventListener('htmx:responseError', function(e) {
88
+ showToast('Error: ' + e.detail.xhr.statusText, 'error');
89
+ });
90
+
91
+ document.body.addEventListener('htmx:sendError', function(e) {
92
+ showToast('Network error occurred', 'error');
93
+ });
94
+
95
+ // Toast Notification System
96
+ function showToast(message, type = 'info') {
97
+ const toast = document.createElement('div');
98
+ const bgColor = type === 'error' ? 'bg-red-500' : type === 'success' ? 'bg-green-500' : 'bg-blue-500';
99
+
100
+ toast.className = `${bgColor} text-white px-6 py-3 rounded-lg shadow-lg transform transition-all duration-300 translate-x-full`;
101
+ toast.innerHTML = `
102
+ <div class="flex items-center">
103
+ <span>${message}</span>
104
+ <button onclick="this.parentElement.parentElement.remove()" class="ml-4 text-white hover:text-gray-200">
105
+ <svg class="w-4 h-4" fill="currentColor" viewBox="0 0 20 20">
106
+ <path fill-rule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clip-rule="evenodd"></path>
107
+ </svg>
108
+ </button>
109
+ </div>
110
+ `;
111
+
112
+ document.getElementById('toast-container').appendChild(toast);
113
+
114
+ // Animate in
115
+ setTimeout(() => {
116
+ toast.classList.remove('translate-x-full');
117
+ }, 100);
118
+
119
+ // Auto remove after 5 seconds
120
+ setTimeout(() => {
121
+ toast.classList.add('translate-x-full');
122
+ setTimeout(() => toast.remove(), 300);
123
+ }, 5000);
124
+ }
125
+
126
+ // Make showToast globally available
127
+ window.showToast = showToast;
128
+
129
+ // Live Console - Server-Sent Events
130
+ function initializeLogStream() {
131
+ console.log('initializeLogStream called, pathname:', window.location.pathname);
132
+
133
+ // Skip if we're on the dedicated console page to avoid conflicts
134
+ if (window.location.pathname.includes('/console')) {
135
+ console.log('Skipping live console on console page');
136
+ return;
137
+ }
138
+
139
+ const consoleComponent = document.querySelector('#live-console');
140
+ if (!consoleComponent) {
141
+ console.error('Live console component not found');
142
+ return;
143
+ }
144
+
145
+ console.log('Initializing live console log stream');
146
+
147
+ let eventSource = null;
148
+ let logCounter = 0;
149
+
150
+ function connectLogStream() {
151
+ if (eventSource) {
152
+ eventSource.close();
153
+ }
154
+
155
+ // For live console, always connect without authentication (admin interface)
156
+ const streamUrl = '/admin/log-stream';
157
+ eventSource = new EventSource(streamUrl);
158
+
159
+ eventSource.onmessage = function(event) {
160
+ try {
161
+ console.log('Received log message:', event.data);
162
+ // Handle different event data formats
163
+ let logData;
164
+ if (event.data.startsWith('data: ')) {
165
+ // Remove "data: " prefix if present
166
+ logData = JSON.parse(event.data.substring(6));
167
+ } else {
168
+ logData = JSON.parse(event.data);
169
+ }
170
+ const logEntry = {
171
+ id: ++logCounter,
172
+ time: new Date(logData.timestamp).toLocaleTimeString(),
173
+ level: logData.level,
174
+ message: logData.message
175
+ };
176
+
177
+ // Get Alpine.js component data
178
+ const alpineData = Alpine.$data(consoleComponent);
179
+
180
+ // Add log and limit to last 1000 entries
181
+ alpineData.logs.push(logEntry);
182
+ if (alpineData.logs.length > 1000) {
183
+ alpineData.logs.shift();
184
+ }
185
+ console.log('Added log entry, total logs:', alpineData.logs.length);
186
+
187
+ // Auto-scroll to bottom if expanded
188
+ if (alpineData.expanded) {
189
+ const consoleBody = document.getElementById('console-body');
190
+ if (consoleBody) {
191
+ setTimeout(() => {
192
+ consoleBody.scrollTop = consoleBody.scrollHeight;
193
+ }, 10);
194
+ }
195
+ }
196
+ } catch (error) {
197
+ console.error('Error processing log message:', error, event.data);
198
+ }
199
+ };
200
+
201
+ eventSource.onerror = function(event) {
202
+ console.error('Log stream error:', event);
203
+ showToast('Log stream disconnected. Attempting to reconnect...', 'error');
204
+
205
+ // Attempt reconnection after 5 seconds
206
+ setTimeout(() => {
207
+ connectLogStream();
208
+ }, 5000);
209
+ };
210
+
211
+ eventSource.onopen = function(event) {
212
+ console.log('Log stream connected successfully');
213
+ };
214
+ }
215
+
216
+ // Initialize connection
217
+ connectLogStream();
218
+
219
+ // Cleanup on page unload
220
+ window.addEventListener('beforeunload', function() {
221
+ if (eventSource) {
222
+ eventSource.close();
223
+ }
224
+ });
225
+ }
226
+
227
+ // Initialize when Alpine.js is ready
228
+ document.addEventListener('alpine:init', function() {
229
+ initializeLogStream();
230
+ });
231
+ </script>
232
+ </body>
233
+ </html>