@superdangerous/app-framework 4.9.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/LICENSE +21 -0
- package/README.md +652 -0
- package/dist/api/logsRouter.d.ts +20 -0
- package/dist/api/logsRouter.d.ts.map +1 -0
- package/dist/api/logsRouter.js +515 -0
- package/dist/api/logsRouter.js.map +1 -0
- package/dist/cli/dev-server.d.ts +7 -0
- package/dist/cli/dev-server.d.ts.map +1 -0
- package/dist/cli/dev-server.js +640 -0
- package/dist/cli/dev-server.js.map +1 -0
- package/dist/cli/index.d.ts +7 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +26 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/core/StandardServer.d.ts +129 -0
- package/dist/core/StandardServer.d.ts.map +1 -0
- package/dist/core/StandardServer.js +453 -0
- package/dist/core/StandardServer.js.map +1 -0
- package/dist/core/apiResponse.d.ts +69 -0
- package/dist/core/apiResponse.d.ts.map +1 -0
- package/dist/core/apiResponse.js +127 -0
- package/dist/core/apiResponse.js.map +1 -0
- package/dist/core/healthCheck.d.ts +160 -0
- package/dist/core/healthCheck.d.ts.map +1 -0
- package/dist/core/healthCheck.js +398 -0
- package/dist/core/healthCheck.js.map +1 -0
- package/dist/core/index.d.ts +40 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/index.js +40 -0
- package/dist/core/index.js.map +1 -0
- package/dist/core/logger.d.ts +117 -0
- package/dist/core/logger.d.ts.map +1 -0
- package/dist/core/logger.js +826 -0
- package/dist/core/logger.js.map +1 -0
- package/dist/core/portUtils.d.ts +71 -0
- package/dist/core/portUtils.d.ts.map +1 -0
- package/dist/core/portUtils.js +240 -0
- package/dist/core/portUtils.js.map +1 -0
- package/dist/core/storageService.d.ts +119 -0
- package/dist/core/storageService.d.ts.map +1 -0
- package/dist/core/storageService.js +405 -0
- package/dist/core/storageService.js.map +1 -0
- package/dist/desktop/bundler.d.ts +40 -0
- package/dist/desktop/bundler.d.ts.map +1 -0
- package/dist/desktop/bundler.js +176 -0
- package/dist/desktop/bundler.js.map +1 -0
- package/dist/desktop/index.d.ts +25 -0
- package/dist/desktop/index.d.ts.map +1 -0
- package/dist/desktop/index.js +15 -0
- package/dist/desktop/index.js.map +1 -0
- package/dist/desktop/native-modules.d.ts +66 -0
- package/dist/desktop/native-modules.d.ts.map +1 -0
- package/dist/desktop/native-modules.js +200 -0
- package/dist/desktop/native-modules.js.map +1 -0
- package/dist/index.d.ts +29 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +39 -0
- package/dist/index.js.map +1 -0
- package/dist/logging/LogCategories.d.ts +87 -0
- package/dist/logging/LogCategories.d.ts.map +1 -0
- package/dist/logging/LogCategories.js +205 -0
- package/dist/logging/LogCategories.js.map +1 -0
- package/dist/middleware/aiErrorHandler.d.ts +31 -0
- package/dist/middleware/aiErrorHandler.d.ts.map +1 -0
- package/dist/middleware/aiErrorHandler.js +181 -0
- package/dist/middleware/aiErrorHandler.js.map +1 -0
- package/dist/middleware/auth.d.ts +101 -0
- package/dist/middleware/auth.d.ts.map +1 -0
- package/dist/middleware/auth.js +230 -0
- package/dist/middleware/auth.js.map +1 -0
- package/dist/middleware/cors.d.ts +56 -0
- package/dist/middleware/cors.d.ts.map +1 -0
- package/dist/middleware/cors.js +123 -0
- package/dist/middleware/cors.js.map +1 -0
- package/dist/middleware/errorHandler.d.ts +13 -0
- package/dist/middleware/errorHandler.d.ts.map +1 -0
- package/dist/middleware/errorHandler.js +85 -0
- package/dist/middleware/errorHandler.js.map +1 -0
- package/dist/middleware/fileUpload.d.ts +62 -0
- package/dist/middleware/fileUpload.d.ts.map +1 -0
- package/dist/middleware/fileUpload.js +175 -0
- package/dist/middleware/fileUpload.js.map +1 -0
- package/dist/middleware/health.d.ts +48 -0
- package/dist/middleware/health.d.ts.map +1 -0
- package/dist/middleware/health.js +143 -0
- package/dist/middleware/health.js.map +1 -0
- package/dist/middleware/index.d.ts +20 -0
- package/dist/middleware/index.d.ts.map +1 -0
- package/dist/middleware/index.js +18 -0
- package/dist/middleware/index.js.map +1 -0
- package/dist/middleware/openapi.d.ts +64 -0
- package/dist/middleware/openapi.d.ts.map +1 -0
- package/dist/middleware/openapi.js +258 -0
- package/dist/middleware/openapi.js.map +1 -0
- package/dist/middleware/requestLogging.d.ts +22 -0
- package/dist/middleware/requestLogging.d.ts.map +1 -0
- package/dist/middleware/requestLogging.js +61 -0
- package/dist/middleware/requestLogging.js.map +1 -0
- package/dist/middleware/session.d.ts +84 -0
- package/dist/middleware/session.d.ts.map +1 -0
- package/dist/middleware/session.js +189 -0
- package/dist/middleware/session.js.map +1 -0
- package/dist/middleware/validation.d.ts +1337 -0
- package/dist/middleware/validation.d.ts.map +1 -0
- package/dist/middleware/validation.js +483 -0
- package/dist/middleware/validation.js.map +1 -0
- package/dist/services/aiService.d.ts +180 -0
- package/dist/services/aiService.d.ts.map +1 -0
- package/dist/services/aiService.js +547 -0
- package/dist/services/aiService.js.map +1 -0
- package/dist/services/conversationStorage.d.ts +38 -0
- package/dist/services/conversationStorage.d.ts.map +1 -0
- package/dist/services/conversationStorage.js +158 -0
- package/dist/services/conversationStorage.js.map +1 -0
- package/dist/services/crossPlatformBuffer.d.ts +84 -0
- package/dist/services/crossPlatformBuffer.d.ts.map +1 -0
- package/dist/services/crossPlatformBuffer.js +246 -0
- package/dist/services/crossPlatformBuffer.js.map +1 -0
- package/dist/services/index.d.ts +17 -0
- package/dist/services/index.d.ts.map +1 -0
- package/dist/services/index.js +18 -0
- package/dist/services/index.js.map +1 -0
- package/dist/services/networkService.d.ts +81 -0
- package/dist/services/networkService.d.ts.map +1 -0
- package/dist/services/networkService.js +268 -0
- package/dist/services/networkService.js.map +1 -0
- package/dist/services/queueService.d.ts +112 -0
- package/dist/services/queueService.d.ts.map +1 -0
- package/dist/services/queueService.js +338 -0
- package/dist/services/queueService.js.map +1 -0
- package/dist/services/settingsService.d.ts +135 -0
- package/dist/services/settingsService.d.ts.map +1 -0
- package/dist/services/settingsService.js +425 -0
- package/dist/services/settingsService.js.map +1 -0
- package/dist/services/systemMonitor.d.ts +208 -0
- package/dist/services/systemMonitor.d.ts.map +1 -0
- package/dist/services/systemMonitor.js +693 -0
- package/dist/services/systemMonitor.js.map +1 -0
- package/dist/services/updateService.d.ts +78 -0
- package/dist/services/updateService.d.ts.map +1 -0
- package/dist/services/updateService.js +252 -0
- package/dist/services/updateService.js.map +1 -0
- package/dist/services/websocketEvents.d.ts +372 -0
- package/dist/services/websocketEvents.d.ts.map +1 -0
- package/dist/services/websocketEvents.js +338 -0
- package/dist/services/websocketEvents.js.map +1 -0
- package/dist/services/websocketServer.d.ts +80 -0
- package/dist/services/websocketServer.d.ts.map +1 -0
- package/dist/services/websocketServer.js +299 -0
- package/dist/services/websocketServer.js.map +1 -0
- package/dist/settings/SettingsSchema.d.ts +151 -0
- package/dist/settings/SettingsSchema.d.ts.map +1 -0
- package/dist/settings/SettingsSchema.js +424 -0
- package/dist/settings/SettingsSchema.js.map +1 -0
- package/dist/testing/TestServer.d.ts +69 -0
- package/dist/testing/TestServer.d.ts.map +1 -0
- package/dist/testing/TestServer.js +250 -0
- package/dist/testing/TestServer.js.map +1 -0
- package/dist/types/index.d.ts +137 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +5 -0
- package/dist/types/index.js.map +1 -0
- package/dist/utils/appPaths.d.ts +74 -0
- package/dist/utils/appPaths.d.ts.map +1 -0
- package/dist/utils/appPaths.js +162 -0
- package/dist/utils/appPaths.js.map +1 -0
- package/dist/utils/fs-utils.d.ts +50 -0
- package/dist/utils/fs-utils.d.ts.map +1 -0
- package/dist/utils/fs-utils.js +114 -0
- package/dist/utils/fs-utils.js.map +1 -0
- package/dist/utils/index.d.ts +12 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +10 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/utils/standardConfig.d.ts +61 -0
- package/dist/utils/standardConfig.d.ts.map +1 -0
- package/dist/utils/standardConfig.js +109 -0
- package/dist/utils/standardConfig.js.map +1 -0
- package/dist/utils/startupBanner.d.ts +34 -0
- package/dist/utils/startupBanner.d.ts.map +1 -0
- package/dist/utils/startupBanner.js +169 -0
- package/dist/utils/startupBanner.js.map +1 -0
- package/dist/utils/startupLogger.d.ts +45 -0
- package/dist/utils/startupLogger.d.ts.map +1 -0
- package/dist/utils/startupLogger.js +200 -0
- package/dist/utils/startupLogger.js.map +1 -0
- package/package.json +151 -0
- package/src/api/logsRouter.ts +600 -0
- package/src/cli/dev-server.ts +803 -0
- package/src/cli/index.ts +31 -0
- package/src/core/StandardServer.ts +587 -0
- package/src/core/apiResponse.ts +202 -0
- package/src/core/healthCheck.ts +565 -0
- package/src/core/index.ts +80 -0
- package/src/core/logger.ts +1092 -0
- package/src/core/portUtils.ts +319 -0
- package/src/core/storageService.ts +595 -0
- package/src/desktop/bundler.ts +271 -0
- package/src/desktop/index.ts +18 -0
- package/src/desktop/native-modules.ts +289 -0
- package/src/index.ts +142 -0
- package/src/logging/LogCategories.ts +302 -0
- package/src/middleware/aiErrorHandler.ts +278 -0
- package/src/middleware/auth.ts +329 -0
- package/src/middleware/cors.ts +187 -0
- package/src/middleware/errorHandler.ts +103 -0
- package/src/middleware/fileUpload.ts +252 -0
- package/src/middleware/health.ts +206 -0
- package/src/middleware/index.ts +71 -0
- package/src/middleware/openapi.ts +305 -0
- package/src/middleware/requestLogging.ts +92 -0
- package/src/middleware/session.ts +238 -0
- package/src/middleware/validation.ts +603 -0
- package/src/services/aiService.ts +789 -0
- package/src/services/conversationStorage.ts +232 -0
- package/src/services/crossPlatformBuffer.ts +341 -0
- package/src/services/index.ts +47 -0
- package/src/services/networkService.ts +351 -0
- package/src/services/queueService.ts +446 -0
- package/src/services/settingsService.ts +549 -0
- package/src/services/systemMonitor.ts +936 -0
- package/src/services/updateService.ts +334 -0
- package/src/services/websocketEvents.ts +409 -0
- package/src/services/websocketServer.ts +394 -0
- package/src/settings/SettingsSchema.ts +664 -0
- package/src/testing/TestServer.ts +312 -0
- package/src/types/index.ts +154 -0
- package/src/utils/appPaths.ts +196 -0
- package/src/utils/fs-utils.ts +130 -0
- package/src/utils/index.ts +15 -0
- package/src/utils/standardConfig.ts +178 -0
- package/src/utils/startupBanner.ts +287 -0
- package/src/utils/startupLogger.ts +268 -0
- package/ui/dist/index.d.mts +1221 -0
- package/ui/dist/index.d.ts +1221 -0
- package/ui/dist/index.js +73 -0
- package/ui/dist/index.js.map +1 -0
- package/ui/dist/index.mjs +73 -0
- package/ui/dist/index.mjs.map +1 -0
|
@@ -0,0 +1,600 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Standardized Logging API Router
|
|
3
|
+
* Provides consistent logging endpoints for all SuperDangerous applications
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import express, { Request, Response } from "express";
|
|
7
|
+
import { getLogger } from "../core/logger.js";
|
|
8
|
+
import path from "path";
|
|
9
|
+
import fs from "fs/promises";
|
|
10
|
+
import { existsSync, createReadStream } from "fs";
|
|
11
|
+
|
|
12
|
+
const router = express.Router();
|
|
13
|
+
|
|
14
|
+
export interface LogEntry {
|
|
15
|
+
timestamp: string;
|
|
16
|
+
level: string;
|
|
17
|
+
message: string;
|
|
18
|
+
source?: string;
|
|
19
|
+
metadata?: any;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface LogFile {
|
|
23
|
+
name: string;
|
|
24
|
+
size: number;
|
|
25
|
+
modified: string;
|
|
26
|
+
path?: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Get recent log entries
|
|
31
|
+
* GET /api/logs/entries?limit=100&level=info
|
|
32
|
+
*/
|
|
33
|
+
router.get("/entries", async (req: Request, res: Response) => {
|
|
34
|
+
try {
|
|
35
|
+
const limit = parseInt(req.query.limit as string) || 100;
|
|
36
|
+
const level = (req.query.level as string) || "all";
|
|
37
|
+
|
|
38
|
+
const logger = getLogger;
|
|
39
|
+
const logs = await logger.getRecentLogs(limit, level);
|
|
40
|
+
|
|
41
|
+
res.json({
|
|
42
|
+
success: true,
|
|
43
|
+
logs: logs || [],
|
|
44
|
+
});
|
|
45
|
+
} catch (_error) {
|
|
46
|
+
console.error("Failed to fetch log entries:", _error);
|
|
47
|
+
res.status(500).json({
|
|
48
|
+
success: false,
|
|
49
|
+
error: "Failed to fetch log entries",
|
|
50
|
+
logs: [],
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Get log files list
|
|
57
|
+
* GET /api/logs/files
|
|
58
|
+
*/
|
|
59
|
+
router.get("/files", async (_req: Request, res: Response) => {
|
|
60
|
+
try {
|
|
61
|
+
const logsDir = path.join(process.cwd(), "data", "logs");
|
|
62
|
+
|
|
63
|
+
if (!existsSync(logsDir)) {
|
|
64
|
+
res.json({
|
|
65
|
+
success: true,
|
|
66
|
+
files: [],
|
|
67
|
+
});
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const files = await fs.readdir(logsDir);
|
|
72
|
+
const logFiles: LogFile[] = [];
|
|
73
|
+
|
|
74
|
+
for (const file of files) {
|
|
75
|
+
if (file.endsWith(".log") || file.endsWith(".txt")) {
|
|
76
|
+
const filePath = path.join(logsDir, file);
|
|
77
|
+
const stats = await fs.stat(filePath);
|
|
78
|
+
|
|
79
|
+
logFiles.push({
|
|
80
|
+
name: file,
|
|
81
|
+
size: stats.size,
|
|
82
|
+
modified: stats.mtime.toISOString(),
|
|
83
|
+
path: filePath,
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Sort by modified date, newest first
|
|
89
|
+
logFiles.sort(
|
|
90
|
+
(a, b) => new Date(b.modified).getTime() - new Date(a.modified).getTime(),
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
res.json({
|
|
94
|
+
success: true,
|
|
95
|
+
files: logFiles,
|
|
96
|
+
});
|
|
97
|
+
} catch (_error) {
|
|
98
|
+
console.error("Failed to fetch log files:", _error);
|
|
99
|
+
res.status(500).json({
|
|
100
|
+
success: false,
|
|
101
|
+
error: "Failed to fetch log files",
|
|
102
|
+
files: [],
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Download a specific log file
|
|
109
|
+
* GET /api/logs/download/:filename
|
|
110
|
+
*/
|
|
111
|
+
router.get("/download/:filename", async (req: Request, res: Response) => {
|
|
112
|
+
try {
|
|
113
|
+
const { filename } = req.params;
|
|
114
|
+
const logsDir = path.join(process.cwd(), "data", "logs");
|
|
115
|
+
const filePath = path.join(logsDir, filename);
|
|
116
|
+
|
|
117
|
+
// Security check - prevent directory traversal
|
|
118
|
+
if (!filePath.startsWith(logsDir)) {
|
|
119
|
+
res.status(403).json({
|
|
120
|
+
success: false,
|
|
121
|
+
error: "Access denied",
|
|
122
|
+
});
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (!existsSync(filePath)) {
|
|
127
|
+
res.status(404).json({
|
|
128
|
+
success: false,
|
|
129
|
+
error: "File not found",
|
|
130
|
+
});
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
res.download(filePath);
|
|
135
|
+
} catch (_error) {
|
|
136
|
+
console.error("Failed to download log file:", _error);
|
|
137
|
+
res.status(500).json({
|
|
138
|
+
success: false,
|
|
139
|
+
error: "Failed to download log file",
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Stream log file content
|
|
146
|
+
* GET /api/logs/stream/:filename
|
|
147
|
+
*/
|
|
148
|
+
router.get("/stream/:filename", async (req: Request, res: Response) => {
|
|
149
|
+
try {
|
|
150
|
+
const { filename } = req.params;
|
|
151
|
+
const logsDir = path.join(process.cwd(), "data", "logs");
|
|
152
|
+
const filePath = path.join(logsDir, filename);
|
|
153
|
+
|
|
154
|
+
// Security check
|
|
155
|
+
if (!filePath.startsWith(logsDir)) {
|
|
156
|
+
res.status(403).json({
|
|
157
|
+
success: false,
|
|
158
|
+
error: "Access denied",
|
|
159
|
+
});
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (!existsSync(filePath)) {
|
|
164
|
+
res.status(404).json({
|
|
165
|
+
success: false,
|
|
166
|
+
error: "File not found",
|
|
167
|
+
});
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
res.setHeader("Content-Type", "text/plain");
|
|
172
|
+
const stream = createReadStream(filePath);
|
|
173
|
+
stream.pipe(res);
|
|
174
|
+
} catch (_error) {
|
|
175
|
+
console.error("Failed to stream log file:", _error);
|
|
176
|
+
res.status(500).json({
|
|
177
|
+
success: false,
|
|
178
|
+
error: "Failed to stream log file",
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Clear log files
|
|
185
|
+
* POST /api/logs/clear
|
|
186
|
+
*/
|
|
187
|
+
router.post("/clear", async (_req: Request, res: Response) => {
|
|
188
|
+
try {
|
|
189
|
+
const logger = getLogger;
|
|
190
|
+
await logger.clearLogs();
|
|
191
|
+
|
|
192
|
+
res.json({
|
|
193
|
+
success: true,
|
|
194
|
+
message: "Logs cleared successfully",
|
|
195
|
+
});
|
|
196
|
+
} catch (_error) {
|
|
197
|
+
console.error("Failed to clear logs:", _error);
|
|
198
|
+
res.status(500).json({
|
|
199
|
+
success: false,
|
|
200
|
+
error: "Failed to clear logs",
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Get archived log files
|
|
207
|
+
* GET /api/logs/archives
|
|
208
|
+
*/
|
|
209
|
+
router.get("/archives", async (_req: Request, res: Response) => {
|
|
210
|
+
try {
|
|
211
|
+
const logsDir = path.join(process.cwd(), "data", "logs");
|
|
212
|
+
|
|
213
|
+
if (!existsSync(logsDir)) {
|
|
214
|
+
return res.json({
|
|
215
|
+
success: true,
|
|
216
|
+
archives: [],
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
const files = await fs.readdir(logsDir);
|
|
221
|
+
const archives: LogFile[] = [];
|
|
222
|
+
|
|
223
|
+
for (const file of files) {
|
|
224
|
+
if (file.endsWith(".log") || file.endsWith(".txt")) {
|
|
225
|
+
const filePath = path.join(logsDir, file);
|
|
226
|
+
const stats = await fs.stat(filePath);
|
|
227
|
+
archives.push({
|
|
228
|
+
name: file,
|
|
229
|
+
size: stats.size,
|
|
230
|
+
modified: stats.mtime.toISOString(),
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Sort by modified date, newest first
|
|
236
|
+
archives.sort(
|
|
237
|
+
(a, b) => new Date(b.modified).getTime() - new Date(a.modified).getTime(),
|
|
238
|
+
);
|
|
239
|
+
|
|
240
|
+
return res.json({
|
|
241
|
+
success: true,
|
|
242
|
+
archives,
|
|
243
|
+
});
|
|
244
|
+
} catch (_error) {
|
|
245
|
+
console.error("Failed to fetch archives:", _error);
|
|
246
|
+
return res.status(500).json({
|
|
247
|
+
success: false,
|
|
248
|
+
error: "Failed to fetch archives",
|
|
249
|
+
archives: [],
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Delete specific archive
|
|
256
|
+
* DELETE /api/logs/archive/:filename
|
|
257
|
+
*/
|
|
258
|
+
router.delete("/archive/:filename", async (req: Request, res: Response) => {
|
|
259
|
+
try {
|
|
260
|
+
const { filename } = req.params;
|
|
261
|
+
const logsDir = path.join(process.cwd(), "data", "logs");
|
|
262
|
+
const filePath = path.join(logsDir, filename);
|
|
263
|
+
|
|
264
|
+
// Security check
|
|
265
|
+
if (!filePath.startsWith(logsDir) || filename.includes("..")) {
|
|
266
|
+
return res.status(403).json({
|
|
267
|
+
success: false,
|
|
268
|
+
error: "Access denied",
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
if (!existsSync(filePath)) {
|
|
273
|
+
return res.status(404).json({
|
|
274
|
+
success: false,
|
|
275
|
+
error: "Archive not found",
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
await fs.unlink(filePath);
|
|
280
|
+
|
|
281
|
+
return res.json({
|
|
282
|
+
success: true,
|
|
283
|
+
message: "Archive deleted successfully",
|
|
284
|
+
});
|
|
285
|
+
} catch (_error) {
|
|
286
|
+
console.error("Failed to delete archive:", _error);
|
|
287
|
+
return res.status(500).json({
|
|
288
|
+
success: false,
|
|
289
|
+
error: "Failed to delete archive",
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* Export logs as text (always)
|
|
296
|
+
* GET /api/logs/export?level=all
|
|
297
|
+
*/
|
|
298
|
+
router.get("/export", async (req: Request, res: Response) => {
|
|
299
|
+
try {
|
|
300
|
+
const level = (req.query.level as string) || "all";
|
|
301
|
+
|
|
302
|
+
const logger = getLogger;
|
|
303
|
+
const logs = await logger.getRecentLogs(10000, level); // Get up to 10k entries for export
|
|
304
|
+
|
|
305
|
+
// Sort newest first
|
|
306
|
+
const sortedLogs = logs.sort(
|
|
307
|
+
(a: LogEntry, b: LogEntry) =>
|
|
308
|
+
new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime(),
|
|
309
|
+
);
|
|
310
|
+
|
|
311
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
|
|
312
|
+
const filename = `logs-${timestamp}.txt`;
|
|
313
|
+
|
|
314
|
+
// Always export as text
|
|
315
|
+
res.setHeader("Content-Type", "text/plain");
|
|
316
|
+
res.setHeader("Content-Disposition", `attachment; filename="${filename}"`);
|
|
317
|
+
|
|
318
|
+
const textLogs = sortedLogs
|
|
319
|
+
.map(
|
|
320
|
+
(log: LogEntry) =>
|
|
321
|
+
`[${log.timestamp}] [${log.level.toUpperCase()}]${log.source ? ` [${log.source}]` : ""} ${log.message}`,
|
|
322
|
+
)
|
|
323
|
+
.join("\n");
|
|
324
|
+
|
|
325
|
+
res.send(textLogs);
|
|
326
|
+
} catch (_error) {
|
|
327
|
+
console.error("Failed to export logs:", _error);
|
|
328
|
+
res.status(500).json({
|
|
329
|
+
success: false,
|
|
330
|
+
error: "Failed to export logs",
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* Get log statistics
|
|
337
|
+
* GET /api/logs/stats
|
|
338
|
+
*/
|
|
339
|
+
router.get("/stats", async (_req: Request, res: Response) => {
|
|
340
|
+
try {
|
|
341
|
+
const logsDir = path.join(process.cwd(), "data", "logs");
|
|
342
|
+
|
|
343
|
+
if (!existsSync(logsDir)) {
|
|
344
|
+
res.json({
|
|
345
|
+
success: true,
|
|
346
|
+
stats: {
|
|
347
|
+
totalSize: 0,
|
|
348
|
+
fileCount: 0,
|
|
349
|
+
oldestLog: null,
|
|
350
|
+
newestLog: null,
|
|
351
|
+
},
|
|
352
|
+
});
|
|
353
|
+
return;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
const files = await fs.readdir(logsDir);
|
|
357
|
+
let totalSize = 0;
|
|
358
|
+
let oldestTime: Date | null = null;
|
|
359
|
+
let newestTime: Date | null = null;
|
|
360
|
+
let fileCount = 0;
|
|
361
|
+
|
|
362
|
+
for (const file of files) {
|
|
363
|
+
if (file.endsWith(".log") || file.endsWith(".txt")) {
|
|
364
|
+
const filePath = path.join(logsDir, file);
|
|
365
|
+
const stats = await fs.stat(filePath);
|
|
366
|
+
|
|
367
|
+
totalSize += stats.size;
|
|
368
|
+
fileCount++;
|
|
369
|
+
|
|
370
|
+
if (!oldestTime || stats.birthtime < oldestTime) {
|
|
371
|
+
oldestTime = stats.birthtime;
|
|
372
|
+
}
|
|
373
|
+
if (!newestTime || stats.mtime > newestTime) {
|
|
374
|
+
newestTime = stats.mtime;
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
res.json({
|
|
380
|
+
success: true,
|
|
381
|
+
stats: {
|
|
382
|
+
totalSize,
|
|
383
|
+
totalSizeFormatted: formatBytes(totalSize),
|
|
384
|
+
fileCount,
|
|
385
|
+
oldestLog: oldestTime?.toISOString() || null,
|
|
386
|
+
newestLog: newestTime?.toISOString() || null,
|
|
387
|
+
},
|
|
388
|
+
});
|
|
389
|
+
} catch (_error) {
|
|
390
|
+
console.error("Failed to get log stats:", _error);
|
|
391
|
+
res.status(500).json({
|
|
392
|
+
success: false,
|
|
393
|
+
error: "Failed to get log statistics",
|
|
394
|
+
});
|
|
395
|
+
}
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
/**
|
|
399
|
+
* Compact logs - Archive old logs
|
|
400
|
+
* POST /api/logs/compact
|
|
401
|
+
*/
|
|
402
|
+
router.post("/compact", async (req: Request, res: Response) => {
|
|
403
|
+
try {
|
|
404
|
+
const { days = 7 } = req.body;
|
|
405
|
+
const logger = getLogger;
|
|
406
|
+
|
|
407
|
+
const stats = await logger.compactLogs(days);
|
|
408
|
+
|
|
409
|
+
res.json({
|
|
410
|
+
success: true,
|
|
411
|
+
stats,
|
|
412
|
+
message: `Logs older than ${days} days have been archived`,
|
|
413
|
+
});
|
|
414
|
+
} catch (_error) {
|
|
415
|
+
res.status(500).json({
|
|
416
|
+
success: false,
|
|
417
|
+
error:
|
|
418
|
+
_error instanceof Error
|
|
419
|
+
? _error.message
|
|
420
|
+
: String(_error) || "Failed to compact logs",
|
|
421
|
+
});
|
|
422
|
+
}
|
|
423
|
+
});
|
|
424
|
+
|
|
425
|
+
/**
|
|
426
|
+
* Clean up zero-length files
|
|
427
|
+
* POST /api/logs/cleanup-zero
|
|
428
|
+
*/
|
|
429
|
+
router.post("/cleanup-zero", async (_req: Request, res: Response) => {
|
|
430
|
+
try {
|
|
431
|
+
const logger = getLogger;
|
|
432
|
+
const result = await logger.cleanupZeroFiles();
|
|
433
|
+
|
|
434
|
+
res.json({
|
|
435
|
+
success: true,
|
|
436
|
+
...result,
|
|
437
|
+
message: `Removed ${result.removed} zero-length files`,
|
|
438
|
+
});
|
|
439
|
+
} catch (_error) {
|
|
440
|
+
res.status(500).json({
|
|
441
|
+
success: false,
|
|
442
|
+
error:
|
|
443
|
+
_error instanceof Error
|
|
444
|
+
? _error.message
|
|
445
|
+
: String(_error) || "Failed to cleanup logs",
|
|
446
|
+
});
|
|
447
|
+
}
|
|
448
|
+
});
|
|
449
|
+
|
|
450
|
+
/**
|
|
451
|
+
* Purge all logs (dangerous)
|
|
452
|
+
* POST /api/logs/purge-all
|
|
453
|
+
*/
|
|
454
|
+
router.post("/purge-all", async (_req: Request, res: Response) => {
|
|
455
|
+
try {
|
|
456
|
+
const logger = getLogger;
|
|
457
|
+
await logger.purgeAllLogs();
|
|
458
|
+
|
|
459
|
+
res.json({
|
|
460
|
+
success: true,
|
|
461
|
+
message: "All logs have been purged",
|
|
462
|
+
});
|
|
463
|
+
} catch (_error) {
|
|
464
|
+
res.status(500).json({
|
|
465
|
+
success: false,
|
|
466
|
+
error:
|
|
467
|
+
_error instanceof Error
|
|
468
|
+
? _error.message
|
|
469
|
+
: String(_error) || "Failed to purge logs",
|
|
470
|
+
});
|
|
471
|
+
}
|
|
472
|
+
});
|
|
473
|
+
|
|
474
|
+
/**
|
|
475
|
+
* Get all log files including archives
|
|
476
|
+
* GET /api/logs/all-files
|
|
477
|
+
*/
|
|
478
|
+
router.get("/all-files", async (_req: Request, res: Response) => {
|
|
479
|
+
try {
|
|
480
|
+
const logger = getLogger;
|
|
481
|
+
const files = await logger.getAllLogFiles();
|
|
482
|
+
|
|
483
|
+
res.json({
|
|
484
|
+
success: true,
|
|
485
|
+
files,
|
|
486
|
+
count: files.length,
|
|
487
|
+
});
|
|
488
|
+
} catch (_error) {
|
|
489
|
+
res.status(500).json({
|
|
490
|
+
success: false,
|
|
491
|
+
error:
|
|
492
|
+
_error instanceof Error
|
|
493
|
+
? _error.message
|
|
494
|
+
: String(_error) || "Failed to list log files",
|
|
495
|
+
files: [],
|
|
496
|
+
});
|
|
497
|
+
}
|
|
498
|
+
});
|
|
499
|
+
|
|
500
|
+
/**
|
|
501
|
+
* Download any log file (including archives)
|
|
502
|
+
* GET /api/logs/download-any/:filename
|
|
503
|
+
*/
|
|
504
|
+
router.get(
|
|
505
|
+
"/download-any/:filename(*)",
|
|
506
|
+
async (req: Request, res: Response) => {
|
|
507
|
+
try {
|
|
508
|
+
const { filename } = req.params;
|
|
509
|
+
|
|
510
|
+
// Security: prevent directory traversal
|
|
511
|
+
const safeName = path.basename(filename);
|
|
512
|
+
|
|
513
|
+
// Check in logs directory first
|
|
514
|
+
const logsDir = path.join(process.cwd(), "data", "logs");
|
|
515
|
+
let filePath = path.join(logsDir, safeName);
|
|
516
|
+
|
|
517
|
+
// If not found, check in archive directory
|
|
518
|
+
if (!existsSync(filePath)) {
|
|
519
|
+
filePath = path.join(logsDir, "archive", safeName);
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
// If still not found and filename includes 'archive/', handle that case
|
|
523
|
+
if (!existsSync(filePath) && filename.startsWith("archive/")) {
|
|
524
|
+
const archiveName = filename.replace("archive/", "");
|
|
525
|
+
filePath = path.join(logsDir, "archive", archiveName);
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
if (!existsSync(filePath)) {
|
|
529
|
+
return res.status(404).json({
|
|
530
|
+
success: false,
|
|
531
|
+
error: "File not found",
|
|
532
|
+
});
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
// Set appropriate headers for download
|
|
536
|
+
const isCompressed = filePath.endsWith(".gz");
|
|
537
|
+
if (isCompressed) {
|
|
538
|
+
res.setHeader("Content-Type", "application/gzip");
|
|
539
|
+
} else {
|
|
540
|
+
res.setHeader("Content-Type", "text/plain");
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
return res.download(filePath, safeName);
|
|
544
|
+
} catch (_error) {
|
|
545
|
+
return res.status(500).json({
|
|
546
|
+
success: false,
|
|
547
|
+
error:
|
|
548
|
+
_error instanceof Error
|
|
549
|
+
? _error.message
|
|
550
|
+
: String(_error) || "Failed to download file",
|
|
551
|
+
});
|
|
552
|
+
}
|
|
553
|
+
},
|
|
554
|
+
);
|
|
555
|
+
|
|
556
|
+
/**
|
|
557
|
+
* Force log rotation
|
|
558
|
+
* POST /api/logs/rotate
|
|
559
|
+
*/
|
|
560
|
+
router.post("/rotate", async (_req: Request, res: Response) => {
|
|
561
|
+
try {
|
|
562
|
+
const logger = getLogger;
|
|
563
|
+
|
|
564
|
+
// Get all logger instances and force rotation
|
|
565
|
+
const loggers = logger.getLoggers();
|
|
566
|
+
for (const [, winstonLogger] of loggers) {
|
|
567
|
+
// Find rotate transport and trigger rotation
|
|
568
|
+
winstonLogger.transports.forEach((transport: any) => {
|
|
569
|
+
if (transport.rotate && typeof transport.rotate === "function") {
|
|
570
|
+
transport.rotate();
|
|
571
|
+
}
|
|
572
|
+
});
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
res.json({
|
|
576
|
+
success: true,
|
|
577
|
+
message: "Log rotation triggered",
|
|
578
|
+
});
|
|
579
|
+
} catch (_error) {
|
|
580
|
+
res.status(500).json({
|
|
581
|
+
success: false,
|
|
582
|
+
error:
|
|
583
|
+
_error instanceof Error
|
|
584
|
+
? _error.message
|
|
585
|
+
: String(_error) || "Failed to rotate logs",
|
|
586
|
+
});
|
|
587
|
+
}
|
|
588
|
+
});
|
|
589
|
+
|
|
590
|
+
// Helper function to format bytes
|
|
591
|
+
function formatBytes(bytes: number, decimals = 2): string {
|
|
592
|
+
if (bytes === 0) return "0 Bytes";
|
|
593
|
+
const k = 1024;
|
|
594
|
+
const dm = decimals < 0 ? 0 : decimals;
|
|
595
|
+
const sizes = ["Bytes", "KB", "MB", "GB"];
|
|
596
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
597
|
+
return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + " " + sizes[i];
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
export default router;
|