fix: host imports running a defaults pass and metrics restart per host (#1384)

This commit is contained in:
LukeGus committed 2026-10-03 15:37:07 -05:00
1 parent 5e31642379
commit 11a22d30eb
6 files changed
+76 -10

No files matched your search

+6 -4
View File
@@ -1,7 +1,7 @@
import { getErrorMessage } from "../utils/error-message.js";
import { sshOptionsForWrite } from "../hosts/ssh-options.js";
import {
applyDefaultsAfterHostWrite,
applyDefaultsAfterHostWrites,
applyHostDefaultsToWrite,
} from "../hosts/defaults/index.js";
import { recompute } from "../hosts/defaults/recompute.js";
@@ -1048,7 +1048,7 @@ app.post(
"/database/import",
authenticateJWT,
upload.single("file"),
async (req, res) => {
DatabaseSaveTrigger.batched(async (req, res) => {
try {
if (!req.file) {
return res.status(400).json({ error: "No file uploaded" });
@@ -1150,6 +1150,7 @@ app.post(
const importedHosts = importDb
.prepare("SELECT * FROM ssh_data")
.all();
const importedHostIds: number[] = [];
for (const host of importedHosts) {
try {
const hostRepository = createCurrentHostRepository();
@@ -1224,7 +1225,7 @@ app.post(
Number(created.id),
importedHostPluginSettings(importDb, host),
);
await applyDefaultsAfterHostWrite(Number(created.id));
importedHostIds.push(Number(created.id));
await importHostProtocolLogins(
importDb,
host,
@@ -1238,6 +1239,7 @@ app.post(
);
}
}
await applyDefaultsAfterHostWrites(importedHostIds);
} catch {
apiLogger.info("ssh_data table not found in import file, skipping");
}
@@ -1402,7 +1404,7 @@ app.post(
details: getErrorMessage(error),
});
}
},
}),
);
/**
@@ -4,7 +4,7 @@ import {
} from "./host-import-order.js";
import { sshOptionsForWrite } from "../../hosts/ssh-options.js";
import {
applyDefaultsAfterHostWrite,
applyDefaultsAfterHostWrites,
applyHostDefaultsToWrite,
} from "../../hosts/defaults/index.js";
import {
@@ -576,6 +576,7 @@ export function registerHostBulkRoutes(
}
const knownAuthTypes = listKnownAuthTypes();
const writtenHostIds: number[] = [];
for (const { host: hostData, index: i, exportId } of orderedHosts) {
try {
const effectiveConnectionType = hostData.connectionType || "ssh";
@@ -811,13 +812,15 @@ export function registerHostBulkRoutes(
savedHostId,
hostData as Record<string, unknown>,
);
await applyDefaultsAfterHostWrite(savedHostId);
writtenHostIds.push(savedHostId);
} catch (error) {
results.failed++;
results.errors.push(`Host ${i + 1}: ${getErrorMessage(error)}`);
}
}
await applyDefaultsAfterHostWrites(writtenHostIds);
res.json({
message: `Import completed: ${results.success} created, ${results.updated} updated, ${results.failed} failed`,
success: results.success,
@@ -935,6 +938,7 @@ export function registerHostBulkRoutes(
}
}
const writtenHostIds: number[] = [];
for (let i = 0; i < hostsToImport.length; i++) {
const hostData = normalizeImportedHost(
hostsToImport[i] as Record<string, unknown>,
@@ -1002,7 +1006,7 @@ export function registerHostBulkRoutes(
existing.id,
sshDataObj,
);
await applyDefaultsAfterHostWrite(existing.id);
writtenHostIds.push(existing.id);
results.updated++;
} else {
sshDataObj.createdAt = new Date().toISOString();
@@ -1010,7 +1014,7 @@ export function registerHostBulkRoutes(
userId,
sshDataObj,
);
await applyDefaultsAfterHostWrite(saved.id);
writtenHostIds.push(saved.id);
results.success++;
}
} catch (error) {
@@ -1021,6 +1025,8 @@ export function registerHostBulkRoutes(
}
}
await applyDefaultsAfterHostWrites(writtenHostIds);
res.json({
message: `Import completed: ${results.success} created, ${results.updated} updated, ${results.failed} failed`,
success: results.success,
+19
View File
@@ -25,3 +25,22 @@ export async function applyDefaultsAfterHostWrite(
});
}
}
/**
* The same for many hosts written in one go (an import), in a single pass
* instead of one per host.
*/
export async function applyDefaultsAfterHostWrites(
hostIds: number[],
): Promise<void> {
if (hostIds.length === 0) return;
try {
await recompute({ hostIds });
} catch (error) {
databaseLogger.warn("Could not apply host defaults after a write", {
operation: "host_defaults_after_write",
hosts: hostIds.length,
error: error instanceof Error ? error.message : String(error),
});
}
}
@@ -6,6 +6,7 @@ const mocks = vi.hoisted(() => ({
update: vi.fn(),
list: vi.fn(),
applyPluginSettings: vi.fn(),
applyDefaults: vi.fn(async () => {}),
}));
vi.mock("../../../database/routes/host-plugin-settings.js", () => ({
applyPluginHostImportSettings: mocks.applyPluginSettings,
@@ -14,6 +15,7 @@ vi.mock("../../../database/routes/host-plugin-settings.js", () => ({
vi.mock("../../../hosts/defaults/index.js", () => ({
applyHostDefaultsToWrite: async () => ({ core: [] }),
applyDefaultsAfterHostWrite: async () => {},
applyDefaultsAfterHostWrites: mocks.applyDefaults,
}));
vi.mock("../../../hosts/defaults/overrides.js", () => ({
changeHostOverrides: async () => {},
@@ -141,6 +143,9 @@ it.each([20, "20"])(
]);
expect(mocks.create.mock.calls[1][1]).not.toHaveProperty("exportId");
expect(mocks.create.mock.calls[1][1]).not.toHaveProperty("id");
// One defaults pass for the whole import, not one per host.
expect(mocks.applyDefaults).toHaveBeenCalledTimes(1);
expect(mocks.applyDefaults).toHaveBeenCalledWith([501, 502]);
},
);