synapse-sdk 1.0.0a98__py3-none-any.whl → 1.0.0b1__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.
- synapse_sdk/cli/__init__.py +139 -84
- synapse_sdk/cli/code_server.py +169 -0
- synapse_sdk/cli/config.py +105 -4
- synapse_sdk/cli/devtools.py +54 -34
- synapse_sdk/clients/base.py +3 -4
- synapse_sdk/devtools/server.py +24 -791
- synapse_sdk/devtools/streamlit_app/__init__.py +5 -0
- synapse_sdk/devtools/streamlit_app/app.py +128 -0
- synapse_sdk/devtools/streamlit_app/services/__init__.py +11 -0
- synapse_sdk/devtools/streamlit_app/services/job_service.py +233 -0
- synapse_sdk/devtools/streamlit_app/services/plugin_service.py +236 -0
- synapse_sdk/devtools/streamlit_app/services/serve_service.py +95 -0
- synapse_sdk/devtools/streamlit_app/ui/__init__.py +15 -0
- synapse_sdk/devtools/streamlit_app/ui/config_tab.py +76 -0
- synapse_sdk/devtools/streamlit_app/ui/deployment_tab.py +66 -0
- synapse_sdk/devtools/streamlit_app/ui/http_tab.py +125 -0
- synapse_sdk/devtools/streamlit_app/ui/jobs_tab.py +573 -0
- synapse_sdk/devtools/streamlit_app/ui/serve_tab.py +346 -0
- synapse_sdk/devtools/streamlit_app/ui/status_bar.py +118 -0
- synapse_sdk/devtools/streamlit_app/utils/__init__.py +40 -0
- synapse_sdk/devtools/streamlit_app/utils/json_viewer.py +197 -0
- synapse_sdk/devtools/streamlit_app/utils/log_formatter.py +38 -0
- synapse_sdk/devtools/streamlit_app/utils/styles.py +241 -0
- synapse_sdk/devtools/streamlit_app/utils/ui_components.py +289 -0
- synapse_sdk/devtools/streamlit_app.py +10 -0
- synapse_sdk/plugins/categories/upload/actions/upload.py +2 -1
- synapse_sdk/utils/converters/dm/__init__.py +0 -1
- {synapse_sdk-1.0.0a98.dist-info → synapse_sdk-1.0.0b1.dist-info}/METADATA +4 -6
- {synapse_sdk-1.0.0a98.dist-info → synapse_sdk-1.0.0b1.dist-info}/RECORD +33 -44
- synapse_sdk/devtools/models.py +0 -55
- synapse_sdk/devtools/utils.py +0 -52
- synapse_sdk/devtools/web/.gitignore +0 -2
- synapse_sdk/devtools/web/README.md +0 -34
- synapse_sdk/devtools/web/dist/index.html +0 -17
- synapse_sdk/devtools/web/index.html +0 -16
- synapse_sdk/devtools/web/jsconfig.json +0 -15
- synapse_sdk/devtools/web/package-lock.json +0 -2609
- synapse_sdk/devtools/web/package.json +0 -27
- synapse_sdk/devtools/web/pnpm-lock.yaml +0 -1055
- synapse_sdk/devtools/web/src/App.jsx +0 -14
- synapse_sdk/devtools/web/src/App.module.css +0 -33
- synapse_sdk/devtools/web/src/assets/favicon.ico +0 -0
- synapse_sdk/devtools/web/src/components/Breadcrumbs.jsx +0 -42
- synapse_sdk/devtools/web/src/components/Layout.jsx +0 -12
- synapse_sdk/devtools/web/src/components/LogViewer.jsx +0 -280
- synapse_sdk/devtools/web/src/components/MessageViewer.jsx +0 -150
- synapse_sdk/devtools/web/src/components/NavigationSidebar.jsx +0 -128
- synapse_sdk/devtools/web/src/components/ServerStatusBar.jsx +0 -245
- synapse_sdk/devtools/web/src/components/icons.jsx +0 -325
- synapse_sdk/devtools/web/src/index.css +0 -470
- synapse_sdk/devtools/web/src/index.jsx +0 -15
- synapse_sdk/devtools/web/src/logo.svg +0 -1
- synapse_sdk/devtools/web/src/router.jsx +0 -34
- synapse_sdk/devtools/web/src/utils/api.js +0 -442
- synapse_sdk/devtools/web/src/views/ApplicationDetailView.jsx +0 -241
- synapse_sdk/devtools/web/src/views/ApplicationsView.jsx +0 -224
- synapse_sdk/devtools/web/src/views/HomeView.jsx +0 -197
- synapse_sdk/devtools/web/src/views/JobDetailView.jsx +0 -310
- synapse_sdk/devtools/web/src/views/PluginView.jsx +0 -914
- synapse_sdk/devtools/web/vite.config.js +0 -13
- {synapse_sdk-1.0.0a98.dist-info → synapse_sdk-1.0.0b1.dist-info}/WHEEL +0 -0
- {synapse_sdk-1.0.0a98.dist-info → synapse_sdk-1.0.0b1.dist-info}/entry_points.txt +0 -0
- {synapse_sdk-1.0.0a98.dist-info → synapse_sdk-1.0.0b1.dist-info}/licenses/LICENSE +0 -0
- {synapse_sdk-1.0.0a98.dist-info → synapse_sdk-1.0.0b1.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
|
-
}
|