TheBird

Mirror.jsx

2.6 kB · jsx · 98 lines

1import { Fragment, useEffect, useState } from "react";23const EVERY = 60 * 1000;45const LINE = /^(\S+)\s+(DEBUG|INFO|WARNING|ERROR|CRITICAL)\s+(.*)$/;67export const when = (seconds) => (seconds ? new Date(seconds * 1000).toISOString().replace("T", " ").slice(0, 19) + " UTC" : "never");89export const ago = (seconds) => {10  if (!seconds) return "never";11  const gap = Math.max(0, Math.floor(Date.now() / 1000 - seconds));12  if (gap < 90) return `${gap} s ago`;13  if (gap < 5400) return `${Math.round(gap / 60)} min ago`;14  if (gap < 172800) return `${(gap / 3600).toFixed(1)} h ago`;15  return `${Math.round(gap / 86400)} d ago`;16};1718export const bytes = (n) => (n == null ? "" : n < 1e6 ? `${(n / 1e3).toFixed(0)} kB` : n < 1e9 ? `${(n / 1e6).toFixed(1)} MB` : `${(n / 1e9).toFixed(2)} GB`);1920async function grab(path) {21  try {22    const reply = await fetch(path, { cache: "no-store" });23    return reply.ok ? await reply.json() : null;24  } catch {25    return null;26  }27}2829export function useMirror(path) {30  const [seen, setSeen] = useState(null);3132  useEffect(() => {33    let live = true;34    let timer = 0;35    const refresh = async () => {36      const data = await grab(path);37      if (live) setSeen({ data });38    };39    const run = () => {40      clearInterval(timer);41      if (document.hidden) return;42      void refresh();43      timer = setInterval(refresh, EVERY);44    };45    document.addEventListener("visibilitychange", run);46    run();47    return () => {48      live = false;49      clearInterval(timer);50      document.removeEventListener("visibilitychange", run);51    };52  }, [path]);5354  return seen;55}5657export function Waiting({ seen }) {58  return <p className="fine">{seen ? "No data yet." : "Loading"}</p>;59}6061export function Rows({ list }) {62  return (63    <table>64      <tbody>65        {list.map(([name, value]) => (66          <tr key={name}>67            <th>{name}</th>68            <td>{String(value ?? "")}</td>69          </tr>70        ))}71      </tbody>72    </table>73  );74}7576export function Code({ lines }) {77  return (78    <pre>79      <code>80        {lines.map((line, i) => {81          const hit = LINE.exec(line);82          return (83            <Fragment key={i}>84              {hit ? (85                <span className={hit[2].toLowerCase()}>86                  <span className="time">{hit[1]}</span> <span className="level">{hit[2].padEnd(7)}</span> <span className="text">{hit[3]}</span>87                </span>88              ) : (89                <span className="text">{line}</span>90              )}91              {i < lines.length - 1 ? "\n" : null}92            </Fragment>93          );94        })}95      </code>96    </pre>97  );98}