@appkit/llamacpp-cli 1.14.1 → 2.1.0
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.
- package/README.md +276 -280
- package/dist/cli.js +133 -23
- package/dist/cli.js.map +1 -1
- package/dist/commands/admin/config.d.ts +1 -1
- package/dist/commands/admin/config.js +5 -5
- package/dist/commands/admin/config.js.map +1 -1
- package/dist/commands/admin/log-config.d.ts +11 -0
- package/dist/commands/admin/log-config.d.ts.map +1 -0
- package/dist/commands/admin/log-config.js +159 -0
- package/dist/commands/admin/log-config.js.map +1 -0
- package/dist/commands/admin/logs.d.ts +2 -3
- package/dist/commands/admin/logs.d.ts.map +1 -1
- package/dist/commands/admin/logs.js +6 -48
- package/dist/commands/admin/logs.js.map +1 -1
- package/dist/commands/admin/status.d.ts.map +1 -1
- package/dist/commands/admin/status.js +1 -0
- package/dist/commands/admin/status.js.map +1 -1
- package/dist/commands/config.d.ts +1 -0
- package/dist/commands/config.d.ts.map +1 -1
- package/dist/commands/config.js +63 -196
- package/dist/commands/config.js.map +1 -1
- package/dist/commands/create.d.ts +3 -2
- package/dist/commands/create.d.ts.map +1 -1
- package/dist/commands/create.js +24 -97
- package/dist/commands/create.js.map +1 -1
- package/dist/commands/delete.d.ts.map +1 -1
- package/dist/commands/delete.js +7 -24
- package/dist/commands/delete.js.map +1 -1
- package/dist/commands/internal/server-wrapper.d.ts +15 -0
- package/dist/commands/internal/server-wrapper.d.ts.map +1 -0
- package/dist/commands/internal/server-wrapper.js +126 -0
- package/dist/commands/internal/server-wrapper.js.map +1 -0
- package/dist/commands/logs-all.d.ts +0 -2
- package/dist/commands/logs-all.d.ts.map +1 -1
- package/dist/commands/logs-all.js +1 -61
- package/dist/commands/logs-all.js.map +1 -1
- package/dist/commands/logs.d.ts +2 -5
- package/dist/commands/logs.d.ts.map +1 -1
- package/dist/commands/logs.js +104 -120
- package/dist/commands/logs.js.map +1 -1
- package/dist/commands/migrate-labels.d.ts +12 -0
- package/dist/commands/migrate-labels.d.ts.map +1 -0
- package/dist/commands/migrate-labels.js +160 -0
- package/dist/commands/migrate-labels.js.map +1 -0
- package/dist/commands/ps.d.ts.map +1 -1
- package/dist/commands/ps.js +2 -1
- package/dist/commands/ps.js.map +1 -1
- package/dist/commands/rm.d.ts.map +1 -1
- package/dist/commands/rm.js +22 -48
- package/dist/commands/rm.js.map +1 -1
- package/dist/commands/router/config.d.ts +1 -1
- package/dist/commands/router/config.js +6 -6
- package/dist/commands/router/config.js.map +1 -1
- package/dist/commands/router/logs.d.ts +2 -4
- package/dist/commands/router/logs.d.ts.map +1 -1
- package/dist/commands/router/logs.js +34 -189
- package/dist/commands/router/logs.js.map +1 -1
- package/dist/commands/router/status.d.ts.map +1 -1
- package/dist/commands/router/status.js +1 -0
- package/dist/commands/router/status.js.map +1 -1
- package/dist/commands/server-show.d.ts.map +1 -1
- package/dist/commands/server-show.js +3 -0
- package/dist/commands/server-show.js.map +1 -1
- package/dist/commands/start.d.ts.map +1 -1
- package/dist/commands/start.js +21 -72
- package/dist/commands/start.js.map +1 -1
- package/dist/commands/stop.d.ts.map +1 -1
- package/dist/commands/stop.js +10 -26
- package/dist/commands/stop.js.map +1 -1
- package/dist/launchers/llamacpp-admin +8 -0
- package/dist/launchers/llamacpp-router +8 -0
- package/dist/launchers/llamacpp-server +8 -0
- package/dist/lib/admin-manager.d.ts +4 -0
- package/dist/lib/admin-manager.d.ts.map +1 -1
- package/dist/lib/admin-manager.js +42 -18
- package/dist/lib/admin-manager.js.map +1 -1
- package/dist/lib/admin-server.d.ts +48 -1
- package/dist/lib/admin-server.d.ts.map +1 -1
- package/dist/lib/admin-server.js +632 -238
- package/dist/lib/admin-server.js.map +1 -1
- package/dist/lib/config-generator.d.ts +1 -0
- package/dist/lib/config-generator.d.ts.map +1 -1
- package/dist/lib/config-generator.js +12 -5
- package/dist/lib/config-generator.js.map +1 -1
- package/dist/lib/keyboard-manager.d.ts +162 -0
- package/dist/lib/keyboard-manager.d.ts.map +1 -0
- package/dist/lib/keyboard-manager.js +247 -0
- package/dist/lib/keyboard-manager.js.map +1 -0
- package/dist/lib/label-migration.d.ts +65 -0
- package/dist/lib/label-migration.d.ts.map +1 -0
- package/dist/lib/label-migration.js +458 -0
- package/dist/lib/label-migration.js.map +1 -0
- package/dist/lib/launchctl-manager.d.ts +9 -0
- package/dist/lib/launchctl-manager.d.ts.map +1 -1
- package/dist/lib/launchctl-manager.js +65 -19
- package/dist/lib/launchctl-manager.js.map +1 -1
- package/dist/lib/log-management-service.d.ts +51 -0
- package/dist/lib/log-management-service.d.ts.map +1 -0
- package/dist/lib/log-management-service.js +124 -0
- package/dist/lib/log-management-service.js.map +1 -0
- package/dist/lib/log-workers.d.ts +70 -0
- package/dist/lib/log-workers.d.ts.map +1 -0
- package/dist/lib/log-workers.js +217 -0
- package/dist/lib/log-workers.js.map +1 -0
- package/dist/lib/model-downloader.d.ts +9 -1
- package/dist/lib/model-downloader.d.ts.map +1 -1
- package/dist/lib/model-downloader.js +98 -1
- package/dist/lib/model-downloader.js.map +1 -1
- package/dist/lib/model-management-service.d.ts +60 -0
- package/dist/lib/model-management-service.d.ts.map +1 -0
- package/dist/lib/model-management-service.js +246 -0
- package/dist/lib/model-management-service.js.map +1 -0
- package/dist/lib/model-management-service.test.d.ts +2 -0
- package/dist/lib/model-management-service.test.d.ts.map +1 -0
- package/dist/lib/model-management-service.test.js.map +1 -0
- package/dist/lib/model-scanner.d.ts +15 -3
- package/dist/lib/model-scanner.d.ts.map +1 -1
- package/dist/lib/model-scanner.js +174 -17
- package/dist/lib/model-scanner.js.map +1 -1
- package/dist/lib/openapi-spec.d.ts +1335 -0
- package/dist/lib/openapi-spec.d.ts.map +1 -0
- package/dist/lib/openapi-spec.js +1017 -0
- package/dist/lib/openapi-spec.js.map +1 -0
- package/dist/lib/router-logger.d.ts +1 -1
- package/dist/lib/router-logger.d.ts.map +1 -1
- package/dist/lib/router-logger.js +13 -11
- package/dist/lib/router-logger.js.map +1 -1
- package/dist/lib/router-manager.d.ts +4 -0
- package/dist/lib/router-manager.d.ts.map +1 -1
- package/dist/lib/router-manager.js +30 -18
- package/dist/lib/router-manager.js.map +1 -1
- package/dist/lib/router-server.d.ts +4 -7
- package/dist/lib/router-server.d.ts.map +1 -1
- package/dist/lib/router-server.js +71 -182
- package/dist/lib/router-server.js.map +1 -1
- package/dist/lib/server-config-service.d.ts +51 -0
- package/dist/lib/server-config-service.d.ts.map +1 -0
- package/dist/lib/server-config-service.js +310 -0
- package/dist/lib/server-config-service.js.map +1 -0
- package/dist/lib/server-config-service.test.d.ts +2 -0
- package/dist/lib/server-config-service.test.d.ts.map +1 -0
- package/dist/lib/server-config-service.test.js.map +1 -0
- package/dist/lib/server-lifecycle-service.d.ts +172 -0
- package/dist/lib/server-lifecycle-service.d.ts.map +1 -0
- package/dist/lib/server-lifecycle-service.js +619 -0
- package/dist/lib/server-lifecycle-service.js.map +1 -0
- package/dist/lib/state-manager.d.ts +18 -1
- package/dist/lib/state-manager.d.ts.map +1 -1
- package/dist/lib/state-manager.js +51 -2
- package/dist/lib/state-manager.js.map +1 -1
- package/dist/lib/status-checker.d.ts +11 -4
- package/dist/lib/status-checker.d.ts.map +1 -1
- package/dist/lib/status-checker.js +34 -1
- package/dist/lib/status-checker.js.map +1 -1
- package/dist/lib/validation-service.d.ts +43 -0
- package/dist/lib/validation-service.d.ts.map +1 -0
- package/dist/lib/validation-service.js +112 -0
- package/dist/lib/validation-service.js.map +1 -0
- package/dist/lib/validation-service.test.d.ts +2 -0
- package/dist/lib/validation-service.test.d.ts.map +1 -0
- package/dist/lib/validation-service.test.js.map +1 -0
- package/dist/scripts/http-log-filter.sh +8 -0
- package/dist/tui/ConfigApp.d.ts.map +1 -1
- package/dist/tui/ConfigApp.js +222 -184
- package/dist/tui/ConfigApp.js.map +1 -1
- package/dist/tui/HistoricalMonitorApp.d.ts.map +1 -1
- package/dist/tui/HistoricalMonitorApp.js +12 -0
- package/dist/tui/HistoricalMonitorApp.js.map +1 -1
- package/dist/tui/ModelsApp.d.ts.map +1 -1
- package/dist/tui/ModelsApp.js +93 -17
- package/dist/tui/ModelsApp.js.map +1 -1
- package/dist/tui/MonitorApp.d.ts.map +1 -1
- package/dist/tui/MonitorApp.js +1 -3
- package/dist/tui/MonitorApp.js.map +1 -1
- package/dist/tui/MultiServerMonitorApp.d.ts +3 -3
- package/dist/tui/MultiServerMonitorApp.d.ts.map +1 -1
- package/dist/tui/MultiServerMonitorApp.js +724 -508
- package/dist/tui/MultiServerMonitorApp.js.map +1 -1
- package/dist/tui/RootNavigator.d.ts.map +1 -1
- package/dist/tui/RootNavigator.js +17 -1
- package/dist/tui/RootNavigator.js.map +1 -1
- package/dist/tui/RouterApp.d.ts +6 -0
- package/dist/tui/RouterApp.d.ts.map +1 -0
- package/dist/tui/RouterApp.js +928 -0
- package/dist/tui/RouterApp.js.map +1 -0
- package/dist/tui/SearchApp.d.ts.map +1 -1
- package/dist/tui/SearchApp.js +27 -6
- package/dist/tui/SearchApp.js.map +1 -1
- package/dist/tui/shared/modal-controller.d.ts +65 -0
- package/dist/tui/shared/modal-controller.d.ts.map +1 -0
- package/dist/tui/shared/modal-controller.js +625 -0
- package/dist/tui/shared/modal-controller.js.map +1 -0
- package/dist/tui/shared/overlay-utils.d.ts +7 -0
- package/dist/tui/shared/overlay-utils.d.ts.map +1 -0
- package/dist/tui/shared/overlay-utils.js +54 -0
- package/dist/tui/shared/overlay-utils.js.map +1 -0
- package/dist/types/admin-config.d.ts +15 -2
- package/dist/types/admin-config.d.ts.map +1 -1
- package/dist/types/model-info.d.ts +5 -0
- package/dist/types/model-info.d.ts.map +1 -1
- package/dist/types/router-config.d.ts +2 -2
- package/dist/types/router-config.d.ts.map +1 -1
- package/dist/types/server-config.d.ts +8 -0
- package/dist/types/server-config.d.ts.map +1 -1
- package/dist/types/server-config.js +25 -0
- package/dist/types/server-config.js.map +1 -1
- package/dist/utils/http-log-filter.d.ts +10 -0
- package/dist/utils/http-log-filter.d.ts.map +1 -0
- package/dist/utils/http-log-filter.js +84 -0
- package/dist/utils/http-log-filter.js.map +1 -0
- package/dist/utils/log-parser.d.ts.map +1 -1
- package/dist/utils/log-parser.js +7 -4
- package/dist/utils/log-parser.js.map +1 -1
- package/dist/utils/log-utils.d.ts +59 -4
- package/dist/utils/log-utils.d.ts.map +1 -1
- package/dist/utils/log-utils.js +150 -11
- package/dist/utils/log-utils.js.map +1 -1
- package/dist/utils/shard-utils.d.ts +72 -0
- package/dist/utils/shard-utils.d.ts.map +1 -0
- package/dist/utils/shard-utils.js +168 -0
- package/dist/utils/shard-utils.js.map +1 -0
- package/package.json +18 -4
- package/src/launchers/llamacpp-admin +8 -0
- package/src/launchers/llamacpp-router +8 -0
- package/src/launchers/llamacpp-server +8 -0
- package/web/dist/assets/index-Byhoy86V.css +1 -0
- package/web/dist/assets/index-HSrgvray.js +50 -0
- package/web/dist/index.html +2 -2
- package/web/dist/assets/index-Bin89Lwr.css +0 -1
- package/web/dist/assets/index-CVmonw3T.js +0 -17
package/dist/lib/admin-server.js
CHANGED
|
@@ -41,14 +41,18 @@ const path = __importStar(require("path"));
|
|
|
41
41
|
const fs = __importStar(require("fs/promises"));
|
|
42
42
|
const file_utils_1 = require("../utils/file-utils");
|
|
43
43
|
const state_manager_1 = require("./state-manager");
|
|
44
|
-
const launchctl_manager_1 = require("./launchctl-manager");
|
|
45
44
|
const model_scanner_1 = require("./model-scanner");
|
|
46
|
-
const config_generator_1 = require("./config-generator");
|
|
47
|
-
const port_manager_1 = require("./port-manager");
|
|
48
45
|
const status_checker_1 = require("./status-checker");
|
|
46
|
+
const server_lifecycle_service_1 = require("./server-lifecycle-service");
|
|
47
|
+
const server_config_service_1 = require("./server-config-service");
|
|
48
|
+
const model_management_service_1 = require("./model-management-service");
|
|
49
49
|
const model_search_1 = require("./model-search");
|
|
50
50
|
const download_job_manager_1 = require("./download-job-manager");
|
|
51
51
|
const router_manager_1 = require("./router-manager");
|
|
52
|
+
const admin_manager_1 = require("./admin-manager");
|
|
53
|
+
const log_management_service_1 = require("./log-management-service");
|
|
54
|
+
const log_workers_1 = require("./log-workers");
|
|
55
|
+
const openapi_spec_1 = require("./openapi-spec");
|
|
52
56
|
/**
|
|
53
57
|
* Admin HTTP server - REST API for managing llama.cpp servers
|
|
54
58
|
*/
|
|
@@ -67,6 +71,7 @@ class AdminServer {
|
|
|
67
71
|
// Graceful shutdown
|
|
68
72
|
process.on('SIGTERM', async () => {
|
|
69
73
|
console.error('[Admin] Received SIGTERM, shutting down gracefully...');
|
|
74
|
+
await this.stopWorkers();
|
|
70
75
|
this.server.close(() => {
|
|
71
76
|
console.error('[Admin] Server closed');
|
|
72
77
|
process.exit(0);
|
|
@@ -74,6 +79,7 @@ class AdminServer {
|
|
|
74
79
|
});
|
|
75
80
|
process.on('SIGINT', async () => {
|
|
76
81
|
console.error('[Admin] Received SIGINT, shutting down gracefully...');
|
|
82
|
+
await this.stopWorkers();
|
|
77
83
|
this.server.close(() => {
|
|
78
84
|
console.error('[Admin] Server closed');
|
|
79
85
|
process.exit(0);
|
|
@@ -82,12 +88,45 @@ class AdminServer {
|
|
|
82
88
|
}
|
|
83
89
|
async start() {
|
|
84
90
|
await this.initialize();
|
|
91
|
+
// Start log management workers if configured
|
|
92
|
+
await this.startWorkers();
|
|
85
93
|
this.server.listen(this.config.port, this.config.host, () => {
|
|
86
94
|
console.error(`[Admin] Listening on http://${this.config.host}:${this.config.port}`);
|
|
87
95
|
console.error(`[Admin] PID: ${process.pid}`);
|
|
88
96
|
console.error(`[Admin] API Key: ${this.config.apiKey}`);
|
|
89
97
|
});
|
|
90
98
|
}
|
|
99
|
+
/**
|
|
100
|
+
* Start log management workers based on configuration
|
|
101
|
+
*/
|
|
102
|
+
async startWorkers() {
|
|
103
|
+
const logConfig = this.config.logManagement;
|
|
104
|
+
if (!logConfig) {
|
|
105
|
+
// No log management configured, use defaults
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
// Start auto-rotate worker
|
|
109
|
+
if (logConfig.autoRotate.enabled) {
|
|
110
|
+
this.autoRotateWorker = new log_workers_1.AutoRotateWorker(logConfig.autoRotate);
|
|
111
|
+
await this.autoRotateWorker.start();
|
|
112
|
+
}
|
|
113
|
+
// Start auto-delete worker
|
|
114
|
+
if (logConfig.autoDelete.enabled) {
|
|
115
|
+
this.autoDeleteWorker = new log_workers_1.AutoDeleteWorker(logConfig.autoDelete);
|
|
116
|
+
await this.autoDeleteWorker.start();
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Stop log management workers
|
|
121
|
+
*/
|
|
122
|
+
async stopWorkers() {
|
|
123
|
+
if (this.autoRotateWorker) {
|
|
124
|
+
await this.autoRotateWorker.stop();
|
|
125
|
+
}
|
|
126
|
+
if (this.autoDeleteWorker) {
|
|
127
|
+
await this.autoDeleteWorker.stop();
|
|
128
|
+
}
|
|
129
|
+
}
|
|
91
130
|
/**
|
|
92
131
|
* Main request handler
|
|
93
132
|
*/
|
|
@@ -114,6 +153,21 @@ class AdminServer {
|
|
|
114
153
|
await this.handleHealth(req, res);
|
|
115
154
|
return;
|
|
116
155
|
}
|
|
156
|
+
// Swagger UI - OpenAPI spec (no auth required)
|
|
157
|
+
if (pathname === '/api-docs.json') {
|
|
158
|
+
this.sendJson(res, 200, openapi_spec_1.openApiSpec);
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
// Swagger UI - HTML interface (no auth required)
|
|
162
|
+
if (pathname === '/api-docs' || pathname.startsWith('/api-docs/')) {
|
|
163
|
+
await this.handleSwaggerUI(req, res, pathname);
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
// Proxy chat requests to router (production mode)
|
|
167
|
+
if (pathname.startsWith('/v1/')) {
|
|
168
|
+
await this.handleRouterProxy(req, res, pathname);
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
117
171
|
// Static files (no auth required)
|
|
118
172
|
if (!pathname.startsWith('/api/')) {
|
|
119
173
|
await this.handleStaticFile(req, res, pathname);
|
|
@@ -155,6 +209,10 @@ class AdminServer {
|
|
|
155
209
|
const serverId = pathname.split('/')[3];
|
|
156
210
|
await this.handleRestartServer(req, res, serverId);
|
|
157
211
|
}
|
|
212
|
+
else if (pathname.match(/^\/api\/servers\/[^/]+\/slots$/) && method === 'GET') {
|
|
213
|
+
const serverId = pathname.split('/')[3];
|
|
214
|
+
await this.handleGetServerSlots(req, res, serverId);
|
|
215
|
+
}
|
|
158
216
|
else if (pathname.match(/^\/api\/servers\/[^/]+\/logs$/) && method === 'GET') {
|
|
159
217
|
const serverId = pathname.split('/')[3];
|
|
160
218
|
await this.handleGetLogs(req, res, serverId, url);
|
|
@@ -214,6 +272,24 @@ class AdminServer {
|
|
|
214
272
|
else if (pathname === '/api/router' && method === 'PATCH') {
|
|
215
273
|
await this.handleUpdateRouter(req, res);
|
|
216
274
|
}
|
|
275
|
+
else if (pathname === '/api/admin' && method === 'GET') {
|
|
276
|
+
await this.handleGetAdmin(req, res);
|
|
277
|
+
}
|
|
278
|
+
else if (pathname === '/api/admin/logs' && method === 'GET') {
|
|
279
|
+
await this.handleGetAdminLogs(req, res);
|
|
280
|
+
}
|
|
281
|
+
else if (pathname === '/api/admin/logs/rotate' && method === 'POST') {
|
|
282
|
+
await this.handleRotateLogs(req, res);
|
|
283
|
+
}
|
|
284
|
+
else if (pathname === '/api/admin/logs/clear-archived' && method === 'POST') {
|
|
285
|
+
await this.handleClearArchivedLogs(req, res);
|
|
286
|
+
}
|
|
287
|
+
else if (pathname === '/api/admin/logs/config' && method === 'PATCH') {
|
|
288
|
+
await this.handleUpdateLogConfig(req, res);
|
|
289
|
+
}
|
|
290
|
+
else if (pathname === '/api/admin/service-logs' && method === 'GET') {
|
|
291
|
+
await this.handleGetAdminServiceLogs(req, res, url);
|
|
292
|
+
}
|
|
217
293
|
else {
|
|
218
294
|
// API endpoint not found
|
|
219
295
|
this.sendError(res, 404, 'Not Found', `Unknown endpoint: ${method} ${pathname}`, 'NOT_FOUND');
|
|
@@ -237,16 +313,68 @@ class AdminServer {
|
|
|
237
313
|
timestamp: new Date().toISOString(),
|
|
238
314
|
});
|
|
239
315
|
}
|
|
316
|
+
/**
|
|
317
|
+
* Proxy router requests (production mode)
|
|
318
|
+
*/
|
|
319
|
+
async handleRouterProxy(req, res, pathname) {
|
|
320
|
+
try {
|
|
321
|
+
// Check if router is running
|
|
322
|
+
const routerStatus = await router_manager_1.routerManager.getStatus();
|
|
323
|
+
if (!routerStatus || !routerStatus.status.isRunning) {
|
|
324
|
+
this.sendError(res, 503, 'Service Unavailable', 'Router is not running. Start router to use chat.', 'ROUTER_NOT_RUNNING');
|
|
325
|
+
return;
|
|
326
|
+
}
|
|
327
|
+
const { config } = routerStatus;
|
|
328
|
+
const routerHost = config.host || '127.0.0.1';
|
|
329
|
+
const routerPort = config.port || 9100;
|
|
330
|
+
// Parse URL to get query string
|
|
331
|
+
const url = new url_1.URL(req.url, `http://${req.headers.host}`);
|
|
332
|
+
const fullPath = pathname + url.search;
|
|
333
|
+
// Create proxy request
|
|
334
|
+
const options = {
|
|
335
|
+
hostname: routerHost,
|
|
336
|
+
port: routerPort,
|
|
337
|
+
path: fullPath,
|
|
338
|
+
method: req.method,
|
|
339
|
+
headers: {
|
|
340
|
+
...req.headers,
|
|
341
|
+
host: `${routerHost}:${routerPort}`,
|
|
342
|
+
},
|
|
343
|
+
};
|
|
344
|
+
const proxyReq = http.request(options, (proxyRes) => {
|
|
345
|
+
// Copy status and headers
|
|
346
|
+
res.writeHead(proxyRes.statusCode || 500, proxyRes.headers);
|
|
347
|
+
// Pipe response (supports streaming SSE)
|
|
348
|
+
proxyRes.pipe(res);
|
|
349
|
+
});
|
|
350
|
+
proxyReq.on('error', (err) => {
|
|
351
|
+
console.error('[Admin] Router proxy error:', err);
|
|
352
|
+
if (!res.headersSent) {
|
|
353
|
+
this.sendError(res, 502, 'Bad Gateway', `Router connection failed: ${err.message}`, 'ROUTER_PROXY_ERROR');
|
|
354
|
+
}
|
|
355
|
+
});
|
|
356
|
+
// Pipe request body
|
|
357
|
+
req.pipe(proxyReq);
|
|
358
|
+
}
|
|
359
|
+
catch (error) {
|
|
360
|
+
console.error('[Admin] Error proxying to router:', error);
|
|
361
|
+
if (!res.headersSent) {
|
|
362
|
+
this.sendError(res, 500, 'Internal Server Error', error.message, 'ROUTER_PROXY_ERROR');
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
}
|
|
240
366
|
/**
|
|
241
367
|
* List all servers
|
|
242
368
|
*/
|
|
243
369
|
async handleListServers(req, res) {
|
|
244
370
|
const servers = await state_manager_1.stateManager.getAllServers();
|
|
245
|
-
// Update status for each server
|
|
371
|
+
// Update status for each server (including health check)
|
|
246
372
|
for (const server of servers) {
|
|
247
373
|
const status = await status_checker_1.statusChecker.checkServer(server);
|
|
248
374
|
server.status = status_checker_1.statusChecker.determineStatus(status, status.portListening);
|
|
249
375
|
server.pid = status.pid || undefined;
|
|
376
|
+
// Add health check result (only meaningful if server is running)
|
|
377
|
+
server.healthy = server.status === 'running' ? status.healthy : undefined;
|
|
250
378
|
}
|
|
251
379
|
this.sendJson(res, 200, { servers });
|
|
252
380
|
}
|
|
@@ -262,6 +390,8 @@ class AdminServer {
|
|
|
262
390
|
const status = await status_checker_1.statusChecker.checkServer(server);
|
|
263
391
|
server.status = status_checker_1.statusChecker.determineStatus(status, status.portListening);
|
|
264
392
|
server.pid = status.pid || undefined;
|
|
393
|
+
// Add health check result (only meaningful if server is running)
|
|
394
|
+
server.healthy = server.status === 'running' ? status.healthy : undefined;
|
|
265
395
|
this.sendJson(res, 200, { server, status });
|
|
266
396
|
}
|
|
267
397
|
/**
|
|
@@ -283,39 +413,6 @@ class AdminServer {
|
|
|
283
413
|
return;
|
|
284
414
|
}
|
|
285
415
|
try {
|
|
286
|
-
// Resolve model path
|
|
287
|
-
const modelPath = await model_scanner_1.modelScanner.resolveModelPath(data.model);
|
|
288
|
-
if (!modelPath) {
|
|
289
|
-
this.sendError(res, 404, 'Not Found', `Model not found: ${data.model}`, 'MODEL_NOT_FOUND');
|
|
290
|
-
return;
|
|
291
|
-
}
|
|
292
|
-
const modelName = path.basename(modelPath);
|
|
293
|
-
// Check if server already exists
|
|
294
|
-
const existingServer = await state_manager_1.stateManager.serverExistsForModel(modelPath);
|
|
295
|
-
if (existingServer) {
|
|
296
|
-
this.sendError(res, 409, 'Conflict', `Server already exists for model: ${modelName}`, 'SERVER_EXISTS');
|
|
297
|
-
return;
|
|
298
|
-
}
|
|
299
|
-
// Get model size
|
|
300
|
-
const modelSize = await model_scanner_1.modelScanner.getModelSize(modelName);
|
|
301
|
-
if (!modelSize) {
|
|
302
|
-
this.sendError(res, 500, 'Internal Server Error', 'Failed to read model file', 'MODEL_READ_ERROR');
|
|
303
|
-
return;
|
|
304
|
-
}
|
|
305
|
-
// Determine port
|
|
306
|
-
let port;
|
|
307
|
-
if (data.port) {
|
|
308
|
-
port_manager_1.portManager.validatePort(data.port);
|
|
309
|
-
const available = await port_manager_1.portManager.isPortAvailable(data.port);
|
|
310
|
-
if (!available) {
|
|
311
|
-
this.sendError(res, 409, 'Conflict', `Port ${data.port} is already in use`, 'PORT_IN_USE');
|
|
312
|
-
return;
|
|
313
|
-
}
|
|
314
|
-
port = data.port;
|
|
315
|
-
}
|
|
316
|
-
else {
|
|
317
|
-
port = await port_manager_1.portManager.findAvailablePort();
|
|
318
|
-
}
|
|
319
416
|
// Parse custom flags if provided
|
|
320
417
|
let customFlags;
|
|
321
418
|
if (data.customFlags) {
|
|
@@ -323,8 +420,8 @@ class AdminServer {
|
|
|
323
420
|
? data.customFlags
|
|
324
421
|
: data.customFlags.split(',').map((f) => f.trim()).filter((f) => f.length > 0);
|
|
325
422
|
}
|
|
326
|
-
//
|
|
327
|
-
const
|
|
423
|
+
// Create server using centralized service
|
|
424
|
+
const result = await server_lifecycle_service_1.serverLifecycleService.createServer(data.model, {
|
|
328
425
|
port: data.port,
|
|
329
426
|
host: data.host,
|
|
330
427
|
threads: data.threads,
|
|
@@ -332,29 +429,25 @@ class AdminServer {
|
|
|
332
429
|
gpuLayers: data.gpuLayers,
|
|
333
430
|
verbose: data.verbose,
|
|
334
431
|
customFlags,
|
|
432
|
+
alias: data.alias,
|
|
335
433
|
});
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
434
|
+
if (!result.success) {
|
|
435
|
+
// Map common errors to appropriate HTTP status codes
|
|
436
|
+
if (result.error?.includes('not found')) {
|
|
437
|
+
this.sendError(res, 404, 'Not Found', result.error, 'MODEL_NOT_FOUND');
|
|
438
|
+
}
|
|
439
|
+
else if (result.error?.includes('already in use') || result.error?.includes('already used')) {
|
|
440
|
+
this.sendError(res, 409, 'Conflict', result.error, 'CONFLICT');
|
|
441
|
+
}
|
|
442
|
+
else if (result.error?.includes('Invalid alias')) {
|
|
443
|
+
this.sendError(res, 400, 'Bad Request', result.error, 'INVALID_ALIAS');
|
|
444
|
+
}
|
|
445
|
+
else {
|
|
446
|
+
this.sendError(res, 500, 'Internal Server Error', result.error || 'Server creation failed', 'CREATE_ERROR');
|
|
447
|
+
}
|
|
346
448
|
return;
|
|
347
449
|
}
|
|
348
|
-
|
|
349
|
-
const status = await status_checker_1.statusChecker.checkServer(serverConfig);
|
|
350
|
-
serverConfig.status = status_checker_1.statusChecker.determineStatus(status, status.portListening);
|
|
351
|
-
serverConfig.pid = status.pid || undefined;
|
|
352
|
-
await state_manager_1.stateManager.updateServerConfig(serverConfig.id, {
|
|
353
|
-
status: serverConfig.status,
|
|
354
|
-
pid: serverConfig.pid,
|
|
355
|
-
lastStarted: new Date().toISOString(),
|
|
356
|
-
});
|
|
357
|
-
this.sendJson(res, 201, { server: serverConfig });
|
|
450
|
+
this.sendJson(res, 201, { server: result.server });
|
|
358
451
|
}
|
|
359
452
|
catch (error) {
|
|
360
453
|
this.sendError(res, 500, 'Internal Server Error', error.message, 'CREATE_ERROR');
|
|
@@ -379,66 +472,56 @@ class AdminServer {
|
|
|
379
472
|
return;
|
|
380
473
|
}
|
|
381
474
|
try {
|
|
382
|
-
//
|
|
383
|
-
|
|
384
|
-
if (data.model !== undefined) {
|
|
385
|
-
const modelPath = await model_scanner_1.modelScanner.resolveModelPath(data.model);
|
|
386
|
-
if (!modelPath) {
|
|
387
|
-
this.sendError(res, 404, 'Not Found', `Model not found: ${data.model}`, 'MODEL_NOT_FOUND');
|
|
388
|
-
return;
|
|
389
|
-
}
|
|
390
|
-
updates.modelPath = modelPath;
|
|
391
|
-
updates.modelName = path.basename(modelPath);
|
|
392
|
-
}
|
|
393
|
-
if (data.port !== undefined) {
|
|
394
|
-
port_manager_1.portManager.validatePort(data.port);
|
|
395
|
-
const available = await port_manager_1.portManager.isPortAvailable(data.port);
|
|
396
|
-
if (!available && data.port !== server.port) {
|
|
397
|
-
this.sendError(res, 409, 'Conflict', `Port ${data.port} is already in use`, 'PORT_IN_USE');
|
|
398
|
-
return;
|
|
399
|
-
}
|
|
400
|
-
updates.port = data.port;
|
|
401
|
-
}
|
|
402
|
-
if (data.host !== undefined)
|
|
403
|
-
updates.host = data.host;
|
|
404
|
-
if (data.threads !== undefined)
|
|
405
|
-
updates.threads = data.threads;
|
|
406
|
-
if (data.ctxSize !== undefined)
|
|
407
|
-
updates.ctxSize = data.ctxSize;
|
|
408
|
-
if (data.gpuLayers !== undefined)
|
|
409
|
-
updates.gpuLayers = data.gpuLayers;
|
|
410
|
-
if (data.verbose !== undefined)
|
|
411
|
-
updates.verbose = data.verbose;
|
|
475
|
+
// Parse custom flags
|
|
476
|
+
let customFlags;
|
|
412
477
|
if (data.customFlags !== undefined) {
|
|
413
|
-
|
|
478
|
+
customFlags = Array.isArray(data.customFlags)
|
|
414
479
|
? data.customFlags
|
|
415
480
|
: data.customFlags.split(',').map((f) => f.trim()).filter((f) => f.length > 0);
|
|
416
481
|
}
|
|
417
|
-
//
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
//
|
|
423
|
-
const
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
482
|
+
// Handle alias empty string/null -> null conversion
|
|
483
|
+
let aliasValue = data.alias;
|
|
484
|
+
if (data.alias === '' || data.alias === null) {
|
|
485
|
+
aliasValue = null; // null means remove alias
|
|
486
|
+
}
|
|
487
|
+
// Delegate to serverConfigService (FIX: now handles model migration properly)
|
|
488
|
+
const result = await server_config_service_1.serverConfigService.updateConfig({
|
|
489
|
+
serverId: server.id,
|
|
490
|
+
updates: {
|
|
491
|
+
model: data.model,
|
|
492
|
+
port: data.port,
|
|
493
|
+
host: data.host,
|
|
494
|
+
threads: data.threads,
|
|
495
|
+
ctxSize: data.ctxSize,
|
|
496
|
+
gpuLayers: data.gpuLayers,
|
|
497
|
+
verbose: data.verbose,
|
|
498
|
+
customFlags,
|
|
499
|
+
alias: aliasValue,
|
|
500
|
+
},
|
|
501
|
+
restartIfNeeded: data.restart === true,
|
|
502
|
+
});
|
|
503
|
+
if (!result.success) {
|
|
504
|
+
// Map common errors to appropriate HTTP status codes
|
|
505
|
+
if (result.error?.includes('not found')) {
|
|
506
|
+
this.sendError(res, 404, 'Not Found', result.error, 'NOT_FOUND');
|
|
507
|
+
}
|
|
508
|
+
else if (result.error?.includes('already in use') || result.error?.includes('already exists')) {
|
|
509
|
+
this.sendError(res, 409, 'Conflict', result.error, 'CONFLICT');
|
|
438
510
|
}
|
|
511
|
+
else if (result.error?.includes('Invalid')) {
|
|
512
|
+
this.sendError(res, 400, 'Bad Request', result.error, 'VALIDATION_ERROR');
|
|
513
|
+
}
|
|
514
|
+
else {
|
|
515
|
+
this.sendError(res, 500, 'Internal Server Error', result.error || 'Update failed', 'UPDATE_ERROR');
|
|
516
|
+
}
|
|
517
|
+
return;
|
|
439
518
|
}
|
|
440
|
-
|
|
441
|
-
this.sendJson(res, 200, {
|
|
519
|
+
// Return updated server (with migration info if applicable)
|
|
520
|
+
this.sendJson(res, 200, {
|
|
521
|
+
server: result.server,
|
|
522
|
+
migrated: result.migrated,
|
|
523
|
+
oldServerId: result.oldServerId,
|
|
524
|
+
});
|
|
442
525
|
}
|
|
443
526
|
catch (error) {
|
|
444
527
|
this.sendError(res, 500, 'Internal Server Error', error.message, 'UPDATE_ERROR');
|
|
@@ -453,58 +536,50 @@ class AdminServer {
|
|
|
453
536
|
this.sendError(res, 404, 'Not Found', `Server not found: ${serverId}`, 'SERVER_NOT_FOUND');
|
|
454
537
|
return;
|
|
455
538
|
}
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
await launchctl_manager_1.launchctlManager.unloadService(server.plistPath);
|
|
461
|
-
await launchctl_manager_1.launchctlManager.waitForServiceStop(server.label, 5000);
|
|
539
|
+
const result = await server_lifecycle_service_1.serverLifecycleService.deleteServer(serverId);
|
|
540
|
+
if (!result.success) {
|
|
541
|
+
if (result.error?.includes('not found')) {
|
|
542
|
+
this.sendError(res, 404, 'Not Found', result.error, 'SERVER_NOT_FOUND');
|
|
462
543
|
}
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
544
|
+
else if (result.error?.includes('already')) {
|
|
545
|
+
this.sendError(res, 409, 'Conflict', result.error, 'OPERATION_IN_PROGRESS');
|
|
546
|
+
}
|
|
547
|
+
else {
|
|
548
|
+
this.sendError(res, 500, 'Internal Server Error', result.error || 'Unknown error', 'DELETE_ERROR');
|
|
549
|
+
}
|
|
550
|
+
return;
|
|
470
551
|
}
|
|
552
|
+
this.sendJson(res, 200, { success: true });
|
|
471
553
|
}
|
|
472
554
|
/**
|
|
473
555
|
* Start server
|
|
474
556
|
*/
|
|
475
557
|
async handleStartServer(req, res, serverId) {
|
|
476
|
-
const server = await state_manager_1.stateManager.findServer(serverId);
|
|
477
|
-
if (!server) {
|
|
478
|
-
this.sendError(res, 404, 'Not Found', `Server not found: ${serverId}`, 'SERVER_NOT_FOUND');
|
|
479
|
-
return;
|
|
480
|
-
}
|
|
481
558
|
try {
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
559
|
+
// Use centralized lifecycle service
|
|
560
|
+
const result = await server_lifecycle_service_1.serverLifecycleService.startServer(serverId);
|
|
561
|
+
if (!result.success) {
|
|
562
|
+
// Map common errors to appropriate HTTP status codes
|
|
563
|
+
if (result.error?.includes('not found')) {
|
|
564
|
+
this.sendError(res, 404, 'Not Found', result.error, 'SERVER_NOT_FOUND');
|
|
565
|
+
}
|
|
566
|
+
else if (result.error?.includes('already running')) {
|
|
567
|
+
this.sendError(res, 409, 'Conflict', result.error, 'ALREADY_RUNNING');
|
|
568
|
+
}
|
|
569
|
+
else if (result.error?.includes('already starting')) {
|
|
570
|
+
this.sendError(res, 409, 'Conflict', result.error, 'OPERATION_IN_PROGRESS');
|
|
571
|
+
}
|
|
572
|
+
else {
|
|
573
|
+
this.sendError(res, 500, 'Internal Server Error', result.error || 'Unknown error', 'START_FAILED');
|
|
574
|
+
}
|
|
496
575
|
return;
|
|
497
576
|
}
|
|
498
|
-
//
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
pid: newStatus.pid || undefined,
|
|
504
|
-
lastStarted: new Date().toISOString(),
|
|
577
|
+
// Return success with server details
|
|
578
|
+
this.sendJson(res, 200, {
|
|
579
|
+
server: result.server,
|
|
580
|
+
metalMemoryMB: result.metalMemoryMB,
|
|
581
|
+
rotatedLogs: result.rotatedLogs,
|
|
505
582
|
});
|
|
506
|
-
const updatedServer = await state_manager_1.stateManager.loadServerConfig(server.id);
|
|
507
|
-
this.sendJson(res, 200, { server: updatedServer });
|
|
508
583
|
}
|
|
509
584
|
catch (error) {
|
|
510
585
|
this.sendError(res, 500, 'Internal Server Error', error.message, 'START_ERROR');
|
|
@@ -514,26 +589,27 @@ class AdminServer {
|
|
|
514
589
|
* Stop server
|
|
515
590
|
*/
|
|
516
591
|
async handleStopServer(req, res, serverId) {
|
|
517
|
-
const server = await state_manager_1.stateManager.findServer(serverId);
|
|
518
|
-
if (!server) {
|
|
519
|
-
this.sendError(res, 404, 'Not Found', `Server not found: ${serverId}`, 'SERVER_NOT_FOUND');
|
|
520
|
-
return;
|
|
521
|
-
}
|
|
522
592
|
try {
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
593
|
+
// Use centralized lifecycle service
|
|
594
|
+
const result = await server_lifecycle_service_1.serverLifecycleService.stopServer(serverId);
|
|
595
|
+
if (!result.success) {
|
|
596
|
+
// Map common errors to appropriate HTTP status codes
|
|
597
|
+
if (result.error?.includes('not found')) {
|
|
598
|
+
this.sendError(res, 404, 'Not Found', result.error, 'SERVER_NOT_FOUND');
|
|
599
|
+
}
|
|
600
|
+
else if (result.error?.includes('already stopped')) {
|
|
601
|
+
this.sendError(res, 409, 'Conflict', result.error, 'NOT_RUNNING');
|
|
602
|
+
}
|
|
603
|
+
else if (result.error?.includes('already stopping')) {
|
|
604
|
+
this.sendError(res, 409, 'Conflict', result.error, 'OPERATION_IN_PROGRESS');
|
|
605
|
+
}
|
|
606
|
+
else {
|
|
607
|
+
this.sendError(res, 500, 'Internal Server Error', result.error || 'Unknown error', 'STOP_FAILED');
|
|
608
|
+
}
|
|
526
609
|
return;
|
|
527
610
|
}
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
await state_manager_1.stateManager.updateServerConfig(server.id, {
|
|
531
|
-
status: 'stopped',
|
|
532
|
-
pid: undefined,
|
|
533
|
-
lastStopped: new Date().toISOString(),
|
|
534
|
-
});
|
|
535
|
-
const updatedServer = await state_manager_1.stateManager.loadServerConfig(server.id);
|
|
536
|
-
this.sendJson(res, 200, { server: updatedServer });
|
|
611
|
+
// Return success with server details
|
|
612
|
+
this.sendJson(res, 200, { server: result.server });
|
|
537
613
|
}
|
|
538
614
|
catch (error) {
|
|
539
615
|
this.sendError(res, 500, 'Internal Server Error', error.message, 'STOP_ERROR');
|
|
@@ -543,39 +619,75 @@ class AdminServer {
|
|
|
543
619
|
* Restart server
|
|
544
620
|
*/
|
|
545
621
|
async handleRestartServer(req, res, serverId) {
|
|
622
|
+
try {
|
|
623
|
+
// Use centralized lifecycle service
|
|
624
|
+
const result = await server_lifecycle_service_1.serverLifecycleService.restartServer(serverId);
|
|
625
|
+
if (!result.success) {
|
|
626
|
+
// Map common errors to appropriate HTTP status codes
|
|
627
|
+
if (result.error?.includes('not found')) {
|
|
628
|
+
this.sendError(res, 404, 'Not Found', result.error, 'SERVER_NOT_FOUND');
|
|
629
|
+
}
|
|
630
|
+
else if (result.error?.includes('Failed to stop')) {
|
|
631
|
+
this.sendError(res, 500, 'Internal Server Error', result.error, 'STOP_FAILED');
|
|
632
|
+
}
|
|
633
|
+
else {
|
|
634
|
+
this.sendError(res, 500, 'Internal Server Error', result.error || 'Unknown error', 'RESTART_FAILED');
|
|
635
|
+
}
|
|
636
|
+
return;
|
|
637
|
+
}
|
|
638
|
+
// Return success with server details
|
|
639
|
+
this.sendJson(res, 200, {
|
|
640
|
+
server: result.server,
|
|
641
|
+
metalMemoryMB: result.metalMemoryMB,
|
|
642
|
+
rotatedLogs: result.rotatedLogs,
|
|
643
|
+
});
|
|
644
|
+
}
|
|
645
|
+
catch (error) {
|
|
646
|
+
this.sendError(res, 500, 'Internal Server Error', error.message, 'RESTART_ERROR');
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
/**
|
|
650
|
+
* Get slot status from a running llama.cpp server
|
|
651
|
+
*/
|
|
652
|
+
async handleGetServerSlots(req, res, serverId) {
|
|
546
653
|
const server = await state_manager_1.stateManager.findServer(serverId);
|
|
547
654
|
if (!server) {
|
|
548
655
|
this.sendError(res, 404, 'Not Found', `Server not found: ${serverId}`, 'SERVER_NOT_FOUND');
|
|
549
656
|
return;
|
|
550
657
|
}
|
|
658
|
+
const emptySlots = { slots: [], activeSlots: 0, idleSlots: 0, totalSlots: 0 };
|
|
551
659
|
try {
|
|
552
|
-
// Stop if running
|
|
553
660
|
const status = await status_checker_1.statusChecker.checkServer(server);
|
|
554
|
-
if (status_checker_1.statusChecker.determineStatus(status, status.portListening)
|
|
555
|
-
|
|
556
|
-
await launchctl_manager_1.launchctlManager.waitForServiceStop(server.label, 5000);
|
|
557
|
-
}
|
|
558
|
-
// Start
|
|
559
|
-
await launchctl_manager_1.launchctlManager.loadService(server.plistPath);
|
|
560
|
-
await launchctl_manager_1.launchctlManager.startService(server.label);
|
|
561
|
-
const started = await launchctl_manager_1.launchctlManager.waitForServiceStart(server.label, 5000);
|
|
562
|
-
if (!started) {
|
|
563
|
-
this.sendError(res, 500, 'Internal Server Error', 'Server failed to start', 'START_FAILED');
|
|
661
|
+
if (status_checker_1.statusChecker.determineStatus(status, status.portListening) !== 'running') {
|
|
662
|
+
this.sendJson(res, 200, emptySlots);
|
|
564
663
|
return;
|
|
565
664
|
}
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
const
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
665
|
+
const host = server.host || '127.0.0.1';
|
|
666
|
+
const controller = new AbortController();
|
|
667
|
+
const timeout = setTimeout(() => controller.abort(), 3000);
|
|
668
|
+
try {
|
|
669
|
+
const response = await fetch(`http://${host}:${server.port}/slots`, { signal: controller.signal });
|
|
670
|
+
clearTimeout(timeout);
|
|
671
|
+
if (!response.ok) {
|
|
672
|
+
this.sendJson(res, 200, emptySlots);
|
|
673
|
+
return;
|
|
674
|
+
}
|
|
675
|
+
const slots = await response.json();
|
|
676
|
+
const activeSlots = slots.filter(s => s.is_processing).length;
|
|
677
|
+
this.sendJson(res, 200, {
|
|
678
|
+
slots,
|
|
679
|
+
activeSlots,
|
|
680
|
+
idleSlots: slots.length - activeSlots,
|
|
681
|
+
totalSlots: slots.length,
|
|
682
|
+
});
|
|
683
|
+
}
|
|
684
|
+
catch {
|
|
685
|
+
clearTimeout(timeout);
|
|
686
|
+
this.sendJson(res, 200, emptySlots);
|
|
687
|
+
}
|
|
576
688
|
}
|
|
577
689
|
catch (error) {
|
|
578
|
-
this.sendError(res, 500, 'Internal Server Error', error.message, '
|
|
690
|
+
this.sendError(res, 500, 'Internal Server Error', error.message, 'SLOTS_ERROR');
|
|
579
691
|
}
|
|
580
692
|
}
|
|
581
693
|
/**
|
|
@@ -588,41 +700,66 @@ class AdminServer {
|
|
|
588
700
|
return;
|
|
589
701
|
}
|
|
590
702
|
try {
|
|
591
|
-
const type = url.searchParams.get('type') || '
|
|
703
|
+
const type = url.searchParams.get('type') || 'activity'; // activity (default), system, http, stderr, stdout, or all
|
|
592
704
|
const lines = parseInt(url.searchParams.get('lines') || '100');
|
|
705
|
+
// Support both new terminology and old parameter names (backward compatibility):
|
|
706
|
+
// - 'activity' (new) or 'http' (old) -> HTTP activity logs only
|
|
707
|
+
// - 'system' (new) -> stderr + stdout (system diagnostic logs, no http)
|
|
708
|
+
// - 'all' (old) -> everything (http + stderr + stdout)
|
|
709
|
+
// - 'stderr' (old) -> stderr only
|
|
710
|
+
// - 'stdout' (old) -> stdout only
|
|
711
|
+
let http = '';
|
|
593
712
|
let stdout = '';
|
|
594
713
|
let stderr = '';
|
|
595
|
-
|
|
714
|
+
// HTTP logs
|
|
715
|
+
if ((type === 'activity' || type === 'http' || type === 'all') && (await (0, file_utils_1.fileExists)(server.httpLogPath))) {
|
|
716
|
+
const content = await fs.readFile(server.httpLogPath, 'utf-8');
|
|
717
|
+
const logLines = content.split('\n');
|
|
718
|
+
http = logLines.slice(-lines).join('\n');
|
|
719
|
+
}
|
|
720
|
+
// Stdout logs
|
|
721
|
+
if ((type === 'system' || type === 'stdout' || type === 'all') && (await (0, file_utils_1.fileExists)(server.stdoutPath))) {
|
|
596
722
|
const content = await fs.readFile(server.stdoutPath, 'utf-8');
|
|
597
723
|
const logLines = content.split('\n');
|
|
598
724
|
stdout = logLines.slice(-lines).join('\n');
|
|
599
725
|
}
|
|
600
|
-
|
|
726
|
+
// Stderr logs
|
|
727
|
+
if ((type === 'system' || type === 'stderr' || type === 'all') && (await (0, file_utils_1.fileExists)(server.stderrPath))) {
|
|
601
728
|
const content = await fs.readFile(server.stderrPath, 'utf-8');
|
|
602
729
|
const logLines = content.split('\n');
|
|
603
730
|
stderr = logLines.slice(-lines).join('\n');
|
|
604
731
|
}
|
|
605
|
-
this.sendJson(res, 200, { stdout, stderr });
|
|
732
|
+
this.sendJson(res, 200, { http, stdout, stderr });
|
|
606
733
|
}
|
|
607
734
|
catch (error) {
|
|
608
735
|
this.sendError(res, 500, 'Internal Server Error', error.message, 'LOGS_ERROR');
|
|
609
736
|
}
|
|
610
737
|
}
|
|
611
738
|
/**
|
|
612
|
-
* List models
|
|
739
|
+
* List models (handles sharded models correctly)
|
|
613
740
|
*/
|
|
614
741
|
async handleListModels(req, res) {
|
|
615
742
|
try {
|
|
616
743
|
const models = await model_scanner_1.modelScanner.scanModels();
|
|
617
|
-
const
|
|
618
|
-
|
|
619
|
-
|
|
744
|
+
const allServers = await state_manager_1.stateManager.getAllServers();
|
|
745
|
+
const modelsWithServers = models.map((model) => {
|
|
746
|
+
// Find servers using this model (handles sharded models)
|
|
747
|
+
const usingServers = allServers.filter(server => {
|
|
748
|
+
if (model.isSharded && model.shardPaths) {
|
|
749
|
+
// Check if server uses any shard of this model
|
|
750
|
+
return model.shardPaths.includes(server.modelPath);
|
|
751
|
+
}
|
|
752
|
+
else {
|
|
753
|
+
// Single-file model: exact path match
|
|
754
|
+
return server.modelPath === model.path;
|
|
755
|
+
}
|
|
756
|
+
});
|
|
620
757
|
return {
|
|
621
758
|
...model,
|
|
622
759
|
serversUsing: usingServers.length,
|
|
623
760
|
serverIds: usingServers.map(s => s.id),
|
|
624
761
|
};
|
|
625
|
-
})
|
|
762
|
+
});
|
|
626
763
|
this.sendJson(res, 200, { models: modelsWithServers });
|
|
627
764
|
}
|
|
628
765
|
catch (error) {
|
|
@@ -759,46 +896,38 @@ class AdminServer {
|
|
|
759
896
|
}
|
|
760
897
|
/**
|
|
761
898
|
* Delete model
|
|
899
|
+
* FIX: Now uses modelManagementService which filters by modelPath (not modelName)
|
|
762
900
|
*/
|
|
763
901
|
async handleDeleteModel(req, res, modelName, url) {
|
|
764
902
|
try {
|
|
765
903
|
const cascade = url.searchParams.get('cascade') === 'true';
|
|
766
|
-
//
|
|
767
|
-
const
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
await launchctl_manager_1.launchctlManager.waitForServiceStop(server.label, 5000);
|
|
782
|
-
}
|
|
783
|
-
await launchctl_manager_1.launchctlManager.deletePlist(server.plistPath);
|
|
784
|
-
await state_manager_1.stateManager.deleteServerConfig(server.id);
|
|
785
|
-
deletedServers.push(server.id);
|
|
904
|
+
// Delegate to modelManagementService (FIX: now filters by modelPath correctly)
|
|
905
|
+
const result = await model_management_service_1.modelManagementService.deleteModel({
|
|
906
|
+
modelIdentifier: modelName,
|
|
907
|
+
cascade,
|
|
908
|
+
});
|
|
909
|
+
if (!result.success) {
|
|
910
|
+
// Map errors to appropriate HTTP status codes
|
|
911
|
+
if (result.error?.includes('not found')) {
|
|
912
|
+
this.sendError(res, 404, 'Not Found', result.error, 'MODEL_NOT_FOUND');
|
|
913
|
+
}
|
|
914
|
+
else if (result.error?.includes('used by')) {
|
|
915
|
+
this.sendError(res, 409, 'Conflict', result.error, 'MODEL_IN_USE');
|
|
916
|
+
}
|
|
917
|
+
else {
|
|
918
|
+
this.sendError(res, 500, 'Internal Server Error', result.error || 'Delete failed', 'DELETE_ERROR');
|
|
786
919
|
}
|
|
787
|
-
}
|
|
788
|
-
// Delete model file
|
|
789
|
-
const modelPath = await model_scanner_1.modelScanner.resolveModelPath(modelName);
|
|
790
|
-
if (!modelPath) {
|
|
791
|
-
this.sendError(res, 404, 'Not Found', `Model not found: ${modelName}`, 'MODEL_NOT_FOUND');
|
|
792
920
|
return;
|
|
793
921
|
}
|
|
794
|
-
|
|
922
|
+
// Success - return deleted servers info
|
|
795
923
|
this.sendJson(res, 200, {
|
|
796
924
|
success: true,
|
|
797
|
-
|
|
925
|
+
modelPath: result.modelPath,
|
|
926
|
+
deletedServers: result.deletedServers,
|
|
798
927
|
});
|
|
799
928
|
}
|
|
800
929
|
catch (error) {
|
|
801
|
-
this.sendError(res, 500, 'Internal Server Error', error.message, '
|
|
930
|
+
this.sendError(res, 500, 'Internal Server Error', error.message, 'DELETE_ERROR');
|
|
802
931
|
}
|
|
803
932
|
}
|
|
804
933
|
/**
|
|
@@ -871,7 +1000,7 @@ class AdminServer {
|
|
|
871
1000
|
config: {
|
|
872
1001
|
port: config.port,
|
|
873
1002
|
host: config.host,
|
|
874
|
-
|
|
1003
|
+
logging: config.logging,
|
|
875
1004
|
requestTimeout: config.requestTimeout,
|
|
876
1005
|
healthCheckInterval: config.healthCheckInterval,
|
|
877
1006
|
},
|
|
@@ -887,6 +1016,41 @@ class AdminServer {
|
|
|
887
1016
|
this.sendError(res, 500, 'Internal Server Error', error.message, 'ROUTER_STATUS_ERROR');
|
|
888
1017
|
}
|
|
889
1018
|
}
|
|
1019
|
+
/**
|
|
1020
|
+
* Get admin status
|
|
1021
|
+
*/
|
|
1022
|
+
async handleGetAdmin(req, res) {
|
|
1023
|
+
try {
|
|
1024
|
+
const adminStatus = await admin_manager_1.adminManager.getStatus();
|
|
1025
|
+
if (!adminStatus) {
|
|
1026
|
+
this.sendJson(res, 200, {
|
|
1027
|
+
status: 'not_configured',
|
|
1028
|
+
config: null,
|
|
1029
|
+
isRunning: false,
|
|
1030
|
+
});
|
|
1031
|
+
return;
|
|
1032
|
+
}
|
|
1033
|
+
const { config, status } = adminStatus;
|
|
1034
|
+
this.sendJson(res, 200, {
|
|
1035
|
+
status: status.isRunning ? 'running' : 'stopped',
|
|
1036
|
+
config: {
|
|
1037
|
+
port: config.port,
|
|
1038
|
+
host: config.host,
|
|
1039
|
+
logging: config.logging,
|
|
1040
|
+
requestTimeout: config.requestTimeout,
|
|
1041
|
+
},
|
|
1042
|
+
pid: status.pid,
|
|
1043
|
+
isRunning: status.isRunning,
|
|
1044
|
+
apiKey: config.apiKey,
|
|
1045
|
+
createdAt: config.createdAt,
|
|
1046
|
+
lastStarted: config.lastStarted,
|
|
1047
|
+
lastStopped: config.lastStopped,
|
|
1048
|
+
});
|
|
1049
|
+
}
|
|
1050
|
+
catch (error) {
|
|
1051
|
+
this.sendError(res, 500, 'Internal Server Error', error.message, 'ADMIN_STATUS_ERROR');
|
|
1052
|
+
}
|
|
1053
|
+
}
|
|
890
1054
|
/**
|
|
891
1055
|
* Start router service
|
|
892
1056
|
*/
|
|
@@ -946,16 +1110,22 @@ class AdminServer {
|
|
|
946
1110
|
this.sendError(res, 404, 'Not Found', 'Router not configured', 'ROUTER_NOT_FOUND');
|
|
947
1111
|
return;
|
|
948
1112
|
}
|
|
949
|
-
const type = url.searchParams.get('type') || 'both'; // stdout, stderr, or both
|
|
1113
|
+
const type = url.searchParams.get('type') || 'both'; // activity, system, stdout, stderr, or both
|
|
950
1114
|
const lines = parseInt(url.searchParams.get('lines') || '100');
|
|
1115
|
+
// Support both new terminology and old parameter names (backward compatibility):
|
|
1116
|
+
// - 'activity' (new) or 'stdout' (old) -> router activity logs
|
|
1117
|
+
// - 'system' (new) or 'stderr' (old) -> system diagnostic logs
|
|
1118
|
+
// - 'both' (old) -> both stdout + stderr
|
|
951
1119
|
let stdout = '';
|
|
952
1120
|
let stderr = '';
|
|
953
|
-
|
|
1121
|
+
// Activity logs (stdout)
|
|
1122
|
+
if ((type === 'activity' || type === 'stdout' || type === 'both') && (await (0, file_utils_1.fileExists)(config.stdoutPath))) {
|
|
954
1123
|
const content = await fs.readFile(config.stdoutPath, 'utf-8');
|
|
955
1124
|
const logLines = content.split('\n');
|
|
956
1125
|
stdout = logLines.slice(-lines).join('\n');
|
|
957
1126
|
}
|
|
958
|
-
|
|
1127
|
+
// System logs (stderr)
|
|
1128
|
+
if ((type === 'system' || type === 'stderr' || type === 'both') && (await (0, file_utils_1.fileExists)(config.stderrPath))) {
|
|
959
1129
|
const content = await fs.readFile(config.stderrPath, 'utf-8');
|
|
960
1130
|
const logLines = content.split('\n');
|
|
961
1131
|
stderr = logLines.slice(-lines).join('\n');
|
|
@@ -966,6 +1136,33 @@ class AdminServer {
|
|
|
966
1136
|
this.sendError(res, 500, 'Internal Server Error', error.message, 'ROUTER_LOGS_ERROR');
|
|
967
1137
|
}
|
|
968
1138
|
}
|
|
1139
|
+
/**
|
|
1140
|
+
* Get admin service logs content
|
|
1141
|
+
*/
|
|
1142
|
+
async handleGetAdminServiceLogs(req, res, url) {
|
|
1143
|
+
try {
|
|
1144
|
+
const type = url.searchParams.get('type') || 'both'; // activity, system, or both
|
|
1145
|
+
const lines = parseInt(url.searchParams.get('lines') || '100');
|
|
1146
|
+
let stdout = '';
|
|
1147
|
+
let stderr = '';
|
|
1148
|
+
// Activity logs (stdout)
|
|
1149
|
+
if ((type === 'activity' || type === 'both') && (await (0, file_utils_1.fileExists)(this.config.stdoutPath))) {
|
|
1150
|
+
const content = await fs.readFile(this.config.stdoutPath, 'utf-8');
|
|
1151
|
+
const logLines = content.split('\n');
|
|
1152
|
+
stdout = logLines.slice(-lines).join('\n');
|
|
1153
|
+
}
|
|
1154
|
+
// System logs (stderr)
|
|
1155
|
+
if ((type === 'system' || type === 'both') && (await (0, file_utils_1.fileExists)(this.config.stderrPath))) {
|
|
1156
|
+
const content = await fs.readFile(this.config.stderrPath, 'utf-8');
|
|
1157
|
+
const logLines = content.split('\n');
|
|
1158
|
+
stderr = logLines.slice(-lines).join('\n');
|
|
1159
|
+
}
|
|
1160
|
+
this.sendJson(res, 200, { stdout, stderr });
|
|
1161
|
+
}
|
|
1162
|
+
catch (error) {
|
|
1163
|
+
this.sendError(res, 500, 'Internal Server Error', error.message, 'ADMIN_SERVICE_LOGS_ERROR');
|
|
1164
|
+
}
|
|
1165
|
+
}
|
|
969
1166
|
/**
|
|
970
1167
|
* Update router configuration
|
|
971
1168
|
*/
|
|
@@ -979,14 +1176,15 @@ class AdminServer {
|
|
|
979
1176
|
return;
|
|
980
1177
|
}
|
|
981
1178
|
// Validate updates
|
|
982
|
-
const allowedFields = ['port', 'host', '
|
|
1179
|
+
const allowedFields = ['port', 'host', 'logging', 'requestTimeout', 'healthCheckInterval'];
|
|
983
1180
|
const invalidFields = Object.keys(updates).filter(key => !allowedFields.includes(key));
|
|
984
1181
|
if (invalidFields.length > 0) {
|
|
985
1182
|
this.sendError(res, 400, 'Bad Request', `Invalid fields: ${invalidFields.join(', ')}`, 'INVALID_FIELDS');
|
|
986
1183
|
return;
|
|
987
1184
|
}
|
|
988
1185
|
// Apply updates
|
|
989
|
-
|
|
1186
|
+
// Restart needed for: port, host (changes plist), or logging (changes router behavior)
|
|
1187
|
+
const needsRestart = updates.port !== undefined || updates.host !== undefined || updates.logging !== undefined;
|
|
990
1188
|
await router_manager_1.routerManager.updateConfig(updates);
|
|
991
1189
|
// Regenerate plist if needed
|
|
992
1190
|
if (needsRestart) {
|
|
@@ -1005,6 +1203,122 @@ class AdminServer {
|
|
|
1005
1203
|
this.sendError(res, 500, 'Internal Server Error', error.message, 'ROUTER_UPDATE_ERROR');
|
|
1006
1204
|
}
|
|
1007
1205
|
}
|
|
1206
|
+
/**
|
|
1207
|
+
* Get admin log information
|
|
1208
|
+
*/
|
|
1209
|
+
async handleGetAdminLogs(req, res) {
|
|
1210
|
+
try {
|
|
1211
|
+
const allLogs = await log_management_service_1.logManagementService.scanAllLogs();
|
|
1212
|
+
// Include log management config
|
|
1213
|
+
const logConfig = this.config.logManagement || {
|
|
1214
|
+
autoRotate: { enabled: true, intervalHours: 24, thresholdMB: 100 },
|
|
1215
|
+
autoDelete: { enabled: true, intervalHours: 24, afterDays: 30 },
|
|
1216
|
+
};
|
|
1217
|
+
// Include worker status
|
|
1218
|
+
const workerStatus = {
|
|
1219
|
+
autoRotate: {
|
|
1220
|
+
enabled: logConfig.autoRotate.enabled,
|
|
1221
|
+
running: this.autoRotateWorker?.isRunning() || false,
|
|
1222
|
+
lastRun: this.autoRotateWorker?.getLastRun()?.toISOString(),
|
|
1223
|
+
},
|
|
1224
|
+
autoDelete: {
|
|
1225
|
+
enabled: logConfig.autoDelete.enabled,
|
|
1226
|
+
running: this.autoDeleteWorker?.isRunning() || false,
|
|
1227
|
+
lastRun: this.autoDeleteWorker?.getLastRun()?.toISOString(),
|
|
1228
|
+
},
|
|
1229
|
+
};
|
|
1230
|
+
this.sendJson(res, 200, {
|
|
1231
|
+
...allLogs,
|
|
1232
|
+
config: logConfig,
|
|
1233
|
+
workers: workerStatus,
|
|
1234
|
+
});
|
|
1235
|
+
}
|
|
1236
|
+
catch (error) {
|
|
1237
|
+
this.sendError(res, 500, 'Internal Server Error', error.message, 'ADMIN_LOGS_ERROR');
|
|
1238
|
+
}
|
|
1239
|
+
}
|
|
1240
|
+
/**
|
|
1241
|
+
* Rotate log files
|
|
1242
|
+
*/
|
|
1243
|
+
async handleRotateLogs(req, res) {
|
|
1244
|
+
try {
|
|
1245
|
+
const body = await this.readBody(req);
|
|
1246
|
+
const { type, serverId, streams } = JSON.parse(body);
|
|
1247
|
+
// Validate request
|
|
1248
|
+
if (!type || !streams || !Array.isArray(streams)) {
|
|
1249
|
+
this.sendError(res, 400, 'Bad Request', 'Missing or invalid type/streams', 'INVALID_REQUEST');
|
|
1250
|
+
return;
|
|
1251
|
+
}
|
|
1252
|
+
if (type === 'server' && !serverId) {
|
|
1253
|
+
this.sendError(res, 400, 'Bad Request', 'Missing serverId for server type', 'INVALID_REQUEST');
|
|
1254
|
+
return;
|
|
1255
|
+
}
|
|
1256
|
+
const archivedFiles = await log_management_service_1.logManagementService.rotateLogs(type, serverId, streams);
|
|
1257
|
+
this.sendJson(res, 200, {
|
|
1258
|
+
success: true,
|
|
1259
|
+
message: `Rotated ${archivedFiles.length} log file(s)`,
|
|
1260
|
+
archivedFiles,
|
|
1261
|
+
});
|
|
1262
|
+
}
|
|
1263
|
+
catch (error) {
|
|
1264
|
+
this.sendError(res, 500, 'Internal Server Error', error.message, 'ROTATE_LOGS_ERROR');
|
|
1265
|
+
}
|
|
1266
|
+
}
|
|
1267
|
+
/**
|
|
1268
|
+
* Clear archived logs
|
|
1269
|
+
*/
|
|
1270
|
+
async handleClearArchivedLogs(req, res) {
|
|
1271
|
+
try {
|
|
1272
|
+
const body = await this.readBody(req);
|
|
1273
|
+
const { serverId } = JSON.parse(body);
|
|
1274
|
+
const result = await log_management_service_1.logManagementService.clearArchivedLogs(serverId);
|
|
1275
|
+
this.sendJson(res, 200, {
|
|
1276
|
+
success: true,
|
|
1277
|
+
count: result.count,
|
|
1278
|
+
totalSize: result.totalSize,
|
|
1279
|
+
});
|
|
1280
|
+
}
|
|
1281
|
+
catch (error) {
|
|
1282
|
+
this.sendError(res, 500, 'Internal Server Error', error.message, 'CLEAR_ARCHIVED_ERROR');
|
|
1283
|
+
}
|
|
1284
|
+
}
|
|
1285
|
+
/**
|
|
1286
|
+
* Update log management configuration
|
|
1287
|
+
*/
|
|
1288
|
+
async handleUpdateLogConfig(req, res) {
|
|
1289
|
+
try {
|
|
1290
|
+
const body = await this.readBody(req);
|
|
1291
|
+
const updates = JSON.parse(body);
|
|
1292
|
+
// Load current admin config
|
|
1293
|
+
const configPath = path.join((0, file_utils_1.getConfigDir)(), 'admin.json');
|
|
1294
|
+
const currentConfig = await (0, file_utils_1.readJson)(configPath);
|
|
1295
|
+
// Merge updates into existing config
|
|
1296
|
+
const newLogConfig = {
|
|
1297
|
+
autoRotate: {
|
|
1298
|
+
...currentConfig.logManagement?.autoRotate || { enabled: true, intervalHours: 24, thresholdMB: 100 },
|
|
1299
|
+
...updates.autoRotate,
|
|
1300
|
+
},
|
|
1301
|
+
autoDelete: {
|
|
1302
|
+
...currentConfig.logManagement?.autoDelete || { enabled: true, intervalHours: 24, afterDays: 30 },
|
|
1303
|
+
...updates.autoDelete,
|
|
1304
|
+
},
|
|
1305
|
+
};
|
|
1306
|
+
// Update admin config
|
|
1307
|
+
currentConfig.logManagement = newLogConfig;
|
|
1308
|
+
await (0, file_utils_1.writeJsonAtomic)(configPath, currentConfig);
|
|
1309
|
+
// Restart workers with new configuration
|
|
1310
|
+
await this.stopWorkers();
|
|
1311
|
+
this.config = currentConfig; // Update in-memory config
|
|
1312
|
+
await this.startWorkers();
|
|
1313
|
+
this.sendJson(res, 200, {
|
|
1314
|
+
success: true,
|
|
1315
|
+
config: newLogConfig,
|
|
1316
|
+
});
|
|
1317
|
+
}
|
|
1318
|
+
catch (error) {
|
|
1319
|
+
this.sendError(res, 500, 'Internal Server Error', error.message, 'UPDATE_LOG_CONFIG_ERROR');
|
|
1320
|
+
}
|
|
1321
|
+
}
|
|
1008
1322
|
/**
|
|
1009
1323
|
* Authenticate request via API key
|
|
1010
1324
|
*/
|
|
@@ -1052,6 +1366,86 @@ class AdminServer {
|
|
|
1052
1366
|
/**
|
|
1053
1367
|
* Serve static files from web/dist directory
|
|
1054
1368
|
*/
|
|
1369
|
+
/**
|
|
1370
|
+
* Serve Swagger UI for API documentation
|
|
1371
|
+
*/
|
|
1372
|
+
async handleSwaggerUI(req, res, pathname) {
|
|
1373
|
+
try {
|
|
1374
|
+
// Get swagger-ui-dist directory
|
|
1375
|
+
const swaggerUiPath = require.resolve('swagger-ui-dist');
|
|
1376
|
+
const swaggerDistDir = path.dirname(swaggerUiPath);
|
|
1377
|
+
// Handle /api-docs -> redirect to /api-docs/
|
|
1378
|
+
if (pathname === '/api-docs') {
|
|
1379
|
+
res.writeHead(302, { Location: '/api-docs/' });
|
|
1380
|
+
res.end();
|
|
1381
|
+
return;
|
|
1382
|
+
}
|
|
1383
|
+
// Handle swagger-initializer.js with custom config
|
|
1384
|
+
if (pathname === '/api-docs/swagger-initializer.js') {
|
|
1385
|
+
const customInitializer = `
|
|
1386
|
+
window.onload = function() {
|
|
1387
|
+
window.ui = SwaggerUIBundle({
|
|
1388
|
+
url: '/api-docs.json',
|
|
1389
|
+
dom_id: '#swagger-ui',
|
|
1390
|
+
deepLinking: true,
|
|
1391
|
+
presets: [
|
|
1392
|
+
SwaggerUIBundle.presets.apis,
|
|
1393
|
+
SwaggerUIStandalonePreset
|
|
1394
|
+
],
|
|
1395
|
+
plugins: [
|
|
1396
|
+
SwaggerUIBundle.plugins.DownloadUrl
|
|
1397
|
+
],
|
|
1398
|
+
layout: "StandaloneLayout"
|
|
1399
|
+
});
|
|
1400
|
+
};
|
|
1401
|
+
`;
|
|
1402
|
+
res.writeHead(200, { 'Content-Type': 'application/javascript; charset=utf-8' });
|
|
1403
|
+
res.end(customInitializer);
|
|
1404
|
+
return;
|
|
1405
|
+
}
|
|
1406
|
+
// Determine which file to serve
|
|
1407
|
+
let filePath;
|
|
1408
|
+
if (pathname === '/api-docs/' || pathname === '/api-docs/index.html') {
|
|
1409
|
+
filePath = path.join(swaggerDistDir, 'index.html');
|
|
1410
|
+
}
|
|
1411
|
+
else {
|
|
1412
|
+
// Serve other swagger-ui assets
|
|
1413
|
+
const assetPath = pathname.replace('/api-docs/', '');
|
|
1414
|
+
filePath = path.join(swaggerDistDir, assetPath);
|
|
1415
|
+
}
|
|
1416
|
+
// Security: Ensure file is within swagger dist directory
|
|
1417
|
+
const resolvedPath = path.resolve(filePath);
|
|
1418
|
+
if (!resolvedPath.startsWith(swaggerDistDir)) {
|
|
1419
|
+
this.sendError(res, 403, 'Forbidden', 'Access denied', 'FORBIDDEN');
|
|
1420
|
+
return;
|
|
1421
|
+
}
|
|
1422
|
+
// Check if file exists
|
|
1423
|
+
if (!(await (0, file_utils_1.fileExists)(resolvedPath))) {
|
|
1424
|
+
this.sendError(res, 404, 'Not Found', `Swagger UI file not found: ${pathname}`, 'SWAGGER_NOT_FOUND');
|
|
1425
|
+
return;
|
|
1426
|
+
}
|
|
1427
|
+
// Determine content type
|
|
1428
|
+
const ext = path.extname(resolvedPath);
|
|
1429
|
+
const contentTypes = {
|
|
1430
|
+
'.html': 'text/html; charset=utf-8',
|
|
1431
|
+
'.js': 'application/javascript; charset=utf-8',
|
|
1432
|
+
'.css': 'text/css; charset=utf-8',
|
|
1433
|
+
'.json': 'application/json; charset=utf-8',
|
|
1434
|
+
'.png': 'image/png',
|
|
1435
|
+
'.svg': 'image/svg+xml',
|
|
1436
|
+
'.map': 'application/json',
|
|
1437
|
+
};
|
|
1438
|
+
const contentType = contentTypes[ext] || 'application/octet-stream';
|
|
1439
|
+
// Read and serve file
|
|
1440
|
+
const content = await fs.readFile(resolvedPath);
|
|
1441
|
+
res.writeHead(200, { 'Content-Type': contentType });
|
|
1442
|
+
res.end(content);
|
|
1443
|
+
}
|
|
1444
|
+
catch (error) {
|
|
1445
|
+
console.error('[Admin] Error serving Swagger UI:', error);
|
|
1446
|
+
this.sendError(res, 500, 'Internal Server Error', error.message, 'SWAGGER_ERROR');
|
|
1447
|
+
}
|
|
1448
|
+
}
|
|
1055
1449
|
async handleStaticFile(req, res, pathname) {
|
|
1056
1450
|
try {
|
|
1057
1451
|
// Resolve web/dist directory relative to project root
|
|
@@ -1129,7 +1523,7 @@ class AdminServer {
|
|
|
1129
1523
|
* Log request
|
|
1130
1524
|
*/
|
|
1131
1525
|
logRequest(method, pathname) {
|
|
1132
|
-
if (this.config.
|
|
1526
|
+
if (this.config.logging) {
|
|
1133
1527
|
console.log(`[Admin] ${method} ${pathname}`);
|
|
1134
1528
|
}
|
|
1135
1529
|
}
|
|
@@ -1137,7 +1531,7 @@ class AdminServer {
|
|
|
1137
1531
|
* Log response
|
|
1138
1532
|
*/
|
|
1139
1533
|
logResponse(method, pathname, statusCode, durationMs) {
|
|
1140
|
-
if (this.config.
|
|
1534
|
+
if (this.config.logging) {
|
|
1141
1535
|
console.log(`[Admin] ${method} ${pathname} ${statusCode} ${durationMs}ms`);
|
|
1142
1536
|
}
|
|
1143
1537
|
}
|