synapse-sdk 1.0.0a98__py3-none-any.whl → 1.0.0b2__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.

Potentially problematic release.


This version of synapse-sdk might be problematic. Click here for more details.

Files changed (65) hide show
  1. synapse_sdk/cli/__init__.py +139 -84
  2. synapse_sdk/cli/code_server.py +169 -0
  3. synapse_sdk/cli/config.py +105 -4
  4. synapse_sdk/cli/devtools.py +54 -34
  5. synapse_sdk/clients/base.py +3 -4
  6. synapse_sdk/devtools/server.py +24 -791
  7. synapse_sdk/devtools/streamlit_app/__init__.py +5 -0
  8. synapse_sdk/devtools/streamlit_app/app.py +128 -0
  9. synapse_sdk/devtools/streamlit_app/services/__init__.py +11 -0
  10. synapse_sdk/devtools/streamlit_app/services/job_service.py +233 -0
  11. synapse_sdk/devtools/streamlit_app/services/plugin_service.py +236 -0
  12. synapse_sdk/devtools/streamlit_app/services/serve_service.py +95 -0
  13. synapse_sdk/devtools/streamlit_app/ui/__init__.py +15 -0
  14. synapse_sdk/devtools/streamlit_app/ui/config_tab.py +76 -0
  15. synapse_sdk/devtools/streamlit_app/ui/deployment_tab.py +66 -0
  16. synapse_sdk/devtools/streamlit_app/ui/http_tab.py +125 -0
  17. synapse_sdk/devtools/streamlit_app/ui/jobs_tab.py +573 -0
  18. synapse_sdk/devtools/streamlit_app/ui/serve_tab.py +346 -0
  19. synapse_sdk/devtools/streamlit_app/ui/status_bar.py +118 -0
  20. synapse_sdk/devtools/streamlit_app/utils/__init__.py +40 -0
  21. synapse_sdk/devtools/streamlit_app/utils/json_viewer.py +197 -0
  22. synapse_sdk/devtools/streamlit_app/utils/log_formatter.py +38 -0
  23. synapse_sdk/devtools/streamlit_app/utils/styles.py +241 -0
  24. synapse_sdk/devtools/streamlit_app/utils/ui_components.py +289 -0
  25. synapse_sdk/devtools/streamlit_app.py +10 -0
  26. synapse_sdk/plugins/categories/upload/actions/upload.py +2 -1
  27. synapse_sdk/utils/converters/coco/from_dm.py +2 -2
  28. synapse_sdk/utils/converters/dm/__init__.py +0 -1
  29. {synapse_sdk-1.0.0a98.dist-info → synapse_sdk-1.0.0b2.dist-info}/METADATA +4 -6
  30. {synapse_sdk-1.0.0a98.dist-info → synapse_sdk-1.0.0b2.dist-info}/RECORD +34 -45
  31. synapse_sdk/devtools/models.py +0 -55
  32. synapse_sdk/devtools/utils.py +0 -52
  33. synapse_sdk/devtools/web/.gitignore +0 -2
  34. synapse_sdk/devtools/web/README.md +0 -34
  35. synapse_sdk/devtools/web/dist/index.html +0 -17
  36. synapse_sdk/devtools/web/index.html +0 -16
  37. synapse_sdk/devtools/web/jsconfig.json +0 -15
  38. synapse_sdk/devtools/web/package-lock.json +0 -2609
  39. synapse_sdk/devtools/web/package.json +0 -27
  40. synapse_sdk/devtools/web/pnpm-lock.yaml +0 -1055
  41. synapse_sdk/devtools/web/src/App.jsx +0 -14
  42. synapse_sdk/devtools/web/src/App.module.css +0 -33
  43. synapse_sdk/devtools/web/src/assets/favicon.ico +0 -0
  44. synapse_sdk/devtools/web/src/components/Breadcrumbs.jsx +0 -42
  45. synapse_sdk/devtools/web/src/components/Layout.jsx +0 -12
  46. synapse_sdk/devtools/web/src/components/LogViewer.jsx +0 -280
  47. synapse_sdk/devtools/web/src/components/MessageViewer.jsx +0 -150
  48. synapse_sdk/devtools/web/src/components/NavigationSidebar.jsx +0 -128
  49. synapse_sdk/devtools/web/src/components/ServerStatusBar.jsx +0 -245
  50. synapse_sdk/devtools/web/src/components/icons.jsx +0 -325
  51. synapse_sdk/devtools/web/src/index.css +0 -470
  52. synapse_sdk/devtools/web/src/index.jsx +0 -15
  53. synapse_sdk/devtools/web/src/logo.svg +0 -1
  54. synapse_sdk/devtools/web/src/router.jsx +0 -34
  55. synapse_sdk/devtools/web/src/utils/api.js +0 -442
  56. synapse_sdk/devtools/web/src/views/ApplicationDetailView.jsx +0 -241
  57. synapse_sdk/devtools/web/src/views/ApplicationsView.jsx +0 -224
  58. synapse_sdk/devtools/web/src/views/HomeView.jsx +0 -197
  59. synapse_sdk/devtools/web/src/views/JobDetailView.jsx +0 -310
  60. synapse_sdk/devtools/web/src/views/PluginView.jsx +0 -914
  61. synapse_sdk/devtools/web/vite.config.js +0 -13
  62. {synapse_sdk-1.0.0a98.dist-info → synapse_sdk-1.0.0b2.dist-info}/WHEEL +0 -0
  63. {synapse_sdk-1.0.0a98.dist-info → synapse_sdk-1.0.0b2.dist-info}/entry_points.txt +0 -0
  64. {synapse_sdk-1.0.0a98.dist-info → synapse_sdk-1.0.0b2.dist-info}/licenses/LICENSE +0 -0
  65. {synapse_sdk-1.0.0a98.dist-info → synapse_sdk-1.0.0b2.dist-info}/top_level.txt +0 -0
