"use client"; import { useMemo } from "react"; import Link from "next/link"; import { useQuery } from "@tanstack/react-query"; import { AppShell } from "@/components/app-shell"; import { Card } from "@/components/ui/card"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "@/components/ui/table"; import { fetchScriptsCatalog } from "@/lib/api/scripts"; /* * /scripts — catalog of everything under scripts/, rendered as the * approved IA-redesign table (name mono · role · status chip · run/source * ghost button). * * The single source of truth is still `scripts/SCRIPTS.md` (CLAUDE.md mandates * updating it on every script change), served verbatim by * GET /api/scripts/catalog. We parse its markdown tables into structured rows * for display — editing remains git/Gitea only, so the per-row "מקור" button * deep-links to the file in Gitea rather than inventing a run-from-UI mutation. */ type ScriptStatus = "active" | "once" | "archive" | "deleted"; type ScriptRow = { name: string; role: string; status: ScriptStatus; }; const STATUS_LABEL: Record = { active: "פעיל", once: "חד-פעמי", archive: "ארכיון", deleted: "נמחק", }; const STATUS_TONE: Record = { active: { wrap: "bg-success-bg text-success", dot: "bg-success" }, once: { wrap: "bg-info-bg text-info", dot: "bg-info" }, archive: { wrap: "bg-rule-soft text-ink-muted", dot: "bg-ink-muted" }, deleted: { wrap: "bg-danger-bg text-danger", dot: "bg-danger" }, }; // "חד-פעמי" / "one-shot" markers inside the Scheduled column of an active row. const ONCE_RE = /חד-?פעמי|one-?shot|בוצע/; /** * Parse SCRIPTS.md markdown tables into typed rows. The file has three * sections with different shapes; we read the first two columns of each * (name + role) and derive status from the section + scheduling note. */ function parseScripts(md: string): ScriptRow[] { const lines = md.split("\n"); const rows: ScriptRow[] = []; let section: ScriptStatus = "active"; for (const raw of lines) { const line = raw.trim(); if (line.startsWith("## ")) { if (line.includes(".archive") || line.includes("הושלמו")) section = "archive"; else if (line.includes("נמחק")) section = "deleted"; else section = "active"; continue; } if (!line.startsWith("|")) continue; // skip header + separator rows const cells = line .split("|") .slice(1, -1) .map((c) => c.trim()); if (cells.length < 2) continue; if (/^-+$/.test(cells[0].replace(/[-:]/g, "-"))) continue; // separator if (cells[0] === "Script") continue; // header if (!cells[0]) continue; const name = cells[0].replace(/`/g, ""); if (!name) continue; let status: ScriptStatus = section; if (section === "active") { const scheduled = cells[3] ?? ""; status = ONCE_RE.test(scheduled) ? "once" : "active"; } // role: active = Purpose (col 2), archive = Original Purpose (col 1), // deleted = Reason (col 1). const role = section === "active" ? cells[2] ?? cells[1] ?? "" : cells[1] ?? ""; rows.push({ name, role: stripMd(role), status }); } return rows; } // Strip bold/inline-code markdown so the role reads as plain text in a cell. function stripMd(s: string): string { return s.replace(/\*\*/g, "").replace(/`/g, ""); } function StatusChip({ status }: { status: ScriptStatus }) { const tone = STATUS_TONE[status]; return ( {STATUS_LABEL[status]} ); } export default function ScriptsPage() { const { data, isLoading, isError, error } = useQuery({ queryKey: ["scripts-catalog"], queryFn: ({ signal }) => fetchScriptsCatalog(signal), }); const rows = useMemo( () => (data?.content ? parseScripts(data.content) : []), [data], ); const lastModified = data?.last_modified != null ? new Date(data.last_modified * 1000).toLocaleDateString("he-IL", { year: "numeric", month: "long", day: "numeric", }) : null; const giteaBase = data?.gitea_url ?? null; return (

סקריפטים

סקריפטי-תחזוקה ותפעול. מקור-האמת הוא{" "} scripts/SCRIPTS.md {" "} — עריכה דרך git, לא מכאן.

{isLoading ? ( טוען קטלוג… ) : isError ? ( שגיאה בטעינת הקטלוג: {(error as Error)?.message ?? "לא ידוע"} ) : ( שם הסקריפט תפקיד סטטוס פעולה {rows.map((s) => { const disabled = s.status === "archive" || s.status === "deleted"; const href = giteaBase ? `${giteaBase.replace(/\/$/, "")}/${s.name}` : null; return ( {s.name} {s.role} {disabled || !href ? ( ) : ( מקור )} ); })}
{lastModified ? (

עודכן לאחרונה: {lastModified}

) : null}
)}
); }