mirror of
https://github.com/fosrl/pangolin.git
synced 2026-02-16 09:56:36 +00:00
✨ filter resources by status
This commit is contained in:
@@ -187,7 +187,9 @@ export const targetHealthCheck = pgTable("targetHealthCheck", {
|
||||
hcFollowRedirects: boolean("hcFollowRedirects").default(true),
|
||||
hcMethod: varchar("hcMethod").default("GET"),
|
||||
hcStatus: integer("hcStatus"), // http code
|
||||
hcHealth: text("hcHealth").default("unknown"), // "unknown", "healthy", "unhealthy"
|
||||
hcHealth: text("hcHealth")
|
||||
.$type<"unknown" | "healthy" | "unhealthy">()
|
||||
.default("unknown"), // "unknown", "healthy", "unhealthy"
|
||||
hcTlsServerName: text("hcTlsServerName")
|
||||
});
|
||||
|
||||
|
||||
@@ -213,7 +213,9 @@ export const targetHealthCheck = sqliteTable("targetHealthCheck", {
|
||||
}).default(true),
|
||||
hcMethod: text("hcMethod").default("GET"),
|
||||
hcStatus: integer("hcStatus"), // http code
|
||||
hcHealth: text("hcHealth").default("unknown"), // "unknown", "healthy", "unhealthy"
|
||||
hcHealth: text("hcHealth")
|
||||
.$type<"unknown" | "healthy" | "unhealthy">()
|
||||
.default("unknown"), // "unknown", "healthy", "unhealthy"
|
||||
hcTlsServerName: text("hcTlsServerName")
|
||||
});
|
||||
|
||||
|
||||
@@ -27,7 +27,8 @@ import {
|
||||
ilike,
|
||||
asc,
|
||||
not,
|
||||
isNull
|
||||
isNull,
|
||||
type SQL
|
||||
} from "drizzle-orm";
|
||||
import logger from "@server/logger";
|
||||
import { fromZodError } from "zod-validation-error";
|
||||
@@ -63,7 +64,7 @@ const listResourcesSchema = z.object({
|
||||
.optional()
|
||||
.catch(undefined),
|
||||
healthStatus: z
|
||||
.enum(["online", "degraded", "offline", "unknown"])
|
||||
.enum(["no_targets", "healthy", "degraded", "offline", "unknown"])
|
||||
.optional()
|
||||
.catch(undefined)
|
||||
});
|
||||
@@ -86,13 +87,18 @@ type JoinedRow = {
|
||||
domainId: string | null;
|
||||
headerAuthId: number | null;
|
||||
|
||||
targetId: number | null;
|
||||
targetIp: string | null;
|
||||
targetPort: number | null;
|
||||
targetEnabled: boolean | null;
|
||||
// total_targets: number;
|
||||
// healthy_targets: number;
|
||||
// unhealthy_targets: number;
|
||||
// unknown_targets: number;
|
||||
|
||||
hcHealth: string | null;
|
||||
hcEnabled: boolean | null;
|
||||
// targetId: number | null;
|
||||
// targetIp: string | null;
|
||||
// targetPort: number | null;
|
||||
// targetEnabled: boolean | null;
|
||||
|
||||
// hcHealth: string | null;
|
||||
// hcEnabled: boolean | null;
|
||||
};
|
||||
|
||||
// grouped by resource with targets[])
|
||||
@@ -117,10 +123,68 @@ export type ResourceWithTargets = {
|
||||
ip: string;
|
||||
port: number;
|
||||
enabled: boolean;
|
||||
healthStatus?: "healthy" | "unhealthy" | "unknown";
|
||||
healthStatus: "healthy" | "unhealthy" | "unknown" | null;
|
||||
}>;
|
||||
};
|
||||
|
||||
// Aggregate filters
|
||||
const total_targets = count(targets.targetId);
|
||||
const healthy_targets = sql<number>`SUM(
|
||||
CASE
|
||||
WHEN ${targetHealthCheck.hcHealth} = 'healthy' THEN 1
|
||||
ELSE 0
|
||||
END
|
||||
) `;
|
||||
const unknown_targets = sql<number>`SUM(
|
||||
CASE
|
||||
WHEN ${targetHealthCheck.hcHealth} = 'unknown' THEN 1
|
||||
ELSE 0
|
||||
END
|
||||
) `;
|
||||
const unhealthy_targets = sql<number>`SUM(
|
||||
CASE
|
||||
WHEN ${targetHealthCheck.hcHealth} = 'unhealthy' THEN 1
|
||||
ELSE 0
|
||||
END
|
||||
) `;
|
||||
|
||||
function countResourcesBase() {
|
||||
return db
|
||||
.select({ count: count() })
|
||||
.from(resources)
|
||||
.leftJoin(
|
||||
resourcePassword,
|
||||
eq(resourcePassword.resourceId, resources.resourceId)
|
||||
)
|
||||
.leftJoin(
|
||||
resourcePincode,
|
||||
eq(resourcePincode.resourceId, resources.resourceId)
|
||||
)
|
||||
.leftJoin(
|
||||
resourceHeaderAuth,
|
||||
eq(resourceHeaderAuth.resourceId, resources.resourceId)
|
||||
)
|
||||
.leftJoin(
|
||||
resourceHeaderAuthExtendedCompatibility,
|
||||
eq(
|
||||
resourceHeaderAuthExtendedCompatibility.resourceId,
|
||||
resources.resourceId
|
||||
)
|
||||
)
|
||||
.leftJoin(targets, eq(targets.resourceId, resources.resourceId))
|
||||
.leftJoin(
|
||||
targetHealthCheck,
|
||||
eq(targetHealthCheck.targetId, targets.targetId)
|
||||
)
|
||||
.groupBy(
|
||||
resources.resourceId,
|
||||
resourcePassword.passwordId,
|
||||
resourcePincode.pincodeId,
|
||||
resourceHeaderAuth.headerAuthId,
|
||||
resourceHeaderAuthExtendedCompatibility.headerAuthExtendedCompatibilityId
|
||||
);
|
||||
}
|
||||
|
||||
function queryResourcesBase() {
|
||||
return db
|
||||
.select({
|
||||
@@ -140,14 +204,7 @@ function queryResourcesBase() {
|
||||
niceId: resources.niceId,
|
||||
headerAuthId: resourceHeaderAuth.headerAuthId,
|
||||
headerAuthExtendedCompatibilityId:
|
||||
resourceHeaderAuthExtendedCompatibility.headerAuthExtendedCompatibilityId,
|
||||
targetId: targets.targetId,
|
||||
targetIp: targets.ip,
|
||||
targetPort: targets.port,
|
||||
targetEnabled: targets.enabled,
|
||||
|
||||
hcHealth: targetHealthCheck.hcHealth,
|
||||
hcEnabled: targetHealthCheck.hcEnabled
|
||||
resourceHeaderAuthExtendedCompatibility.headerAuthExtendedCompatibilityId
|
||||
})
|
||||
.from(resources)
|
||||
.leftJoin(
|
||||
@@ -173,6 +230,13 @@ function queryResourcesBase() {
|
||||
.leftJoin(
|
||||
targetHealthCheck,
|
||||
eq(targetHealthCheck.targetId, targets.targetId)
|
||||
)
|
||||
.groupBy(
|
||||
resources.resourceId,
|
||||
resourcePassword.passwordId,
|
||||
resourcePincode.pincodeId,
|
||||
resourceHeaderAuth.headerAuthId,
|
||||
resourceHeaderAuthExtendedCompatibility.headerAuthExtendedCompatibilityId
|
||||
);
|
||||
}
|
||||
|
||||
@@ -323,45 +387,52 @@ export async function listResources(
|
||||
}
|
||||
}
|
||||
|
||||
let aggregateFilters: SQL<any> | null | undefined = null;
|
||||
|
||||
if (typeof healthStatus !== "undefined") {
|
||||
switch (healthStatus) {
|
||||
case "online":
|
||||
case "healthy":
|
||||
aggregateFilters = and(
|
||||
sql`${total_targets} > 0`,
|
||||
sql`${healthy_targets} = ${total_targets}`
|
||||
);
|
||||
break;
|
||||
default:
|
||||
case "degraded":
|
||||
aggregateFilters = and(
|
||||
sql`${total_targets} > 0`,
|
||||
sql`${unhealthy_targets} > 0`
|
||||
);
|
||||
break;
|
||||
case "no_targets":
|
||||
aggregateFilters = sql`${total_targets} = 0`;
|
||||
break;
|
||||
case "offline":
|
||||
aggregateFilters = and(
|
||||
sql`${total_targets} > 0`,
|
||||
sql`${healthy_targets} = 0`,
|
||||
sql`${unhealthy_targets} = ${total_targets}`
|
||||
);
|
||||
break;
|
||||
case "unknown":
|
||||
aggregateFilters = and(
|
||||
sql`${total_targets} > 0`,
|
||||
sql`${unknown_targets} = ${total_targets}`
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const countQuery: any = db
|
||||
.select({ count: count() })
|
||||
.from(resources)
|
||||
.leftJoin(
|
||||
resourcePassword,
|
||||
eq(resourcePassword.resourceId, resources.resourceId)
|
||||
)
|
||||
.leftJoin(
|
||||
resourcePincode,
|
||||
eq(resourcePincode.resourceId, resources.resourceId)
|
||||
)
|
||||
.leftJoin(
|
||||
resourceHeaderAuth,
|
||||
eq(resourceHeaderAuth.resourceId, resources.resourceId)
|
||||
)
|
||||
.leftJoin(
|
||||
resourceHeaderAuthExtendedCompatibility,
|
||||
eq(
|
||||
resourceHeaderAuthExtendedCompatibility.resourceId,
|
||||
resources.resourceId
|
||||
)
|
||||
)
|
||||
.leftJoin(targets, eq(targets.resourceId, resources.resourceId))
|
||||
.leftJoin(
|
||||
targetHealthCheck,
|
||||
eq(targetHealthCheck.targetId, targets.targetId)
|
||||
)
|
||||
.where(conditions);
|
||||
let baseQuery = queryResourcesBase();
|
||||
let countQuery = countResourcesBase().where(conditions);
|
||||
|
||||
const baseQuery = queryResourcesBase();
|
||||
if (aggregateFilters) {
|
||||
// @ts-expect-error idk why this is causing a type error
|
||||
baseQuery = baseQuery.having(aggregateFilters);
|
||||
}
|
||||
if (aggregateFilters) {
|
||||
// @ts-expect-error idk why this is causing a type error
|
||||
countQuery = countQuery.having(aggregateFilters);
|
||||
}
|
||||
|
||||
const rows: JoinedRow[] = await baseQuery
|
||||
.where(conditions)
|
||||
@@ -369,6 +440,27 @@ export async function listResources(
|
||||
.offset(pageSize * (page - 1))
|
||||
.orderBy(asc(resources.resourceId));
|
||||
|
||||
const resourceIdList = rows.map((row) => row.resourceId);
|
||||
const allResourceTargets =
|
||||
resourceIdList.length === 0
|
||||
? []
|
||||
: await db
|
||||
.select({
|
||||
targetId: targets.targetId,
|
||||
resourceId: targets.resourceId,
|
||||
ip: targets.ip,
|
||||
port: targets.port,
|
||||
enabled: targets.enabled,
|
||||
healthStatus: targetHealthCheck.hcHealth,
|
||||
hcEnabled: targetHealthCheck.hcEnabled
|
||||
})
|
||||
.from(targets)
|
||||
.where(sql`${targets.resourceId} in ${resourceIdList}`)
|
||||
.leftJoin(
|
||||
targetHealthCheck,
|
||||
eq(targetHealthCheck.targetId, targets.targetId)
|
||||
);
|
||||
|
||||
// avoids TS issues with reduce/never[]
|
||||
const map = new Map<number, ResourceWithTargets>();
|
||||
|
||||
@@ -396,30 +488,9 @@ export async function listResources(
|
||||
map.set(row.resourceId, entry);
|
||||
}
|
||||
|
||||
if (
|
||||
row.targetId != null &&
|
||||
row.targetIp &&
|
||||
row.targetPort != null &&
|
||||
row.targetEnabled != null
|
||||
) {
|
||||
let healthStatus: "healthy" | "unhealthy" | "unknown" =
|
||||
"unknown";
|
||||
|
||||
if (row.hcEnabled && row.hcHealth) {
|
||||
healthStatus = row.hcHealth as
|
||||
| "healthy"
|
||||
| "unhealthy"
|
||||
| "unknown";
|
||||
}
|
||||
|
||||
entry.targets.push({
|
||||
targetId: row.targetId,
|
||||
ip: row.targetIp,
|
||||
port: row.targetPort,
|
||||
enabled: row.targetEnabled,
|
||||
healthStatus: healthStatus
|
||||
});
|
||||
}
|
||||
entry.targets = allResourceTargets.filter(
|
||||
(t) => t.resourceId === entry.resourceId
|
||||
);
|
||||
}
|
||||
|
||||
const resourcesList: ResourceWithTargets[] = Array.from(map.values());
|
||||
|
||||
@@ -111,6 +111,9 @@ const listSitesSchema = z.object({
|
||||
.catch(undefined)
|
||||
});
|
||||
|
||||
function countSitesBase() {
|
||||
return db.select({ count: count() }).from(sites);
|
||||
}
|
||||
function querySitesBase() {
|
||||
return db
|
||||
.select({
|
||||
@@ -242,10 +245,7 @@ export async function listSites(
|
||||
conditions = and(conditions, eq(sites.online, online));
|
||||
}
|
||||
|
||||
const countQuery = db
|
||||
.select({ count: count() })
|
||||
.from(sites)
|
||||
.where(conditions);
|
||||
const countQuery = countSitesBase().where(conditions);
|
||||
|
||||
const siteListQuery = baseQuery
|
||||
.where(conditions)
|
||||
|
||||
Reference in New Issue
Block a user