"use client"; import Link from "next/link"; import { AppShell } from "@/components/app-shell"; import { Card, CardContent } from "@/components/ui/card"; import { Badge } from "@/components/ui/badge"; import { Skeleton } from "@/components/ui/skeleton"; import { useOperations, type OpsService, type OperationsSnapshot, } from "@/lib/api/operations"; function mb(bytes: number): string { return `${Math.round((bytes || 0) / 1024 / 1024)}MB`; } function uptime(ms: number): string { if (!ms) return "—"; const secs = Math.floor((Date.now() - ms) / 1000); if (secs < 60) return `${secs}ש׳`; if (secs < 3600) return `${Math.floor(secs / 60)}ד׳`; if (secs < 86400) return `${Math.floor(secs / 3600)}ש׳`; return `${Math.floor(secs / 86400)}י׳`; } const STATUS_VARIANT: Record = { online: "default", done: "default", approved: "default", completed: "default", stopped: "secondary", pending: "secondary", pending_review: "secondary", running: "outline", processing: "outline", failed: "destructive", errored: "destructive", manual: "destructive", open: "secondary", }; function StatusBadge({ value, count }: { value: string; count?: number }) { return ( {value} {count !== undefined ? {count} : null} ); } const SERVICE_LABELS: Record = { "legal-court-fetch-service": "שירות אחזור פסיקה (דפדפן נט המשפט)", "legal-court-fetch-xvfb": "צג וירטואלי (Xvfb) לדפדפן", "legal-court-fetch-drain": "תזמון: ניקוז תור אחזור (שעתי)", "legal-metadata-drain": "תזמון: חילוץ מטא-דאטה (Gemini, ×15 דק׳)", "legal-halacha-drain": "תזמון: חילוץ הלכות (Claude, ×שעתיים)", "legal-reaper": "מנקה תהליכים-יתומים (נגד דליפות זיכרון)", "legal-chat-service": "שירות צ׳אט אימון (גשר ל-claude CLI)", }; function ServicesSection({ data }: { data: OperationsSnapshot }) { return (

שירותי רקע (pm2)

דמונים ומשימות-תזמון על שרת המארח. "cron" = רץ לפי לוח-זמנים (מציג "stopped" בין הרצות — תקין).

{data.services_error ? (

{data.services_error}

) : data.services.length === 0 ? (

אין שירותים.

) : (
{data.services.map((s: OpsService) => (
{s.cron ? ( {s.cron} ) : null}
{SERVICE_LABELS[s.name] ?? s.name}
{s.name}
{mb(s.memory_bytes)}
↻{s.restarts}
{!s.cron ?
{uptime(s.uptime_ms)}
: null}
))}
)}
); } function StatusRow({ by }: { by: Record }) { const entries = Object.entries(by).filter(([, n]) => n > 0); if (entries.length === 0) return ריק; return (
{entries .sort((a, b) => b[1] - a[1]) .map(([k, n]) => ( ))}
); } function PipelineCard({ title, desc, children, }: { title: string; desc: string; children: React.ReactNode; }) { return (

{title}

{desc}

{children}
); } export default function OperationsPage() { const { data, isLoading, error } = useOperations(); return (

תפעול — מה רץ ברקע

כל מה שמוריד ומנתח אוטומטית: שירותי-המארח, משימות-התזמון, ותורי-העבודה. מתרענן כל 5 שניות.

{error ? ( שגיאה בטעינת מצב התפעול: {String(error)} ) : isLoading || !data ? (
) : ( <>

בתור לחילוץ: {data.pipelines.metadata_extraction.queued}

בתור לחילוץ: {data.pipelines.halacha_extraction.queued}

{data.pipelines.digests.linked}/{data.pipelines.digests.total} מקושרים לפסיקה

אחזורים אחרונים (תור)

{data.pipelines.court_fetch.recent.map((j) => (
{j.citation_raw?.slice(0, 48) || j.case_number_norm} {j.error ? (
{j.error.slice(0, 80)}
) : null}
{(j.tier || "").slice(0, 7)}
))}

נקלטו לאחרונה (מבתי-משפט)

{data.pipelines.ingested_recent.map((r) => (
{r.case_number} {r.created_at?.slice(0, 16).replace("T", " ")}
))} {data.pipelines.ingested_recent.length === 0 ? (

עדיין אין.

) : null}
)}
); }