CD / update-wiki (push) Successful in 8s
quality / commitlint (push) Skipped
quality / changes (push) Successful in 5s
quality / docker-check (push) Skipped
quality / web (push) Successful in 54s
quality / api (push) Successful in 46s
CD / quality (push) Successful in 1m49s
CD / publish (push) Successful in 1m40s
Несколько источников проб сразу и правило агрегации на сервисе вместо XOR Local/Cloudflare. Co-authored-by: Cursor <[email protected]>
306 lines
10 KiB
TypeScript
306 lines
10 KiB
TypeScript
import { eq } from "drizzle-orm";
|
|
import type { HealthWorkerStatus } from "@cfdm/shared";
|
|
import type { Db } from "./client.js";
|
|
import { appSettings } from "./schema.js";
|
|
|
|
const SETTINGS_ID = "settings-main";
|
|
|
|
export type HealthEngineSettings = {
|
|
healthCheckCron: string;
|
|
healthDegradedFailures: number;
|
|
healthDownFailures: number;
|
|
healthLatencyWarnMs: number;
|
|
healthSuccessRecoveries: number;
|
|
};
|
|
|
|
export type AppSettingsDto = {
|
|
id: string;
|
|
vpsTrackerUrl: string;
|
|
vpsTrackerIntegrationTokenSet: boolean;
|
|
vpsTrackerSyncEnabled: boolean;
|
|
vpsTrackerLastSyncAt: string | null;
|
|
showQuickActions: boolean;
|
|
healthWorkerUrl: string;
|
|
healthWorkerTokenSet: boolean;
|
|
healthWorkerStatus: HealthWorkerStatus;
|
|
healthWorkerAccountId: string;
|
|
healthWorkerKvNamespaceId: string;
|
|
healthWorkerError: string | null;
|
|
healthWorkerDeployedAt: string | null;
|
|
healthWorkerLastIngestAt: string | null;
|
|
globalpingTokenSet: boolean;
|
|
globalpingLocations: string;
|
|
globalpingLimit: number;
|
|
} & HealthEngineSettings;
|
|
|
|
export type AppSettingsPatch = {
|
|
vpsTrackerUrl?: string;
|
|
vpsTrackerIntegrationToken?: string;
|
|
vpsTrackerSyncEnabled?: boolean;
|
|
showQuickActions?: boolean;
|
|
healthCheckCron?: string;
|
|
healthDegradedFailures?: number;
|
|
healthDownFailures?: number;
|
|
healthLatencyWarnMs?: number;
|
|
healthSuccessRecoveries?: number;
|
|
healthWorkerUrl?: string;
|
|
healthWorkerToken?: string;
|
|
healthWorkerAccountId?: string | null;
|
|
healthWorkerKvNamespaceId?: string | null;
|
|
healthWorkerError?: string | null;
|
|
healthWorkerDeployedAt?: string | null;
|
|
healthWorkerLastIngestAt?: string | null;
|
|
globalpingToken?: string;
|
|
globalpingLocations?: string;
|
|
globalpingLimit?: number;
|
|
};
|
|
|
|
export type HealthEngineFallbacks = HealthEngineSettings & {
|
|
healthWorkerUrl: string;
|
|
healthWorkerTokenSet: boolean;
|
|
};
|
|
|
|
function coalesceInt(value: number | null | undefined, fallback: number): number {
|
|
return value == null || Number.isNaN(value) || value < 1 ? fallback : value;
|
|
}
|
|
|
|
function workerStatus(
|
|
row: typeof appSettings.$inferSelect,
|
|
envUrl: string,
|
|
): HealthWorkerStatus {
|
|
if (row.health_worker_error?.trim()) return "error";
|
|
const url = row.health_worker_url?.trim() || envUrl;
|
|
const kv = row.health_worker_kv_namespace_id?.trim();
|
|
if (kv && url) return "ready";
|
|
return "missing";
|
|
}
|
|
|
|
function toDto(
|
|
row: typeof appSettings.$inferSelect,
|
|
fallbacks?: HealthEngineFallbacks,
|
|
): AppSettingsDto {
|
|
const env = fallbacks ?? {
|
|
healthCheckCron: "0 */2 * * * *",
|
|
healthDegradedFailures: 1,
|
|
healthDownFailures: 2,
|
|
healthLatencyWarnMs: 1000,
|
|
healthSuccessRecoveries: 2,
|
|
healthWorkerUrl: "",
|
|
healthWorkerTokenSet: false,
|
|
};
|
|
return {
|
|
id: row.id,
|
|
vpsTrackerUrl: row.vps_tracker_url?.trim() ?? "",
|
|
vpsTrackerIntegrationTokenSet: Boolean(
|
|
row.vps_tracker_integration_token?.trim(),
|
|
),
|
|
vpsTrackerSyncEnabled: Boolean(row.vps_tracker_sync_enabled),
|
|
vpsTrackerLastSyncAt: row.vps_tracker_last_sync_at,
|
|
showQuickActions:
|
|
row.show_quick_actions == null ? true : Boolean(row.show_quick_actions),
|
|
healthCheckCron: row.health_check_cron?.trim() || env.healthCheckCron,
|
|
healthDegradedFailures: coalesceInt(
|
|
row.health_degraded_failures,
|
|
env.healthDegradedFailures,
|
|
),
|
|
healthDownFailures: coalesceInt(
|
|
row.health_down_failures,
|
|
env.healthDownFailures,
|
|
),
|
|
healthLatencyWarnMs: coalesceInt(
|
|
row.health_latency_warn_ms,
|
|
env.healthLatencyWarnMs,
|
|
),
|
|
healthSuccessRecoveries: coalesceInt(
|
|
row.health_success_recoveries,
|
|
env.healthSuccessRecoveries,
|
|
),
|
|
healthWorkerUrl: row.health_worker_url?.trim() || env.healthWorkerUrl,
|
|
healthWorkerTokenSet:
|
|
Boolean(row.health_worker_token?.trim()) || env.healthWorkerTokenSet,
|
|
healthWorkerAccountId: row.health_worker_account_id?.trim() ?? "",
|
|
healthWorkerKvNamespaceId: row.health_worker_kv_namespace_id?.trim() ?? "",
|
|
healthWorkerError: row.health_worker_error?.trim() || null,
|
|
healthWorkerDeployedAt: row.health_worker_deployed_at ?? null,
|
|
healthWorkerLastIngestAt: row.health_worker_last_ingest_at ?? null,
|
|
healthWorkerStatus: workerStatus(row, env.healthWorkerUrl),
|
|
globalpingTokenSet: Boolean(row.globalping_token?.trim()),
|
|
globalpingLocations: row.globalping_locations?.trim() || "World",
|
|
globalpingLimit:
|
|
row.globalping_limit == null ||
|
|
Number.isNaN(row.globalping_limit) ||
|
|
row.globalping_limit < 1
|
|
? 3
|
|
: Math.min(10, row.globalping_limit),
|
|
};
|
|
}
|
|
|
|
export function getAppSettings(
|
|
db: Db,
|
|
fallbacks?: HealthEngineFallbacks,
|
|
): AppSettingsDto {
|
|
const row = db
|
|
.select()
|
|
.from(appSettings)
|
|
.where(eq(appSettings.id, SETTINGS_ID))
|
|
.get();
|
|
if (!row) {
|
|
db.insert(appSettings).values({ id: SETTINGS_ID }).run();
|
|
return toDto(
|
|
db.select().from(appSettings).where(eq(appSettings.id, SETTINGS_ID)).get()!,
|
|
fallbacks,
|
|
);
|
|
}
|
|
return toDto(row, fallbacks);
|
|
}
|
|
|
|
export function getAppSettingsSecrets(db: Db): {
|
|
vpsTrackerUrl: string;
|
|
vpsTrackerIntegrationToken: string;
|
|
vpsTrackerSyncEnabled: boolean;
|
|
healthWorkerUrl: string;
|
|
healthWorkerToken: string;
|
|
globalpingToken: string;
|
|
globalpingLocations: string;
|
|
globalpingLimit: number;
|
|
} {
|
|
const row = db
|
|
.select()
|
|
.from(appSettings)
|
|
.where(eq(appSettings.id, SETTINGS_ID))
|
|
.get();
|
|
const limit = row?.globalping_limit;
|
|
return {
|
|
vpsTrackerUrl: row?.vps_tracker_url?.trim() ?? "",
|
|
vpsTrackerIntegrationToken:
|
|
row?.vps_tracker_integration_token?.trim() ?? "",
|
|
vpsTrackerSyncEnabled: Boolean(row?.vps_tracker_sync_enabled),
|
|
healthWorkerUrl: row?.health_worker_url?.trim() ?? "",
|
|
healthWorkerToken: row?.health_worker_token?.trim() ?? "",
|
|
globalpingToken: row?.globalping_token?.trim() ?? "",
|
|
globalpingLocations: row?.globalping_locations?.trim() || "World",
|
|
globalpingLimit:
|
|
limit == null || Number.isNaN(limit) || limit < 1
|
|
? 3
|
|
: Math.min(10, limit),
|
|
};
|
|
}
|
|
|
|
export function updateAppSettings(
|
|
db: Db,
|
|
patch: AppSettingsPatch,
|
|
fallbacks?: HealthEngineFallbacks,
|
|
): AppSettingsDto {
|
|
const existing = db
|
|
.select()
|
|
.from(appSettings)
|
|
.where(eq(appSettings.id, SETTINGS_ID))
|
|
.get();
|
|
if (!existing) {
|
|
db.insert(appSettings).values({ id: SETTINGS_ID }).run();
|
|
}
|
|
const current = db
|
|
.select()
|
|
.from(appSettings)
|
|
.where(eq(appSettings.id, SETTINGS_ID))
|
|
.get()!;
|
|
|
|
db.update(appSettings)
|
|
.set({
|
|
vps_tracker_url:
|
|
patch.vpsTrackerUrl !== undefined
|
|
? patch.vpsTrackerUrl
|
|
: current.vps_tracker_url,
|
|
vps_tracker_integration_token:
|
|
patch.vpsTrackerIntegrationToken !== undefined &&
|
|
patch.vpsTrackerIntegrationToken.trim() !== ""
|
|
? patch.vpsTrackerIntegrationToken
|
|
: current.vps_tracker_integration_token,
|
|
vps_tracker_sync_enabled:
|
|
patch.vpsTrackerSyncEnabled !== undefined
|
|
? patch.vpsTrackerSyncEnabled
|
|
: current.vps_tracker_sync_enabled,
|
|
show_quick_actions:
|
|
patch.showQuickActions !== undefined
|
|
? patch.showQuickActions
|
|
: current.show_quick_actions,
|
|
health_check_cron:
|
|
patch.healthCheckCron !== undefined
|
|
? patch.healthCheckCron.trim()
|
|
: current.health_check_cron,
|
|
health_degraded_failures:
|
|
patch.healthDegradedFailures !== undefined
|
|
? patch.healthDegradedFailures
|
|
: current.health_degraded_failures,
|
|
health_down_failures:
|
|
patch.healthDownFailures !== undefined
|
|
? patch.healthDownFailures
|
|
: current.health_down_failures,
|
|
health_latency_warn_ms:
|
|
patch.healthLatencyWarnMs !== undefined
|
|
? patch.healthLatencyWarnMs
|
|
: current.health_latency_warn_ms,
|
|
health_success_recoveries:
|
|
patch.healthSuccessRecoveries !== undefined
|
|
? patch.healthSuccessRecoveries
|
|
: current.health_success_recoveries,
|
|
health_worker_url:
|
|
patch.healthWorkerUrl !== undefined
|
|
? patch.healthWorkerUrl.trim() || null
|
|
: current.health_worker_url,
|
|
health_worker_token:
|
|
patch.healthWorkerToken !== undefined &&
|
|
patch.healthWorkerToken.trim() !== ""
|
|
? patch.healthWorkerToken
|
|
: current.health_worker_token,
|
|
health_worker_account_id:
|
|
patch.healthWorkerAccountId !== undefined
|
|
? patch.healthWorkerAccountId?.trim() || null
|
|
: current.health_worker_account_id,
|
|
health_worker_kv_namespace_id:
|
|
patch.healthWorkerKvNamespaceId !== undefined
|
|
? patch.healthWorkerKvNamespaceId?.trim() || null
|
|
: current.health_worker_kv_namespace_id,
|
|
health_worker_error:
|
|
patch.healthWorkerError !== undefined
|
|
? patch.healthWorkerError?.trim() || null
|
|
: current.health_worker_error,
|
|
health_worker_deployed_at:
|
|
patch.healthWorkerDeployedAt !== undefined
|
|
? patch.healthWorkerDeployedAt
|
|
: current.health_worker_deployed_at,
|
|
health_worker_last_ingest_at:
|
|
patch.healthWorkerLastIngestAt !== undefined
|
|
? patch.healthWorkerLastIngestAt
|
|
: current.health_worker_last_ingest_at,
|
|
globalping_token:
|
|
patch.globalpingToken !== undefined &&
|
|
patch.globalpingToken.trim() !== ""
|
|
? patch.globalpingToken
|
|
: current.globalping_token,
|
|
globalping_locations:
|
|
patch.globalpingLocations !== undefined
|
|
? patch.globalpingLocations.trim() || "World"
|
|
: current.globalping_locations,
|
|
globalping_limit:
|
|
patch.globalpingLimit !== undefined
|
|
? Math.min(10, Math.max(1, patch.globalpingLimit))
|
|
: current.globalping_limit,
|
|
updated_at: new Date().toISOString(),
|
|
})
|
|
.where(eq(appSettings.id, SETTINGS_ID))
|
|
.run();
|
|
|
|
return getAppSettings(db, fallbacks);
|
|
}
|
|
|
|
export function touchVpsTrackerSync(db: Db): void {
|
|
db.update(appSettings)
|
|
.set({
|
|
vps_tracker_last_sync_at: new Date().toISOString(),
|
|
updated_at: new Date().toISOString(),
|
|
})
|
|
.where(eq(appSettings.id, SETTINGS_ID))
|
|
.run();
|
|
}
|