diff --git a/server/model/status_page.js b/server/model/status_page.js index 4984ce429..fe986fee3 100644 --- a/server/model/status_page.js +++ b/server/model/status_page.js @@ -317,7 +317,7 @@ class StatusPage extends BeanModel { for (const monitor of group.monitorList) { monitors.push({ name: monitor.name, - status: monitor.status || 2 // 1=up, 0=down, 2=pending + status: monitor.status || 2, // 1=up, 0=down, 2=pending }); } } @@ -330,9 +330,8 @@ class StatusPage extends BeanModel { } // Get icon - use getIcon() method if available, otherwise use icon property - const icon = (statusPage.getIcon && typeof statusPage.getIcon === "function") - ? statusPage.getIcon() - : statusPage.icon; + const icon = + statusPage.getIcon && typeof statusPage.getIcon === "function" ? statusPage.getIcon() : statusPage.icon; // If no detailed monitor data, create array with count for display const monitorsForDisplay = monitors.length > 0 ? monitors : heartbeats.map(() => ({})); @@ -344,7 +343,7 @@ class StatusPage extends BeanModel { statusColor, icon, showPoweredBy: !!statusPage.show_powered_by, - monitors: monitorsForDisplay + monitors: monitorsForDisplay, }); } diff --git a/server/routers/status-page-router.js b/server/routers/status-page-router.js index 3b960c337..792e5abd0 100644 --- a/server/routers/status-page-router.js +++ b/server/routers/status-page-router.js @@ -245,9 +245,7 @@ router.get("/api/status-page/:slug/image", cache("5 minutes"), async (request, r try { // Get Status Page - let statusPage = await R.findOne("status_page", " slug = ? ", [ - slug - ]); + let statusPage = await R.findOne("status_page", " slug = ? ", [slug]); if (!statusPage) { sendHttpError(response, "Status Page Not Found"); @@ -261,7 +259,6 @@ router.get("/api/status-page/:slug/image", cache("5 minutes"), async (request, r response.type("image/png"); response.setHeader("Cache-Control", "public, max-age=300"); // 5 minutes response.send(pngBuffer); - } catch (error) { sendHttpError(response, error.message); } diff --git a/server/utils/og-image.js b/server/utils/og-image.js index 35199af84..35654fb84 100644 --- a/server/utils/og-image.js +++ b/server/utils/og-image.js @@ -9,7 +9,7 @@ const { MAINTENANCE, UP, DOWN, - PENDING + PENDING, } = require("../util-server"); // Image dimensions (Open Graph standard) @@ -94,10 +94,7 @@ function truncateText(text, maxLength) { * @returns {object} Status counts */ function countMonitorsByStatus(monitors) { - const counts = { up: 0, - down: 0, - pending: 0, - maintenance: 0 }; + const counts = { up: 0, down: 0, pending: 0, maintenance: 0 }; monitors.forEach((monitor) => { if (monitor.status === UP) { @@ -165,7 +162,7 @@ function generateIndividualMonitorDetails(monitors, startY) { const displayMonitors = monitors.slice(0, MAX_INDIVIDUAL_MONITORS); displayMonitors.forEach((monitor, index) => { - const y = startY + (index * 35); + const y = startY + index * 35; const statusColor = getMonitorStatusColor(monitor.status); const monitorName = truncateText(monitor.name, MAX_MONITOR_NAME_LENGTH); @@ -306,28 +303,11 @@ ${monitorDetailsSVG}${footerSVG} * @returns {Promise} PNG image buffer */ async function generateOGImage(statusPageData) { - const { - title, - statusDescription, - statusColor, - icon, - showPoweredBy, - monitors - } = statusPageData; + const { title, statusDescription, statusColor, icon, showPoweredBy, monitors } = statusPageData; - const svg = generateOGImageSVG( - title, - statusDescription, - statusColor, - icon, - !!showPoweredBy, - Date.now(), - monitors - ); + const svg = generateOGImageSVG(title, statusDescription, statusColor, icon, !!showPoweredBy, Date.now(), monitors); - const pngBuffer = await sharp(Buffer.from(svg)) - .png() - .toBuffer(); + const pngBuffer = await sharp(Buffer.from(svg)).png().toBuffer(); return pngBuffer; } diff --git a/test/backend-test/test-status-page-og-image.js b/test/backend-test/test-status-page-og-image.js index 68bafee5e..39968707e 100644 --- a/test/backend-test/test-status-page-og-image.js +++ b/test/backend-test/test-status-page-og-image.js @@ -5,12 +5,7 @@ const StatusPage = require("../../server/model/status_page"); const { generateOGImageSVG, getStatusColor, escapeXml } = require("../../server/utils/og-image"); const fs = require("fs"); const path = require("path"); -const { - STATUS_PAGE_ALL_UP, - STATUS_PAGE_PARTIAL_DOWN, - STATUS_PAGE_ALL_DOWN, - MAINTENANCE -} = require("../../src/util"); +const { STATUS_PAGE_ALL_UP, STATUS_PAGE_PARTIAL_DOWN, STATUS_PAGE_ALL_DOWN, MAINTENANCE } = require("../../src/util"); const SNAPSHOTS_DIR = path.join(__dirname, "snapshots", "og-images"); @@ -27,38 +22,38 @@ const TEST_SCENARIOS = [ name: "all-up", title: "Production", statusCode: STATUS_PAGE_ALL_UP, - monitorCount: 8 + monitorCount: 8, }, { name: "all-down", title: "Infrastructure", statusCode: STATUS_PAGE_ALL_DOWN, - monitorCount: 6 + monitorCount: 6, }, { name: "maintenance", title: "Maintenance", statusCode: MAINTENANCE, - monitorCount: 3 + monitorCount: 3, }, { name: "partial", title: "API Services", statusCode: STATUS_PAGE_PARTIAL_DOWN, - monitorCount: 12 + monitorCount: 12, }, { name: "all-up-many-monitors", title: "Enterprise", statusCode: STATUS_PAGE_ALL_UP, - monitorCount: 200 + monitorCount: 200, }, { name: "all-up-no-branding", title: "Production", statusCode: STATUS_PAGE_ALL_UP, monitorCount: 8, - showPoweredBy: false + showPoweredBy: false, }, { name: "all-up-custom-icon-no-branding", @@ -66,7 +61,7 @@ const TEST_SCENARIOS = [ statusCode: STATUS_PAGE_ALL_UP, monitorCount: 8, icon: "/icon.svg", - showPoweredBy: false + showPoweredBy: false, }, { name: "all-up-custom-icon-with-branding", @@ -74,14 +69,14 @@ const TEST_SCENARIOS = [ statusCode: STATUS_PAGE_ALL_UP, monitorCount: 8, icon: "/icon.svg", - showPoweredBy: true + showPoweredBy: true, }, { name: "all-up-long-title", title: "This is an extremely long status page title that will need to be truncated", statusCode: STATUS_PAGE_ALL_UP, - monitorCount: 5 - } + monitorCount: 5, + }, ]; /** @@ -114,7 +109,7 @@ function createMockStatusPage(overrides = {}, rssData = null) { slug: "test", title: "Test Page", description: "Test Description", - ...overrides + ...overrides, }; const originalGetRSSData = StatusPage.getRSSPageData; @@ -127,8 +122,7 @@ function createMockStatusPage(overrides = {}, rssData = null) { StatusPage.getRSSPageData = originalGetRSSData; }; - return { mockPage, - cleanup }; + return { mockPage, cleanup }; } /** @@ -141,13 +135,9 @@ async function assertValidPNG(buffer) { assert.ok(buffer.length > 0, "Expected non-empty buffer"); // Check PNG signature (first 8 bytes) - const pngSignature = Buffer.from([ 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A ]); + const pngSignature = Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]); const actualSignature = buffer.slice(0, 8); - assert.deepStrictEqual( - actualSignature, - pngSignature, - "Expected valid PNG signature" - ); + assert.deepStrictEqual(actualSignature, pngSignature, "Expected valid PNG signature"); // Use sharp to verify metadata const metadata = await sharp(buffer).metadata(); @@ -171,7 +161,7 @@ describe("OG Image Helper Functions", () => { }); test("escapeXml() replaces double quote with entity", () => { - assert.strictEqual(escapeXml("\""), """); + assert.strictEqual(escapeXml('"'), """); }); test("escapeXml() replaces single quote with entity", () => { @@ -210,71 +200,31 @@ describe("OG Image Helper Functions", () => { describe("generateOGImageSVG()", () => { test("generateOGImageSVG() starts with XML declaration", () => { - const svg = generateOGImageSVG( - "Test", - "All OK", - "#10b981", - null, - true, - FIXED_TIMESTAMP, - Array(5).fill({}) - ); + const svg = generateOGImageSVG("Test", "All OK", "#10b981", null, true, FIXED_TIMESTAMP, Array(5).fill({})); assert.ok(svg.startsWith(" { - const svg = generateOGImageSVG( - "Test", - "All OK", - "#10b981", - null, - true, - FIXED_TIMESTAMP, - Array(5).fill({}) - ); + const svg = generateOGImageSVG("Test", "All OK", "#10b981", null, true, FIXED_TIMESTAMP, Array(5).fill({})); - assert.ok(svg.includes("xmlns=\"http://www.w3.org/2000/svg\""), "Expected SVG namespace"); + assert.ok(svg.includes('xmlns="http://www.w3.org/2000/svg"'), "Expected SVG namespace"); }); test("generateOGImageSVG() sets width to 1200", () => { - const svg = generateOGImageSVG( - "Test", - "All OK", - "#10b981", - null, - true, - FIXED_TIMESTAMP, - Array(5).fill({}) - ); + const svg = generateOGImageSVG("Test", "All OK", "#10b981", null, true, FIXED_TIMESTAMP, Array(5).fill({})); - assert.ok(svg.includes("width=\"1200\""), "Expected width of 1200"); + assert.ok(svg.includes('width="1200"'), "Expected width of 1200"); }); test("generateOGImageSVG() sets height to 630", () => { - const svg = generateOGImageSVG( - "Test", - "All OK", - "#10b981", - null, - true, - FIXED_TIMESTAMP, - Array(5).fill({}) - ); + const svg = generateOGImageSVG("Test", "All OK", "#10b981", null, true, FIXED_TIMESTAMP, Array(5).fill({})); - assert.ok(svg.includes("height=\"630\""), "Expected height of 630"); + assert.ok(svg.includes('height="630"'), "Expected height of 630"); }); test("generateOGImageSVG() includes closing SVG tag", () => { - const svg = generateOGImageSVG( - "Test", - "All OK", - "#10b981", - null, - true, - FIXED_TIMESTAMP, - Array(5).fill({}) - ); + const svg = generateOGImageSVG("Test", "All OK", "#10b981", null, true, FIXED_TIMESTAMP, Array(5).fill({})); assert.ok(svg.includes(""), "Expected closing svg tag"); }); @@ -293,25 +243,21 @@ describe("generateOGImageSVG()", () => { // Create realistic monitor mix for status summary monitors = []; const upCount = Math.floor(scenario.monitorCount * 0.85); // 85% up - const downCount = Math.floor(scenario.monitorCount * 0.10); // 10% down + const downCount = Math.floor(scenario.monitorCount * 0.1); // 10% down const maintenanceCount = Math.floor(scenario.monitorCount * 0.03); // 3% maintenance const pendingCount = scenario.monitorCount - upCount - downCount - maintenanceCount; // Rest pending for (let i = 0; i < upCount; i++) { - monitors.push({ name: `Monitor ${i + 1}`, - status: 1 }); + monitors.push({ name: `Monitor ${i + 1}`, status: 1 }); } for (let i = 0; i < downCount; i++) { - monitors.push({ name: `Monitor ${upCount + i + 1}`, - status: 0 }); + monitors.push({ name: `Monitor ${upCount + i + 1}`, status: 0 }); } for (let i = 0; i < maintenanceCount; i++) { - monitors.push({ name: `Monitor ${upCount + downCount + i + 1}`, - status: 3 }); + monitors.push({ name: `Monitor ${upCount + downCount + i + 1}`, status: 3 }); } for (let i = 0; i < pendingCount; i++) { - monitors.push({ name: `Monitor ${upCount + downCount + maintenanceCount + i + 1}`, - status: 2 }); + monitors.push({ name: `Monitor ${upCount + downCount + maintenanceCount + i + 1}`, status: 2 }); } } else { monitors = Array(scenario.monitorCount).fill({}); // Show count @@ -381,10 +327,10 @@ describe("StatusPage Model", () => { {}, { heartbeats: [{ status: 1 }], - statusDescription: "All Systems Operational" + statusDescription: "All Systems Operational", } ); - + try { const buffer = await StatusPage.generateOGImage(mockPage); await assertValidPNG(buffer); @@ -394,20 +340,20 @@ describe("StatusPage Model", () => { cleanup(); } }); - + test("generateOGImage() handles status page with custom icon and no branding", async () => { const { mockPage, cleanup } = createMockStatusPage( { title: "Production", show_powered_by: false, - icon: "/icon.svg" + icon: "/icon.svg", }, { heartbeats: [{ status: 1 }, { status: 0 }], - statusDescription: "Partially Degraded Service" + statusDescription: "Partially Degraded Service", } ); - + try { const buffer = await StatusPage.generateOGImage(mockPage); await assertValidPNG(buffer); diff --git a/test/e2e/specs/status-page.spec.js b/test/e2e/specs/status-page.spec.js index 29d95f7c4..600f854bf 100644 --- a/test/e2e/specs/status-page.spec.js +++ b/test/e2e/specs/status-page.spec.js @@ -239,7 +239,7 @@ test.describe("Status Page", () => { expect(buffer.length).toBeGreaterThan(0); // Verify PNG signature - const pngSignature = Buffer.from([ 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A ]); + const pngSignature = Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]); expect(buffer.subarray(0, 8)).toEqual(pngSignature); // Test 404 for non-existent page