mirror of
https://github.com/fosrl/pangolin.git
synced 2026-05-07 04:47:08 +00:00
173 lines
6.1 KiB
TypeScript
173 lines
6.1 KiB
TypeScript
import { APP_PATH } from "@server/lib/consts";
|
|
import Database from "better-sqlite3";
|
|
import path from "path";
|
|
|
|
const version = "1.18.3";
|
|
|
|
export default async function migration() {
|
|
console.log(`Running setup script ${version}...`);
|
|
|
|
const location = path.join(APP_PATH, "db", "db.sqlite");
|
|
const db = new Database(location);
|
|
|
|
try {
|
|
db.pragma("foreign_keys = OFF");
|
|
|
|
db.transaction(() => {
|
|
db.prepare(
|
|
`
|
|
CREATE TABLE 'trialNotifications' (
|
|
'notificationId' integer PRIMARY KEY AUTOINCREMENT NOT NULL,
|
|
'subscriptionId' text NOT NULL,
|
|
'notificationType' text NOT NULL,
|
|
'sentAt' integer NOT NULL,
|
|
FOREIGN KEY ('subscriptionId') REFERENCES 'subscriptions'('subscriptionId') ON UPDATE no action ON DELETE cascade
|
|
);
|
|
`
|
|
).run();
|
|
})();
|
|
|
|
db.pragma("foreign_keys = ON");
|
|
|
|
console.log("Migrated database");
|
|
|
|
// Fix names for health checks that don't have one
|
|
const healthChecksWithoutName = db
|
|
.prepare(
|
|
`SELECT
|
|
thc."targetHealthCheckId",
|
|
r."name" AS "resourceName",
|
|
t."ip",
|
|
t."port"
|
|
FROM 'targetHealthCheck' thc
|
|
JOIN 'targets' t ON thc."targetId" = t."targetId"
|
|
JOIN 'resources' r ON t."resourceId" = r."resourceId"
|
|
WHERE thc."name" IS NULL OR thc."name" = ''`
|
|
)
|
|
.all() as {
|
|
targetHealthCheckId: number;
|
|
resourceName: string;
|
|
ip: string;
|
|
port: number;
|
|
}[];
|
|
|
|
console.log(
|
|
`Found ${healthChecksWithoutName.length} targetHealthCheck row(s) with missing names`
|
|
);
|
|
|
|
if (healthChecksWithoutName.length > 0) {
|
|
const updateName = db.prepare(
|
|
`UPDATE 'targetHealthCheck' SET "name" = ? WHERE "targetHealthCheckId" = ?`
|
|
);
|
|
const updateAllNames = db.transaction(() => {
|
|
for (const hc of healthChecksWithoutName) {
|
|
updateName.run(
|
|
`Resource ${hc.resourceName} - ${hc.ip}:${hc.port}`,
|
|
hc.targetHealthCheckId
|
|
);
|
|
}
|
|
});
|
|
updateAllNames();
|
|
console.log(
|
|
`Updated names for ${healthChecksWithoutName.length} targetHealthCheck row(s)`
|
|
);
|
|
}
|
|
|
|
// Recompute resource health by aggregating across the resource's
|
|
// targets' target health checks, then update resources.health and
|
|
// insert a statusHistory entry for any resource whose health changed.
|
|
const resourceTargetHealthRows = db
|
|
.prepare(
|
|
`SELECT
|
|
r."resourceId" AS "resourceId",
|
|
r."orgId" AS "orgId",
|
|
r."health" AS "currentHealth",
|
|
thc."hcHealth" AS "hcHealth"
|
|
FROM 'resources' r
|
|
LEFT JOIN 'targets' t ON t."resourceId" = r."resourceId"
|
|
LEFT JOIN 'targetHealthCheck' thc ON thc."targetId" = t."targetId"`
|
|
)
|
|
.all() as {
|
|
resourceId: number;
|
|
orgId: string;
|
|
currentHealth: string | null;
|
|
hcHealth: string | null;
|
|
}[];
|
|
|
|
const resourceHealthMap = new Map<
|
|
number,
|
|
{
|
|
hasHealthy: boolean;
|
|
hasUnhealthy: boolean;
|
|
hasUnknown: boolean;
|
|
orgId: string;
|
|
currentHealth: string | null;
|
|
}
|
|
>();
|
|
for (const row of resourceTargetHealthRows) {
|
|
const entry = resourceHealthMap.get(row.resourceId) ?? {
|
|
hasHealthy: false,
|
|
hasUnhealthy: false,
|
|
hasUnknown: false,
|
|
orgId: row.orgId,
|
|
currentHealth: row.currentHealth
|
|
};
|
|
const status = row.hcHealth ?? "unknown";
|
|
if (status === "healthy") entry.hasHealthy = true;
|
|
else if (status === "unhealthy") entry.hasUnhealthy = true;
|
|
else entry.hasUnknown = true;
|
|
resourceHealthMap.set(row.resourceId, entry);
|
|
}
|
|
|
|
const updateResourceHealth = db.prepare(
|
|
`UPDATE 'resources' SET "health" = ? WHERE "resourceId" = ?`
|
|
);
|
|
const insertResourceHistory = db.prepare(
|
|
`INSERT INTO 'statusHistory' ("entityType", "entityId", "orgId", "status", "timestamp") VALUES (?, ?, ?, ?, ?)`
|
|
);
|
|
|
|
const now = Math.floor(Date.now() / 1000);
|
|
let updatedResourceCount = 0;
|
|
|
|
const recomputeAll = db.transaction(() => {
|
|
for (const [resourceId, entry] of resourceHealthMap.entries()) {
|
|
let aggregated:
|
|
| "healthy"
|
|
| "unhealthy"
|
|
| "degraded"
|
|
| "unknown";
|
|
if (entry.hasHealthy && entry.hasUnhealthy) {
|
|
aggregated = "degraded";
|
|
} else if (entry.hasHealthy) {
|
|
aggregated = "healthy";
|
|
} else if (entry.hasUnhealthy) {
|
|
aggregated = "unhealthy";
|
|
} else {
|
|
aggregated = "unknown";
|
|
}
|
|
|
|
if (entry.currentHealth !== aggregated) {
|
|
updateResourceHealth.run(aggregated, resourceId);
|
|
insertResourceHistory.run(
|
|
"resource",
|
|
resourceId,
|
|
entry.orgId,
|
|
aggregated,
|
|
now
|
|
);
|
|
updatedResourceCount++;
|
|
}
|
|
}
|
|
});
|
|
recomputeAll();
|
|
console.log(
|
|
`Recomputed health for ${updatedResourceCount} resource(s) based on target health checks`
|
|
);
|
|
} catch (e) {
|
|
console.log("Failed to migrate db:", e);
|
|
throw e;
|
|
}
|
|
|
|
console.log(`${version} migration complete`);
|
|
}
|