feat(integrations): add PatchMon integration and widget (#6119)

Co-authored-by: Thomas <thomas@ajnart.fr>
This commit is contained in:
Josh PrestonandThomas authored and GitHub committed 2026-07-10 17:48:43 +02:00
1 parent 174c5f98f8
commit 33395ae226
41 files changed
+2982 -17

No files matched your search

@@ -0,0 +1,36 @@
import { describe, expect, test } from "vitest";
import { getGridCols, shouldShowComplianceHeroText } from "../layout-utils";
describe("getGridCols", () => {
test("returns 1 column for narrow widths", () => {
expect(getGridCols(128)).toBe(1);
expect(getGridCols(219)).toBe(1);
});
test("returns 2 columns at 220px breakpoint", () => {
expect(getGridCols(220)).toBe(2);
expect(getGridCols(256)).toBe(2);
});
test("returns 3 columns at 380px breakpoint", () => {
expect(getGridCols(380)).toBe(3);
});
test("returns 4 columns at 500px breakpoint", () => {
expect(getGridCols(500)).toBe(4);
});
});
describe("shouldShowComplianceHeroText", () => {
test("returns false for 1-column-wide layouts", () => {
expect(shouldShowComplianceHeroText(128)).toBe(false);
expect(shouldShowComplianceHeroText(219)).toBe(false);
});
test("returns true for 2+-column-wide layouts", () => {
expect(shouldShowComplianceHeroText(220)).toBe(true);
expect(shouldShowComplianceHeroText(256)).toBe(true);
expect(shouldShowComplianceHeroText(384)).toBe(true);
});
});
@@ -0,0 +1,171 @@
import { describe, expect, test } from "vitest";
import type { WidgetComponentProps } from "../../definition";
import { resolveStatColor } from "../stat-colors";
const defaultOptions = {
enableThresholdColors: true,
useCustomThresholds: false,
showTotalHosts: true,
showHostsNeedingUpdates: true,
showSecurityUpdates: true,
showUpToDateHosts: false,
showHostsWithSecurityUpdates: false,
showRecentUpdates24h: false,
showTotalOutdatedPackages: false,
showTotalRepos: false,
showOsDistribution: false,
osDistributionLimit: "0",
showOsVersion: true,
hostsNeedingUpdatesThresholdMode: "absolute",
hostsNeedingUpdatesWarningAt: 1,
hostsNeedingUpdatesCriticalAt: 5,
securityUpdatesThresholdMode: "absolute",
securityUpdatesWarningAt: 1,
securityUpdatesCriticalAt: 10,
hostsWithSecurityUpdatesThresholdMode: "absolute",
hostsWithSecurityUpdatesWarningAt: 1,
hostsWithSecurityUpdatesCriticalAt: 1,
upToDateHostsThresholdMode: "percent",
upToDateHostsWarningAt: 90,
upToDateHostsCriticalAt: 90,
totalOutdatedPackagesThresholdMode: "absolute",
totalOutdatedPackagesWarningAt: 1,
totalOutdatedPackagesCriticalAt: 50,
showComplianceHero: true,
osDisplayMode: "bars",
} satisfies WidgetComponentProps<"patchmon">["options"];
const context = { totalHosts: 100 };
describe("resolveStatColor presets", () => {
test("hostsNeedingUpdates uses green, yellow, and red boundaries", () => {
expect(resolveStatColor("hostsNeedingUpdates", 0, context, defaultOptions)).toBe("green");
expect(resolveStatColor("hostsNeedingUpdates", 1, context, defaultOptions)).toBe("yellow");
expect(resolveStatColor("hostsNeedingUpdates", 4, context, defaultOptions)).toBe("yellow");
expect(resolveStatColor("hostsNeedingUpdates", 5, context, defaultOptions)).toBe("red");
});
test("securityUpdates uses green and yellow for low counts", () => {
expect(resolveStatColor("securityUpdates", 0, context, defaultOptions)).toBe("green");
expect(resolveStatColor("securityUpdates", 1, context, defaultOptions)).toBe("yellow");
expect(resolveStatColor("securityUpdates", 10, context, defaultOptions)).toBe("red");
});
test("hostsWithSecurityUpdates skips yellow", () => {
expect(resolveStatColor("hostsWithSecurityUpdates", 0, context, defaultOptions)).toBe("green");
expect(resolveStatColor("hostsWithSecurityUpdates", 1, context, defaultOptions)).toBe("red");
});
test("upToDateHosts uses percent of total hosts", () => {
expect(resolveStatColor("upToDateHosts", 100, context, defaultOptions)).toBe("green");
expect(resolveStatColor("upToDateHosts", 95, context, defaultOptions)).toBe("green");
expect(resolveStatColor("upToDateHosts", 90, context, defaultOptions)).toBe("green");
expect(resolveStatColor("upToDateHosts", 80, context, defaultOptions)).toBe("red");
});
test("neutral stats ignore threshold coloring", () => {
expect(resolveStatColor("totalHosts", 100, context, defaultOptions)).toBe("neutral");
expect(resolveStatColor("totalRepos", 12, context, defaultOptions)).toBe("neutral");
expect(resolveStatColor("recentUpdates24h", 34, context, defaultOptions)).toBe("neutral");
});
});
describe("resolveStatColor options", () => {
test("returns neutral when threshold colors are disabled", () => {
expect(
resolveStatColor("hostsNeedingUpdates", 10, context, {
...defaultOptions,
enableThresholdColors: false,
}),
).toBe("neutral");
});
test("supports custom absolute thresholds", () => {
expect(
resolveStatColor("hostsNeedingUpdates", 4, context, {
...defaultOptions,
useCustomThresholds: true,
hostsNeedingUpdatesWarningAt: 5,
hostsNeedingUpdatesCriticalAt: 10,
}),
).toBe("green");
});
test("supports custom percent thresholds for lower-is-better stats", () => {
expect(
resolveStatColor("hostsNeedingUpdates", 5, context, {
...defaultOptions,
useCustomThresholds: true,
hostsNeedingUpdatesThresholdMode: "percent",
hostsNeedingUpdatesWarningAt: 10,
hostsNeedingUpdatesCriticalAt: 20,
}),
).toBe("green");
expect(
resolveStatColor("hostsNeedingUpdates", 15, context, {
...defaultOptions,
useCustomThresholds: true,
hostsNeedingUpdatesThresholdMode: "percent",
hostsNeedingUpdatesWarningAt: 10,
hostsNeedingUpdatesCriticalAt: 20,
}),
).toBe("yellow");
});
test("handles zero total hosts for percent-based stats", () => {
expect(resolveStatColor("upToDateHosts", 0, { totalHosts: 0 }, defaultOptions)).toBe("green");
expect(
resolveStatColor("hostsNeedingUpdates", 1, { totalHosts: 0 }, {
...defaultOptions,
useCustomThresholds: true,
hostsNeedingUpdatesThresholdMode: "percent",
}),
).toBe("red");
});
test("clamps invalid percent thresholds when resolving colors", () => {
expect(
resolveStatColor("hostsNeedingUpdates", 50, context, {
...defaultOptions,
useCustomThresholds: true,
hostsNeedingUpdatesThresholdMode: "percent",
hostsNeedingUpdatesWarningAt: 40,
hostsNeedingUpdatesCriticalAt: 200,
}),
).toBe("yellow");
});
test("supports custom absolute thresholds for higher-is-better stats", () => {
expect(
resolveStatColor("upToDateHosts", 85, context, {
...defaultOptions,
useCustomThresholds: true,
upToDateHostsThresholdMode: "absolute",
upToDateHostsWarningAt: 80,
upToDateHostsCriticalAt: 60,
}),
).toBe("green");
expect(
resolveStatColor("upToDateHosts", 70, context, {
...defaultOptions,
useCustomThresholds: true,
upToDateHostsThresholdMode: "absolute",
upToDateHostsWarningAt: 80,
upToDateHostsCriticalAt: 60,
}),
).toBe("yellow");
expect(
resolveStatColor("upToDateHosts", 50, context, {
...defaultOptions,
useCustomThresholds: true,
upToDateHostsThresholdMode: "absolute",
upToDateHostsWarningAt: 80,
upToDateHostsCriticalAt: 60,
}),
).toBe("red");
});
});
@@ -0,0 +1,97 @@
import { describe, expect, test } from "vitest";
import { z } from "zod/v4";
import { clampPercentThreshold, patchmonOptionsSuperRefine } from "../threshold-validation";
const baseOptions = {
enableThresholdColors: true,
useCustomThresholds: true,
hostsNeedingUpdatesThresholdMode: "percent",
hostsNeedingUpdatesWarningAt: 10,
hostsNeedingUpdatesCriticalAt: 20,
securityUpdatesThresholdMode: "absolute",
securityUpdatesWarningAt: 1,
securityUpdatesCriticalAt: 10,
hostsWithSecurityUpdatesThresholdMode: "absolute",
hostsWithSecurityUpdatesWarningAt: 1,
hostsWithSecurityUpdatesCriticalAt: 1,
upToDateHostsThresholdMode: "percent",
upToDateHostsWarningAt: 90,
upToDateHostsCriticalAt: 90,
totalOutdatedPackagesThresholdMode: "absolute",
totalOutdatedPackagesWarningAt: 1,
totalOutdatedPackagesCriticalAt: 50,
};
const optionsSchema = z.object({
hostsNeedingUpdatesWarningAt: z.number().int().min(0),
hostsNeedingUpdatesCriticalAt: z.number().int().min(0),
securityUpdatesWarningAt: z.number().int().min(0),
securityUpdatesCriticalAt: z.number().int().min(0),
hostsWithSecurityUpdatesWarningAt: z.number().int().min(0),
hostsWithSecurityUpdatesCriticalAt: z.number().int().min(0),
upToDateHostsWarningAt: z.number().int().min(0),
upToDateHostsCriticalAt: z.number().int().min(0),
totalOutdatedPackagesWarningAt: z.number().int().min(0),
totalOutdatedPackagesCriticalAt: z.number().int().min(0),
enableThresholdColors: z.boolean(),
useCustomThresholds: z.boolean(),
hostsNeedingUpdatesThresholdMode: z.enum(["absolute", "percent"]),
securityUpdatesThresholdMode: z.enum(["absolute", "percent"]),
hostsWithSecurityUpdatesThresholdMode: z.enum(["absolute", "percent"]),
upToDateHostsThresholdMode: z.enum(["absolute", "percent"]),
totalOutdatedPackagesThresholdMode: z.enum(["absolute", "percent"]),
}).superRefine(patchmonOptionsSuperRefine);
describe("patchmonOptionsSuperRefine", () => {
test("accepts percent thresholds within 0-100", () => {
const result = optionsSchema.safeParse(baseOptions);
expect(result.success).toBe(true);
});
test("rejects percent thresholds above 100", () => {
const result = optionsSchema.safeParse({
...baseOptions,
hostsNeedingUpdatesWarningAt: 101,
});
expect(result.success).toBe(false);
if (!result.success) {
expect(result.error.issues.some((issue) => issue.path.includes("hostsNeedingUpdatesWarningAt"))).toBe(true);
}
});
test("allows absolute thresholds above 100", () => {
const result = optionsSchema.safeParse({
...baseOptions,
hostsNeedingUpdatesThresholdMode: "absolute",
hostsNeedingUpdatesWarningAt: 150,
hostsNeedingUpdatesCriticalAt: 200,
});
expect(result.success).toBe(true);
});
test("skips validation when custom thresholds are disabled", () => {
const result = optionsSchema.safeParse({
...baseOptions,
useCustomThresholds: false,
hostsNeedingUpdatesWarningAt: 500,
});
expect(result.success).toBe(true);
});
});
describe("clampPercentThreshold", () => {
test("clamps percent values to 0-100", () => {
expect(clampPercentThreshold(150, "percent")).toBe(100);
expect(clampPercentThreshold(-5, "percent")).toBe(0);
expect(clampPercentThreshold(42.9, "percent")).toBe(42);
});
test("leaves absolute values unchanged", () => {
expect(clampPercentThreshold(150, "absolute")).toBe(150);
});
});