Initial commit: DashCaddy v1.0

Full codebase including API server (32 modules + routes), dashboard frontend,
DashCA certificate distribution, installer script, and deployment skills.
This commit is contained in:
2026-03-05 02:26:12 -08:00
commit f61e85d9a7
337 changed files with 75282 additions and 0 deletions

115
dashcaddy-api/metrics.js Normal file
View File

@@ -0,0 +1,115 @@
/**
* Simple metrics collector for DashCaddy API
* Tracks request counts, durations, errors, and business metrics
* No external dependencies — all in-memory
*/
class Metrics {
constructor() {
this.startTime = Date.now();
this.requests = {
total: 0,
byStatus: {},
byMethod: {},
byPath: {}
};
this.errors = {
total: 0,
byType: {}
};
this.business = {
containersDeployed: 0,
containersDeleted: 0,
containerUpdates: 0,
dnsRecordsCreated: 0,
backupsCreated: 0,
totpLogins: 0,
siteAdded: 0,
siteRemoved: 0,
credentialRotations: 0
};
}
recordRequest(method, path, statusCode, durationMs) {
this.requests.total++;
this.requests.byStatus[statusCode] = (this.requests.byStatus[statusCode] || 0) + 1;
this.requests.byMethod[method] = (this.requests.byMethod[method] || 0) + 1;
const normalized = this.normalizePath(path);
if (!this.requests.byPath[normalized]) {
this.requests.byPath[normalized] = { count: 0, totalDuration: 0 };
}
const entry = this.requests.byPath[normalized];
entry.count++;
entry.totalDuration += durationMs;
}
recordError(errorType) {
this.errors.total++;
this.errors.byType[errorType] = (this.errors.byType[errorType] || 0) + 1;
}
recordBusinessEvent(eventType) {
if (eventType in this.business) {
this.business[eventType]++;
}
}
normalizePath(p) {
return p
.replace(/\/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/gi, '/:id')
.replace(/\/[0-9a-f]{12,}/gi, '/:id')
.replace(/\/\d+/g, '/:n');
}
getSummary() {
const uptimeMs = Date.now() - this.startTime;
const uptimeSec = Math.floor(uptimeMs / 1000);
const topEndpoints = Object.entries(this.requests.byPath)
.sort((a, b) => b[1].count - a[1].count)
.slice(0, 15)
.map(([path, s]) => ({ path, count: s.count, avgMs: Math.round(s.totalDuration / s.count) }));
return {
uptime: { ms: uptimeMs, human: this.formatUptime(uptimeSec) },
requests: {
total: this.requests.total,
perSecond: uptimeSec > 0 ? +(this.requests.total / uptimeSec).toFixed(2) : 0,
byStatus: this.requests.byStatus,
byMethod: this.requests.byMethod,
topEndpoints
},
errors: {
total: this.errors.total,
rate: this.requests.total > 0 ? +((this.errors.total / this.requests.total) * 100).toFixed(2) : 0,
byType: this.errors.byType
},
business: this.business,
process: {
memory: process.memoryUsage(),
pid: process.pid,
nodeVersion: process.version
}
};
}
formatUptime(sec) {
const d = Math.floor(sec / 86400);
const h = Math.floor((sec % 86400) / 3600);
const m = Math.floor((sec % 3600) / 60);
const s = sec % 60;
if (d > 0) return `${d}d ${h}h ${m}m`;
if (h > 0) return `${h}h ${m}m ${s}s`;
if (m > 0) return `${m}m ${s}s`;
return `${s}s`;
}
reset() {
this.startTime = Date.now();
this.requests = { total: 0, byStatus: {}, byMethod: {}, byPath: {} };
this.errors = { total: 0, byType: {} };
}
}
module.exports = new Metrics();