91 lines · 3.4 KB
import { loadRepoById } from "../repos";
import { notifyQuietly } from "./deliver";
import { accountAdmins, repoAdmins } from "./recipients";

const OFFLINE_AFTER_MS = 10 * 60 * 1000;
/** Runners silent for longer than this are considered abandoned and no longer reported. */
const FORGET_AFTER_MS = 24 * 60 * 60 * 1000;
const RUNNERS_PER_SWEEP = 50;

type RunnerRow = { id: string; name: string; repo_id: string | null; owner_id: string | null; last_seen_at: number };

async function notifyRunnerOffline(runner: RunnerRow): Promise<void> {
  const minutes = Math.round((Date.now() - runner.last_seen_at) / 60_000);
  const details = [
    `Runner "${runner.name}" has not checked in for ${minutes} minutes.`,
    "Queued jobs for it will wait.",
  ];
  if (runner.repo_id) {
    const repo = await loadRepoById(runner.repo_id);
    if (!repo) return;
    await notifyQuietly({
      category: "runner",
      repoId: repo.id,
      accountId: null,
      access: "admin",
      recipients: await repoAdmins(repo.id),
      title: `Runner ${runner.name} is offline (${repo.owner_name}/${repo.name})`,
      details,
      url: `/${repo.owner_name}/${repo.name}/settings/runners`,
    });
    return;
  }
  if (!runner.owner_id) return;
  await notifyQuietly({
    category: "runner",
    repoId: null,
    accountId: runner.owner_id,
    access: "account",
    recipients: await accountAdmins(runner.owner_id),
    title: `Runner ${runner.name} is offline`,