Skip to content

Commit 092863a

Browse files
authored
Merge pull request #305 from techulus/automation/release/v0.60.0
Release v0.60.0
2 parents 81f3fb2 + 22a065c commit 092863a

19 files changed

Lines changed: 868 additions & 10 deletions

‎web/app/api/inngest/route.ts‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import {
2020
previewServiceReconcileWorkflow,
2121
previewSyncWorkflow,
2222
registryArtifactRetention,
23+
resourceUsageCheck,
2324
restoreTriggerWorkflow,
2425
restoreWorkflow,
2526
rolloutWorkflow,
@@ -52,6 +53,7 @@ export const { GET, POST, PUT } = serve({
5253
controlPlaneUpdateCheck,
5354
agentUpgradeTimeoutCheck,
5455
registryArtifactRetention,
56+
resourceUsageCheck,
5557
migrationWorkflow,
5658
sentryFailureWorkflow,
5759
backupWorkflow,

‎web/app/api/v1/agent/builds/[id]/status/route.ts‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -283,7 +283,7 @@ export async function POST(
283283
update.status === "completed"
284284
? "Preview image built; preparing deployment"
285285
: update.status === "failed"
286-
? update.error || "Preview build failed"
286+
? "Preview build failed"
287287
: `Preview build ${update.status}...`,
288288
logUrl,
289289
});
@@ -325,7 +325,7 @@ export async function POST(
325325
build.githubDeploymentId,
326326
"failure",
327327
{
328-
description: update.error || "Build failed",
328+
description: "Build failed",
329329
logUrl,
330330
environmentUrl,
331331
},

‎web/components/cluster/cluster-health-summary.tsx‎

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,11 @@ type ClusterHealthData = {
99
onlineServers: number;
1010
networkHealthy: number;
1111
containerHealthy: number;
12+
resourceWarnings: {
13+
cpu: number;
14+
memory: number;
15+
disk: number;
16+
};
1217
};
1318
};
1419

@@ -30,6 +35,7 @@ export function ClusterHealthSummary({
3035

3136
const stats = [
3237
{
38+
key: "servers",
3339
label: "servers",
3440
value: `${summary.onlineServers}/${summary.totalServers}`,
3541
subtitle: "online",
@@ -38,17 +44,35 @@ export function ClusterHealthSummary({
3844
summary.onlineServers === summary.totalServers,
3945
},
4046
{
47+
key: "tunnels",
4148
label: "tunnels",
4249
value: `${summary.networkHealthy}/${summary.onlineServers}`,
4350
subtitle: "connected",
4451
healthy: anyOnline && summary.networkHealthy === summary.onlineServers,
4552
},
4653
{
54+
key: "runtimes",
4755
label: "runtimes",
4856
value: `${summary.containerHealthy}/${summary.onlineServers}`,
4957
subtitle: "responsive",
5058
healthy: anyOnline && summary.containerHealthy === summary.onlineServers,
5159
},
60+
...(
61+
[
62+
["cpu", "CPU"],
63+
["memory", "memory"],
64+
["disk", "disk"],
65+
] as const
66+
).map(([key, label]) => {
67+
const count = summary.resourceWarnings[key];
68+
return {
69+
key,
70+
label: count === 1 ? "server" : "servers",
71+
value: String(count),
72+
subtitle: `at high ${label} usage`,
73+
healthy: count === 0,
74+
};
75+
}),
5276
];
5377

5478
const degraded = stats.filter((stat) => !stat.healthy);
@@ -65,7 +89,7 @@ export function ClusterHealthSummary({
6589
return (
6690
<div className="flex flex-wrap items-center gap-x-4 gap-y-1 text-sm">
6791
{degraded.map((stat) => (
68-
<div key={stat.label} className="flex items-center gap-1.5">
92+
<div key={stat.key} className="flex items-center gap-1.5">
6993
<span className="h-2 w-2 shrink-0 rounded-full bg-amber-500" />
7094
<span className="font-semibold tabular-nums text-foreground">
7195
{stat.value}

‎web/components/settings/email-settings.tsx‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ type Props = {
1717

1818
type AlertField =
1919
| "serverOfflineAlert"
20+
| "resourceUsageAlert"
2021
| "buildFailure"
2122
| "deploymentFailure"
2223
| "deploymentMovedAlert"
@@ -34,6 +35,12 @@ const ALERT_SETTINGS: AlertSetting[] = [
3435
label: "Server Offline Alert",
3536
description: "Receive a notification when a server goes offline",
3637
},
38+
{
39+
field: "resourceUsageAlert",
40+
label: "Resource Usage Alert",
41+
description:
42+
"Receive a notification when server CPU, memory, or disk usage is high",
43+
},
3744
{
3845
field: "buildFailure",
3946
label: "Build Failure Alert",
@@ -59,6 +66,7 @@ const ALERT_SETTINGS: AlertSetting[] = [
5966

6067
type State = {
6168
serverOfflineAlert: boolean;
69+
resourceUsageAlert: boolean;
6270
buildFailure: boolean;
6371
deploymentFailure: boolean;
6472
deploymentMovedAlert: boolean;
@@ -74,6 +82,7 @@ function createInitialState(props: Props): State {
7482
const { initialAlertsConfig: alertsConfig } = props;
7583
return {
7684
serverOfflineAlert: alertsConfig?.serverOfflineAlert ?? true,
85+
resourceUsageAlert: alertsConfig?.resourceUsageAlert ?? true,
7786
buildFailure: alertsConfig?.buildFailure ?? true,
7887
deploymentFailure: alertsConfig?.deploymentFailure ?? true,
7988
deploymentMovedAlert: alertsConfig?.deploymentMovedAlert ?? true,
@@ -104,6 +113,7 @@ export function EmailSettings({ initialAlertsConfig }: Props) {
104113
try {
105114
await updateEmailAlertsConfig({
106115
serverOfflineAlert: state.serverOfflineAlert,
116+
resourceUsageAlert: state.resourceUsageAlert,
107117
buildFailure: state.buildFailure,
108118
deploymentFailure: state.deploymentFailure,
109119
deploymentMovedAlert: state.deploymentMovedAlert,
@@ -125,6 +135,8 @@ export function EmailSettings({ initialAlertsConfig }: Props) {
125135
const hasAlertsChanges =
126136
state.serverOfflineAlert !==
127137
(initialAlertsConfig?.serverOfflineAlert ?? true) ||
138+
state.resourceUsageAlert !==
139+
(initialAlertsConfig?.resourceUsageAlert ?? true) ||
128140
state.buildFailure !== (initialAlertsConfig?.buildFailure ?? true) ||
129141
state.deploymentFailure !==
130142
(initialAlertsConfig?.deploymentFailure ?? true) ||

‎web/db/queries.ts‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,7 @@ export async function getClusterHealth() {
165165
networkHealth: servers.networkHealth,
166166
containerHealth: servers.containerHealth,
167167
agentHealth: servers.agentHealth,
168+
resourceAlerts: servers.resourceAlerts,
168169
})
169170
.from(servers);
170171

@@ -176,13 +177,20 @@ export async function getClusterHealth() {
176177
const containerHealthy = onlineServers.filter(
177178
(s) => s.containerHealth?.runtimeResponsive,
178179
).length;
180+
const resourceWarnings = {
181+
cpu: onlineServers.filter((server) => server.resourceAlerts?.cpu).length,
182+
memory: onlineServers.filter((server) => server.resourceAlerts?.memory)
183+
.length,
184+
disk: onlineServers.filter((server) => server.resourceAlerts?.disk).length,
185+
};
179186

180187
return {
181188
summary: {
182189
totalServers: allServers.length,
183190
onlineServers: onlineServers.length,
184191
networkHealthy,
185192
containerHealthy,
193+
resourceWarnings,
186194
},
187195
servers: allServers.map((server) => ({
188196
id: server.id,

‎web/db/schema.ts‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -353,6 +353,17 @@ export type AgentHealth = {
353353
capabilities?: string[];
354354
};
355355

356+
export type ServerResourceAlert = {
357+
usagePercent: number;
358+
thresholdPercent: number;
359+
detectedAt: string;
360+
notificationEnqueued: boolean;
361+
};
362+
363+
export type ServerResourceAlerts = Partial<
364+
Record<"cpu" | "memory" | "disk", ServerResourceAlert>
365+
>;
366+
356367
export type CrowdSecDecision = {
357368
scope: string;
358369
value: string;
@@ -427,6 +438,7 @@ export const servers = pgTable("servers", {
427438
networkHealth: jsonb("network_health").$type<NetworkHealth>(),
428439
containerHealth: jsonb("container_health").$type<ContainerHealth>(),
429440
agentHealth: jsonb("agent_health").$type<AgentHealth>(),
441+
resourceAlerts: jsonb("resource_alerts").$type<ServerResourceAlerts>(),
430442
crowdsecHealth: jsonb("crowdsec_health").$type<CrowdSecHealth>(),
431443
agentUpgradeTargetVersion: text("agent_upgrade_target_version"),
432444
agentUpgradeStatus: text("agent_upgrade_status", {

‎web/lib/email/index.ts‎

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,48 @@ async function sendManualRecoveryRequiredAlert(
208208
});
209209
}
210210

211+
type ServerResourceUsageAlertOptions = Extract<
212+
NotificationEvent,
213+
{ kind: "server.resource_usage" }
214+
> & { to: string };
215+
216+
async function sendServerResourceUsageAlert(
217+
options: ServerResourceUsageAlertOptions,
218+
): Promise<void> {
219+
const baseUrl = getAppBaseUrl();
220+
const serverUrl = baseUrl
221+
? `${baseUrl}/dashboard/servers/${options.serverId}`
222+
: undefined;
223+
const resource =
224+
options.resource === "cpu"
225+
? "CPU"
226+
: `${options.resource.charAt(0).toUpperCase()}${options.resource.slice(1)}`;
227+
const usage = `${options.usagePercent.toFixed(1)}%`;
228+
229+
await sendAlert({
230+
to: options.to,
231+
subject: `Alert: High ${resource} usage on "${options.serverName}"`,
232+
template: Alert({
233+
bannerText: `HIGH ${resource.toUpperCase()} USAGE`,
234+
heading: `${resource} Usage Alert`,
235+
description: `${resource} usage on "${options.serverName}" has reached ${usage}, above the ${options.thresholdPercent}% threshold.`,
236+
details: [
237+
{ label: "Server", value: options.serverName },
238+
{ label: "Resource", value: resource },
239+
{ label: "Usage", value: usage },
240+
{ label: "Threshold", value: `${options.thresholdPercent}%` },
241+
{
242+
label: "Detected At",
243+
value: formatDateTimeUtc(new Date(options.detectedAt)),
244+
},
245+
],
246+
buttonText: serverUrl ? "View Server" : undefined,
247+
buttonUrl: serverUrl,
248+
baseUrl,
249+
}),
250+
});
251+
}
252+
211253
type BuildFailureAlertOptions = {
212254
to: string;
213255
serviceId: string;
@@ -451,6 +493,9 @@ export async function deliverNotificationEmail(
451493
case "manual_recovery.required":
452494
await sendManualRecoveryRequiredAlert({ ...event, to });
453495
return;
496+
case "server.resource_usage":
497+
await sendServerResourceUsageAlert({ ...event, to });
498+
return;
454499
case "build.failed":
455500
await sendBuildFailureAlert({ ...event, to });
456501
return;

‎web/lib/inngest/events/notification.ts‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,16 @@ export type NotificationEvent =
1515
impactedReplicas: number;
1616
serviceNames: string[];
1717
}
18+
| {
19+
kind: "server.resource_usage";
20+
occurrenceId: string;
21+
serverId: string;
22+
serverName: string;
23+
resource: "cpu" | "memory" | "disk";
24+
usagePercent: number;
25+
thresholdPercent: number;
26+
detectedAt: string;
27+
}
1828
| {
1929
kind: "build.failed";
2030
occurrenceId: string;

‎web/lib/inngest/functions/crons.ts‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { cleanupOldBackups, runScheduledBackups } from "@/lib/backup-scheduler";
1010
import { checkAndPersistControlPlaneUpdate } from "@/lib/control-plane-updates";
1111
import { cleanupReadNotifications } from "@/lib/notifications";
1212
import { cleanupRegistryArtifactsDaily } from "@/lib/registry-retention";
13+
import { evaluateServerResourceAlerts } from "@/lib/server-resource-alerts";
1314
import { cleanupOldServiceCommands } from "@/lib/service-command-retention";
1415
import {
1516
checkAndRecoverStaleServers,
@@ -70,6 +71,19 @@ export const autoscalingCheck = inngest.createFunction(
7071
},
7172
);
7273

74+
export const resourceUsageCheck = inngest.createFunction(
75+
{
76+
id: "cron-resource-usage-check",
77+
triggers: [cron("* * * * *")],
78+
singleton: { mode: "skip" },
79+
},
80+
async ({ step }) => {
81+
await step.run("evaluate-server-resource-alerts", () =>
82+
evaluateServerResourceAlerts(),
83+
);
84+
},
85+
);
86+
7387
export const scheduledDeploymentsCheck = inngest.createFunction(
7488
{
7589
id: "cron-scheduled-deployments",

‎web/lib/inngest/functions/index.ts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ export {
99
controlPlaneUpdateCheck,
1010
notificationRetention,
1111
previewReconciliation,
12+
resourceUsageCheck,
1213
serviceCommandRetention,
1314
serviceCronDispatcher,
1415
oldBackupsCleanup,

0 commit comments

Comments
 (0)