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,192 @@
|
|
|
1
|
+
<div class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
|
|
2
|
+
<div class="sm:flex sm:items-start">
|
|
3
|
+
<div class="w-full">
|
|
4
|
+
<!-- Header -->
|
|
5
|
+
<div class="flex items-center justify-between mb-4">
|
|
6
|
+
<h3 class="text-lg leading-6 font-medium text-gray-900">Job Details</h3>
|
|
7
|
+
<button
|
|
8
|
+
@click="open = false"
|
|
9
|
+
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"
|
|
10
|
+
>
|
|
11
|
+
<span class="sr-only">Close</span>
|
|
12
|
+
<svg class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
13
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
|
14
|
+
</svg>
|
|
15
|
+
</button>
|
|
16
|
+
</div>
|
|
17
|
+
|
|
18
|
+
<!-- Job Information -->
|
|
19
|
+
<div class="grid grid-cols-1 gap-6 sm:grid-cols-2">
|
|
20
|
+
<div>
|
|
21
|
+
<h4 class="text-sm font-medium text-gray-900 mb-3">Basic Information</h4>
|
|
22
|
+
<dl class="space-y-2">
|
|
23
|
+
<div>
|
|
24
|
+
<dt class="text-xs font-medium text-gray-500 uppercase tracking-wider">Name</dt>
|
|
25
|
+
<dd class="text-sm text-gray-900">{{ job.name or "Unnamed Job" }}</dd>
|
|
26
|
+
</div>
|
|
27
|
+
<div>
|
|
28
|
+
<dt class="text-xs font-medium text-gray-500 uppercase tracking-wider">ID</dt>
|
|
29
|
+
<dd class="text-sm text-gray-900 font-mono">{{ job.id }}</dd>
|
|
30
|
+
</div>
|
|
31
|
+
<div>
|
|
32
|
+
<dt class="text-xs font-medium text-gray-500 uppercase tracking-wider">Agent</dt>
|
|
33
|
+
<dd class="text-sm text-gray-900">{{ job.agent_name or "-" }}</dd>
|
|
34
|
+
</div>
|
|
35
|
+
<div>
|
|
36
|
+
<dt class="text-xs font-medium text-gray-500 uppercase tracking-wider">Status</dt>
|
|
37
|
+
<dd>
|
|
38
|
+
<span class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full
|
|
39
|
+
{% if job.status == 'completed' %}bg-green-100 text-green-800
|
|
40
|
+
{% elif job.status == 'in_progress' %}bg-blue-100 text-blue-800
|
|
41
|
+
{% elif job.status == 'failed' %}bg-red-100 text-red-800
|
|
42
|
+
{% elif job.status == 'cancelled' %}bg-gray-100 text-gray-800
|
|
43
|
+
{% else %}bg-yellow-100 text-yellow-800{% endif %}">
|
|
44
|
+
{{ job.status.replace('_', ' ').title() }}
|
|
45
|
+
</span>
|
|
46
|
+
</dd>
|
|
47
|
+
</div>
|
|
48
|
+
</dl>
|
|
49
|
+
</div>
|
|
50
|
+
|
|
51
|
+
<div>
|
|
52
|
+
<h4 class="text-sm font-medium text-gray-900 mb-3">Timestamps</h4>
|
|
53
|
+
<dl class="space-y-2">
|
|
54
|
+
<div>
|
|
55
|
+
<dt class="text-xs font-medium text-gray-500 uppercase tracking-wider">Created</dt>
|
|
56
|
+
<dd class="text-sm text-gray-900">{{ job.created_at or "-" }}</dd>
|
|
57
|
+
</div>
|
|
58
|
+
<div>
|
|
59
|
+
<dt class="text-xs font-medium text-gray-500 uppercase tracking-wider">Finished</dt>
|
|
60
|
+
<dd class="text-sm text-gray-900">{{ job.finished_at or "-" }}</dd>
|
|
61
|
+
</div>
|
|
62
|
+
<div>
|
|
63
|
+
<dt class="text-xs font-medium text-gray-500 uppercase tracking-wider">Cases</dt>
|
|
64
|
+
<dd class="text-sm text-gray-900">{{ (job.case_ids | length) if job.case_ids else 0 }} related case(s)</dd>
|
|
65
|
+
</div>
|
|
66
|
+
</dl>
|
|
67
|
+
</div>
|
|
68
|
+
</div>
|
|
69
|
+
|
|
70
|
+
<!-- Job Context -->
|
|
71
|
+
{% if job.job_context %}
|
|
72
|
+
<div class="mt-6">
|
|
73
|
+
<h4 class="text-sm font-medium text-gray-900 mb-3">Job Context</h4>
|
|
74
|
+
<div class="bg-gray-50 p-4 rounded-lg">
|
|
75
|
+
<dl class="grid grid-cols-1 gap-2 sm:grid-cols-2">
|
|
76
|
+
{% if job.job_context.workspace_id %}
|
|
77
|
+
<div>
|
|
78
|
+
<dt class="text-xs font-medium text-gray-500">Workspace ID</dt>
|
|
79
|
+
<dd class="text-sm text-gray-900 font-mono">{{ job.job_context.workspace_id }}</dd>
|
|
80
|
+
</div>
|
|
81
|
+
{% endif %}
|
|
82
|
+
{% if job.job_context.mission_name %}
|
|
83
|
+
<div>
|
|
84
|
+
<dt class="text-xs font-medium text-gray-500">Mission</dt>
|
|
85
|
+
<dd class="text-sm text-gray-900">{{ job.job_context.mission_name }}</dd>
|
|
86
|
+
</div>
|
|
87
|
+
{% endif %}
|
|
88
|
+
{% if job.job_context.started_by %}
|
|
89
|
+
<div>
|
|
90
|
+
<dt class="text-xs font-medium text-gray-500">Started By</dt>
|
|
91
|
+
<dd class="text-sm text-gray-900">{{ job.job_context.started_by }}</dd>
|
|
92
|
+
</div>
|
|
93
|
+
{% endif %}
|
|
94
|
+
</dl>
|
|
95
|
+
</div>
|
|
96
|
+
</div>
|
|
97
|
+
{% endif %}
|
|
98
|
+
|
|
99
|
+
<!-- Related Cases -->
|
|
100
|
+
{% if cases %}
|
|
101
|
+
<div class="mt-6">
|
|
102
|
+
<h4 class="text-sm font-medium text-gray-900 mb-3">Related Cases ({{ cases | length }})</h4>
|
|
103
|
+
<div class="bg-white overflow-hidden shadow rounded-lg">
|
|
104
|
+
<ul role="list" class="divide-y divide-gray-200">
|
|
105
|
+
{% for case in cases %}
|
|
106
|
+
<li class="px-4 py-3 hover:bg-gray-50">
|
|
107
|
+
<div class="flex items-center justify-between">
|
|
108
|
+
<div class="flex items-center">
|
|
109
|
+
<div>
|
|
110
|
+
<p class="text-sm font-medium text-gray-900">{{ case.name or case.id[:8] + "..." }}</p>
|
|
111
|
+
<p class="text-sm text-gray-500">{{ case.description or "No description" }}</p>
|
|
112
|
+
</div>
|
|
113
|
+
</div>
|
|
114
|
+
<div class="flex items-center space-x-3">
|
|
115
|
+
<span class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full
|
|
116
|
+
{% if case.status == 'completed' %}bg-green-100 text-green-800
|
|
117
|
+
{% elif case.status == 'in_progress' %}bg-blue-100 text-blue-800
|
|
118
|
+
{% elif case.status == 'failed' %}bg-red-100 text-red-800
|
|
119
|
+
{% elif case.status == 'cancelled' %}bg-gray-100 text-gray-800
|
|
120
|
+
{% else %}bg-yellow-100 text-yellow-800{% endif %}">
|
|
121
|
+
{{ case.status.replace('_', ' ').title() }}
|
|
122
|
+
</span>
|
|
123
|
+
<button
|
|
124
|
+
onclick="showCaseDetails('{{ case.id }}')"
|
|
125
|
+
class="text-blue-600 hover:text-blue-900 text-sm font-medium"
|
|
126
|
+
>
|
|
127
|
+
View
|
|
128
|
+
</button>
|
|
129
|
+
</div>
|
|
130
|
+
</div>
|
|
131
|
+
</li>
|
|
132
|
+
{% endfor %}
|
|
133
|
+
</ul>
|
|
134
|
+
</div>
|
|
135
|
+
</div>
|
|
136
|
+
{% endif %}
|
|
137
|
+
</div>
|
|
138
|
+
</div>
|
|
139
|
+
</div>
|
|
140
|
+
|
|
141
|
+
<!-- Modal Footer -->
|
|
142
|
+
<div class="bg-gray-50 px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse">
|
|
143
|
+
<button
|
|
144
|
+
@click="open = false"
|
|
145
|
+
type="button"
|
|
146
|
+
class="mt-3 w-full inline-flex justify-center rounded-md border border-gray-300 shadow-sm px-4 py-2 bg-white text-base font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm"
|
|
147
|
+
>
|
|
148
|
+
Close
|
|
149
|
+
</button>
|
|
150
|
+
|
|
151
|
+
<!-- Status Update Button -->
|
|
152
|
+
{% if job.status not in ['completed', 'failed', 'cancelled'] %}
|
|
153
|
+
<div class="relative inline-block text-left" x-data="{ statusOpen: false }">
|
|
154
|
+
<button
|
|
155
|
+
@click="statusOpen = !statusOpen"
|
|
156
|
+
type="button"
|
|
157
|
+
class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm"
|
|
158
|
+
>
|
|
159
|
+
Update Status
|
|
160
|
+
</button>
|
|
161
|
+
|
|
162
|
+
<div x-show="statusOpen" @click.away="statusOpen = false" x-cloak class="origin-top-right absolute right-0 mt-2 w-56 rounded-md shadow-lg bg-white ring-1 ring-black ring-opacity-5 z-50">
|
|
163
|
+
<div class="py-1" role="menu">
|
|
164
|
+
<button
|
|
165
|
+
hx-post="/admin/api/jobs/{{ job.id }}/status"
|
|
166
|
+
hx-vals='{"status": "completed"}'
|
|
167
|
+
hx-on::after-request="if(event.detail.successful) { window.showToast('Job status updated', 'success'); open = false; htmx.trigger('#jobs-table-container', 'refresh'); }"
|
|
168
|
+
class="block w-full text-left px-4 py-2 text-sm text-gray-700 hover:bg-gray-100"
|
|
169
|
+
>
|
|
170
|
+
Mark as Completed
|
|
171
|
+
</button>
|
|
172
|
+
<button
|
|
173
|
+
hx-post="/admin/api/jobs/{{ job.id }}/status"
|
|
174
|
+
hx-vals='{"status": "failed"}'
|
|
175
|
+
hx-on::after-request="if(event.detail.successful) { window.showToast('Job status updated', 'success'); open = false; htmx.trigger('#jobs-table-container', 'refresh'); }"
|
|
176
|
+
class="block w-full text-left px-4 py-2 text-sm text-gray-700 hover:bg-gray-100"
|
|
177
|
+
>
|
|
178
|
+
Mark as Failed
|
|
179
|
+
</button>
|
|
180
|
+
<button
|
|
181
|
+
hx-post="/admin/api/jobs/{{ job.id }}/status"
|
|
182
|
+
hx-vals='{"status": "cancelled"}'
|
|
183
|
+
hx-on::after-request="if(event.detail.successful) { window.showToast('Job status updated', 'success'); open = false; htmx.trigger('#jobs-table-container', 'refresh'); }"
|
|
184
|
+
class="block w-full text-left px-4 py-2 text-sm text-gray-700 hover:bg-gray-100"
|
|
185
|
+
>
|
|
186
|
+
Cancel Job
|
|
187
|
+
</button>
|
|
188
|
+
</div>
|
|
189
|
+
</div>
|
|
190
|
+
</div>
|
|
191
|
+
{% endif %}
|
|
192
|
+
</div>
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
{% extends "base.html" %}
|
|
2
|
+
|
|
3
|
+
{% block title %}Jobs - 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
|
+
Jobs
|
|
12
|
+
</h2>
|
|
13
|
+
<p class="mt-1 text-sm text-gray-500">Manage all jobs in the system</p>
|
|
14
|
+
</div>
|
|
15
|
+
<div class="mt-4 flex md:mt-0">
|
|
16
|
+
<button
|
|
17
|
+
hx-get="/admin/api/jobs"
|
|
18
|
+
hx-target="#jobs-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
|
+
</div>
|
|
29
|
+
</div>
|
|
30
|
+
|
|
31
|
+
<!-- Filters -->
|
|
32
|
+
<div class="mt-6 bg-white shadow rounded-lg" x-data="{ filtersOpen: false }">
|
|
33
|
+
<div class="px-6 py-4 border-b border-gray-200">
|
|
34
|
+
<button
|
|
35
|
+
@click="filtersOpen = !filtersOpen"
|
|
36
|
+
class="flex items-center text-sm font-medium text-gray-700 hover:text-gray-900"
|
|
37
|
+
>
|
|
38
|
+
<svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
39
|
+
<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>
|
|
40
|
+
</svg>
|
|
41
|
+
Filters
|
|
42
|
+
<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">
|
|
43
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"></path>
|
|
44
|
+
</svg>
|
|
45
|
+
</button>
|
|
46
|
+
</div>
|
|
47
|
+
|
|
48
|
+
<div x-show="filtersOpen" x-cloak class="px-6 py-4 border-b border-gray-200">
|
|
49
|
+
<form
|
|
50
|
+
hx-get="/admin/api/jobs"
|
|
51
|
+
hx-target="#jobs-table-container"
|
|
52
|
+
hx-trigger="change, submit"
|
|
53
|
+
class="grid grid-cols-1 gap-4 sm:grid-cols-4"
|
|
54
|
+
>
|
|
55
|
+
<!-- Status Filter -->
|
|
56
|
+
<div>
|
|
57
|
+
<label for="status" class="block text-sm font-medium text-gray-700">Status</label>
|
|
58
|
+
<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">
|
|
59
|
+
<option value="">All statuses</option>
|
|
60
|
+
<option value="stopped">Stopped</option>
|
|
61
|
+
<option value="in_progress">In Progress</option>
|
|
62
|
+
<option value="completed">Completed</option>
|
|
63
|
+
<option value="failed">Failed</option>
|
|
64
|
+
<option value="cancelled">Cancelled</option>
|
|
65
|
+
<option value="awaiting">Awaiting</option>
|
|
66
|
+
</select>
|
|
67
|
+
</div>
|
|
68
|
+
|
|
69
|
+
<!-- Agent Filter -->
|
|
70
|
+
<div>
|
|
71
|
+
<label for="agent_name" class="block text-sm font-medium text-gray-700">Agent</label>
|
|
72
|
+
<input type="text" name="agent_name" id="agent_name" placeholder="Agent name..." class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm">
|
|
73
|
+
</div>
|
|
74
|
+
|
|
75
|
+
<!-- Search -->
|
|
76
|
+
<div>
|
|
77
|
+
<label for="search" class="block text-sm font-medium text-gray-700">Search</label>
|
|
78
|
+
<input type="text" name="search" id="search" placeholder="Job name or ID..." class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm">
|
|
79
|
+
</div>
|
|
80
|
+
|
|
81
|
+
<!-- Sort -->
|
|
82
|
+
<div>
|
|
83
|
+
<label for="sort" class="block text-sm font-medium text-gray-700">Sort by</label>
|
|
84
|
+
<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">
|
|
85
|
+
<option value="-created_at">Newest first</option>
|
|
86
|
+
<option value="created_at">Oldest first</option>
|
|
87
|
+
<option value="name">Name A-Z</option>
|
|
88
|
+
<option value="-name">Name Z-A</option>
|
|
89
|
+
<option value="status">Status</option>
|
|
90
|
+
</select>
|
|
91
|
+
</div>
|
|
92
|
+
</form>
|
|
93
|
+
</div>
|
|
94
|
+
</div>
|
|
95
|
+
|
|
96
|
+
<!-- Jobs Table -->
|
|
97
|
+
<div id="jobs-table-container" class="mt-6">
|
|
98
|
+
<div class="bg-white shadow overflow-hidden rounded-md">
|
|
99
|
+
<div class="px-4 py-5 sm:p-6">
|
|
100
|
+
<div class="text-center">
|
|
101
|
+
<svg class="mx-auto h-12 w-12 text-gray-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
102
|
+
<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" />
|
|
103
|
+
</svg>
|
|
104
|
+
<h3 class="mt-2 text-sm font-medium text-gray-900">Loading jobs...</h3>
|
|
105
|
+
<p class="mt-1 text-sm text-gray-500">Please wait while we fetch the job data.</p>
|
|
106
|
+
</div>
|
|
107
|
+
</div>
|
|
108
|
+
</div>
|
|
109
|
+
</div>
|
|
110
|
+
</div>
|
|
111
|
+
|
|
112
|
+
<!-- Job Detail Modal -->
|
|
113
|
+
<div id="job-modal" class="fixed inset-0 z-50" x-data="{ open: false }" x-show="open" x-cloak>
|
|
114
|
+
<div class="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0">
|
|
115
|
+
<div class="fixed inset-0 transition-opacity bg-gray-500 bg-opacity-75" @click="open = false"></div>
|
|
116
|
+
|
|
117
|
+
<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">
|
|
118
|
+
<div id="job-modal-content">
|
|
119
|
+
<!-- Content will be loaded here via HTMX -->
|
|
120
|
+
</div>
|
|
121
|
+
</div>
|
|
122
|
+
</div>
|
|
123
|
+
</div>
|
|
124
|
+
|
|
125
|
+
<!-- Case Detail Modal -->
|
|
126
|
+
<div id="case-modal" class="fixed inset-0 z-50" x-data="{ open: false }" x-show="open" x-cloak>
|
|
127
|
+
<div class="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0">
|
|
128
|
+
<div class="fixed inset-0 transition-opacity bg-gray-500 bg-opacity-75" @click="open = false"></div>
|
|
129
|
+
|
|
130
|
+
<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">
|
|
131
|
+
<div id="case-modal-content">
|
|
132
|
+
<!-- Content will be loaded here via HTMX -->
|
|
133
|
+
</div>
|
|
134
|
+
</div>
|
|
135
|
+
</div>
|
|
136
|
+
</div>
|
|
137
|
+
|
|
138
|
+
<script>
|
|
139
|
+
// Auto-load jobs on page load
|
|
140
|
+
document.addEventListener('DOMContentLoaded', function() {
|
|
141
|
+
// Load jobs immediately
|
|
142
|
+
htmx.ajax('GET', '/admin/api/jobs', {target: '#jobs-table-container'});
|
|
143
|
+
|
|
144
|
+
// Set up auto-refresh interval for jobs every 30 seconds
|
|
145
|
+
setInterval(function() {
|
|
146
|
+
htmx.ajax('GET', '/admin/api/jobs', {target: '#jobs-table-container'});
|
|
147
|
+
}, 30000); // Refresh every 30 seconds
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
// Handle job modal
|
|
151
|
+
document.body.addEventListener('htmx:afterRequest', function(e) {
|
|
152
|
+
if (e.detail.target.id === 'job-modal-content') {
|
|
153
|
+
// Show modal after content loads
|
|
154
|
+
const modal = document.getElementById('job-modal');
|
|
155
|
+
if (modal && modal._x_dataStack && modal._x_dataStack[0]) {
|
|
156
|
+
modal._x_dataStack[0].open = true;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Handle case modal
|
|
161
|
+
if (e.detail.target.id === 'case-modal-content') {
|
|
162
|
+
// Show modal after content loads
|
|
163
|
+
const modal = document.getElementById('case-modal');
|
|
164
|
+
if (modal && modal._x_dataStack && modal._x_dataStack[0]) {
|
|
165
|
+
modal._x_dataStack[0].open = true;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
// Global function to show job details
|
|
171
|
+
window.showJobDetails = function(jobId) {
|
|
172
|
+
htmx.ajax('GET', `/admin/api/jobs/${jobId}`, {target: '#job-modal-content'});
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
// Global function to show case details
|
|
176
|
+
window.showCaseDetails = function(caseId) {
|
|
177
|
+
htmx.ajax('GET', `/admin/api/cases/${caseId}`, {target: '#case-modal-content'});
|
|
178
|
+
};
|
|
179
|
+
</script>
|
|
180
|
+
{% endblock %}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
<div class="bg-white shadow overflow-hidden rounded-md">
|
|
2
|
+
<table class="min-w-full divide-y divide-gray-200">
|
|
3
|
+
<thead class="bg-gray-50">
|
|
4
|
+
<tr>
|
|
5
|
+
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
|
6
|
+
Job
|
|
7
|
+
</th>
|
|
8
|
+
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
|
9
|
+
Agent
|
|
10
|
+
</th>
|
|
11
|
+
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
|
12
|
+
Status
|
|
13
|
+
</th>
|
|
14
|
+
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
|
15
|
+
Cases
|
|
16
|
+
</th>
|
|
17
|
+
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
|
18
|
+
Created
|
|
19
|
+
</th>
|
|
20
|
+
<th scope="col" class="relative px-6 py-3">
|
|
21
|
+
<span class="sr-only">Actions</span>
|
|
22
|
+
</th>
|
|
23
|
+
</tr>
|
|
24
|
+
</thead>
|
|
25
|
+
<tbody class="bg-white divide-y divide-gray-200">
|
|
26
|
+
{% for job in jobs %}
|
|
27
|
+
<tr class="hover:bg-gray-50">
|
|
28
|
+
<td class="px-6 py-4 whitespace-nowrap">
|
|
29
|
+
<div>
|
|
30
|
+
<div class="text-sm font-medium text-gray-900">{{ job.name }}</div>
|
|
31
|
+
<div class="text-sm text-gray-500">{{ job.id }}</div>
|
|
32
|
+
</div>
|
|
33
|
+
</td>
|
|
34
|
+
<td class="px-6 py-4 whitespace-nowrap">
|
|
35
|
+
<div class="text-sm text-gray-900">{{ job.agent_name }}</div>
|
|
36
|
+
</td>
|
|
37
|
+
<td class="px-6 py-4 whitespace-nowrap">
|
|
38
|
+
<span class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full
|
|
39
|
+
{% if job.status == 'completed' %}bg-green-100 text-green-800
|
|
40
|
+
{% elif job.status == 'in_progress' %}bg-blue-100 text-blue-800
|
|
41
|
+
{% elif job.status == 'failed' %}bg-red-100 text-red-800
|
|
42
|
+
{% elif job.status == 'cancelled' %}bg-gray-100 text-gray-800
|
|
43
|
+
{% else %}bg-yellow-100 text-yellow-800{% endif %}">
|
|
44
|
+
{{ job.status.replace('_', ' ').title() }}
|
|
45
|
+
</span>
|
|
46
|
+
</td>
|
|
47
|
+
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
|
|
48
|
+
{{ job.case_count }}
|
|
49
|
+
</td>
|
|
50
|
+
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
|
|
51
|
+
{% if job.created_at %}
|
|
52
|
+
{{ job.created_at[:10] }}
|
|
53
|
+
{% else %}
|
|
54
|
+
-
|
|
55
|
+
{% endif %}
|
|
56
|
+
</td>
|
|
57
|
+
<td class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
|
|
58
|
+
<button
|
|
59
|
+
onclick="showJobDetails('{{ job.id }}')"
|
|
60
|
+
class="text-blue-600 hover:text-blue-900 mr-4"
|
|
61
|
+
>
|
|
62
|
+
View
|
|
63
|
+
</button>
|
|
64
|
+
<button
|
|
65
|
+
hx-delete="/admin/api/jobs/{{ job.id }}"
|
|
66
|
+
hx-confirm="Are you sure you want to delete this job and all its cases?"
|
|
67
|
+
hx-trigger="click"
|
|
68
|
+
hx-on::after-request="if(event.detail.successful) { window.showToast('Job deleted successfully', 'success'); htmx.trigger('#jobs-table-container', 'refresh'); }"
|
|
69
|
+
class="text-red-600 hover:text-red-900"
|
|
70
|
+
>
|
|
71
|
+
Delete
|
|
72
|
+
</button>
|
|
73
|
+
</td>
|
|
74
|
+
</tr>
|
|
75
|
+
{% else %}
|
|
76
|
+
<tr>
|
|
77
|
+
<td colspan="6" class="px-6 py-12 text-center">
|
|
78
|
+
<svg class="mx-auto h-12 w-12 text-gray-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
79
|
+
<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" />
|
|
80
|
+
</svg>
|
|
81
|
+
<h3 class="mt-2 text-sm font-medium text-gray-900">No jobs found</h3>
|
|
82
|
+
<p class="mt-1 text-sm text-gray-500">Try adjusting your filters or search criteria.</p>
|
|
83
|
+
</td>
|
|
84
|
+
</tr>
|
|
85
|
+
{% endfor %}
|
|
86
|
+
</tbody>
|
|
87
|
+
</table>
|
|
88
|
+
|
|
89
|
+
<!-- Pagination -->
|
|
90
|
+
{% if total > 0 %}
|
|
91
|
+
<div class="bg-white px-4 py-3 border-t border-gray-200 sm:px-6">
|
|
92
|
+
<div class="flex items-center justify-between">
|
|
93
|
+
<div class="flex items-center">
|
|
94
|
+
<p class="text-sm text-gray-700">
|
|
95
|
+
Showing {{ skip + 1 }} to {{ skip + jobs|length }} of {{ total }} results
|
|
96
|
+
</p>
|
|
97
|
+
</div>
|
|
98
|
+
<div class="flex space-x-2">
|
|
99
|
+
{% if has_prev %}
|
|
100
|
+
<button
|
|
101
|
+
hx-get="/admin/api/jobs?skip={{ skip - limit }}&limit={{ limit }}"
|
|
102
|
+
hx-target="#jobs-table-container"
|
|
103
|
+
class="px-3 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-md hover:bg-gray-50"
|
|
104
|
+
>
|
|
105
|
+
Previous
|
|
106
|
+
</button>
|
|
107
|
+
{% endif %}
|
|
108
|
+
|
|
109
|
+
{% if has_next %}
|
|
110
|
+
<button
|
|
111
|
+
hx-get="/admin/api/jobs?skip={{ skip + limit }}&limit={{ limit }}"
|
|
112
|
+
hx-target="#jobs-table-container"
|
|
113
|
+
class="px-3 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-md hover:bg-gray-50"
|
|
114
|
+
>
|
|
115
|
+
Next
|
|
116
|
+
</button>
|
|
117
|
+
{% endif %}
|
|
118
|
+
</div>
|
|
119
|
+
</div>
|
|
120
|
+
</div>
|
|
121
|
+
{% endif %}
|
|
122
|
+
</div>
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
<!-- Navigation Component -->
|
|
2
|
+
<nav class="bg-white shadow-sm border-b border-gray-200" x-data="{ mobileMenuOpen: false, jobsDropdownOpen: false, docsDropdownOpen: false }">
|
|
3
|
+
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
|
4
|
+
<div class="flex justify-between h-16">
|
|
5
|
+
<div class="flex">
|
|
6
|
+
<!-- Logo -->
|
|
7
|
+
<div class="flex-shrink-0 flex items-center">
|
|
8
|
+
<a href="/admin" class="flex items-center">
|
|
9
|
+
<div class="w-10 h-10 mr-3 flex items-center justify-center">
|
|
10
|
+
<img src="https://cdn.do.supervaize.com/brand/logos/pill-black-green-orange.svg" alt="Supervaizer" class="w-10 h-10">
|
|
11
|
+
</div>
|
|
12
|
+
<h1 class="text-xl font-bold text-gray-900">Supervaizer</h1>
|
|
13
|
+
</a>
|
|
14
|
+
</div>
|
|
15
|
+
|
|
16
|
+
<!-- Desktop Navigation -->
|
|
17
|
+
<div class="hidden sm:ml-6 sm:flex sm:space-x-1 items-center">
|
|
18
|
+
<!-- Dashboard -->
|
|
19
|
+
<a href="/admin"
|
|
20
|
+
class="nav-link text-gray-500 hover:text-gray-700 hover:bg-gray-50 px-3 py-2 rounded-md text-sm font-medium transition-colors"
|
|
21
|
+
:class="window.location.pathname === '/admin' ? 'text-blue-600 bg-blue-50' : ''">
|
|
22
|
+
Dashboard
|
|
23
|
+
</a>
|
|
24
|
+
|
|
25
|
+
<!-- Server -->
|
|
26
|
+
<a href="/admin/server"
|
|
27
|
+
class="nav-link text-gray-500 hover:text-gray-700 hover:bg-gray-50 px-3 py-2 rounded-md text-sm font-medium transition-colors"
|
|
28
|
+
:class="window.location.pathname === '/admin/server' ? 'text-blue-600 bg-blue-50' : ''">
|
|
29
|
+
Server
|
|
30
|
+
</a>
|
|
31
|
+
|
|
32
|
+
<!-- Agents -->
|
|
33
|
+
<a href="/admin/agents"
|
|
34
|
+
class="nav-link text-gray-500 hover:text-gray-700 hover:bg-gray-50 px-3 py-2 rounded-md text-sm font-medium transition-colors"
|
|
35
|
+
:class="window.location.pathname === '/admin/agents' ? 'text-blue-600 bg-blue-50' : ''">
|
|
36
|
+
Agents
|
|
37
|
+
</a>
|
|
38
|
+
|
|
39
|
+
<!-- Jobs Dropdown -->
|
|
40
|
+
<div class="relative" @click.away="jobsDropdownOpen = false">
|
|
41
|
+
<button @click="jobsDropdownOpen = !jobsDropdownOpen"
|
|
42
|
+
class="nav-link text-gray-500 hover:text-gray-700 hover:bg-gray-50 px-3 py-2 rounded-md text-sm font-medium transition-colors flex items-center"
|
|
43
|
+
:class="window.location.pathname.startsWith('/admin/jobs') || window.location.pathname.startsWith('/admin/cases') ? 'text-blue-600 bg-blue-50' : ''">
|
|
44
|
+
Jobs
|
|
45
|
+
<svg class="ml-1 h-4 w-4 transition-transform" :class="jobsDropdownOpen ? 'rotate-180' : ''" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
46
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"></path>
|
|
47
|
+
</svg>
|
|
48
|
+
</button>
|
|
49
|
+
|
|
50
|
+
<div x-show="jobsDropdownOpen"
|
|
51
|
+
x-transition:enter="transition ease-out duration-200"
|
|
52
|
+
x-transition:enter-start="opacity-0 transform scale-95"
|
|
53
|
+
x-transition:enter-end="opacity-100 transform scale-100"
|
|
54
|
+
x-transition:leave="transition ease-in duration-150"
|
|
55
|
+
x-transition:leave-start="opacity-100 transform scale-100"
|
|
56
|
+
x-transition:leave-end="opacity-0 transform scale-95"
|
|
57
|
+
class="absolute left-0 mt-2 w-48 rounded-md shadow-lg bg-white ring-1 ring-black ring-opacity-5 z-50"
|
|
58
|
+
x-cloak>
|
|
59
|
+
<div class="py-1">
|
|
60
|
+
<a href="/admin/jobs" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 transition-colors">All Jobs</a>
|
|
61
|
+
<a href="/admin/cases" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 transition-colors">Cases</a>
|
|
62
|
+
</div>
|
|
63
|
+
</div>
|
|
64
|
+
</div>
|
|
65
|
+
|
|
66
|
+
<!-- Documentation Dropdown -->
|
|
67
|
+
<div class="relative" @click.away="docsDropdownOpen = false">
|
|
68
|
+
<button @click="docsDropdownOpen = !docsDropdownOpen"
|
|
69
|
+
class="nav-link text-gray-500 hover:text-gray-700 hover:bg-gray-50 px-3 py-2 rounded-md text-sm font-medium transition-colors flex items-center">
|
|
70
|
+
Documentation
|
|
71
|
+
<svg class="ml-1 h-4 w-4 transition-transform" :class="docsDropdownOpen ? 'rotate-180' : ''" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
72
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"></path>
|
|
73
|
+
</svg>
|
|
74
|
+
</button>
|
|
75
|
+
|
|
76
|
+
<div x-show="docsDropdownOpen"
|
|
77
|
+
x-transition:enter="transition ease-out duration-200"
|
|
78
|
+
x-transition:enter-start="opacity-0 transform scale-95"
|
|
79
|
+
x-transition:enter-end="opacity-100 transform scale-100"
|
|
80
|
+
x-transition:leave="transition ease-in duration-150"
|
|
81
|
+
x-transition:leave-start="opacity-100 transform scale-100"
|
|
82
|
+
x-transition:leave-end="opacity-0 transform scale-95"
|
|
83
|
+
class="absolute left-0 mt-2 w-48 rounded-md shadow-lg bg-white ring-1 ring-black ring-opacity-5 z-50"
|
|
84
|
+
x-cloak>
|
|
85
|
+
<div class="py-1">
|
|
86
|
+
<a href="/docs" target="_blank" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 transition-colors">
|
|
87
|
+
Swagger API
|
|
88
|
+
<svg class="inline ml-1 h-3 w-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
89
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14"></path>
|
|
90
|
+
</svg>
|
|
91
|
+
</a>
|
|
92
|
+
<a href="/redoc" target="_blank" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 transition-colors">
|
|
93
|
+
ReDoc
|
|
94
|
+
<svg class="inline ml-1 h-3 w-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
95
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14"></path>
|
|
96
|
+
</svg>
|
|
97
|
+
</a>
|
|
98
|
+
</div>
|
|
99
|
+
</div>
|
|
100
|
+
</div>
|
|
101
|
+
|
|
102
|
+
<!-- Console -->
|
|
103
|
+
<a href="/admin/console"
|
|
104
|
+
class="nav-link text-gray-500 hover:text-gray-700 hover:bg-gray-50 px-3 py-2 rounded-md text-sm font-medium transition-colors"
|
|
105
|
+
:class="window.location.pathname === '/admin/console' ? 'text-blue-600 bg-blue-50' : ''">
|
|
106
|
+
Console
|
|
107
|
+
</a>
|
|
108
|
+
</div>
|
|
109
|
+
</div>
|
|
110
|
+
|
|
111
|
+
<!-- Right side -->
|
|
112
|
+
<div class="flex items-center space-x-4">
|
|
113
|
+
<span class="text-sm text-gray-500">API {{ api_version }}</span>
|
|
114
|
+
|
|
115
|
+
<!-- Mobile menu button -->
|
|
116
|
+
<button @click="mobileMenuOpen = !mobileMenuOpen" class="sm:hidden inline-flex items-center justify-center p-2 rounded-md text-gray-400 hover:text-gray-500 hover:bg-gray-100">
|
|
117
|
+
<svg class="h-6 w-6" :class="mobileMenuOpen ? 'hidden' : 'block'" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
118
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16" />
|
|
119
|
+
</svg>
|
|
120
|
+
<svg class="h-6 w-6" :class="mobileMenuOpen ? 'block' : 'hidden'" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
121
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
|
122
|
+
</svg>
|
|
123
|
+
</button>
|
|
124
|
+
</div>
|
|
125
|
+
</div>
|
|
126
|
+
</div>
|
|
127
|
+
|
|
128
|
+
<!-- Mobile menu -->
|
|
129
|
+
<div x-show="mobileMenuOpen" x-cloak class="sm:hidden">
|
|
130
|
+
<div class="pt-2 pb-3 space-y-1 bg-gray-50 border-t border-gray-200">
|
|
131
|
+
<a href="/admin" class="block pl-3 pr-4 py-2 text-base font-medium text-gray-500 hover:text-gray-700 hover:bg-gray-100">Dashboard</a>
|
|
132
|
+
<a href="/admin/server" class="block pl-3 pr-4 py-2 text-base font-medium text-gray-500 hover:text-gray-700 hover:bg-gray-100">Server</a>
|
|
133
|
+
<a href="/admin/agents" class="block pl-3 pr-4 py-2 text-base font-medium text-gray-500 hover:text-gray-700 hover:bg-gray-100">Agents</a>
|
|
134
|
+
|
|
135
|
+
<!-- Jobs section -->
|
|
136
|
+
<div class="pl-3 pr-4 py-2">
|
|
137
|
+
<div class="text-base font-medium text-gray-700 mb-2">Jobs</div>
|
|
138
|
+
<a href="/admin/jobs" class="block pl-4 py-1 text-sm text-gray-500 hover:text-gray-700">All Jobs</a>
|
|
139
|
+
<a href="/admin/cases" class="block pl-4 py-1 text-sm text-gray-500 hover:text-gray-700">Cases</a>
|
|
140
|
+
</div>
|
|
141
|
+
|
|
142
|
+
<!-- Documentation section -->
|
|
143
|
+
<div class="pl-3 pr-4 py-2">
|
|
144
|
+
<div class="text-base font-medium text-gray-700 mb-2">Documentation</div>
|
|
145
|
+
<a href="/docs" target="_blank" class="block pl-4 py-1 text-sm text-gray-500 hover:text-gray-700">Swagger API</a>
|
|
146
|
+
<a href="/redoc" target="_blank" class="block pl-4 py-1 text-sm text-gray-500 hover:text-gray-700">ReDoc</a>
|
|
147
|
+
<a href="https://github.com/supervaize/supervaizer-controller" target="_blank" class="block pl-4 py-1 text-sm text-gray-500 hover:text-gray-700">Supervaizer GitHub</a>
|
|
148
|
+
</div>
|
|
149
|
+
|
|
150
|
+
<a href="/admin/console" class="block pl-3 pr-4 py-2 text-base font-medium text-gray-500 hover:text-gray-700 hover:bg-gray-100">Console</a>
|
|
151
|
+
</div>
|
|
152
|
+
</div>
|
|
153
|
+
</nav>
|