Reduce output of config validation errors

This commit is contained in:
shamoon
2026-06-02 20:59:58 -07:00
parent 91f3b9df5c
commit cbb5667512
4 changed files with 41 additions and 12 deletions
+21 -3
View File
@@ -2,12 +2,15 @@ import { beforeEach, describe, expect, it, vi } from "vitest";
import createMockRes from "test-utils/create-mock-res"; import createMockRes from "test-utils/create-mock-res";
const { checkAndCopyConfig } = vi.hoisted(() => ({ const { checkAndCopyConfig, getSettings } = vi.hoisted(() => ({
checkAndCopyConfig: vi.fn(), checkAndCopyConfig: vi.fn(),
getSettings: vi.fn(() => ({})),
})); }));
vi.mock("utils/config/config", () => ({ vi.mock("utils/config/config", () => ({
default: checkAndCopyConfig, default: checkAndCopyConfig,
getSettings,
CONF_DIR: "/tmp",
})); }));
import handler from "pages/api/validate"; import handler from "pages/api/validate";
@@ -18,13 +21,28 @@ describe("pages/api/validate", () => {
}); });
it("returns errors for any configs that don't validate", async () => { it("returns errors for any configs that don't validate", async () => {
checkAndCopyConfig.mockReturnValueOnce(true).mockReturnValueOnce("settings bad").mockReturnValue(true); checkAndCopyConfig
.mockReturnValueOnce(true)
.mockReturnValueOnce({
name: "YAMLException",
config: "settings.yaml",
reason: "settings bad",
mark: { line: 1 },
})
.mockReturnValue(true);
const req = {}; const req = {};
const res = createMockRes(); const res = createMockRes();
await handler(req, res); await handler(req, res);
expect(res.body).toEqual(["settings bad"]); expect(res.body).toEqual([
{
name: "YAMLException",
config: "settings.yaml",
reason: "settings bad",
mark: { line: 1 },
},
]);
}); });
}); });
+3 -4
View File
@@ -324,13 +324,12 @@ describe("pages/index Index routing + SWR branches", () => {
}); });
it("renders config errors when /api/validate returns a list of errors", async () => { it("renders config errors when /api/validate returns a list of errors", async () => {
state.validateData = [{ config: "services.yaml", reason: "broken", mark: { snippet: "x: y" } }]; state.validateData = [{ config: "services.yaml", name: "Service 1", reason: "broken", mark: { line: 4 } }];
await renderIndex({ initialSettings: { title: "Homepage", layout: {} }, settings: { layout: {} } }); await renderIndex({ initialSettings: { title: "Homepage", layout: {} }, settings: { layout: {} } });
expect(screen.getByText("services.yaml")).toBeInTheDocument(); expect(screen.getByText(/services.yaml/)).toBeInTheDocument();
expect(screen.getByText("broken")).toBeInTheDocument(); expect(screen.getByText(/line 4/)).toBeInTheDocument();
expect(screen.getByText("x: y")).toBeInTheDocument();
}); });
it("marks the UI stale when the hash changes and triggers a revalidate reload", async () => { it("marks the UI stale when the hash changes and triggers a revalidate reload", async () => {
+12 -2
View File
@@ -1,9 +1,19 @@
import checkAndCopyConfig from "utils/config/config"; import checkAndCopyConfig from "utils/config/config";
import createLogger from "utils/logger";
const configs = ["docker.yaml", "settings.yaml", "services.yaml", "bookmarks.yaml", "kubernetes.yaml", "proxmox.yaml"]; const configs = ["docker.yaml", "settings.yaml", "services.yaml", "bookmarks.yaml", "kubernetes.yaml", "proxmox.yaml"];
const logger = createLogger("configValidationHandler");
export default async function handler(req, res) { export default async function handler(req, res) {
const errors = configs.map((config) => checkAndCopyConfig(config)).filter((status) => status !== true); let errors = configs.map((config) => checkAndCopyConfig(config)).filter((status) => status !== true);
if (errors.length > 0) {
logger.error("Configuration validation errors", errors);
errors = errors.map((error) => ({
name: error.name,
config: error.config,
reason: error.reason,
mark: { line: error.mark?.line },
}));
}
res.send(errors); res.send(errors);
} }
+5 -3
View File
@@ -167,11 +167,13 @@ function Index({ initialSettings, fallback }) {
> >
<div className="bg-amber-200 text-amber-800 dark:text-amber-200 dark:bg-amber-800 p-2 rounded-md font-bold"> <div className="bg-amber-200 text-amber-800 dark:text-amber-200 dark:bg-amber-800 p-2 rounded-md font-bold">
<BiError className="float-right w-6 h-6" /> <BiError className="float-right w-6 h-6" />
{error.config} {error.name} - {error.config}
</div> </div>
<div className="p-2 text-theme-100 dark:text-theme-200"> <div className="p-2 text-theme-100 dark:text-theme-200">
<pre className="opacity-50 font-bold pb-2">{error.reason}</pre> <pre className="opacity-50 font-bold pb-2">
<pre className="text-sm">{error.mark.snippet}</pre> Reason: "{error.reason}" at line {error.mark?.line}
</pre>
<pre className="font-italic">Check logs for details.</pre>
</div> </div>
</div> </div>
))} ))}