ostool-server 0.7.2

Server for managing development boards, serial sessions, and TFTP artifacts
import { useEffect, useState } from "react";
import { formatLeaseRemaining } from "@/utils/time";
import { useList, useResource } from "@/api/events";
import { api } from "@/api/client";
import { DataTable, Row, Cell, ConfirmAction } from "@/components/forms";
import { Badge } from "@/components/ui/badge";
import { Link } from "react-router-dom";
export default function Sessions() {
  const sessions = useList("sessions"),
    runtimes = useResource("runtimes") ?? {};
  return (
    <>
      <header className="page-header">
        <h1>会话租约</h1>
      </header>
      <p className="page-description">租约释放及失败恢复状态实时同步。</p>
      <DataTable
        headers={["Session", "开发板", "客户端", "创建 / 到期", "状态", "操作"]}
        empty={!sessions.length}
      >
        {sessions.map((s) => (
          <Row key={s.id}>
            <Cell className="mono">{s.id}</Cell>
            <Cell>
              <Link
                className="text-link"
                to={`/boards/${encodeURIComponent(s.board_id)}`}
              >
                {s.board_id}
              </Link>
            </Cell>
            <Cell>{s.client_name ?? "—"}</Cell>
            <Cell>
              <LeaseClock expires={s.expires_at} />
              <small>
                创建 {new Date(s.created_at).toLocaleString("zh-CN")}
              </small>
              <small>
                到期 {new Date(s.expires_at).toLocaleString("zh-CN")}
              </small>
            </Cell>
            <Cell>
              <Badge
                variant={
                  runtimes[s.board_id]?.last_release_error
                    ? "destructive"
                    : "secondary"
                }
              >
                {runtimes[s.board_id]?.last_release_error
                  ? "释放失败,等待重试"
                  : s.state === "releasing"
                    ? "释放中"
                    : "占用中"}
              </Badge>
              <small className="text-destructive">
                {runtimes[s.board_id]?.last_release_error}
              </small>
            </Cell>
            <Cell>
              <ConfirmAction
                label="强制释放"
                description={`请求释放 ${s.board_id} 的会话?服务器会停止任务并断电,清理完成后资源才重新可用。`}
                action={() => api.deleteSession(s.id)}
                disabled={s.state === "releasing"}
              />
            </Cell>
          </Row>
        ))}
      </DataTable>
    </>
  );
}

function LeaseClock({ expires }: { expires: string }) {
  const [now, setNow] = useState(Date.now);
  useEffect(() => {
    const timer = setInterval(() => setNow(Date.now()), 1000);
    return () => clearInterval(timer);
  }, []);
  // Local clock rendering only; this never fetches or changes the lease.
  return <span>{`剩余 ${formatLeaseRemaining(expires, new Date(now))}`}</span>;
}