Unverified Commit 7507cfaa authored by Lemon-miaow's avatar Lemon-miaow
Browse files

Revert "feat(panel): wire role-switcher into the app shell"

This reverts commit 50b8487f.
parent 29f5341c
Loading
Loading
Loading
Loading
+0 −6
Changes for panel/src/App.tsx: 0 added lines, 6 removed lines.
Original line number Diff line number Diff line
import { BrowserRouter, Routes, Route, Navigate } from "react-router-dom";
import { ThemeProvider } from "@/lib/theme";
import { TierProvider } from "@/lib/tier";
import { ViewModeProvider } from "@/lib/viewmode-store";
import { AppShell } from "@/components/AppShell";
import { RequireAdmin } from "@/components/RequireAdmin";
import { RequireAuth } from "@/components/RequireAuth";
@@ -25,10 +24,6 @@ export default function App() {
  return (
    <ThemeProvider>
      <TierProvider>
        {/* ViewModeProvider sits inside TierProvider (it reads the live is_admin flag)
            and outside BrowserRouter (it holds no route state; the switcher navigates
            via useNavigate from within the router). */}
        <ViewModeProvider>
        <BrowserRouter>
        <Routes>
          {/* Pre-app local-password surfaces (spec §B1). They sit OUTSIDE
@@ -69,7 +64,6 @@ export default function App() {
          </Route>
        </Routes>
      </BrowserRouter>
      </ViewModeProvider>
    </TierProvider>
  </ThemeProvider>
  );
+7 −16
Changes for panel/src/components/AppShell.tsx: 7 added lines, 16 removed lines.
Original line number Diff line number Diff line
@@ -5,11 +5,8 @@ import { useTranslation } from "react-i18next";
import * as SelectPrimitive from "@radix-ui/react-select";
import { cn } from "@/lib/utils";
import { useTier } from "@/lib/tier";
import { type NavSection } from "@/lib/nav";
import { sectionsForView } from "@/lib/viewmode";
import { useViewMode } from "@/lib/viewmode-store";
import { visibleSections, type NavSection } from "@/lib/nav";
import { Select, SelectContent, SelectItem } from "@/components/ui/select";
import { RoleSwitcher } from "@/components/RoleSwitcher";
import { useTheme } from "@/lib/theme";
import { api } from "@/lib/api";

@@ -141,14 +138,11 @@ function ThemeToggle() {

export function AppShell() {
  const { isAdmin } = useTier();
  const { view } = useViewMode();
  const { t, i18n } = useTranslation("navigation");
  // Sections are derived from is_admin AND the chosen home: visibleSections drops
  // every admin-gated section for a non-admin (fail-closed — false while /me loads or
  // on failure), then the role-switcher's view narrows further to the home the admin
  // is currently viewing. sectionsForView can only ever narrow, never widen, so an
  // admin who steps down to the User-home sees a plain user's sidebar.
  const sections = sectionsForView(view, isAdmin);
  // Sections are derived purely from is_admin: User-Side always, Admin/SysAdmin
  // only for admins. isAdmin is fail-closed (false while /me loads or on failure),
  // so admin sections appear only once identity is confirmed.
  const sections = visibleSections(isAdmin);

  // Sync document metadata with the active language.
  useEffect(() => {
@@ -182,13 +176,10 @@ export function AppShell() {
            </span>
          </div>

          {/* 2. Role switcher (admin-only; renders nothing for a plain user) */}
          <RoleSwitcher />

          {/* 3. Profile Card */}
          {/* 2. Profile Card */}
          <UserStrip />

          {/* 4. Branding Sign-off (sits tight at the absolute bottom with leading-tight and centered) */}
          {/* 3. Branding Sign-off (sits tight at the absolute bottom with leading-tight and centered) */}
          <div className="px-1 text-[10px] text-muted-foreground/50 leading-tight text-center">
            {t("common:brand_tagline")}
          </div>
+0 −69
Changes for panel/src/components/RoleSwitcher.tsx: 0 added lines, 69 removed lines.
Original line number Diff line number Diff line
import { useNavigate } from "react-router-dom";
import { useTranslation } from "react-i18next";
import { Eye } from "lucide-react";
import { useTier } from "@/lib/tier";
import { useViewMode } from "@/lib/viewmode-store";
import {
  availableViewModes,
  effectiveViewMode,
  landingPathForView,
  viewModeLabelKey,
  type ViewMode,
} from "@/lib/viewmode";
import {
  Select,
  SelectContent,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from "@/components/ui/select";

// The avatar role-switcher (DESIGN-WEB-3SIDES): an admin can move between the three
// homes — User-Side, Admin-Side, SysAdmin-Side — viewing the app as each. The control
// is admin-only and renders nothing for everyone else: availableViewModes returns just
// ["user"] for a non-admin (and during the fail-closed loading window), so there is
// no home to switch into and the whole widget collapses. This is UX, not a gate — the
// sidebar narrowing it drives only declutters; every /admin and /ops call is 403-gated
// server-side regardless of the chosen view.
export function RoleSwitcher() {
  const { isAdmin } = useTier();
  const { view, setView } = useViewMode();
  const navigate = useNavigate();
  const { t } = useTranslation("navigation");

  const modes = availableViewModes(isAdmin);
  // Only an admin has more than one home; everyone else gets no switcher at all.
  if (modes.length <= 1) return null;

  function onChange(raw: string) {
    // The menu only offers entitled homes, but re-gate anyway: the value crosses a
    // string boundary and effectiveViewMode is the single authority on what is allowed.
    const next = effectiveViewMode(raw as ViewMode, isAdmin);
    setView(next);
    // Land on the chosen home's root so switching shows a meaningful page rather than
    // whatever route the principal happened to be on.
    navigate(landingPathForView(next));
  }

  return (
    <Select value={view} onValueChange={onChange}>
      <SelectTrigger
        className="h-8 gap-2 px-2.5 text-[13px]"
        aria-label={t("view_switch_label")}
        title={t("view_switch_label")}
      >
        <span className="flex min-w-0 items-center gap-2">
          <Eye className="h-3.5 w-3.5 shrink-0 text-muted-foreground" />
          <SelectValue />
        </span>
      </SelectTrigger>
      <SelectContent align="start">
        {modes.map((m) => (
          <SelectItem key={m} value={m}>
            {t(viewModeLabelKey(m))}
          </SelectItem>
        ))}
      </SelectContent>
    </Select>
  );
}
+1 −5
Changes for panel/src/i18n/resources/en-US/navigation.json: 1 added line, 5 removed lines.
Original line number Diff line number Diff line
@@ -6,9 +6,5 @@
  "admin_servers": "Servers",
  "admin_images": "Images",
  "sysadmin_section": "SysAdmin",
  "sysadmin_fleet": "Fleet",
  "view_switch_label": "Viewing as",
  "view_user": "User",
  "view_admin": "Admin",
  "view_ops": "SysAdmin"
  "sysadmin_fleet": "Fleet"
}
+1 −5
Changes for panel/src/i18n/resources/zh-CN/navigation.json: 1 added line, 5 removed lines.
Original line number Diff line number Diff line
@@ -6,9 +6,5 @@
  "admin_servers": "服务器",
  "admin_images": "镜像",
  "sysadmin_section": "系统管理",
  "sysadmin_fleet": "全平台服务器",
  "view_switch_label": "当前视图",
  "view_user": "用户",
  "view_admin": "管理",
  "view_ops": "系统管理"
  "sysadmin_fleet": "全平台服务器"
}
Loading