mirror of
https://github.com/fosrl/pangolin.git
synced 2026-05-06 20:37:45 +00:00
172
server/setup/scriptsSqlite/1.18.3.ts
Normal file
172
server/setup/scriptsSqlite/1.18.3.ts
Normal file
@@ -0,0 +1,172 @@
|
||||
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`);
|
||||
}
|
||||
Reference in New Issue
Block a user