@@ -1,442 +0,0 @@
1
- import { createSignal, createEffect, onCleanup } from "solid-js";
2
-
3
- // API configuration
4
- const getApiConfig = () => {
5
- const port = window.VITE_API_PORT || 8080;
6
- const host = window.VITE_API_HOST || "localhost";
7
- return {
8
- baseURL: `http://${host}:${port}`,
9
- headers: {
10
- 'Content-Type': 'application/json',
11
- },
12
- };
13
- };
14
-
15
- // Generic API client
16
- export class ApiClient {
17
- constructor() {
18
- this.config = getApiConfig();
19
- }
20
-
21
- async request(endpoint, options = {}) {
22
- const url = `${this.config.baseURL}${endpoint}`;
23
- const config = {
24
- ...options,
25
- headers: {
26
- ...this.config.headers,
27
- ...options.headers,
28
- },
29
- };
30
-
31
- try {
32
- const response = await fetch(url, config);
33
-
34
- if (!response.ok) {
35
- throw new Error(`HTTP ${response.status}: ${response.statusText}`);
36
- }
37
-
38
- const contentType = response.headers.get('content-type');
39
- if (contentType && contentType.includes('application/json')) {
40
- return { data: await response.json(), error: null };
41
- } else {
42
- return { data: await response.text(), error: null };
43
- }
44
- } catch (error) {
45
- console.error('API request failed:', error);
46
- return { data: null, error: error.message };
47
- }
48
- }
49
-
50
- async get(endpoint) {
51
- return this.request(endpoint, { method: 'GET' });
52
- }
53
-
54
- async post(endpoint, data) {
55
- return this.request(endpoint, {
56
- method: 'POST',
57
- body: JSON.stringify(data),
58
- });
59
- }
60
-
61
- async put(endpoint, data) {
62
- return this.request(endpoint, {
63
- method: 'PUT',
64
- body: JSON.stringify(data),
65
- });
66
- }
67
-
68
- async delete(endpoint) {
69
- return this.request(endpoint, { method: 'DELETE' });
70
- }
71
- }
72
-
73
- // Singleton API client
74
- export const apiClient = new ApiClient();
75
-
76
- // Hook for API GET requests with automatic reactivity
77
- export function createApiResource(endpoint, options = {}) {
78
- const [data, setData] = createSignal(null);
79
- const [loading, setLoading] = createSignal(true);
80
- const [error, setError] = createSignal(null);
81
-
82
- const fetchData = async () => {
83
- // Support function endpoints
84
- const resolvedEndpoint = typeof endpoint === 'function' ? endpoint() : endpoint;
85
- if (!resolvedEndpoint) {
86
- setLoading(false);
87
- return;
88
- }
89
-
90
- setLoading(true);
91
- setError(null);
92
-
93
- const result = await apiClient.get(resolvedEndpoint);
94
-
95
- if (result.error) {
96
- setError(result.error);
97
- setData(null);
98
- } else {
99
- setData(result.data);
100
- setError(null);
101
- }
102
-
103
- setLoading(false);
104
- };
105
-
106
- // Auto-refresh functionality with reduced intervals
107
- let intervalId;
108
- if (options.autoRefresh && options.refreshInterval) {
109
- createEffect(() => {
110
- fetchData();
111
- intervalId = setInterval(fetchData, options.refreshInterval);
112
-
113
- onCleanup(() => {
114
- if (intervalId) clearInterval(intervalId);
115
- });
116
- });
117
- } else {
118
- createEffect(fetchData);
119
- }
120
-
121
- const refresh = () => fetchData();
122
-
123
- return {
124
- data,
125
- loading,
126
- error,
127
- refresh,
128
- };
129
- }
130
-
131
- // Hook for API mutations (POST, PUT, DELETE)
132
- export function createApiMutation(mutationFn) {
133
- const [loading, setLoading] = createSignal(false);
134
- const [error, setError] = createSignal(null);
135
- const [data, setData] = createSignal(null);
136
-
137
- const mutate = async (...args) => {
138
- setLoading(true);
139
- setError(null);
140
-
141
- try {
142
- const result = await mutationFn(...args);
143
- setData(result);
144
- setError(null);
145
- return result;
146
- } catch (err) {
147
- setError(err.message);
148
- throw err;
149
- } finally {
150
- setLoading(false);
151
- }
152
- };
153
-
154
- return {
155
- mutate,
156
- data,
157
- loading,
158
- error,
159
- };
160
- }
161
-
162
- // Jobs API
163
- export function createJobsResource() {
164
- return createApiResource('/jobs', {
165
- autoRefresh: true,
166
- refreshInterval: 3000
167
- });
168
- }
169
-
170
- export function createJobResource(jobId) {
171
- return createApiResource(() => {
172
- const id = typeof jobId === 'function' ? jobId() : jobId;
173
- return id ? `/jobs/${id}` : null;
174
- });
175
- }
176
-
177
- // Applications API
178
- export function createApplicationsResource() {
179
- return createApiResource('/serve_applications', {
180
- autoRefresh: true,
181
- refreshInterval: 3000
182
- });
183
- }
184
-
185
- export function createApplicationResource(appId) {
186
- return createApiResource(() => {
187
- const id = typeof appId === 'function' ? appId() : appId;
188
- return id ? `/serve_applications/${id}` : null;
189
- });
190
- }
191
-
192
- // Status and health API
193
- export function createStatusResource() {
194
- return createApiResource('/status');
195
- }
196
-
197
- export function createBackendHealthResource() {
198
- return createApiResource('/health/backend', {
199
- autoRefresh: true,
200
- refreshInterval: 5000
201
- });
202
- }
203
-
204
- export function createAgentHealthResource() {
205
- return createApiResource('/health/agent', {
206
- autoRefresh: true,
207
- refreshInterval: 5000
208
- });
209
- }
210
-
211
- export function createAuthTokenResource() {
212
- return createApiResource('/auth/token');
213
- }
214
-
215
- // User API - requires token and backend host
216
- export async function fetchUserInfo(token, backendHost) {
217
- if (!token || !backendHost) return null;
218
-
219
- try {
220
- const response = await fetch(`${backendHost}/users/me/`, {
221
- method: 'GET',
222
- headers: {
223
- 'Synapse-Access-Token': token,
224
- 'Content-Type': 'application/json',
225
- },
226
- });
227
-
228
- if (response.ok) {
229
- const userData = await response.json();
230
- return {
231
- name: userData.name || userData.username || "Unknown",
232
- email: userData.email || "No email",
233
- };
234
- }
235
- return null;
236
- } catch (error) {
237
- console.error('Error fetching user info:', error);
238
- return null;
239
- }
240
- }
241
-
242
- // Plugin configuration API
243
- export function createConfigResource() {
244
- return createApiResource('/config');
245
- }
246
-
247
- export function createConfigMutation() {
248
- return createApiMutation(async (config) => {
249
- // Ensure proper data structure for backend
250
- const configPayload = {
251
- config: config
252
- };
253
-
254
- const result = await apiClient.put('/config', configPayload);
255
- if (result.error) throw new Error(result.error);
256
- return result.data;
257
- });
258
- }
259
-
260
- // Plugin validation API
261
- export function createValidationMutation() {
262
- return createApiMutation(async (config) => {
263
- // Ensure proper data structure for backend
264
- const configPayload = {
265
- config: config
266
- };
267
-
268
- const result = await apiClient.post('/config/validate', configPayload);
269
- if (result.error) throw new Error(result.error);
270
- return result.data;
271
- });
272
- }
273
-
274
- // Job log streaming
275
- export function createJobLogsStream(jobId) {
276
- const [logs, setLogs] = createSignal([]);
277
- const [streaming, setStreaming] = createSignal(false);
278
- const [error, setError] = createSignal(null);
279
-
280
- let reader = null;
281
-
282
- const parseLogLine = (line) => {
283
- const timestampMatch = line.match(
284
- /^(\d{4}-\d{2}-\d{2}[T ]\d{2}:\d{2}:\d{2}(?:\.\d{3})?(?:Z|[+-]\d{2}:\d{2})?)/
285
- );
286
- const levelMatch = line.match(
287
- /\[(ERROR|WARN|WARNING|INFO|DEBUG)\]|\b(ERROR|WARN|WARNING|INFO|DEBUG)\b/i
288
- );
289
-
290
- let timestamp = undefined;
291
- let level = undefined;
292
- let message = line;
293
-
294
- if (timestampMatch) {
295
- timestamp = timestampMatch[1];
296
- message = line.substring(timestampMatch[0].length).trim();
297
- }
298
-
299
- if (levelMatch) {
300
- level = (levelMatch[1] || levelMatch[2])?.toLowerCase();
301
- message = message.replace(
302
- /\[(ERROR|WARN|WARNING|INFO|DEBUG)\]|\b(ERROR|WARN|WARNING|INFO|DEBUG)\b/i,
303
- ""
304
- ).trim();
305
- }
306
-
307
- return { message, level, timestamp };
308
- };
309
-
310
- const startStreaming = async () => {
311
- if (streaming() || !jobId) return;
312
-
313
- try {
314
- setStreaming(true);
315
- setError(null);
316
-
317
- const config = getApiConfig();
318
- const response = await fetch(`${config.baseURL}/jobs/${jobId}/logs`);
319
-
320
- if (!response.ok || !response.body) {
321
- throw new Error(`HTTP ${response.status}`);
322
- }
323
-
324
- const decoder = new TextDecoder();
325
- reader = response.body.getReader();
326
- let buffer = "";
327
-
328
- while (true) {
329
- const { value, done } = await reader.read();
330
- if (done) break;
331
-
332
- buffer += decoder.decode(value, { stream: true });
333
- const lines = buffer.split(/\r?\n/);
334
- buffer = lines.pop() || "";
335
-
336
- for (const line of lines) {
337
- if (!line.trim()) continue;
338
-
339
- // Handle SSE format (data: prefix)
340
- if (line.startsWith('data: ')) {
341
- const content = line.substring(6).trim();
342
-
343
- // Check if this is an error message from the server
344
- if (content.includes('Agent client error') ||
345
- content.includes('Agent connection error') ||
346
- content.includes('Agent error:') ||
347
- content.includes('Unexpected error:')) {
348
- setError(content);
349
- // Still add as a log entry so user can see the error
350
- setLogs(prev => [...prev, parseLogLine(content)]);
351
- } else {
352
- // Normal log line
353
- setLogs(prev => [...prev, parseLogLine(content)]);
354
- }
355
- } else {
356
- // Handle non-SSE format (backward compatibility)
357
- setLogs(prev => [...prev, parseLogLine(line)]);
358
- }
359
- }
360
- }
361
-
362
- if (buffer.trim()) {
363
- setLogs(prev => [...prev, parseLogLine(buffer)]);
364
- }
365
- } catch (err) {
366
- setError(err.message);
367
- console.error("Error streaming logs:", err);
368
- } finally {
369
- setStreaming(false);
370
- }
371
- };
372
-
373
- const stopStreaming = () => {
374
- if (reader) {
375
- reader.cancel();
376
- reader = null;
377
- }
378
- setStreaming(false);
379
- };
380
-
381
- const clearLogs = () => {
382
- setLogs([]);
383
- };
384
-
385
- onCleanup(stopStreaming);
386
-
387
- return {
388
- logs,
389
- streaming,
390
- error,
391
- startStreaming,
392
- stopStreaming,
393
- clearLogs,
394
- };
395
- }
396
-
397
- // Utility functions
398
- export function getJobStatusVariant(status) {
399
- switch (status?.toLowerCase()) {
400
- case "succeeded":
401
- return "success";
402
- case "failed":
403
- case "cancelled":
404
- return "error";
405
- case "running":
406
- return "info";
407
- case "pending":
408
- return "warning";
409
- default:
410
- return "default";
411
- }
412
- }
413
-
414
- export function formatTimestamp(timeStr) {
415
- if (!timeStr) return "N/A";
416
- try {
417
- const date = new Date(timeStr);
418
- return date.toLocaleString("sv-SE").replace("T", " ");
419
- } catch {
420
- return timeStr;
421
- }
422
- }
423
-
424
- export function formatJobDuration(startTime, endTime) {
425
- if (!startTime) return "N/A";
426
-
427
- const start = new Date(startTime);
428
- const end = endTime ? new Date(endTime) : new Date();
429
- const durationMs = end.getTime() - start.getTime();
430
-
431
- const seconds = Math.floor(durationMs / 1000);
432
- const minutes = Math.floor(seconds / 60);
433
- const hours = Math.floor(minutes / 60);
434
-
435
- if (hours > 0) {
436
- return `${hours}h ${minutes % 60}m ${seconds % 60}s`;
437
- } else if (minutes > 0) {
438
- return `${minutes}m ${seconds % 60}s`;
439
- } else {
440
- return `${seconds}s`;
441
- }
442
- }
@@ -1,241 +0,0 @@
1
- import { Show, For, createMemo } from "solid-js";
2
- import { useParams, useNavigate } from "@solidjs/router";
3
- import { createApplicationResource, formatTimestamp } from "../utils/api";
4
- import { ArrowLeftIcon, RefreshIcon, CubeIcon } from "../components/icons";
5
- import Breadcrumbs from "../components/Breadcrumbs";
6
-
7
- export default function ApplicationDetailView() {
8
- const params = useParams();
9
- const navigate = useNavigate();
10
-
11
- const appId = () => params.id;
12
- const { data, loading, error, refresh } = createApplicationResource(() => appId());
13
-
14
- const getStatusClass = (status) => {
15
- switch (status?.toUpperCase()) {
16
- case "RUNNING":
17
- return "badge-outline badge-success";
18
- case "DEPLOYING":
19
- return "badge-outline badge-info";
20
- case "DEPLOY_FAILED":
21
- return "badge-outline badge-error";
22
- case "DELETING":
23
- return "badge-outline badge-warning";
24
- default:
25
- return "badge-outline badge-neutral";
26
- }
27
- };
28
-
29
- const hasDeployments = createMemo(() =>
30
- data()?.deployments && data().deployments.length > 0
31
- );
32
-
33
- return (
34
- <div class="p-6">
35
- {/* Breadcrumbs */}
36
- <Breadcrumbs
37
- items={[
38
- { label: "Serve Apps", path: "/serve_applications" },
39
- { label: data()?.name || appId() || "Application Details" }
40
- ]}
41
- />
42
-
43
- {/* Header */}
44
- <div class="flex justify-between items-center mb-6">
45
- <div class="flex items-center gap-4">
46
- <button
47
- class="btn btn-sm btn-outline"
48
- onClick={() => navigate(-1)}
49
- >
50
- <ArrowLeftIcon class="w-4 h-4 mr-2" />
51
- Back
52
- </button>
53
- <div>
54
- <h1 class="text-2xl font-semibold text-slate-900">Application Details</h1>
55
- <p class="text-sm text-slate-600">View serve application details and deployments</p>
56
- </div>
57
- </div>
58
- <button
59
- class="btn btn-sm btn-primary"
60
- onClick={refresh}
61
- disabled={loading()}
62
- >
63
- <RefreshIcon class="w-4 h-4 mr-2" />
64
- {loading() ? "Refreshing..." : "Refresh"}
65
- </button>
66
- </div>
67
-
68
- {/* Error State */}
69
- <Show when={error()}>
70
- <div class="alert alert-error mb-6">
71
- <div>
72
- <h3 class="font-medium">Failed to load application details</h3>
73
- <div class="text-sm">{error()}</div>
74
- </div>
75
- </div>
76
- </Show>
77
-
78
- {/* Loading State */}
79
- <Show when={loading() && !data()}>
80
- <div class="flex flex-col items-center justify-center py-12">
81
- <div class="loading loading-spinner loading-lg mb-4"></div>
82
- <p class="text-slate-500">Loading application details...</p>
83
- </div>
84
- </Show>
85
-
86
- {/* Application Not Found */}
87
- <Show when={!loading() && !data() && !error()}>
88
- <div class="flex flex-col items-center justify-center py-12">
89
- <div class="text-slate-400 mb-4">
90
- <CubeIcon class="w-16 h-16" />
91
- </div>
92
- <h3 class="text-lg font-medium text-slate-900 mb-2">Application not found</h3>
93
- <p class="text-slate-500">The requested application could not be found</p>
94
- </div>
95
- </Show>
96
-
97
- {/* Application Details */}
98
- <Show when={data()}>
99
- <div class="space-y-6">
100
- {/* Application Overview */}
101
- <div class="card bg-white shadow-professional">
102
- <div class="card-body p-6">
103
- <h2 class="text-lg font-semibold text-slate-900 mb-4">Overview</h2>
104
-
105
- <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
106
- <div class="bg-slate-50 p-4 rounded-lg">
107
- <div class="text-xs font-medium text-slate-600 uppercase tracking-wide mb-1">
108
- Application Name
109
- </div>
110
- <div class="font-medium text-slate-900">
111
- {data().name}
112
- </div>
113
- </div>
114
-
115
- <div class="bg-slate-50 p-4 rounded-lg">
116
- <div class="text-xs font-medium text-slate-600 uppercase tracking-wide mb-1">
117
- Status
118
- </div>
119
- <span class={`badge badge-sm ${getStatusClass(data().status)}`}>
120
- {data().status}
121
- </span>
122
- </div>
123
-
124
- <div class="bg-slate-50 p-4 rounded-lg">
125
- <div class="text-xs font-medium text-slate-600 uppercase tracking-wide mb-1">
126
- Route Prefix
127
- </div>
128
- <code class="text-sm bg-slate-200 px-2 py-1 rounded">
129
- {data().route_prefix || '/'}
130
- </code>
131
- </div>
132
-
133
- <Show when={data().docs_path}>
134
- <div class="bg-slate-50 p-4 rounded-lg">
135
- <div class="text-xs font-medium text-slate-600 uppercase tracking-wide mb-1">
136
- Docs Path
137
- </div>
138
- <code class="text-sm text-slate-700">
139
- {data().docs_path}
140
- </code>
141
- </div>
142
- </Show>
143
-
144
- <div class="bg-slate-50 p-4 rounded-lg">
145
- <div class="text-xs font-medium text-slate-600 uppercase tracking-wide mb-1">
146
- Deployments
147
- </div>
148
- <div class="text-sm text-slate-700">
149
- {data().deployments?.length || 0} deployment{(data().deployments?.length || 0) !== 1 ? 's' : ''}
150
- </div>
151
- </div>
152
-
153
- <Show when={data().last_deployed_time_s}>
154
- <div class="bg-slate-50 p-4 rounded-lg">
155
- <div class="text-xs font-medium text-slate-600 uppercase tracking-wide mb-1">
156
- Last Deployed
157
- </div>
158
- <div class="text-sm text-slate-700">
159
- {new Date(data().last_deployed_time_s * 1000).toLocaleString()}
160
- </div>
161
- </div>
162
- </Show>
163
- </div>
164
- </div>
165
- </div>
166
-
167
- {/* Deployments */}
168
- <Show when={hasDeployments()}>
169
- <div class="card bg-white shadow-professional">
170
- <div class="card-body p-6">
171
- <h2 class="text-lg font-semibold text-slate-900 mb-4">Deployments</h2>
172
-
173
- <div class="overflow-x-auto">
174
- <table class="table table-xs w-full">
175
- <thead>
176
- <tr class="border-slate-200">
177
- <th class="bg-slate-50 text-slate-600 font-medium text-xs">Name</th>
178
- <th class="bg-slate-50 text-slate-600 font-medium text-xs">Status</th>
179
- <th class="bg-slate-50 text-slate-600 font-medium text-xs">Replicas</th>
180
- <th class="bg-slate-50 text-slate-600 font-medium text-xs">Route Prefix</th>
181
- <th class="bg-slate-50 text-slate-600 font-medium text-xs">Message</th>
182
- </tr>
183
- </thead>
184
- <tbody>
185
- <For each={data().deployments}>
186
- {(deployment) => (
187
- <tr class="border-slate-100">
188
- <td class="py-3">
189
- <span class="font-medium text-slate-900">{deployment.name}</span>
190
- </td>
191
- <td>
192
- <span class={`badge badge-sm ${getStatusClass(deployment.status)}`}>
193
- {deployment.status}
194
- </span>
195
- </td>
196
- <td>
197
- <div class="flex items-center gap-2">
198
- <span class="text-sm">{deployment.replica_count || 0}</span>
199
- <Show when={deployment.target_replica_count !== deployment.replica_count}>
200
- <span class="text-xs text-slate-500">
201
- → {deployment.target_replica_count}
202
- </span>
203
- </Show>
204
- </div>
205
- </td>
206
- <td>
207
- <code class="text-xs bg-slate-100 px-2 py-1 rounded">
208
- {deployment.route_prefix || '/'}
209
- </code>
210
- </td>
211
- <td class="max-w-xs">
212
- <div class="truncate text-sm text-slate-600">
213
- {deployment.message || '—'}
214
- </div>
215
- </td>
216
- </tr>
217
- )}
218
- </For>
219
- </tbody>
220
- </table>
221
- </div>
222
- </div>
223
- </div>
224
- </Show>
225
-
226
- {/* Raw Application Data */}
227
- <div class="card bg-white shadow-professional">
228
- <div class="card-body p-6">
229
- <h2 class="text-lg font-semibold text-slate-900 mb-4">Raw Data</h2>
230
- <div class="bg-slate-50 p-4 rounded-lg">
231
- <pre class="text-xs text-slate-700 overflow-auto max-h-96">
232
- {JSON.stringify(data(), null, 2)}
233
- </pre>
234
- </div>
235
- </div>
236
- </div>
237
- </div>
238
- </Show>
239
- </div>
240
- );
241
- }