ostool-server 0.7.2

Server for managing development boards, serial sessions, and TFTP artifacts
import { lazy, Suspense, useEffect, useState } from "react";
import {
  BrowserRouter,
  NavLink,
  Navigate,
  Route,
  Routes,
} from "react-router-dom";
import {
  LayoutDashboardIcon,
  CircuitBoardIcon,
  FileIcon,
  ClockIcon,
  NetworkIcon,
  SettingsIcon,
  MenuIcon,
} from "lucide-react";
import { admin, useConnection, useErrors } from "@/api/events";
import { Button } from "@/components/ui/button";
import { Skeleton } from "@/components/ui/skeleton";
import { Toaster } from "@/components/ui/sonner";
import { Notice } from "@/components/forms";
const Boards = lazy(() => import("./pages/Boards"));
const BoardEditor = lazy(() => import("./pages/BoardEditor"));
const Overview = lazy(() => import("./pages/Overview"));
const Dtbs = lazy(() => import("./pages/Dtbs"));
const Sessions = lazy(() => import("./pages/Sessions"));
const Tftp = lazy(() =>
  import("./pages/Settings").then((m) => ({ default: m.Tftp })),
);
const Server = lazy(() =>
  import("./pages/Settings").then((m) => ({ default: m.Server })),
);
const navigation = [
  ["overview", "总览", LayoutDashboardIcon],
  ["boards", "开发板", CircuitBoardIcon],
  ["dtbs", "DTB", FileIcon],
  ["sessions", "会话租约", ClockIcon],
  ["tftp", "TFTP", NetworkIcon],
  ["server", "Server 配置", SettingsIcon],
] as const;
export default function App() {
  const connection = useConnection(),
    errors = useErrors();
  const [menu, setMenu] = useState(false);
  useEffect(() => {
    admin.connect();
    return admin.disconnect;
  }, []);
  return (
    <BrowserRouter basename="/admin">
      <div className="app-shell">
        <header className="mobile-header">
          <strong>ostool</strong>
          <Button
            variant="ghost"
            aria-label="切换导航"
            onClick={() => setMenu((v) => !v)}
          >
            <MenuIcon />
          </Button>
        </header>
        <aside className={menu ? "sidebar expanded" : "sidebar"}>
          <NavLink to="/overview" className="brand">
            ostool
          </NavLink>
          <nav aria-label="主导航">
            {navigation.map(([path, label, Icon]) => (
              <NavLink
                key={path}
                to={`/${path}`}
                onClick={() => setMenu(false)}
              >
                <Icon aria-hidden="true" />
                {label}
              </NavLink>
            ))}
          </nav>
          <div className="connection" role="status">
            <span
              className={
                connection === "connected"
                  ? "connection-dot connected"
                  : "connection-dot"
              }
            />
            {connection === "connected"
              ? "实时已连接"
              : connection === "connecting"
                ? "正在连接…"
                : "连接中断,正在重连…"}
            {connection !== "connected" && (
              <Button variant="ghost" size="sm" onClick={admin.restart}>
                重连
              </Button>
            )}
          </div>
        </aside>
        <main>
          {connection === "reconnecting" && (
            <Notice>
              连接暂时中断,当前内容和输入已保留,恢复后自动同步。
            </Notice>
          )}
          {Object.entries(errors).map(([topic, error]) => (
            <Notice key={topic}>
              {topic}:{error}
            </Notice>
          ))}
          <Suspense fallback={<Skeleton className="h-48 w-full" />}>
            <Routes>
              <Route path="/" element={<Navigate replace to="/overview" />} />
              <Route path="/overview" element={<Overview />} />
              <Route path="/boards" element={<Boards />} />
              <Route path="/boards/new" element={<BoardEditor />} />
              <Route path="/boards/:boardId" element={<BoardEditor />} />
              <Route path="/dtbs" element={<Dtbs />} />
              <Route path="/sessions" element={<Sessions />} />
              <Route path="/tftp" element={<Tftp />} />
              <Route path="/server" element={<Server />} />
              <Route
                path="*"
                element={<Notice>页面不存在。请选择左侧管理模块。</Notice>}
              />
            </Routes>
          </Suspense>
        </main>
        <Toaster
          theme="light"
          position="top-right"
          duration={2500}
          richColors
          closeButton
        />
      </div>
    </BrowserRouter>
  );
}