@testsmith/perfornium 0.6.3 → 0.6.5
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/dist/cli/commands/distributed.js +2 -2
- package/dist/cli/commands/report.js +2 -2
- package/dist/cli/commands/run.js +2 -0
- package/dist/config/parser.js +2 -2
- package/dist/config/types/global-config.d.ts +82 -2
- package/dist/config/types/scenario-config.d.ts +2 -2
- package/dist/core/data/data-manager.d.ts +70 -0
- package/dist/core/data/data-manager.js +186 -0
- package/dist/core/data/data-provider.d.ts +85 -0
- package/dist/core/data/data-provider.js +468 -0
- package/dist/core/data/index.d.ts +8 -0
- package/dist/core/data/index.js +13 -0
- package/dist/core/execution/check-evaluator.d.ts +10 -0
- package/dist/core/execution/check-evaluator.js +79 -0
- package/dist/core/execution/data-extractor.d.ts +6 -0
- package/dist/core/execution/data-extractor.js +70 -0
- package/dist/core/execution/index.d.ts +3 -0
- package/dist/core/execution/index.js +9 -0
- package/dist/core/execution/json-payload-processor.d.ts +7 -0
- package/dist/core/execution/json-payload-processor.js +140 -0
- package/dist/core/factories/index.d.ts +2 -0
- package/dist/core/factories/index.js +7 -0
- package/dist/core/factories/output-handler-factory.d.ts +10 -0
- package/dist/core/factories/output-handler-factory.js +91 -0
- package/dist/core/factories/protocol-handler-factory.d.ts +12 -0
- package/dist/core/factories/protocol-handler-factory.js +96 -0
- package/dist/core/index.d.ts +3 -2
- package/dist/core/index.js +8 -3
- package/dist/core/reporting/dashboard-reporter.d.ts +17 -0
- package/dist/core/reporting/dashboard-reporter.js +127 -0
- package/dist/core/reporting/index.d.ts +1 -0
- package/dist/core/reporting/index.js +5 -0
- package/dist/core/step-executor.d.ts +6 -20
- package/dist/core/step-executor.js +72 -366
- package/dist/core/strategies/index.d.ts +2 -0
- package/dist/core/strategies/index.js +7 -0
- package/dist/core/strategies/scenario-selector.d.ts +13 -0
- package/dist/core/strategies/scenario-selector.js +37 -0
- package/dist/core/strategies/think-time-strategy.d.ts +15 -0
- package/dist/core/strategies/think-time-strategy.js +71 -0
- package/dist/core/test-runner.d.ts +4 -11
- package/dist/core/test-runner.js +105 -312
- package/dist/core/virtual-user.d.ts +7 -37
- package/dist/core/virtual-user.js +29 -269
- package/dist/dashboard/routes/api.d.ts +64 -0
- package/dist/dashboard/routes/api.js +569 -0
- package/dist/dashboard/routes/index.d.ts +2 -0
- package/dist/dashboard/routes/index.js +7 -0
- package/dist/dashboard/routes/static.d.ts +6 -0
- package/dist/dashboard/routes/static.js +76 -0
- package/dist/dashboard/server.d.ts +8 -84
- package/dist/dashboard/server.js +76 -2007
- package/dist/dashboard/services/file-scanner.d.ts +7 -0
- package/dist/dashboard/services/file-scanner.js +114 -0
- package/dist/dashboard/services/index.d.ts +5 -0
- package/dist/dashboard/services/index.js +13 -0
- package/dist/dashboard/services/influxdb-service.d.ts +41 -0
- package/dist/dashboard/services/influxdb-service.js +329 -0
- package/dist/dashboard/services/metrics-parser.d.ts +12 -0
- package/dist/dashboard/services/metrics-parser.js +209 -0
- package/dist/dashboard/services/results-manager.d.ts +17 -0
- package/dist/dashboard/services/results-manager.js +311 -0
- package/dist/dashboard/services/test-executor.d.ts +41 -0
- package/dist/dashboard/services/test-executor.js +250 -0
- package/dist/dashboard/services/workers-manager.d.ts +13 -0
- package/dist/dashboard/services/workers-manager.js +81 -0
- package/dist/dashboard/templates/index.html +122 -0
- package/dist/dashboard/templates/scripts/main.js +3280 -0
- package/dist/dashboard/templates/styles.css +402 -0
- package/dist/dashboard/types.d.ts +168 -0
- package/dist/dashboard/types.js +2 -0
- package/dist/distributed/result-aggregator.js +1 -3
- package/dist/metrics/batch/batch-processor.d.ts +27 -0
- package/dist/metrics/batch/batch-processor.js +85 -0
- package/dist/metrics/batch/index.d.ts +1 -0
- package/dist/metrics/batch/index.js +5 -0
- package/dist/metrics/collector.d.ts +46 -45
- package/dist/metrics/collector.js +179 -640
- package/dist/metrics/core/error-tracker.d.ts +9 -0
- package/dist/metrics/core/error-tracker.js +52 -0
- package/dist/metrics/core/index.d.ts +3 -0
- package/dist/metrics/core/index.js +9 -0
- package/dist/metrics/core/result-storage.d.ts +19 -0
- package/dist/metrics/core/result-storage.js +56 -0
- package/dist/metrics/core/statistics-engine.d.ts +27 -0
- package/dist/metrics/core/statistics-engine.js +91 -0
- package/dist/metrics/output/file-writer.d.ts +19 -0
- package/dist/metrics/output/file-writer.js +129 -0
- package/dist/metrics/output/index.d.ts +2 -0
- package/dist/metrics/output/index.js +10 -0
- package/dist/metrics/output/influxdb-writer.d.ts +89 -0
- package/dist/metrics/output/influxdb-writer.js +404 -0
- package/dist/metrics/realtime/dispatcher.d.ts +18 -0
- package/dist/metrics/realtime/dispatcher.js +45 -0
- package/dist/metrics/realtime/endpoints/graphite.d.ts +3 -0
- package/dist/metrics/realtime/endpoints/graphite.js +61 -0
- package/dist/metrics/realtime/endpoints/influxdb.d.ts +3 -0
- package/dist/metrics/realtime/endpoints/influxdb.js +35 -0
- package/dist/metrics/realtime/endpoints/webhook.d.ts +3 -0
- package/dist/metrics/realtime/endpoints/webhook.js +22 -0
- package/dist/metrics/realtime/endpoints/websocket.d.ts +3 -0
- package/dist/metrics/realtime/endpoints/websocket.js +25 -0
- package/dist/metrics/realtime/index.d.ts +5 -0
- package/dist/metrics/realtime/index.js +13 -0
- package/dist/metrics/reporting/index.d.ts +3 -0
- package/dist/metrics/reporting/index.js +9 -0
- package/dist/metrics/reporting/step-statistics.d.ts +6 -0
- package/dist/metrics/reporting/step-statistics.js +59 -0
- package/dist/metrics/reporting/summary-generator.d.ts +16 -0
- package/dist/metrics/reporting/summary-generator.js +46 -0
- package/dist/metrics/reporting/timeline-calculator.d.ts +7 -0
- package/dist/metrics/reporting/timeline-calculator.js +86 -0
- package/dist/metrics/types.d.ts +58 -0
- package/dist/outputs/csv.d.ts +2 -0
- package/dist/outputs/csv.js +21 -2
- package/dist/outputs/json.js +6 -2
- package/dist/protocols/rest/handler.d.ts +4 -53
- package/dist/protocols/rest/handler.js +73 -454
- package/dist/protocols/rest/request/auth-handler.d.ts +4 -0
- package/dist/protocols/rest/request/auth-handler.js +30 -0
- package/dist/protocols/rest/request/body-processor.d.ts +11 -0
- package/dist/protocols/rest/request/body-processor.js +62 -0
- package/dist/protocols/rest/request/index.d.ts +2 -0
- package/dist/protocols/rest/request/index.js +7 -0
- package/dist/protocols/rest/response/checks.d.ts +6 -0
- package/dist/protocols/rest/response/checks.js +71 -0
- package/dist/protocols/rest/response/index.d.ts +2 -0
- package/dist/protocols/rest/response/index.js +7 -0
- package/dist/protocols/rest/response/size-calculator.d.ts +12 -0
- package/dist/protocols/rest/response/size-calculator.js +64 -0
- package/dist/protocols/web/browser/highlight.d.ts +7 -0
- package/dist/protocols/web/browser/highlight.js +47 -0
- package/dist/protocols/web/browser/index.d.ts +4 -0
- package/dist/protocols/web/browser/index.js +11 -0
- package/dist/protocols/web/browser/manager.d.ts +20 -0
- package/dist/protocols/web/browser/manager.js +189 -0
- package/dist/protocols/web/browser/screenshot.d.ts +8 -0
- package/dist/protocols/web/browser/screenshot.js +69 -0
- package/dist/protocols/web/browser/storage.d.ts +5 -0
- package/dist/protocols/web/browser/storage.js +45 -0
- package/dist/protocols/web/commands/index.d.ts +5 -0
- package/dist/protocols/web/commands/index.js +11 -0
- package/dist/protocols/web/commands/interaction.d.ts +13 -0
- package/dist/protocols/web/commands/interaction.js +68 -0
- package/dist/protocols/web/commands/measurement.d.ts +16 -0
- package/dist/protocols/web/commands/measurement.js +33 -0
- package/dist/protocols/web/commands/navigation.d.ts +11 -0
- package/dist/protocols/web/commands/navigation.js +43 -0
- package/dist/protocols/web/commands/types.d.ts +12 -0
- package/dist/protocols/web/commands/types.js +2 -0
- package/dist/protocols/web/commands/verification.d.ts +11 -0
- package/dist/protocols/web/commands/verification.js +98 -0
- package/dist/protocols/web/handler.d.ts +19 -30
- package/dist/protocols/web/handler.js +160 -650
- package/dist/protocols/web/network/capture.d.ts +19 -0
- package/dist/protocols/web/network/capture.js +225 -0
- package/dist/protocols/web/network/filters.d.ts +5 -0
- package/dist/protocols/web/network/filters.js +49 -0
- package/dist/protocols/web/network/index.d.ts +4 -0
- package/dist/protocols/web/network/index.js +9 -0
- package/dist/protocols/web/network/types.d.ts +13 -0
- package/dist/protocols/web/network/types.js +2 -0
- package/dist/protocols/web/network/utils.d.ts +8 -0
- package/dist/protocols/web/network/utils.js +29 -0
- package/dist/recorder/native-recorder.js +2 -1
- package/dist/reporting/chart-data/index.d.ts +5 -0
- package/dist/reporting/chart-data/index.js +13 -0
- package/dist/reporting/chart-data/network.d.ts +25 -0
- package/dist/reporting/chart-data/network.js +78 -0
- package/dist/reporting/chart-data/scenario.d.ts +37 -0
- package/dist/reporting/chart-data/scenario.js +76 -0
- package/dist/reporting/chart-data/step-statistics.d.ts +24 -0
- package/dist/reporting/chart-data/step-statistics.js +94 -0
- package/dist/reporting/chart-data/throughput.d.ts +16 -0
- package/dist/reporting/chart-data/throughput.js +24 -0
- package/dist/reporting/chart-data/timeline.d.ts +17 -0
- package/dist/reporting/chart-data/timeline.js +46 -0
- package/dist/reporting/handlebars-helpers.d.ts +1 -0
- package/dist/reporting/handlebars-helpers.js +63 -0
- package/dist/reporting/{enhanced-html-generator.d.ts → html-generator.d.ts} +1 -1
- package/dist/reporting/{enhanced-html-generator.js → html-generator.js} +10 -7
- package/dist/reporting/templates/{enhanced-report.hbs → report.hbs} +9 -9
- package/dist/utils/data-utils.d.ts +17 -0
- package/dist/utils/data-utils.js +129 -0
- package/dist/utils/template.js +2 -2
- package/package.json +5 -2
- package/dist/core/csv-data-provider.d.ts +0 -47
- package/dist/core/csv-data-provider.js +0 -265
- package/dist/reporting/generator.d.ts +0 -42
- package/dist/reporting/generator.js +0 -1217
- package/dist/reporting/templates/html.hbs +0 -2453
|
@@ -0,0 +1,569 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ApiRoutes = void 0;
|
|
4
|
+
const influxdb_service_1 = require("../services/influxdb-service");
|
|
5
|
+
const influxdb_writer_1 = require("../../metrics/output/influxdb-writer");
|
|
6
|
+
const logger_1 = require("../../utils/logger");
|
|
7
|
+
class ApiRoutes {
|
|
8
|
+
constructor(fileScanner, resultsManager, testExecutor, workersManager, liveTests, callbacks, influxService) {
|
|
9
|
+
this.testMetricsWriter = null;
|
|
10
|
+
this.fileScanner = fileScanner;
|
|
11
|
+
this.resultsManager = resultsManager;
|
|
12
|
+
this.testExecutor = testExecutor;
|
|
13
|
+
this.workersManager = workersManager;
|
|
14
|
+
this.liveTests = liveTests;
|
|
15
|
+
this.onInfraUpdate = callbacks?.onInfraUpdate;
|
|
16
|
+
this.influxService = influxService || new influxdb_service_1.InfluxDBService();
|
|
17
|
+
}
|
|
18
|
+
async initialize() {
|
|
19
|
+
await this.influxService.connect();
|
|
20
|
+
// Initialize test metrics writer for querying stored test data
|
|
21
|
+
this.testMetricsWriter = await (0, influxdb_writer_1.initInfluxDBWriter)();
|
|
22
|
+
}
|
|
23
|
+
async handleGetResults(res) {
|
|
24
|
+
const results = await this.resultsManager.scanResults();
|
|
25
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
26
|
+
res.end(JSON.stringify(results));
|
|
27
|
+
}
|
|
28
|
+
async handleGetResult(res, id) {
|
|
29
|
+
// First load the base result from file (for metadata, summary, etc.)
|
|
30
|
+
const fullResult = await this.resultsManager.loadFullResult(id);
|
|
31
|
+
if (!fullResult) {
|
|
32
|
+
res.writeHead(404, { 'Content-Type': 'application/json' });
|
|
33
|
+
res.end(JSON.stringify({ error: 'Result not found' }));
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
// Add source metadata
|
|
37
|
+
const sourceInfo = {
|
|
38
|
+
summary: 'file', // Summary/metadata always from file (contains aggregated stats)
|
|
39
|
+
individual_results: 'file', // Will be updated to 'influxdb' if available
|
|
40
|
+
network_calls: fullResult.network_calls?.length ? 'file' : 'none',
|
|
41
|
+
infrastructure_metrics: fullResult.infrastructure_metrics ? 'file' : 'none'
|
|
42
|
+
};
|
|
43
|
+
// Try to load individual test results from InfluxDB
|
|
44
|
+
if (this.testMetricsWriter?.isEnabled() && fullResult.timestamp && fullResult.duration) {
|
|
45
|
+
try {
|
|
46
|
+
const startTime = new Date(fullResult.timestamp);
|
|
47
|
+
const endTime = new Date(startTime.getTime() + (fullResult.duration * 1000));
|
|
48
|
+
logger_1.logger.info(`Querying InfluxDB for test: "${fullResult.name}", time range: ${startTime.toISOString()} - ${endTime.toISOString()}`);
|
|
49
|
+
// Query test results from InfluxDB
|
|
50
|
+
const influxResults = await this.testMetricsWriter.queryResults({
|
|
51
|
+
testName: fullResult.name,
|
|
52
|
+
startTime,
|
|
53
|
+
endTime
|
|
54
|
+
});
|
|
55
|
+
logger_1.logger.info(`InfluxDB returned ${influxResults.length} test results`);
|
|
56
|
+
if (influxResults.length > 0) {
|
|
57
|
+
// Replace file-based results with InfluxDB results
|
|
58
|
+
fullResult.results = influxResults;
|
|
59
|
+
sourceInfo.individual_results = 'influxdb';
|
|
60
|
+
sourceInfo.influxdb_result_count = influxResults.length;
|
|
61
|
+
}
|
|
62
|
+
// Query network calls from InfluxDB
|
|
63
|
+
const influxNetworkCalls = await this.testMetricsWriter.queryNetworkCalls({
|
|
64
|
+
testName: fullResult.name,
|
|
65
|
+
startTime,
|
|
66
|
+
endTime
|
|
67
|
+
});
|
|
68
|
+
logger_1.logger.info(`InfluxDB returned ${influxNetworkCalls.length} network calls`);
|
|
69
|
+
if (influxNetworkCalls.length > 0) {
|
|
70
|
+
fullResult.network_calls = influxNetworkCalls;
|
|
71
|
+
sourceInfo.network_calls = 'influxdb';
|
|
72
|
+
sourceInfo.influxdb_network_call_count = influxNetworkCalls.length;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
catch (e) {
|
|
76
|
+
logger_1.logger.error(`Could not fetch test results from InfluxDB: ${e.message}`);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
logger_1.logger.debug(`InfluxDB query skipped: enabled=${this.testMetricsWriter?.isEnabled()}, timestamp=${fullResult.timestamp}, duration=${fullResult.duration}`);
|
|
81
|
+
}
|
|
82
|
+
// Check if we can augment infrastructure from InfluxDB
|
|
83
|
+
if (!fullResult.infrastructure_metrics || Object.keys(fullResult.infrastructure_metrics).length === 0) {
|
|
84
|
+
if (this.influxService.isEnabled() && fullResult.timestamp && fullResult.duration) {
|
|
85
|
+
try {
|
|
86
|
+
const startTime = new Date(fullResult.timestamp);
|
|
87
|
+
const endTime = new Date(startTime.getTime() + (fullResult.duration * 1000));
|
|
88
|
+
const infraFromDB = await this.influxService.queryMetricsByTestRun('', startTime, endTime);
|
|
89
|
+
if (Object.keys(infraFromDB).length > 0) {
|
|
90
|
+
fullResult.infrastructure_metrics = infraFromDB;
|
|
91
|
+
sourceInfo.infrastructure_metrics = 'influxdb';
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
catch (e) {
|
|
95
|
+
logger_1.logger.debug(`Could not fetch infra from InfluxDB: ${e.message}`);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
// Add source info to response
|
|
100
|
+
fullResult._source = sourceInfo;
|
|
101
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
102
|
+
res.end(JSON.stringify(fullResult));
|
|
103
|
+
}
|
|
104
|
+
async handleDeleteResult(res, id) {
|
|
105
|
+
try {
|
|
106
|
+
await this.resultsManager.deleteResult(id);
|
|
107
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
108
|
+
res.end(JSON.stringify({ status: 'deleted', id: decodeURIComponent(id) }));
|
|
109
|
+
}
|
|
110
|
+
catch (e) {
|
|
111
|
+
logger_1.logger.error(`Failed to delete result ${id}:`, e.message);
|
|
112
|
+
res.writeHead(404, { 'Content-Type': 'application/json' });
|
|
113
|
+
res.end(JSON.stringify({ error: 'Result not found', details: e.message }));
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
async handleExportResult(res, id, url) {
|
|
117
|
+
try {
|
|
118
|
+
const format = (url.searchParams.get('format') || 'json');
|
|
119
|
+
const includeNetworkCalls = url.searchParams.get('includeNetworkCalls') === 'true';
|
|
120
|
+
const fullResult = await this.resultsManager.loadFullResult(id);
|
|
121
|
+
if (!fullResult) {
|
|
122
|
+
res.writeHead(404, { 'Content-Type': 'application/json' });
|
|
123
|
+
res.end(JSON.stringify({ error: 'Result not found' }));
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
// Optionally exclude network calls
|
|
127
|
+
const exportData = includeNetworkCalls ? fullResult : { ...fullResult, network_calls: undefined };
|
|
128
|
+
if (format === 'csv') {
|
|
129
|
+
// Export as CSV
|
|
130
|
+
const csv = this.resultToCSV(exportData, includeNetworkCalls);
|
|
131
|
+
res.writeHead(200, {
|
|
132
|
+
'Content-Type': 'text/csv',
|
|
133
|
+
'Content-Disposition': `attachment; filename="${fullResult.name}-${new Date(fullResult.timestamp).toISOString().slice(0, 10)}.csv"`
|
|
134
|
+
});
|
|
135
|
+
res.end(csv);
|
|
136
|
+
}
|
|
137
|
+
else {
|
|
138
|
+
// Export as JSON
|
|
139
|
+
res.writeHead(200, {
|
|
140
|
+
'Content-Type': 'application/json',
|
|
141
|
+
'Content-Disposition': `attachment; filename="${fullResult.name}-${new Date(fullResult.timestamp).toISOString().slice(0, 10)}.json"`
|
|
142
|
+
});
|
|
143
|
+
res.end(JSON.stringify(exportData, null, 2));
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
catch (e) {
|
|
147
|
+
logger_1.logger.error(`Failed to export result ${id}:`, e.message);
|
|
148
|
+
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
149
|
+
res.end(JSON.stringify({ error: 'Export failed', details: e.message }));
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
async handleImportResult(req, res) {
|
|
153
|
+
try {
|
|
154
|
+
const body = await this.readBody(req);
|
|
155
|
+
const data = JSON.parse(body);
|
|
156
|
+
// Validate required fields
|
|
157
|
+
if (!data.summary) {
|
|
158
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
159
|
+
res.end(JSON.stringify({ error: 'Invalid result format: missing summary' }));
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
// Generate a unique ID if not present
|
|
163
|
+
const timestamp = data.timestamp || new Date().toISOString();
|
|
164
|
+
const name = data.name || 'Imported Result';
|
|
165
|
+
const timestampStr = new Date(timestamp).toISOString().replace(/[:.]/g, '-').slice(0, 19);
|
|
166
|
+
const id = data.id || `${name}-${timestampStr}`;
|
|
167
|
+
// Save to results directory
|
|
168
|
+
const result = await this.resultsManager.saveResult(id, {
|
|
169
|
+
...data,
|
|
170
|
+
id,
|
|
171
|
+
name,
|
|
172
|
+
timestamp,
|
|
173
|
+
_imported: true,
|
|
174
|
+
_imported_at: new Date().toISOString()
|
|
175
|
+
});
|
|
176
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
177
|
+
res.end(JSON.stringify({ status: 'imported', id: result.id, name: result.name }));
|
|
178
|
+
}
|
|
179
|
+
catch (e) {
|
|
180
|
+
logger_1.logger.error('Failed to import result:', e.message);
|
|
181
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
182
|
+
res.end(JSON.stringify({ error: 'Import failed', details: e.message }));
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
resultToCSV(result, includeNetworkCalls = false) {
|
|
186
|
+
const lines = [];
|
|
187
|
+
// Summary header
|
|
188
|
+
lines.push('# Test Result Summary');
|
|
189
|
+
lines.push(`Name,${result.name}`);
|
|
190
|
+
lines.push(`Timestamp,${result.timestamp}`);
|
|
191
|
+
lines.push(`Duration,${result.duration}`);
|
|
192
|
+
lines.push('');
|
|
193
|
+
// Summary metrics
|
|
194
|
+
lines.push('# Summary Metrics');
|
|
195
|
+
lines.push('Metric,Value');
|
|
196
|
+
lines.push(`Total Requests,${result.summary.total_requests}`);
|
|
197
|
+
lines.push(`Successful Requests,${result.summary.successful_requests}`);
|
|
198
|
+
lines.push(`Failed Requests,${result.summary.failed_requests}`);
|
|
199
|
+
lines.push(`Avg Response Time (ms),${result.summary.avg_response_time}`);
|
|
200
|
+
lines.push(`Min Response Time (ms),${result.summary.min_response_time}`);
|
|
201
|
+
lines.push(`Max Response Time (ms),${result.summary.max_response_time}`);
|
|
202
|
+
lines.push(`P50 Response Time (ms),${result.summary.p50_response_time}`);
|
|
203
|
+
lines.push(`P95 Response Time (ms),${result.summary.p95_response_time}`);
|
|
204
|
+
lines.push(`P99 Response Time (ms),${result.summary.p99_response_time}`);
|
|
205
|
+
lines.push(`Requests per Second,${result.summary.requests_per_second}`);
|
|
206
|
+
lines.push(`Success Rate (%),${result.summary.success_rate}`);
|
|
207
|
+
lines.push(`Error Rate (%),${result.summary.error_rate}`);
|
|
208
|
+
lines.push('');
|
|
209
|
+
// Step statistics if available
|
|
210
|
+
if (result.step_statistics && result.step_statistics.length > 0) {
|
|
211
|
+
lines.push('# Step Statistics');
|
|
212
|
+
lines.push('Step Name,Scenario,Total Requests,Failed Requests,Success Rate,Avg Response Time,Min Response Time,Max Response Time,P50,P95,P99');
|
|
213
|
+
for (const step of result.step_statistics) {
|
|
214
|
+
lines.push([
|
|
215
|
+
step.step_name,
|
|
216
|
+
step.scenario || '',
|
|
217
|
+
step.total_requests || 0,
|
|
218
|
+
step.failed_requests || 0,
|
|
219
|
+
step.success_rate || 0,
|
|
220
|
+
step.avg_response_time || 0,
|
|
221
|
+
step.min_response_time || 0,
|
|
222
|
+
step.max_response_time || 0,
|
|
223
|
+
step.percentiles?.['50'] || 0,
|
|
224
|
+
step.percentiles?.['95'] || 0,
|
|
225
|
+
step.percentiles?.['99'] || 0
|
|
226
|
+
].join(','));
|
|
227
|
+
}
|
|
228
|
+
lines.push('');
|
|
229
|
+
}
|
|
230
|
+
// Individual results if available
|
|
231
|
+
if (result.results && result.results.length > 0) {
|
|
232
|
+
lines.push('# Individual Results');
|
|
233
|
+
lines.push('Timestamp,Scenario,Action,VU ID,Duration (ms),Success,Status,Error');
|
|
234
|
+
for (const r of result.results) {
|
|
235
|
+
lines.push([
|
|
236
|
+
r.timestamp || '',
|
|
237
|
+
r.scenario || '',
|
|
238
|
+
r.action || '',
|
|
239
|
+
r.vu_id || 0,
|
|
240
|
+
r.duration || 0,
|
|
241
|
+
r.success ? 'true' : 'false',
|
|
242
|
+
r.status || '',
|
|
243
|
+
(r.error || '').replace(/,/g, ';')
|
|
244
|
+
].join(','));
|
|
245
|
+
}
|
|
246
|
+
lines.push('');
|
|
247
|
+
}
|
|
248
|
+
// Network calls if available and requested
|
|
249
|
+
if (includeNetworkCalls && result.network_calls && result.network_calls.length > 0) {
|
|
250
|
+
lines.push('# Network Calls');
|
|
251
|
+
lines.push('Timestamp,URL,Method,Status,Duration (ms),Size,Success,Error');
|
|
252
|
+
for (const call of result.network_calls) {
|
|
253
|
+
lines.push([
|
|
254
|
+
call.timestamp || '',
|
|
255
|
+
(call.request_url || call.url || '').replace(/,/g, '%2C'),
|
|
256
|
+
call.request_method || call.method || 'GET',
|
|
257
|
+
call.response_status || call.status || 0,
|
|
258
|
+
call.duration || 0,
|
|
259
|
+
call.response_size || call.size || 0,
|
|
260
|
+
call.success ? 'true' : 'false',
|
|
261
|
+
(call.error || '').replace(/,/g, ';')
|
|
262
|
+
].join(','));
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
return lines.join('\n');
|
|
266
|
+
}
|
|
267
|
+
async handleCompare(res, ids) {
|
|
268
|
+
const results = await Promise.all(ids.map(id => this.resultsManager.loadFullResult(id)));
|
|
269
|
+
const validResults = results.filter(r => r !== null);
|
|
270
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
271
|
+
res.end(JSON.stringify({
|
|
272
|
+
results: validResults,
|
|
273
|
+
comparison: this.resultsManager.generateComparison(validResults)
|
|
274
|
+
}));
|
|
275
|
+
}
|
|
276
|
+
handleGetLive(res) {
|
|
277
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
278
|
+
res.end(JSON.stringify(Array.from(this.liveTests.values())));
|
|
279
|
+
}
|
|
280
|
+
async handleGetTests(res) {
|
|
281
|
+
const tests = await this.fileScanner.scanTestFiles();
|
|
282
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
283
|
+
res.end(JSON.stringify(tests));
|
|
284
|
+
}
|
|
285
|
+
async handleRunTest(req, res) {
|
|
286
|
+
const body = await this.readBody(req);
|
|
287
|
+
const { testPath, options } = JSON.parse(body);
|
|
288
|
+
const result = this.testExecutor.runTest(testPath, options);
|
|
289
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
290
|
+
res.end(JSON.stringify(result));
|
|
291
|
+
}
|
|
292
|
+
handleStopTest(res, testId) {
|
|
293
|
+
const stopped = this.testExecutor.stopTest(testId);
|
|
294
|
+
if (stopped) {
|
|
295
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
296
|
+
res.end(JSON.stringify({ status: 'stopped' }));
|
|
297
|
+
}
|
|
298
|
+
else {
|
|
299
|
+
res.writeHead(404, { 'Content-Type': 'application/json' });
|
|
300
|
+
res.end(JSON.stringify({ error: 'Test not found' }));
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
async handleGetWorkers(res) {
|
|
304
|
+
const workersInfo = await this.workersManager.getWorkers();
|
|
305
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
306
|
+
res.end(JSON.stringify(workersInfo));
|
|
307
|
+
}
|
|
308
|
+
async handleInfraMetrics(req, res) {
|
|
309
|
+
try {
|
|
310
|
+
const body = await this.readBody(req);
|
|
311
|
+
const payload = JSON.parse(body);
|
|
312
|
+
// Validate required fields
|
|
313
|
+
if (!payload.host || payload.type !== 'infrastructure_metrics') {
|
|
314
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
315
|
+
res.end(JSON.stringify({ error: 'Invalid payload: missing host or type' }));
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
318
|
+
const metrics = {
|
|
319
|
+
host: payload.host,
|
|
320
|
+
timestamp: payload.timestamp || new Date().toISOString(),
|
|
321
|
+
interval_seconds: payload.interval_seconds || 5,
|
|
322
|
+
metrics: payload.metrics || {}
|
|
323
|
+
};
|
|
324
|
+
// Write to InfluxDB (also stores in fallback buffer)
|
|
325
|
+
await this.influxService.writeMetrics(metrics);
|
|
326
|
+
// Broadcast to WebSocket clients
|
|
327
|
+
if (this.onInfraUpdate) {
|
|
328
|
+
this.onInfraUpdate(metrics);
|
|
329
|
+
}
|
|
330
|
+
logger_1.logger.debug(`Infrastructure metrics received from ${metrics.host}`);
|
|
331
|
+
res.writeHead(202, { 'Content-Type': 'application/json' });
|
|
332
|
+
res.end(JSON.stringify({
|
|
333
|
+
accepted: true,
|
|
334
|
+
host: metrics.host,
|
|
335
|
+
influxdb: this.influxService.isEnabled()
|
|
336
|
+
}));
|
|
337
|
+
}
|
|
338
|
+
catch (error) {
|
|
339
|
+
logger_1.logger.error('Failed to process infrastructure metrics:', error.message);
|
|
340
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
341
|
+
res.end(JSON.stringify({ error: 'Invalid JSON payload' }));
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
async handleGetInfra(res, host) {
|
|
345
|
+
try {
|
|
346
|
+
if (host) {
|
|
347
|
+
const metrics = await this.influxService.queryMetrics({ host });
|
|
348
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
349
|
+
res.end(JSON.stringify({ host, metrics }));
|
|
350
|
+
}
|
|
351
|
+
else {
|
|
352
|
+
const result = await this.influxService.getLatestMetrics();
|
|
353
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
354
|
+
res.end(JSON.stringify(result));
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
catch (error) {
|
|
358
|
+
logger_1.logger.error('Failed to get infra metrics:', error.message);
|
|
359
|
+
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
360
|
+
res.end(JSON.stringify({ error: error.message }));
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
async handleGetInfraByTestRun(res, startTime, endTime) {
|
|
364
|
+
try {
|
|
365
|
+
const start = new Date(startTime);
|
|
366
|
+
const end = new Date(endTime);
|
|
367
|
+
if (isNaN(start.getTime()) || isNaN(end.getTime())) {
|
|
368
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
369
|
+
res.end(JSON.stringify({ error: 'Invalid date format. Use ISO 8601.' }));
|
|
370
|
+
return;
|
|
371
|
+
}
|
|
372
|
+
const metrics = await this.influxService.queryMetricsByTestRun('', start, end);
|
|
373
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
374
|
+
res.end(JSON.stringify({
|
|
375
|
+
startTime: start.toISOString(),
|
|
376
|
+
endTime: end.toISOString(),
|
|
377
|
+
infrastructure_metrics: metrics
|
|
378
|
+
}));
|
|
379
|
+
}
|
|
380
|
+
catch (error) {
|
|
381
|
+
logger_1.logger.error('Failed to get infra by test run:', error.message);
|
|
382
|
+
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
383
|
+
res.end(JSON.stringify({ error: error.message }));
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
async handleExportInfra(req, res, url) {
|
|
387
|
+
try {
|
|
388
|
+
const format = (url.searchParams.get('format') || 'json');
|
|
389
|
+
const host = url.searchParams.get('host') || undefined;
|
|
390
|
+
const startTime = url.searchParams.get('start') ? new Date(url.searchParams.get('start')) : undefined;
|
|
391
|
+
const endTime = url.searchParams.get('end') ? new Date(url.searchParams.get('end')) : undefined;
|
|
392
|
+
const data = await this.influxService.exportMetrics({ host, startTime, endTime }, format);
|
|
393
|
+
const contentType = format === 'csv' ? 'text/csv' : 'application/json';
|
|
394
|
+
const filename = `infra-metrics-${new Date().toISOString().slice(0, 10)}.${format}`;
|
|
395
|
+
res.writeHead(200, {
|
|
396
|
+
'Content-Type': contentType,
|
|
397
|
+
'Content-Disposition': `attachment; filename="${filename}"`
|
|
398
|
+
});
|
|
399
|
+
res.end(data);
|
|
400
|
+
}
|
|
401
|
+
catch (error) {
|
|
402
|
+
logger_1.logger.error('Failed to export infra metrics:', error.message);
|
|
403
|
+
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
404
|
+
res.end(JSON.stringify({ error: error.message }));
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
async handleImportInfra(req, res, url) {
|
|
408
|
+
try {
|
|
409
|
+
const format = (url.searchParams.get('format') || 'json');
|
|
410
|
+
const body = await this.readBody(req);
|
|
411
|
+
const count = await this.influxService.importMetrics(body, format);
|
|
412
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
413
|
+
res.end(JSON.stringify({
|
|
414
|
+
imported: count,
|
|
415
|
+
format,
|
|
416
|
+
influxdb: this.influxService.isEnabled()
|
|
417
|
+
}));
|
|
418
|
+
}
|
|
419
|
+
catch (error) {
|
|
420
|
+
logger_1.logger.error('Failed to import infra metrics:', error.message);
|
|
421
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
422
|
+
res.end(JSON.stringify({ error: error.message }));
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
async handleGetInfraStatus(res) {
|
|
426
|
+
try {
|
|
427
|
+
const hosts = await this.influxService.getHosts();
|
|
428
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
429
|
+
res.end(JSON.stringify({
|
|
430
|
+
influxdb_enabled: this.influxService.isEnabled(),
|
|
431
|
+
hosts_count: hosts.length,
|
|
432
|
+
hosts
|
|
433
|
+
}));
|
|
434
|
+
}
|
|
435
|
+
catch (error) {
|
|
436
|
+
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
437
|
+
res.end(JSON.stringify({ error: error.message }));
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
/**
|
|
441
|
+
* Get a snapshot of all current infrastructure metrics for saving with test results
|
|
442
|
+
*/
|
|
443
|
+
getInfraSnapshot() {
|
|
444
|
+
return this.influxService.getSnapshot();
|
|
445
|
+
}
|
|
446
|
+
/**
|
|
447
|
+
* Query infrastructure metrics for a specific test run time range
|
|
448
|
+
*/
|
|
449
|
+
async getInfraForTestRun(startTime, endTime) {
|
|
450
|
+
return this.influxService.queryMetricsByTestRun('', startTime, endTime);
|
|
451
|
+
}
|
|
452
|
+
/**
|
|
453
|
+
* Get list of test runs stored in InfluxDB
|
|
454
|
+
*/
|
|
455
|
+
async handleGetTestRuns(res) {
|
|
456
|
+
try {
|
|
457
|
+
if (!this.testMetricsWriter?.isEnabled()) {
|
|
458
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
459
|
+
res.end(JSON.stringify({ enabled: false, runs: [] }));
|
|
460
|
+
return;
|
|
461
|
+
}
|
|
462
|
+
const runs = await this.testMetricsWriter.getTestRuns(100);
|
|
463
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
464
|
+
res.end(JSON.stringify({
|
|
465
|
+
enabled: true,
|
|
466
|
+
runs
|
|
467
|
+
}));
|
|
468
|
+
}
|
|
469
|
+
catch (error) {
|
|
470
|
+
logger_1.logger.error('Failed to get test runs:', error.message);
|
|
471
|
+
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
472
|
+
res.end(JSON.stringify({ error: error.message }));
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
/**
|
|
476
|
+
* Query test results from InfluxDB for a specific test run
|
|
477
|
+
*/
|
|
478
|
+
async handleGetTestMetrics(res, url) {
|
|
479
|
+
try {
|
|
480
|
+
if (!this.testMetricsWriter?.isEnabled()) {
|
|
481
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
482
|
+
res.end(JSON.stringify({ enabled: false, results: [], networkCalls: [] }));
|
|
483
|
+
return;
|
|
484
|
+
}
|
|
485
|
+
const testId = url.searchParams.get('testId') || undefined;
|
|
486
|
+
const testName = url.searchParams.get('testName') || undefined;
|
|
487
|
+
const startTime = url.searchParams.get('start') ? new Date(url.searchParams.get('start')) : undefined;
|
|
488
|
+
const endTime = url.searchParams.get('end') ? new Date(url.searchParams.get('end')) : undefined;
|
|
489
|
+
const limit = url.searchParams.get('limit') ? parseInt(url.searchParams.get('limit')) : undefined;
|
|
490
|
+
const results = await this.testMetricsWriter.queryResults({
|
|
491
|
+
testId,
|
|
492
|
+
testName,
|
|
493
|
+
startTime,
|
|
494
|
+
endTime,
|
|
495
|
+
limit
|
|
496
|
+
});
|
|
497
|
+
const networkCalls = await this.testMetricsWriter.queryNetworkCalls({
|
|
498
|
+
testId,
|
|
499
|
+
testName,
|
|
500
|
+
startTime,
|
|
501
|
+
endTime,
|
|
502
|
+
limit
|
|
503
|
+
});
|
|
504
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
505
|
+
res.end(JSON.stringify({
|
|
506
|
+
enabled: true,
|
|
507
|
+
results,
|
|
508
|
+
networkCalls
|
|
509
|
+
}));
|
|
510
|
+
}
|
|
511
|
+
catch (error) {
|
|
512
|
+
logger_1.logger.error('Failed to get test metrics:', error.message);
|
|
513
|
+
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
514
|
+
res.end(JSON.stringify({ error: error.message }));
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
/**
|
|
518
|
+
* Export test data from InfluxDB
|
|
519
|
+
*/
|
|
520
|
+
async handleExportTestData(res, url) {
|
|
521
|
+
try {
|
|
522
|
+
if (!this.testMetricsWriter?.isEnabled()) {
|
|
523
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
524
|
+
res.end(JSON.stringify({ error: 'InfluxDB not enabled' }));
|
|
525
|
+
return;
|
|
526
|
+
}
|
|
527
|
+
const testId = url.searchParams.get('testId');
|
|
528
|
+
const format = (url.searchParams.get('format') || 'json');
|
|
529
|
+
if (!testId) {
|
|
530
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
531
|
+
res.end(JSON.stringify({ error: 'testId parameter required' }));
|
|
532
|
+
return;
|
|
533
|
+
}
|
|
534
|
+
const data = await this.testMetricsWriter.exportTestData(testId, format);
|
|
535
|
+
const contentType = format === 'csv' ? 'text/csv' : 'application/json';
|
|
536
|
+
const filename = `test-data-${testId}.${format}`;
|
|
537
|
+
res.writeHead(200, {
|
|
538
|
+
'Content-Type': contentType,
|
|
539
|
+
'Content-Disposition': `attachment; filename="${filename}"`
|
|
540
|
+
});
|
|
541
|
+
res.end(data);
|
|
542
|
+
}
|
|
543
|
+
catch (error) {
|
|
544
|
+
logger_1.logger.error('Failed to export test data:', error.message);
|
|
545
|
+
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
546
|
+
res.end(JSON.stringify({ error: error.message }));
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
/**
|
|
550
|
+
* Get InfluxDB status for test metrics
|
|
551
|
+
*/
|
|
552
|
+
async handleGetTestMetricsStatus(res) {
|
|
553
|
+
const enabled = this.testMetricsWriter?.isEnabled() || false;
|
|
554
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
555
|
+
res.end(JSON.stringify({
|
|
556
|
+
influxdb_enabled: enabled,
|
|
557
|
+
message: enabled ? 'Test metrics storage active' : 'Test metrics stored in files only'
|
|
558
|
+
}));
|
|
559
|
+
}
|
|
560
|
+
async readBody(req) {
|
|
561
|
+
return new Promise((resolve, reject) => {
|
|
562
|
+
let body = '';
|
|
563
|
+
req.on('data', chunk => body += chunk);
|
|
564
|
+
req.on('end', () => resolve(body));
|
|
565
|
+
req.on('error', reject);
|
|
566
|
+
});
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
exports.ApiRoutes = ApiRoutes;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.StaticRoutes = exports.ApiRoutes = void 0;
|
|
4
|
+
var api_1 = require("./api");
|
|
5
|
+
Object.defineProperty(exports, "ApiRoutes", { enumerable: true, get: function () { return api_1.ApiRoutes; } });
|
|
6
|
+
var static_1 = require("./static");
|
|
7
|
+
Object.defineProperty(exports, "StaticRoutes", { enumerable: true, get: function () { return static_1.StaticRoutes; } });
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.StaticRoutes = void 0;
|
|
37
|
+
const fs = __importStar(require("fs/promises"));
|
|
38
|
+
const path = __importStar(require("path"));
|
|
39
|
+
const logger_1 = require("../../utils/logger");
|
|
40
|
+
class StaticRoutes {
|
|
41
|
+
constructor() {
|
|
42
|
+
this.templatesDir = path.join(__dirname, '../templates');
|
|
43
|
+
}
|
|
44
|
+
async serve(req, res, pathname) {
|
|
45
|
+
try {
|
|
46
|
+
let filePath;
|
|
47
|
+
let contentType;
|
|
48
|
+
if (pathname === '/' || pathname === '/index.html') {
|
|
49
|
+
filePath = path.join(this.templatesDir, 'index.html');
|
|
50
|
+
contentType = 'text/html';
|
|
51
|
+
}
|
|
52
|
+
else if (pathname === '/styles.css') {
|
|
53
|
+
filePath = path.join(this.templatesDir, 'styles.css');
|
|
54
|
+
contentType = 'text/css';
|
|
55
|
+
}
|
|
56
|
+
else if (pathname.startsWith('/scripts/')) {
|
|
57
|
+
filePath = path.join(this.templatesDir, pathname);
|
|
58
|
+
contentType = 'application/javascript';
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
res.writeHead(404, { 'Content-Type': 'text/plain' });
|
|
62
|
+
res.end('Not found');
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
const content = await fs.readFile(filePath, 'utf-8');
|
|
66
|
+
res.writeHead(200, { 'Content-Type': contentType });
|
|
67
|
+
res.end(content);
|
|
68
|
+
}
|
|
69
|
+
catch (error) {
|
|
70
|
+
logger_1.logger.error(`Failed to serve static file ${pathname}:`, error);
|
|
71
|
+
res.writeHead(404, { 'Content-Type': 'text/plain' });
|
|
72
|
+
res.end('Not found');
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
exports.StaticRoutes = StaticRoutes;
|