SPB Git

spb/trouve-ka Public

Trouve-KA — moteur de recherche web indépendant, Québec-first. Crawler distribué, index OpenSearch, ranking bilingue, galerie d'images. En prod : www.trouve-ka.com

Python 76.8% TypeScript 15.7% SQL 3.9% Shell 1.4% CSS 1.3% Dockerfile 0.7%
3.9 KB · 141 lines tsx
Raw Blame History
1/**2 * Trouve-KA — panneau client d'état du moteur (auto-refresh 10 s)3 * Author: Simon-Pierre Boucher4 * Contact: contact@spboucher.ai5 */67"use client";89import { useEffect, useState } from "react";10import { Stat } from "@/components/stat";11import { Badge } from "@/components/ui/badge";12import { formatNumber } from "@/lib/format";13import type { StatusResponse } from "@/lib/types";1415const REFRESH_MS = 10_000;1617function crawlerBadge(state: string) {18  if (state === "running") {19    return (20      <span className="stat-chip">21        <span className="pulse-dot" aria-hidden="true" />22        Robot en marche23      </span>24    );25  }26  if (state === "paused") {27    return <Badge variant="warning">Robot en pause</Badge>;28  }29  return <Badge>Robot&nbsp;: {state || "état inconnu"}</Badge>;30}3132export function StatusPanel() {33  const [status, setStatus] = useState<StatusResponse | null>(null);34  const [error, setError] = useState(false);35  const [updatedAt, setUpdatedAt] = useState<string>("");3637  useEffect(() => {38    let active = true;3940    async function load() {41      try {42        const res = await fetch("/api/status");43        if (!res.ok) throw new Error(`Erreur ${res.status}`);44        const data = (await res.json()) as StatusResponse;45        if (!active) return;46        setStatus(data);47        setError(false);48        setUpdatedAt(49          new Intl.DateTimeFormat("fr-CA", {50            hour: "2-digit",51            minute: "2-digit",52            second: "2-digit",53          }).format(new Date()),54        );55      } catch {56        if (active) setError(true);57      }58    }5960    load();61    const id = setInterval(load, REFRESH_MS);62    return () => {63      active = false;64      clearInterval(id);65    };66  }, []);6768  if (error && !status) {69    return (70      <p className="mt-8 text-ink-2">71        Impossible de joindre le moteur pour le moment. Réessayez sous peu.72      </p>73    );74  }7576  if (!status) {77    return (78      <div aria-hidden="true" className="mt-8 grid animate-pulse gap-4 sm:grid-cols-3">79        {Array.from({ length: 6 }, (_, i) => (80          <div key={i} className="h-24 rounded-card bg-line" />81        ))}82      </div>83    );84  }8586  return (87    <div className="mt-8">88      {/* Chiffre-vedette : la taille de l'index. */}89      <div className="gk-card p-6">90        <p className="klabel">Pages indexées</p>91        <p className="mt-2 font-display text-5xl font-bold tracking-[-0.03em] text-ink sm:text-6xl">92          {formatNumber(status.pages_indexed)}93        </p>94        <div className="mt-5 flex flex-wrap items-center gap-2.5">95          {crawlerBadge(status.crawler_state)}96          {status.search_ok ? (97            <Badge variant="success">Recherche opérationnelle</Badge>98          ) : (99            <Badge variant="danger">Recherche dégradée</Badge>100          )}101        </div>102      </div>103104      <div className="mt-5 grid gap-4 sm:grid-cols-3">105        <Stat106          label="Domaines connus"107          value={formatNumber(status.domains_count)}108        />109        <Stat110          label="Pages indexées (dernière heure)"111          value={formatNumber(status.indexed_last_hour)}112        />113        <Stat114          label="Pages téléchargées (dernière heure)"115          value={formatNumber(status.fetched_last_hour)}116        />117        <Stat118          label="URL en attente de crawl"119          value={formatNumber(status.frontier_pending)}120        />121        <Stat122          label="URL en cours de crawl"123          value={formatNumber(status.frontier_in_progress)}124        />125        <Stat126          label="Erreurs (dernière heure)"127          value={formatNumber(status.errors_last_hour)}128        />129      </div>130131      <p className="gk-mono mt-5 text-[11px] text-ink-3" aria-live="polite">132        {error133          ? "Mise à jour interrompue — nouvelles tentatives en cours."134          : updatedAt135            ? `Dernière mise à jour à ${updatedAt}.`136            : ""}137      </p>138    </div>139  );140}141