Scaffold Homelab Manager foundation
Monorepo (Express+TS+Drizzle/libSQL server, React+Vite+Tabler web) matching the stack used by ScheduleTaskManager and Sloth Manager. Includes Authentik OIDC login with local admin/operator/viewer roles (first user becomes admin), a generalized audit log, encrypted-at-rest storage for future integration API tokens, the DB schema for all planned modules, and the Tabler-styled app shell/nav. Also ports the Secrets (expiry tracker) and IP Addresses (IPAM) modules from Sloth Manager onto the new stack. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
commit
6bd2ed52c1
52 files changed
+8103
No files matched your search
@@ -0,0 +1,103 @@
|
||||
import { useState, type ReactNode } from "react";
|
||||
import { NavLink } from "react-router-dom";
|
||||
import {
|
||||
IconLayoutDashboard,
|
||||
IconServer2,
|
||||
IconWorld,
|
||||
IconNetwork,
|
||||
IconKey,
|
||||
IconPlugConnected,
|
||||
IconUsers,
|
||||
IconHistory,
|
||||
IconSettings,
|
||||
IconMenu2,
|
||||
} from "@tabler/icons-react";
|
||||
import type { CurrentUser } from "../api/client";
|
||||
|
||||
interface NavItem {
|
||||
to: string;
|
||||
label: string;
|
||||
icon: ReactNode;
|
||||
minRole?: "operator" | "admin";
|
||||
}
|
||||
|
||||
const NAV_ITEMS: NavItem[] = [
|
||||
{ to: "/", label: "Dashboard", icon: <IconLayoutDashboard size={20} /> },
|
||||
{ to: "/servers", label: "Servers & Tasks", icon: <IconServer2 size={20} /> },
|
||||
{ to: "/dns", label: "DNS", icon: <IconWorld size={20} /> },
|
||||
{ to: "/ipam", label: "IP Addresses", icon: <IconNetwork size={20} /> },
|
||||
{ to: "/secrets", label: "Secrets", icon: <IconKey size={20} /> },
|
||||
{ to: "/integrations", label: "Integrations", icon: <IconPlugConnected size={20} /> },
|
||||
{ to: "/users", label: "Users", icon: <IconUsers size={20} />, minRole: "admin" },
|
||||
{ to: "/audit-log", label: "Audit Log", icon: <IconHistory size={20} />, minRole: "operator" },
|
||||
{ to: "/settings", label: "Settings", icon: <IconSettings size={20} />, minRole: "admin" },
|
||||
];
|
||||
|
||||
const roleRank: Record<CurrentUser["role"], number> = { viewer: 0, operator: 1, admin: 2 };
|
||||
|
||||
export default function AppShell({ user, children }: { user: CurrentUser; children: ReactNode }) {
|
||||
const [sidebarOpen, setSidebarOpen] = useState(false);
|
||||
const visibleItems = NAV_ITEMS.filter(
|
||||
(item) => !item.minRole || roleRank[user.role] >= roleRank[item.minRole],
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="page">
|
||||
<aside className="navbar navbar-vertical navbar-expand-lg" data-bs-theme="dark">
|
||||
<div className="container-fluid">
|
||||
<button
|
||||
type="button"
|
||||
className="navbar-toggler"
|
||||
onClick={() => setSidebarOpen((open) => !open)}
|
||||
aria-label="Toggle navigation"
|
||||
>
|
||||
<IconMenu2 size={20} />
|
||||
</button>
|
||||
<h1 className="navbar-brand navbar-brand-autodark">
|
||||
<NavLink to="/">Homelab Manager</NavLink>
|
||||
</h1>
|
||||
<div className={`navbar-collapse collapse ${sidebarOpen ? "show" : ""}`}>
|
||||
<ul className="navbar-nav pt-lg-3">
|
||||
{visibleItems.map((item) => (
|
||||
<li className="nav-item" key={item.to}>
|
||||
<NavLink
|
||||
to={item.to}
|
||||
end={item.to === "/"}
|
||||
className="nav-link"
|
||||
onClick={() => setSidebarOpen(false)}
|
||||
>
|
||||
<span className="nav-link-icon d-md-none d-lg-inline-block">{item.icon}</span>
|
||||
<span className="nav-link-title">{item.label}</span>
|
||||
</NavLink>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<div className="page-wrapper">
|
||||
<div className="page-header d-print-none">
|
||||
<div className="container-xl">
|
||||
<div className="row g-2 align-items-center">
|
||||
<div className="col">
|
||||
<span className="text-secondary">
|
||||
Signed in as {user.name ?? user.email ?? user.sub} · {user.role}
|
||||
</span>
|
||||
</div>
|
||||
<div className="col-auto ms-auto d-print-none">
|
||||
<a className="btn btn-outline-secondary btn-sm" href="/auth/logout">
|
||||
Sign out
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="page-body">
|
||||
<div className="container-xl">{children}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user