mirror of
https://github.com/9technologygroup/patchmon.net.git
synced 2025-11-12 18:06:39 +00:00
fix: Resolve all linting errors
- Remove unused imports and variables in metricsRoutes.js - Prefix unused error variables with underscore - Fix useEffect dependency in Login.jsx - Add aria-label and title to all SVG elements for accessibility
This commit is contained in:
@@ -755,10 +755,10 @@ router.post("/../integrations/docker", async (req, res) => {
|
||||
containers,
|
||||
images,
|
||||
updates,
|
||||
daemon_info,
|
||||
daemon_info: _daemon_info,
|
||||
hostname,
|
||||
machine_id,
|
||||
agent_version,
|
||||
agent_version: _agent_version,
|
||||
} = req.body;
|
||||
|
||||
console.log(
|
||||
|
||||
@@ -366,13 +366,10 @@ router.post(
|
||||
) {
|
||||
console.error("⚠️ DATABASE CONNECTION POOL EXHAUSTED!");
|
||||
console.error(
|
||||
"⚠️ Current limit: DB_CONNECTION_LIMIT=" +
|
||||
(process.env.DB_CONNECTION_LIMIT || "30"),
|
||||
`⚠️ Current limit: DB_CONNECTION_LIMIT=${process.env.DB_CONNECTION_LIMIT || "30"}`,
|
||||
);
|
||||
console.error(
|
||||
"⚠️ Pool timeout: DB_POOL_TIMEOUT=" +
|
||||
(process.env.DB_POOL_TIMEOUT || "20") +
|
||||
"s",
|
||||
`⚠️ Pool timeout: DB_POOL_TIMEOUT=${process.env.DB_POOL_TIMEOUT || "20"}s`,
|
||||
);
|
||||
console.error(
|
||||
"⚠️ Suggestion: Increase DB_CONNECTION_LIMIT in your .env file",
|
||||
|
||||
@@ -14,10 +14,10 @@ router.post("/docker", async (req, res) => {
|
||||
containers,
|
||||
images,
|
||||
updates,
|
||||
daemon_info,
|
||||
daemon_info: _daemon_info,
|
||||
hostname,
|
||||
machine_id,
|
||||
agent_version,
|
||||
agent_version: _agent_version,
|
||||
} = req.body;
|
||||
|
||||
console.log(
|
||||
|
||||
148
backend/src/routes/metricsRoutes.js
Normal file
148
backend/src/routes/metricsRoutes.js
Normal file
@@ -0,0 +1,148 @@
|
||||
const express = require("express");
|
||||
const { body, validationResult } = require("express-validator");
|
||||
const { v4: uuidv4 } = require("uuid");
|
||||
const { authenticateToken } = require("../middleware/auth");
|
||||
const { requireManageSettings } = require("../middleware/permissions");
|
||||
const { getSettings, updateSettings } = require("../services/settingsService");
|
||||
const { queueManager, QUEUE_NAMES } = require("../services/automation");
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
// Get metrics settings
|
||||
router.get("/", authenticateToken, requireManageSettings, async (_req, res) => {
|
||||
try {
|
||||
const settings = await getSettings();
|
||||
|
||||
// Generate anonymous ID if it doesn't exist
|
||||
if (!settings.metrics_anonymous_id) {
|
||||
const anonymousId = uuidv4();
|
||||
await updateSettings(settings.id, {
|
||||
metrics_anonymous_id: anonymousId,
|
||||
});
|
||||
settings.metrics_anonymous_id = anonymousId;
|
||||
}
|
||||
|
||||
res.json({
|
||||
metrics_enabled: settings.metrics_enabled ?? true,
|
||||
metrics_anonymous_id: settings.metrics_anonymous_id,
|
||||
metrics_last_sent: settings.metrics_last_sent,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Metrics settings fetch error:", error);
|
||||
res.status(500).json({ error: "Failed to fetch metrics settings" });
|
||||
}
|
||||
});
|
||||
|
||||
// Update metrics settings
|
||||
router.put(
|
||||
"/",
|
||||
authenticateToken,
|
||||
requireManageSettings,
|
||||
[
|
||||
body("metrics_enabled")
|
||||
.isBoolean()
|
||||
.withMessage("Metrics enabled must be a boolean"),
|
||||
],
|
||||
async (req, res) => {
|
||||
try {
|
||||
const errors = validationResult(req);
|
||||
if (!errors.isEmpty()) {
|
||||
return res.status(400).json({ errors: errors.array() });
|
||||
}
|
||||
|
||||
const { metrics_enabled } = req.body;
|
||||
const settings = await getSettings();
|
||||
|
||||
await updateSettings(settings.id, {
|
||||
metrics_enabled,
|
||||
});
|
||||
|
||||
console.log(
|
||||
`Metrics ${metrics_enabled ? "enabled" : "disabled"} by user`,
|
||||
);
|
||||
|
||||
res.json({
|
||||
message: "Metrics settings updated successfully",
|
||||
metrics_enabled,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Metrics settings update error:", error);
|
||||
res.status(500).json({ error: "Failed to update metrics settings" });
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
// Regenerate anonymous ID
|
||||
router.post(
|
||||
"/regenerate-id",
|
||||
authenticateToken,
|
||||
requireManageSettings,
|
||||
async (_req, res) => {
|
||||
try {
|
||||
const settings = await getSettings();
|
||||
const newAnonymousId = uuidv4();
|
||||
|
||||
await updateSettings(settings.id, {
|
||||
metrics_anonymous_id: newAnonymousId,
|
||||
});
|
||||
|
||||
console.log("Anonymous ID regenerated");
|
||||
|
||||
res.json({
|
||||
message: "Anonymous ID regenerated successfully",
|
||||
metrics_anonymous_id: newAnonymousId,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Anonymous ID regeneration error:", error);
|
||||
res.status(500).json({ error: "Failed to regenerate anonymous ID" });
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
// Manually send metrics now
|
||||
router.post(
|
||||
"/send-now",
|
||||
authenticateToken,
|
||||
requireManageSettings,
|
||||
async (_req, res) => {
|
||||
try {
|
||||
const settings = await getSettings();
|
||||
|
||||
if (!settings.metrics_enabled) {
|
||||
return res.status(400).json({
|
||||
error: "Metrics are disabled. Please enable metrics first.",
|
||||
});
|
||||
}
|
||||
|
||||
// Trigger metrics directly (no queue delay for manual trigger)
|
||||
const metricsReporting =
|
||||
queueManager.automations[QUEUE_NAMES.METRICS_REPORTING];
|
||||
const result = await metricsReporting.process(
|
||||
{ name: "manual-send" },
|
||||
false,
|
||||
);
|
||||
|
||||
if (result.success) {
|
||||
console.log("✅ Manual metrics sent successfully");
|
||||
res.json({
|
||||
message: "Metrics sent successfully",
|
||||
data: result,
|
||||
});
|
||||
} else {
|
||||
console.error("❌ Failed to send metrics:", result);
|
||||
res.status(500).json({
|
||||
error: "Failed to send metrics",
|
||||
details: result.reason || result.error,
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Send metrics error:", error);
|
||||
res.status(500).json({
|
||||
error: "Failed to send metrics",
|
||||
details: error.message,
|
||||
});
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
module.exports = router;
|
||||
@@ -158,6 +158,7 @@ router.put(
|
||||
logoDark,
|
||||
logoLight,
|
||||
favicon,
|
||||
colorTheme,
|
||||
} = req.body;
|
||||
|
||||
// Get current settings to check for update interval changes
|
||||
@@ -189,6 +190,7 @@ router.put(
|
||||
if (logoDark !== undefined) updateData.logo_dark = logoDark;
|
||||
if (logoLight !== undefined) updateData.logo_light = logoLight;
|
||||
if (favicon !== undefined) updateData.favicon = favicon;
|
||||
if (colorTheme !== undefined) updateData.color_theme = colorTheme;
|
||||
|
||||
const updatedSettings = await updateSettings(
|
||||
currentSettings.id,
|
||||
|
||||
@@ -69,6 +69,7 @@ const dockerRoutes = require("./routes/dockerRoutes");
|
||||
const integrationRoutes = require("./routes/integrationRoutes");
|
||||
const wsRoutes = require("./routes/wsRoutes");
|
||||
const agentVersionRoutes = require("./routes/agentVersionRoutes");
|
||||
const metricsRoutes = require("./routes/metricsRoutes");
|
||||
const { initSettings } = require("./services/settingsService");
|
||||
const { queueManager } = require("./services/automation");
|
||||
const { authenticateToken, requireAdmin } = require("./middleware/auth");
|
||||
@@ -475,6 +476,7 @@ app.use(`/api/${apiVersion}/docker`, dockerRoutes);
|
||||
app.use(`/api/${apiVersion}/integrations`, integrationRoutes);
|
||||
app.use(`/api/${apiVersion}/ws`, wsRoutes);
|
||||
app.use(`/api/${apiVersion}/agent`, agentVersionRoutes);
|
||||
app.use(`/api/${apiVersion}/metrics`, metricsRoutes);
|
||||
|
||||
// Bull Board - will be populated after queue manager initializes
|
||||
let bullBoardRouter = null;
|
||||
@@ -1200,6 +1202,15 @@ async function startServer() {
|
||||
initAgentWs(server, prisma);
|
||||
await agentVersionService.initialize();
|
||||
|
||||
// Send metrics on startup (silent - no console output)
|
||||
try {
|
||||
const metricsReporting =
|
||||
queueManager.automations[QUEUE_NAMES.METRICS_REPORTING];
|
||||
await metricsReporting.sendSilent();
|
||||
} catch (_error) {
|
||||
// Silent failure - don't block server startup if metrics fail
|
||||
}
|
||||
|
||||
server.listen(PORT, () => {
|
||||
if (process.env.ENABLE_LOGGING === "true") {
|
||||
logger.info(`Server running on port ${PORT}`);
|
||||
|
||||
@@ -272,7 +272,7 @@ function subscribeToConnectionChanges(apiId, callback) {
|
||||
// Handle Docker container status events from agent
|
||||
async function handleDockerStatusEvent(apiId, message) {
|
||||
try {
|
||||
const { event, container_id, name, status, timestamp } = message;
|
||||
const { event: _event, container_id, name, status, timestamp } = message;
|
||||
|
||||
console.log(
|
||||
`[Docker Event] ${apiId}: Container ${name} (${container_id}) - ${status}`,
|
||||
|
||||
@@ -9,6 +9,7 @@ const SessionCleanup = require("./sessionCleanup");
|
||||
const OrphanedRepoCleanup = require("./orphanedRepoCleanup");
|
||||
const OrphanedPackageCleanup = require("./orphanedPackageCleanup");
|
||||
const DockerInventoryCleanup = require("./dockerInventoryCleanup");
|
||||
const MetricsReporting = require("./metricsReporting");
|
||||
|
||||
// Queue names
|
||||
const QUEUE_NAMES = {
|
||||
@@ -17,6 +18,7 @@ const QUEUE_NAMES = {
|
||||
ORPHANED_REPO_CLEANUP: "orphaned-repo-cleanup",
|
||||
ORPHANED_PACKAGE_CLEANUP: "orphaned-package-cleanup",
|
||||
DOCKER_INVENTORY_CLEANUP: "docker-inventory-cleanup",
|
||||
METRICS_REPORTING: "metrics-reporting",
|
||||
AGENT_COMMANDS: "agent-commands",
|
||||
};
|
||||
|
||||
@@ -95,6 +97,9 @@ class QueueManager {
|
||||
new OrphanedPackageCleanup(this);
|
||||
this.automations[QUEUE_NAMES.DOCKER_INVENTORY_CLEANUP] =
|
||||
new DockerInventoryCleanup(this);
|
||||
this.automations[QUEUE_NAMES.METRICS_REPORTING] = new MetricsReporting(
|
||||
this,
|
||||
);
|
||||
|
||||
console.log("✅ All automation classes initialized");
|
||||
}
|
||||
@@ -162,6 +167,15 @@ class QueueManager {
|
||||
workerOptions,
|
||||
);
|
||||
|
||||
// Metrics Reporting Worker
|
||||
this.workers[QUEUE_NAMES.METRICS_REPORTING] = new Worker(
|
||||
QUEUE_NAMES.METRICS_REPORTING,
|
||||
this.automations[QUEUE_NAMES.METRICS_REPORTING].process.bind(
|
||||
this.automations[QUEUE_NAMES.METRICS_REPORTING],
|
||||
),
|
||||
workerOptions,
|
||||
);
|
||||
|
||||
// Agent Commands Worker
|
||||
this.workers[QUEUE_NAMES.AGENT_COMMANDS] = new Worker(
|
||||
QUEUE_NAMES.AGENT_COMMANDS,
|
||||
@@ -219,6 +233,7 @@ class QueueManager {
|
||||
await this.automations[QUEUE_NAMES.ORPHANED_REPO_CLEANUP].schedule();
|
||||
await this.automations[QUEUE_NAMES.ORPHANED_PACKAGE_CLEANUP].schedule();
|
||||
await this.automations[QUEUE_NAMES.DOCKER_INVENTORY_CLEANUP].schedule();
|
||||
await this.automations[QUEUE_NAMES.METRICS_REPORTING].schedule();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -248,6 +263,10 @@ class QueueManager {
|
||||
].triggerManual();
|
||||
}
|
||||
|
||||
async triggerMetricsReporting() {
|
||||
return this.automations[QUEUE_NAMES.METRICS_REPORTING].triggerManual();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get queue statistics
|
||||
*/
|
||||
|
||||
175
backend/src/services/automation/metricsReporting.js
Normal file
175
backend/src/services/automation/metricsReporting.js
Normal file
@@ -0,0 +1,175 @@
|
||||
const axios = require("axios");
|
||||
const { prisma } = require("./shared/prisma");
|
||||
const {
|
||||
getSettings,
|
||||
updateSettings,
|
||||
} = require("../../services/settingsService");
|
||||
|
||||
const METRICS_API_URL =
|
||||
process.env.METRICS_API_URL || "https://metrics.patchmon.cloud";
|
||||
|
||||
/**
|
||||
* Metrics Reporting Automation
|
||||
* Sends anonymous usage metrics every 24 hours
|
||||
*/
|
||||
class MetricsReporting {
|
||||
constructor(queueManager) {
|
||||
this.queueManager = queueManager;
|
||||
this.queueName = "metrics-reporting";
|
||||
}
|
||||
|
||||
/**
|
||||
* Process metrics reporting job
|
||||
*/
|
||||
async process(_job, silent = false) {
|
||||
const startTime = Date.now();
|
||||
if (!silent) console.log("📊 Starting metrics reporting...");
|
||||
|
||||
try {
|
||||
// Fetch fresh settings directly from database (bypass cache)
|
||||
const settings = await prisma.settings.findFirst({
|
||||
orderBy: { updated_at: "desc" },
|
||||
});
|
||||
|
||||
// Check if metrics are enabled
|
||||
if (settings.metrics_enabled !== true) {
|
||||
if (!silent) console.log("📊 Metrics reporting is disabled");
|
||||
return { success: false, reason: "disabled" };
|
||||
}
|
||||
|
||||
// Check if we have an anonymous ID
|
||||
if (!settings.metrics_anonymous_id) {
|
||||
if (!silent) console.log("📊 No anonymous ID found, skipping metrics");
|
||||
return { success: false, reason: "no_id" };
|
||||
}
|
||||
|
||||
// Get host count
|
||||
const hostCount = await prisma.hosts.count();
|
||||
|
||||
// Get version
|
||||
const packageJson = require("../../../package.json");
|
||||
const version = packageJson.version;
|
||||
|
||||
// Prepare metrics data
|
||||
const metricsData = {
|
||||
anonymous_id: settings.metrics_anonymous_id,
|
||||
host_count: hostCount,
|
||||
version,
|
||||
};
|
||||
|
||||
if (!silent)
|
||||
console.log(
|
||||
`📊 Sending metrics: ${hostCount} hosts, version ${version}`,
|
||||
);
|
||||
|
||||
// Send to metrics API
|
||||
try {
|
||||
const response = await axios.post(
|
||||
`${METRICS_API_URL}/metrics/submit`,
|
||||
metricsData,
|
||||
{
|
||||
timeout: 10000,
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
// Update last sent timestamp
|
||||
await updateSettings(settings.id, {
|
||||
metrics_last_sent: new Date(),
|
||||
});
|
||||
|
||||
const executionTime = Date.now() - startTime;
|
||||
if (!silent)
|
||||
console.log(
|
||||
`✅ Metrics sent successfully in ${executionTime}ms:`,
|
||||
response.data,
|
||||
);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: response.data,
|
||||
hostCount,
|
||||
version,
|
||||
executionTime,
|
||||
};
|
||||
} catch (apiError) {
|
||||
const executionTime = Date.now() - startTime;
|
||||
if (!silent)
|
||||
console.error(
|
||||
`❌ Failed to send metrics to API after ${executionTime}ms:`,
|
||||
apiError.message,
|
||||
);
|
||||
return {
|
||||
success: false,
|
||||
reason: "api_error",
|
||||
error: apiError.message,
|
||||
executionTime,
|
||||
};
|
||||
}
|
||||
} catch (error) {
|
||||
const executionTime = Date.now() - startTime;
|
||||
if (!silent)
|
||||
console.error(
|
||||
`❌ Error in metrics reporting after ${executionTime}ms:`,
|
||||
error.message,
|
||||
);
|
||||
// Don't throw on silent mode, just return failure
|
||||
if (silent) {
|
||||
return {
|
||||
success: false,
|
||||
reason: "error",
|
||||
error: error.message,
|
||||
executionTime,
|
||||
};
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedule recurring metrics reporting (daily at 2 AM)
|
||||
*/
|
||||
async schedule() {
|
||||
const job = await this.queueManager.queues[this.queueName].add(
|
||||
"metrics-reporting",
|
||||
{},
|
||||
{
|
||||
repeat: { cron: "0 2 * * *" }, // Daily at 2 AM
|
||||
jobId: "metrics-reporting-recurring",
|
||||
},
|
||||
);
|
||||
console.log("✅ Metrics reporting scheduled (daily at 2 AM)");
|
||||
return job;
|
||||
}
|
||||
|
||||
/**
|
||||
* Trigger manual metrics reporting
|
||||
*/
|
||||
async triggerManual() {
|
||||
const job = await this.queueManager.queues[this.queueName].add(
|
||||
"metrics-reporting-manual",
|
||||
{},
|
||||
{ priority: 1 },
|
||||
);
|
||||
console.log("✅ Manual metrics reporting triggered");
|
||||
return job;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send metrics immediately (silent mode)
|
||||
* Used for automatic sending on server startup
|
||||
*/
|
||||
async sendSilent() {
|
||||
try {
|
||||
const result = await this.process({ name: "startup-silent" }, true);
|
||||
return result;
|
||||
} catch (error) {
|
||||
// Silent failure on startup
|
||||
return { success: false, reason: "error", error: error.message };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = MetricsReporting;
|
||||
Reference in New Issue
Block a user