@manojkmfsi/monodog 1.0.23 → 1.0.25
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/.turbo/turbo-build.log +1 -1
- package/CHANGELOG.md +12 -0
- package/dist/controllers/{commitController.js → commit-controller.js} +5 -4
- package/dist/controllers/{configController.js → config-controller.js} +3 -3
- package/dist/controllers/{healthController.js → health-controller.js} +3 -3
- package/dist/controllers/{packageController.js → package-controller.js} +6 -6
- package/dist/index.js +11 -236
- package/dist/middleware/dashboard-startup.js +124 -0
- package/dist/middleware/error-handler.js +36 -0
- package/dist/middleware/index.js +23 -0
- package/dist/middleware/logger.js +63 -0
- package/dist/middleware/security.js +78 -0
- package/dist/middleware/server-startup.js +117 -0
- package/dist/repositories/commit-repository.js +97 -0
- package/dist/repositories/dependency-repository.js +97 -0
- package/dist/repositories/index.js +18 -0
- package/dist/repositories/package-health-repository.js +65 -0
- package/dist/repositories/package-repository.js +126 -0
- package/dist/repositories/prisma-client.js +57 -0
- package/dist/routes/{commitRoutes.js → commit-routes.js} +2 -2
- package/dist/routes/{configRoutes.js → config-routes.js} +3 -3
- package/dist/routes/{healthRoutes.js → health-routes.js} +3 -3
- package/dist/routes/{packageRoutes.js → package-routes.js} +5 -5
- package/dist/serve.js +15 -2
- package/dist/services/{commitService.js → commit-service.js} +2 -2
- package/dist/services/{configService.js → config-service.js} +2 -40
- package/dist/services/{healthService.js → health-service.js} +11 -63
- package/dist/services/{packageService.js → package-service.js} +80 -54
- package/dist/types/git.js +11 -0
- package/dist/types/index.js +1 -0
- package/package.json +10 -3
- package/prisma/schema/commit.prisma +11 -0
- package/prisma/schema/dependency-info.prisma +12 -0
- package/prisma/schema/health-status.prisma +14 -0
- package/prisma/schema/package-health.prisma +15 -0
- package/prisma/schema/package.prisma +21 -0
- package/prisma/schema/schema.prisma +15 -0
- package/src/controllers/{commitController.ts → commit-controller.ts} +7 -5
- package/src/controllers/{configController.ts → config-controller.ts} +4 -3
- package/src/controllers/{healthController.ts → health-controller.ts} +4 -3
- package/src/controllers/{packageController.ts → package-controller.ts} +7 -6
- package/src/index.ts +9 -281
- package/src/middleware/dashboard-startup.ts +149 -0
- package/src/middleware/error-handler.ts +50 -0
- package/src/middleware/index.ts +20 -0
- package/src/middleware/logger.ts +58 -0
- package/src/middleware/security.ts +81 -0
- package/src/middleware/server-startup.ts +142 -0
- package/src/repositories/commit-repository.ts +107 -0
- package/src/repositories/dependency-repository.ts +109 -0
- package/src/repositories/index.ts +10 -0
- package/src/repositories/package-health-repository.ts +75 -0
- package/src/repositories/package-repository.ts +142 -0
- package/src/repositories/prisma-client.ts +25 -0
- package/src/routes/{commitRoutes.ts → commit-routes.ts} +1 -1
- package/src/routes/{configRoutes.ts → config-routes.ts} +1 -1
- package/src/routes/{healthRoutes.ts → health-routes.ts} +1 -1
- package/src/routes/{packageRoutes.ts → package-routes.ts} +1 -1
- package/src/serve.ts +19 -3
- package/src/services/{commitService.ts → commit-service.ts} +1 -1
- package/src/services/{configService.ts → config-service.ts} +22 -9
- package/src/services/{gitService.ts → git-service.ts} +4 -4
- package/src/services/{healthService.ts → health-service.ts} +17 -35
- package/src/services/package-service.ts +201 -0
- package/src/types/database.ts +57 -1
- package/src/types/git.ts +8 -8
- package/src/types/index.ts +1 -1
- package/dist/utils/db-utils.js +0 -227
- package/prisma/schema.prisma +0 -116
- package/src/services/packageService.ts +0 -115
- package/src/types/monorepo-scanner.d.ts +0 -32
- package/src/utils/db-utils.ts +0 -220
- /package/dist/services/{gitService.js → git-service.js} +0 -0
- /package/prisma/migrations/{20251219074511_create_unique_composite_key_for_commits → 20251219090102_composite_key_for_table_commits}/migration.sql +0 -0
package/.turbo/turbo-build.log
CHANGED
package/CHANGELOG.md
CHANGED
|
@@ -1,23 +1,24 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.getCommitsByPath = void 0;
|
|
4
|
-
const
|
|
4
|
+
const commit_service_1 = require("../services/commit-service");
|
|
5
5
|
const getCommitsByPath = async (_req, res) => {
|
|
6
6
|
try {
|
|
7
7
|
const { packagePath } = _req.params;
|
|
8
8
|
const decodedPath = decodeURIComponent(packagePath);
|
|
9
9
|
console.log('Fetching commits for path:', decodedPath);
|
|
10
10
|
console.log('Current working directory:', process.cwd());
|
|
11
|
-
const commits = await (0,
|
|
11
|
+
const commits = await (0, commit_service_1.getCommitsByPathService)(decodedPath);
|
|
12
12
|
console.log(`Successfully fetched ${commits.length} commits for ${decodedPath}`);
|
|
13
13
|
res.json(commits);
|
|
14
14
|
}
|
|
15
15
|
catch (error) {
|
|
16
|
+
const err = error;
|
|
16
17
|
console.error('Error fetching commit details:', error);
|
|
17
18
|
res.status(500).json({
|
|
18
19
|
error: 'Failed to fetch commit details',
|
|
19
|
-
message:
|
|
20
|
-
stack: process.env.NODE_ENV === 'development' ?
|
|
20
|
+
message: err?.message,
|
|
21
|
+
stack: process.env.NODE_ENV === 'development' ? err?.stack : undefined,
|
|
21
22
|
});
|
|
22
23
|
}
|
|
23
24
|
};
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.updateConfigFile = exports.getConfigurationFiles = void 0;
|
|
4
|
-
const
|
|
4
|
+
const config_service_1 = require("../services/config-service");
|
|
5
5
|
const getConfigurationFiles = async (_req, res) => {
|
|
6
6
|
try {
|
|
7
7
|
const rootDir = _req.app.locals.rootPath;
|
|
8
8
|
console.log('Monorepo root directory:', rootDir);
|
|
9
|
-
const configFiles = await (0,
|
|
9
|
+
const configFiles = await (0, config_service_1.getConfigurationFilesService)(rootDir);
|
|
10
10
|
res.json(configFiles);
|
|
11
11
|
}
|
|
12
12
|
catch (error) {
|
|
@@ -29,7 +29,7 @@ const updateConfigFile = async (_req, res) => {
|
|
|
29
29
|
});
|
|
30
30
|
}
|
|
31
31
|
const rootDir = _req.app.locals.rootPath;
|
|
32
|
-
const result = await (0,
|
|
32
|
+
const result = await (0, config_service_1.updateConfigFileService)(id, rootDir, content);
|
|
33
33
|
res.json(result);
|
|
34
34
|
}
|
|
35
35
|
catch (error) {
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.refreshHealth = exports.getPackagesHealth = void 0;
|
|
4
|
-
const
|
|
4
|
+
const health_service_1 = require("../services/health-service");
|
|
5
5
|
const getPackagesHealth = async (_req, res) => {
|
|
6
6
|
try {
|
|
7
|
-
const health = await (0,
|
|
7
|
+
const health = await (0, health_service_1.getHealthSummaryService)();
|
|
8
8
|
res.json(health);
|
|
9
9
|
}
|
|
10
10
|
catch (error) {
|
|
@@ -17,7 +17,7 @@ const getPackagesHealth = async (_req, res) => {
|
|
|
17
17
|
exports.getPackagesHealth = getPackagesHealth;
|
|
18
18
|
const refreshHealth = async (_req, res) => {
|
|
19
19
|
try {
|
|
20
|
-
const health = await (0,
|
|
20
|
+
const health = await (0, health_service_1.healthRefreshService)(_req.app.locals.rootPath);
|
|
21
21
|
res.json(health);
|
|
22
22
|
}
|
|
23
23
|
catch (error) {
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.updatePackageConfig = exports.getPackageDetail = exports.refreshPackages = exports.getPackages = void 0;
|
|
4
|
-
const
|
|
5
|
-
const
|
|
4
|
+
const config_service_1 = require("../services/config-service");
|
|
5
|
+
const package_service_1 = require("../services/package-service");
|
|
6
6
|
const getPackages = async (_req, res) => {
|
|
7
7
|
try {
|
|
8
|
-
const transformedPackages = await (0,
|
|
8
|
+
const transformedPackages = await (0, package_service_1.getPackagesService)(_req.app.locals.rootPath);
|
|
9
9
|
res.json(transformedPackages);
|
|
10
10
|
}
|
|
11
11
|
catch (error) {
|
|
@@ -16,7 +16,7 @@ exports.getPackages = getPackages;
|
|
|
16
16
|
const refreshPackages = async (_req, res) => {
|
|
17
17
|
console.log('Refreshing packages from source...' + _req.app.locals.rootPath);
|
|
18
18
|
try {
|
|
19
|
-
const packages = await (0,
|
|
19
|
+
const packages = await (0, package_service_1.refreshPackagesService)(_req.app.locals.rootPath);
|
|
20
20
|
res.json(packages);
|
|
21
21
|
}
|
|
22
22
|
catch (error) {
|
|
@@ -27,7 +27,7 @@ exports.refreshPackages = refreshPackages;
|
|
|
27
27
|
const getPackageDetail = async (_req, res) => {
|
|
28
28
|
const { name } = _req.params;
|
|
29
29
|
try {
|
|
30
|
-
const packageDetail = await (0,
|
|
30
|
+
const packageDetail = await (0, package_service_1.getPackageDetailService)(name);
|
|
31
31
|
res.json(packageDetail);
|
|
32
32
|
}
|
|
33
33
|
catch (error) {
|
|
@@ -46,7 +46,7 @@ const updatePackageConfig = async (req, res) => {
|
|
|
46
46
|
}
|
|
47
47
|
console.log('Updating package configuration for:', packageName);
|
|
48
48
|
console.log('Package path:', packagePath);
|
|
49
|
-
const updatedPackage = await (0,
|
|
49
|
+
const updatedPackage = await (0, config_service_1.updatePackageConfigurationService)(packagePath, packageName, config);
|
|
50
50
|
return res.json({
|
|
51
51
|
success: true,
|
|
52
52
|
message: 'Package configuration updated successfully',
|
package/dist/index.js
CHANGED
|
@@ -1,238 +1,13 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
2
|
+
/**
|
|
3
|
+
* Monodog Application Entry Point
|
|
4
|
+
*
|
|
5
|
+
* This file exports the main server and dashboard startup functions
|
|
6
|
+
* All middleware, security, and error handling logic has been moved to separate files
|
|
7
|
+
*/
|
|
5
8
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.startServer =
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
const body_parser_1 = require("body-parser");
|
|
12
|
-
const helmet_1 = __importDefault(require("helmet"));
|
|
13
|
-
const config_loader_1 = require("./config-loader");
|
|
14
|
-
const packageRoutes_1 = __importDefault(require("./routes/packageRoutes"));
|
|
15
|
-
const commitRoutes_1 = __importDefault(require("./routes/commitRoutes"));
|
|
16
|
-
const healthRoutes_1 = __importDefault(require("./routes/healthRoutes"));
|
|
17
|
-
const configRoutes_1 = __importDefault(require("./routes/configRoutes"));
|
|
18
|
-
// Security constants
|
|
19
|
-
const PORT_MIN = 1024;
|
|
20
|
-
const PORT_MAX = 65535;
|
|
21
|
-
// Validate port number
|
|
22
|
-
function validatePort(port) {
|
|
23
|
-
const portNum = typeof port === 'string' ? parseInt(port, 10) : port;
|
|
24
|
-
if (isNaN(portNum) || portNum < PORT_MIN || portNum > PORT_MAX) {
|
|
25
|
-
throw new Error(`Port must be between ${PORT_MIN} and ${PORT_MAX}`);
|
|
26
|
-
}
|
|
27
|
-
return portNum;
|
|
28
|
-
}
|
|
29
|
-
// Global error handler
|
|
30
|
-
const errorHandler = (err, req, res, _next) => {
|
|
31
|
-
const status = err.status || err.statusCode || 500;
|
|
32
|
-
console.error('[ERROR]', {
|
|
33
|
-
status,
|
|
34
|
-
method: req.method,
|
|
35
|
-
path: req.path,
|
|
36
|
-
message: err.message,
|
|
37
|
-
});
|
|
38
|
-
res.status(status).json({
|
|
39
|
-
error: 'Internal server error'
|
|
40
|
-
});
|
|
41
|
-
};
|
|
42
|
-
// The main function exported and called by the CLI
|
|
43
|
-
function startServer(rootPath) {
|
|
44
|
-
try {
|
|
45
|
-
const port = config_loader_1.appConfig.server.port;
|
|
46
|
-
const host = config_loader_1.appConfig.server.host;
|
|
47
|
-
const validatedPort = validatePort(port);
|
|
48
|
-
const app = (0, express_1.default)();
|
|
49
|
-
// Set request timeout (30 seconds)
|
|
50
|
-
app.use((req, res, next) => {
|
|
51
|
-
req.setTimeout(30000);
|
|
52
|
-
res.setTimeout(30000);
|
|
53
|
-
next();
|
|
54
|
-
});
|
|
55
|
-
app.locals.rootPath = rootPath;
|
|
56
|
-
// Security middleware with CSP allowing API calls
|
|
57
|
-
const apiHost = host === '0.0.0.0' ? 'localhost' : host;
|
|
58
|
-
const apiUrl = process.env.API_URL || `http://${apiHost}:${validatedPort}`;
|
|
59
|
-
const dashboardHost = config_loader_1.appConfig.dashboard.host === '0.0.0.0' ? 'localhost' : config_loader_1.appConfig.dashboard.host;
|
|
60
|
-
const dashboardUrl = `http://${dashboardHost}:${config_loader_1.appConfig.dashboard.port}`;
|
|
61
|
-
app.use((0, helmet_1.default)({
|
|
62
|
-
contentSecurityPolicy: {
|
|
63
|
-
directives: {
|
|
64
|
-
defaultSrc: ["'self'"],
|
|
65
|
-
connectSrc: ["'self'", apiUrl, 'http://localhost:*', 'http://127.0.0.1:*'],
|
|
66
|
-
scriptSrc: ["'self'"],
|
|
67
|
-
imgSrc: ["'self'", 'data:', 'https:'],
|
|
68
|
-
},
|
|
69
|
-
},
|
|
70
|
-
}));
|
|
71
|
-
app.use((0, cors_1.default)({
|
|
72
|
-
origin: process.env.CORS_ORIGIN || dashboardUrl,
|
|
73
|
-
credentials: true,
|
|
74
|
-
methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
|
|
75
|
-
allowedHeaders: ['Content-Type', 'Authorization'],
|
|
76
|
-
}));
|
|
77
|
-
app.use((0, body_parser_1.json)({ limit: '1mb' }));
|
|
78
|
-
// Request logging middleware (safe version)
|
|
79
|
-
app.use((_req, _res, next) => {
|
|
80
|
-
console.log(`[${new Date().toISOString()}] ${_req.method} ${_req.path}`);
|
|
81
|
-
next();
|
|
82
|
-
});
|
|
83
|
-
app.use('/api/packages', packageRoutes_1.default);
|
|
84
|
-
// Get commit details
|
|
85
|
-
app.use('/api/commits/', commitRoutes_1.default);
|
|
86
|
-
// Health check endpoint
|
|
87
|
-
app.use('/api/health/', healthRoutes_1.default);
|
|
88
|
-
// Configuration endpoint
|
|
89
|
-
app.use('/api/config/', configRoutes_1.default);
|
|
90
|
-
// 404 handler
|
|
91
|
-
app.use('*', (_, res) => {
|
|
92
|
-
res.status(404).json({
|
|
93
|
-
error: 'Endpoint not found',
|
|
94
|
-
timestamp: Date.now(),
|
|
95
|
-
});
|
|
96
|
-
});
|
|
97
|
-
// Global error handler (must be last)
|
|
98
|
-
app.use(errorHandler);
|
|
99
|
-
const server = app.listen(validatedPort, host, () => {
|
|
100
|
-
console.log(`Backend server running on http://${host}:${validatedPort}`);
|
|
101
|
-
console.log(`API endpoints available:`);
|
|
102
|
-
console.log(` - GET /api/health`);
|
|
103
|
-
console.log(` - GET /api/packages/refresh`);
|
|
104
|
-
console.log(` - GET /api/packages`);
|
|
105
|
-
console.log(` - GET /api/packages/:name`);
|
|
106
|
-
console.log(` - PUT /api/packages/update-config`);
|
|
107
|
-
console.log(` - GET /api/commits/:packagePath`);
|
|
108
|
-
console.log(` - GET /api/health/packages`);
|
|
109
|
-
console.log(` - PUT /api/config/files/:id`);
|
|
110
|
-
console.log(` - GET /api/config/files`);
|
|
111
|
-
});
|
|
112
|
-
server.on('error', (err) => {
|
|
113
|
-
// Handle common errors like EADDRINUSE (port already in use)
|
|
114
|
-
if (err.code === 'EADDRINUSE') {
|
|
115
|
-
console.error(`Error: Port ${validatedPort} is already in use. Please specify a different port.`);
|
|
116
|
-
process.exit(1);
|
|
117
|
-
}
|
|
118
|
-
else if (err.code === 'EACCES') {
|
|
119
|
-
console.error(`Error: Permission denied to listen on port ${validatedPort}. Use a port above 1024.`);
|
|
120
|
-
process.exit(1);
|
|
121
|
-
}
|
|
122
|
-
else {
|
|
123
|
-
console.error('Server failed to start:', err.message);
|
|
124
|
-
process.exit(1);
|
|
125
|
-
}
|
|
126
|
-
});
|
|
127
|
-
// Graceful shutdown
|
|
128
|
-
process.on('SIGTERM', () => {
|
|
129
|
-
console.log('SIGTERM signal received: closing HTTP server');
|
|
130
|
-
server.close(() => {
|
|
131
|
-
console.log('HTTP server closed');
|
|
132
|
-
process.exit(0);
|
|
133
|
-
});
|
|
134
|
-
});
|
|
135
|
-
}
|
|
136
|
-
catch (error) {
|
|
137
|
-
console.error('Failed to start server:', error.message);
|
|
138
|
-
process.exit(1);
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
function serveDashboard(rootPath) {
|
|
142
|
-
try {
|
|
143
|
-
const port = config_loader_1.appConfig.dashboard.port;
|
|
144
|
-
const host = config_loader_1.appConfig.dashboard.host;
|
|
145
|
-
const validatedPort = validatePort(port);
|
|
146
|
-
const app = (0, express_1.default)();
|
|
147
|
-
// Security middleware
|
|
148
|
-
const serverHost = config_loader_1.appConfig.server.host === '0.0.0.0' ? 'localhost' : config_loader_1.appConfig.server.host;
|
|
149
|
-
const apiUrl = process.env.API_URL || `http://${serverHost}:${config_loader_1.appConfig.server.port}`;
|
|
150
|
-
app.use((0, helmet_1.default)({
|
|
151
|
-
contentSecurityPolicy: {
|
|
152
|
-
directives: {
|
|
153
|
-
defaultSrc: ["'self'"],
|
|
154
|
-
connectSrc: ["'self'", apiUrl, 'http://localhost:*', 'http://127.0.0.1:*'],
|
|
155
|
-
scriptSrc: ["'self'"],
|
|
156
|
-
imgSrc: ["'self'", 'data:', 'https:'],
|
|
157
|
-
},
|
|
158
|
-
},
|
|
159
|
-
}));
|
|
160
|
-
// Strict CORS for dashboard
|
|
161
|
-
app.use((0, cors_1.default)({
|
|
162
|
-
origin: false, // Don't allow any origin for static assets
|
|
163
|
-
}));
|
|
164
|
-
// Set request timeout
|
|
165
|
-
app.use((req, res, next) => {
|
|
166
|
-
req.setTimeout(30000);
|
|
167
|
-
res.setTimeout(30000);
|
|
168
|
-
next();
|
|
169
|
-
});
|
|
170
|
-
app.get('/env-config.js', (req, res) => {
|
|
171
|
-
res.setHeader('Content-Type', 'application/javascript');
|
|
172
|
-
res.setHeader('Cache-Control', 'private, no-cache, no-store, must-revalidate');
|
|
173
|
-
const serverHost = config_loader_1.appConfig.server.host === '0.0.0.0' ? 'localhost' : config_loader_1.appConfig.server.host;
|
|
174
|
-
const apiUrl = process.env.API_URL || `http://${serverHost}:${config_loader_1.appConfig.server.port}`;
|
|
175
|
-
res.send(`window.ENV = { API_URL: "${apiUrl}" };`);
|
|
176
|
-
});
|
|
177
|
-
// This code makes sure that any request that does not matches a static file
|
|
178
|
-
// in the build folder, will just serve index.html. Client side routing is
|
|
179
|
-
// going to make sure that the correct content will be loaded.
|
|
180
|
-
app.use((req, res, next) => {
|
|
181
|
-
if (/(.ico|.js|.css|.jpg|.png|.map|.woff|.woff2|.ttf)$/i.test(req.path)) {
|
|
182
|
-
next();
|
|
183
|
-
}
|
|
184
|
-
else {
|
|
185
|
-
res.header('Cache-Control', 'private, no-cache, no-store, must-revalidate');
|
|
186
|
-
res.header('Expires', '-1');
|
|
187
|
-
res.header('Pragma', 'no-cache');
|
|
188
|
-
res.sendFile('index.html', {
|
|
189
|
-
root: path_1.default.resolve(__dirname, '..', 'monodog-dashboard', 'dist'),
|
|
190
|
-
}, (err) => {
|
|
191
|
-
if (err) {
|
|
192
|
-
console.error('Error serving index.html:', err.message);
|
|
193
|
-
res.status(500).json({ error: 'Internal server error' });
|
|
194
|
-
}
|
|
195
|
-
});
|
|
196
|
-
}
|
|
197
|
-
});
|
|
198
|
-
const staticPath = path_1.default.join(__dirname, '..', 'monodog-dashboard', 'dist');
|
|
199
|
-
console.log('Serving static files from:', staticPath);
|
|
200
|
-
app.use(express_1.default.static(staticPath, {
|
|
201
|
-
maxAge: '1d',
|
|
202
|
-
etag: false,
|
|
203
|
-
dotfiles: 'deny', // Don't serve dot files
|
|
204
|
-
}));
|
|
205
|
-
// Global error handler
|
|
206
|
-
app.use(errorHandler);
|
|
207
|
-
const server = app.listen(validatedPort, host, () => {
|
|
208
|
-
console.log(`Dashboard listening on http://${host}:${validatedPort}`);
|
|
209
|
-
console.log('Press Ctrl+C to quit.');
|
|
210
|
-
});
|
|
211
|
-
server.on('error', (err) => {
|
|
212
|
-
if (err.code === 'EADDRINUSE') {
|
|
213
|
-
console.error(`Error: Port ${validatedPort} is already in use.`);
|
|
214
|
-
process.exit(1);
|
|
215
|
-
}
|
|
216
|
-
else if (err.code === 'EACCES') {
|
|
217
|
-
console.error(`Error: Permission denied to listen on port ${validatedPort}.`);
|
|
218
|
-
process.exit(1);
|
|
219
|
-
}
|
|
220
|
-
else {
|
|
221
|
-
console.error('Server failed to start:', err.message);
|
|
222
|
-
process.exit(1);
|
|
223
|
-
}
|
|
224
|
-
});
|
|
225
|
-
// Graceful shutdown
|
|
226
|
-
process.on('SIGTERM', () => {
|
|
227
|
-
console.log('SIGTERM signal received: closing dashboard server');
|
|
228
|
-
server.close(() => {
|
|
229
|
-
console.log('Dashboard server closed');
|
|
230
|
-
process.exit(0);
|
|
231
|
-
});
|
|
232
|
-
});
|
|
233
|
-
}
|
|
234
|
-
catch (error) {
|
|
235
|
-
console.error('Failed to start dashboard:', error.message);
|
|
236
|
-
process.exit(1);
|
|
237
|
-
}
|
|
238
|
-
}
|
|
9
|
+
exports.serveDashboard = exports.startServer = void 0;
|
|
10
|
+
var server_startup_1 = require("./middleware/server-startup");
|
|
11
|
+
Object.defineProperty(exports, "startServer", { enumerable: true, get: function () { return server_startup_1.startServer; } });
|
|
12
|
+
var dashboard_startup_1 = require("./middleware/dashboard-startup");
|
|
13
|
+
Object.defineProperty(exports, "serveDashboard", { enumerable: true, get: function () { return dashboard_startup_1.serveDashboard; } });
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Dashboard server startup logic
|
|
4
|
+
*/
|
|
5
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
6
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
7
|
+
};
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.serveDashboard = serveDashboard;
|
|
10
|
+
const express_1 = __importDefault(require("express"));
|
|
11
|
+
const path_1 = __importDefault(require("path"));
|
|
12
|
+
const logger_1 = require("./logger");
|
|
13
|
+
const config_loader_1 = require("../config-loader");
|
|
14
|
+
const error_handler_1 = require("./error-handler");
|
|
15
|
+
const security_1 = require("./security");
|
|
16
|
+
// Security constants
|
|
17
|
+
const PORT_MIN = 1024;
|
|
18
|
+
const PORT_MAX = 65535;
|
|
19
|
+
/**
|
|
20
|
+
* Validate port number
|
|
21
|
+
*/
|
|
22
|
+
function validatePort(port) {
|
|
23
|
+
const portNum = typeof port === 'string' ? parseInt(port, 10) : port;
|
|
24
|
+
if (isNaN(portNum) || portNum < PORT_MIN || portNum > PORT_MAX) {
|
|
25
|
+
throw new Error(`Port must be between ${PORT_MIN} and ${PORT_MAX}`);
|
|
26
|
+
}
|
|
27
|
+
return portNum;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Create Express app for dashboard with middleware
|
|
31
|
+
*/
|
|
32
|
+
function createDashboardApp() {
|
|
33
|
+
const app = (0, express_1.default)();
|
|
34
|
+
// Timeout middleware
|
|
35
|
+
app.use((0, security_1.createTimeoutMiddleware)());
|
|
36
|
+
// Security setup
|
|
37
|
+
const serverHost = config_loader_1.appConfig.server.host === '0.0.0.0'
|
|
38
|
+
? 'localhost'
|
|
39
|
+
: config_loader_1.appConfig.server.host;
|
|
40
|
+
const apiUrl = (0, security_1.buildApiUrl)(serverHost, config_loader_1.appConfig.server.port);
|
|
41
|
+
app.use((0, security_1.createHelmetMiddleware)(apiUrl));
|
|
42
|
+
app.use((0, security_1.createDashboardCorsMiddleware)());
|
|
43
|
+
// Environment config endpoint
|
|
44
|
+
app.get('/env-config.js', (_req, res) => {
|
|
45
|
+
res.setHeader('Content-Type', 'application/javascript');
|
|
46
|
+
res.setHeader('Cache-Control', 'private, no-cache, no-store, must-revalidate');
|
|
47
|
+
res.send(`window.ENV = { API_URL: "${apiUrl}" };`);
|
|
48
|
+
});
|
|
49
|
+
// Request logging
|
|
50
|
+
app.use(logger_1.httpLogger);
|
|
51
|
+
// app.use(requestLogger);
|
|
52
|
+
// SPA routing: serve index.html for non-static routes
|
|
53
|
+
app.use((_req, _res, next) => {
|
|
54
|
+
if (/(.ico|.js|.css|.jpg|.png|.map|.woff|.woff2|.ttf)$/i.test(_req.path)) {
|
|
55
|
+
next();
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
_res.header('Cache-Control', 'private, no-cache, no-store, must-revalidate');
|
|
59
|
+
_res.header('Expires', '-1');
|
|
60
|
+
_res.header('Pragma', 'no-cache');
|
|
61
|
+
_res.sendFile('index.html', {
|
|
62
|
+
root: path_1.default.resolve(__dirname, '..', '..', 'monodog-dashboard', 'dist'),
|
|
63
|
+
}, (err) => {
|
|
64
|
+
if (err) {
|
|
65
|
+
logger_1.AppLogger.error('Error serving index.html:', err);
|
|
66
|
+
_res.status(500).json({ error: 'Internal server error' });
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
// Static files
|
|
72
|
+
const staticPath = path_1.default.join(__dirname, '..', '..', 'monodog-dashboard', 'dist');
|
|
73
|
+
logger_1.AppLogger.debug('Serving static files from:', { path: staticPath });
|
|
74
|
+
app.use(express_1.default.static(staticPath, {
|
|
75
|
+
maxAge: '1d',
|
|
76
|
+
etag: false,
|
|
77
|
+
dotfiles: 'deny',
|
|
78
|
+
}));
|
|
79
|
+
// Global error handler (must be last)
|
|
80
|
+
app.use(error_handler_1.errorHandler);
|
|
81
|
+
return app;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Start the dashboard server
|
|
85
|
+
*/
|
|
86
|
+
function serveDashboard(rootPath) {
|
|
87
|
+
try {
|
|
88
|
+
const port = config_loader_1.appConfig.dashboard.port;
|
|
89
|
+
const host = config_loader_1.appConfig.dashboard.host;
|
|
90
|
+
const validatedPort = validatePort(port);
|
|
91
|
+
const app = createDashboardApp();
|
|
92
|
+
const server = app.listen(validatedPort, host, () => {
|
|
93
|
+
console.log(`Dashboard listening on http://${host}:${validatedPort}`);
|
|
94
|
+
console.log('Press Ctrl+C to quit.');
|
|
95
|
+
});
|
|
96
|
+
server.on('error', (err) => {
|
|
97
|
+
if (err.code === 'EADDRINUSE') {
|
|
98
|
+
logger_1.AppLogger.error(`Port ${validatedPort} is already in use.`, err);
|
|
99
|
+
process.exit(1);
|
|
100
|
+
}
|
|
101
|
+
else if (err.code === 'EACCES') {
|
|
102
|
+
logger_1.AppLogger.error(`Permission denied to listen on port ${validatedPort}.`, err);
|
|
103
|
+
process.exit(1);
|
|
104
|
+
}
|
|
105
|
+
else {
|
|
106
|
+
logger_1.AppLogger.error('Server failed to start:', err);
|
|
107
|
+
process.exit(1);
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
// Graceful shutdown
|
|
111
|
+
process.on('SIGTERM', () => {
|
|
112
|
+
logger_1.AppLogger.info('SIGTERM signal received: closing dashboard server');
|
|
113
|
+
server.close(() => {
|
|
114
|
+
logger_1.AppLogger.info('Dashboard server closed');
|
|
115
|
+
process.exit(0);
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
catch (error) {
|
|
120
|
+
const err = error;
|
|
121
|
+
logger_1.AppLogger.error('Failed to start dashboard:', err);
|
|
122
|
+
process.exit(1);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Error handling middleware for Express
|
|
4
|
+
*/
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.notFoundHandler = exports.errorHandler = void 0;
|
|
7
|
+
const logger_1 = require("./logger");
|
|
8
|
+
/**
|
|
9
|
+
* Global error handler middleware
|
|
10
|
+
* Must be registered last in the middleware chain
|
|
11
|
+
*/
|
|
12
|
+
const errorHandler = (err, req, res, _next) => {
|
|
13
|
+
const status = err.status || err.statusCode || 500;
|
|
14
|
+
logger_1.AppLogger.error('Request error occurred', {
|
|
15
|
+
status,
|
|
16
|
+
method: req.method,
|
|
17
|
+
path: req.path,
|
|
18
|
+
message: err.message,
|
|
19
|
+
timestamp: new Date().toISOString(),
|
|
20
|
+
});
|
|
21
|
+
res.status(status).json({
|
|
22
|
+
error: 'Internal server error',
|
|
23
|
+
timestamp: Date.now(),
|
|
24
|
+
});
|
|
25
|
+
};
|
|
26
|
+
exports.errorHandler = errorHandler;
|
|
27
|
+
/**
|
|
28
|
+
* 404 Not Found handler
|
|
29
|
+
*/
|
|
30
|
+
const notFoundHandler = (_req, res) => {
|
|
31
|
+
res.status(404).json({
|
|
32
|
+
error: 'Endpoint not found',
|
|
33
|
+
timestamp: Date.now(),
|
|
34
|
+
});
|
|
35
|
+
};
|
|
36
|
+
exports.notFoundHandler = notFoundHandler;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Middleware exports
|
|
4
|
+
*/
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.AppLogger = exports.httpLogger = exports.serveDashboard = exports.startServer = exports.buildDashboardUrl = exports.buildApiUrl = exports.createTimeoutMiddleware = exports.createDashboardCorsMiddleware = exports.createApiCorsMiddleware = exports.createHelmetMiddleware = exports.notFoundHandler = exports.errorHandler = void 0;
|
|
7
|
+
var error_handler_1 = require("./error-handler");
|
|
8
|
+
Object.defineProperty(exports, "errorHandler", { enumerable: true, get: function () { return error_handler_1.errorHandler; } });
|
|
9
|
+
Object.defineProperty(exports, "notFoundHandler", { enumerable: true, get: function () { return error_handler_1.notFoundHandler; } });
|
|
10
|
+
var security_1 = require("./security");
|
|
11
|
+
Object.defineProperty(exports, "createHelmetMiddleware", { enumerable: true, get: function () { return security_1.createHelmetMiddleware; } });
|
|
12
|
+
Object.defineProperty(exports, "createApiCorsMiddleware", { enumerable: true, get: function () { return security_1.createApiCorsMiddleware; } });
|
|
13
|
+
Object.defineProperty(exports, "createDashboardCorsMiddleware", { enumerable: true, get: function () { return security_1.createDashboardCorsMiddleware; } });
|
|
14
|
+
Object.defineProperty(exports, "createTimeoutMiddleware", { enumerable: true, get: function () { return security_1.createTimeoutMiddleware; } });
|
|
15
|
+
Object.defineProperty(exports, "buildApiUrl", { enumerable: true, get: function () { return security_1.buildApiUrl; } });
|
|
16
|
+
Object.defineProperty(exports, "buildDashboardUrl", { enumerable: true, get: function () { return security_1.buildDashboardUrl; } });
|
|
17
|
+
var server_startup_1 = require("./server-startup");
|
|
18
|
+
Object.defineProperty(exports, "startServer", { enumerable: true, get: function () { return server_startup_1.startServer; } });
|
|
19
|
+
var dashboard_startup_1 = require("./dashboard-startup");
|
|
20
|
+
Object.defineProperty(exports, "serveDashboard", { enumerable: true, get: function () { return dashboard_startup_1.serveDashboard; } });
|
|
21
|
+
var logger_1 = require("./logger");
|
|
22
|
+
Object.defineProperty(exports, "httpLogger", { enumerable: true, get: function () { return logger_1.httpLogger; } });
|
|
23
|
+
Object.defineProperty(exports, "AppLogger", { enumerable: true, get: function () { return logger_1.AppLogger; } });
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Logger configuration using Morgan
|
|
4
|
+
*/
|
|
5
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
6
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
7
|
+
};
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.AppLogger = exports.httpLogger = void 0;
|
|
10
|
+
const morgan_1 = __importDefault(require("morgan"));
|
|
11
|
+
/**
|
|
12
|
+
* HTTP request logger middleware using Morgan
|
|
13
|
+
*/
|
|
14
|
+
exports.httpLogger = (0, morgan_1.default)('dev');
|
|
15
|
+
/**
|
|
16
|
+
* Application logger for non-HTTP events
|
|
17
|
+
*/
|
|
18
|
+
class AppLogger {
|
|
19
|
+
static info(message, data) {
|
|
20
|
+
if (process.env.LOG_LEVEL == 'info' || process.env.LOG_LEVEL == 'debug') {
|
|
21
|
+
if (data) {
|
|
22
|
+
console.log(`${this.prefix} [INFO]`, message, JSON.stringify(data, null, 2));
|
|
23
|
+
}
|
|
24
|
+
else {
|
|
25
|
+
console.log(`${this.prefix} [INFO]`, message);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
static error(message, error) {
|
|
30
|
+
if (error instanceof Error) {
|
|
31
|
+
console.error(`${this.prefix} [ERROR]`, message, {
|
|
32
|
+
message: error.message,
|
|
33
|
+
stack: error.stack,
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
else if (error) {
|
|
37
|
+
console.error(`${this.prefix} [ERROR]`, message, error);
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
console.error(`${this.prefix} [ERROR]`, message);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
static warn(message, data) {
|
|
44
|
+
if (data) {
|
|
45
|
+
console.warn(`${this.prefix} [WARN]`, message, JSON.stringify(data, null, 2));
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
console.warn(`${this.prefix} [WARN]`, message);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
static debug(message, data) {
|
|
52
|
+
if (process.env.LOG_LEVEL == 'debug') {
|
|
53
|
+
if (data) {
|
|
54
|
+
console.log(`${this.prefix} [DEBUG]`, message, JSON.stringify(data, null, 2));
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
console.log(`${this.prefix} [DEBUG]`, message);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
exports.AppLogger = AppLogger;
|
|
63
|
+
AppLogger.prefix = '[APP]';
|