Compare commits

...
2 Commits
Author SHA1 Message Date
nessi 5040ac2f16 feat: change agent installer to use restart instead of enable --now for systemd service activation
CI / frontend (push) Failing after 27s
CI / backend (push) Failing after 2s
Replace `systemctl enable --now` with separate `systemctl enable` and `systemctl restart` commands to ensure agent service restarts on reinstall rather than silently failing when service already exists
2026-07-09 14:58:22 +02:00
nessi 926a1b8165 feat: redesign sidebar navigation with grouped sections, improved visual hierarchy, and enhanced active state indicators
Restructure navigation into three logical groups (Operate, Network, Security) with uppercase section labels, move system-related items (Jobs, Audit, Users, Settings) to separate bottom section with border separator, add NF logo badge next to app title in header, extract SidebarLink component with refined styling including left accent bar for active items, update active state to
2026-07-09 14:58:15 +02:00
2 changed files with 83 additions and 35 deletions
+2 -1
View File
@@ -730,7 +730,8 @@ EOF
if command -v systemctl >/dev/null 2>&1; then if command -v systemctl >/dev/null 2>&1; then
systemctl daemon-reload systemctl daemon-reload
systemctl enable --now "$SERVICE_NAME.service" systemctl enable "$SERVICE_NAME.service"
systemctl restart "$SERVICE_NAME.service"
systemctl status "$SERVICE_NAME.service" --no-pager || true systemctl status "$SERVICE_NAME.service" --no-pager || true
else else
echo "systemctl not found. Run manually: python3 $INSTALL_DIR/nexafabric-agent.py --config $CONFIG_DIR/config.json" echo "systemctl not found. Run manually: python3 $INSTALL_DIR/nexafabric-agent.py --config $CONFIG_DIR/config.json"
+68 -21
View File
@@ -18,31 +18,76 @@ import {
SquareStack, SquareStack,
Sun, Sun,
Users, Users,
type LucideIcon,
} from "lucide-react"; } from "lucide-react";
import { useEffect } from "react"; import { useEffect } from "react";
import { token } from "../api/client"; import { token } from "../api/client";
import { useTheme } from "../stores/theme"; import { useTheme } from "../stores/theme";
const nav = [ const navGroups = [
{
label: "Operate",
items: [
{ to: "/", label: "Dashboard", icon: LayoutDashboard }, { to: "/", label: "Dashboard", icon: LayoutDashboard },
{ to: "/clusters", label: "Clusters", icon: Server }, { to: "/clusters", label: "Clusters", icon: Server },
{ to: "/nodes", label: "Nodes", icon: Activity }, { to: "/nodes", label: "Nodes", icon: Activity },
{ to: "/workloads", label: "VMs/LXCs", icon: Blocks }, { to: "/workloads", label: "VMs/LXCs", icon: Blocks },
],
},
{
label: "Network",
items: [
{ to: "/networks", label: "Networks", icon: Network }, { to: "/networks", label: "Networks", icon: Network },
{ to: "/ipam", label: "IPAM", icon: Database }, { to: "/ipam", label: "IPAM", icon: Database },
{ to: "/services", label: "Service Catalog", icon: SquareStack },
{ to: "/tenants", label: "Tenants", icon: BriefcaseBusiness }, { to: "/tenants", label: "Tenants", icon: BriefcaseBusiness },
],
},
{
label: "Security",
items: [
{ to: "/security-groups", label: "Security Groups", icon: Shield }, { to: "/security-groups", label: "Security Groups", icon: Shield },
{ to: "/policies", label: "Policies", icon: GitBranch }, { to: "/policies", label: "Policies", icon: GitBranch },
{ to: "/services", label: "Service Catalog", icon: SquareStack },
{ to: "/designer", label: "Policy Designer", icon: LockKeyhole }, { to: "/designer", label: "Policy Designer", icon: LockKeyhole },
{ to: "/firewall", label: "Firewall Preview", icon: Flame }, { to: "/firewall", label: "Firewall Preview", icon: Flame },
],
},
];
const systemNav = [
{ to: "/jobs", label: "Jobs", icon: ClipboardList }, { to: "/jobs", label: "Jobs", icon: ClipboardList },
{ to: "/audit", label: "Audit Logs", icon: BookOpen }, { to: "/audit", label: "Audit Logs", icon: BookOpen },
{ to: "/users", label: "Users", icon: Users }, { to: "/users", label: "Users", icon: Users },
{ to: "/settings", label: "Settings", icon: Settings }, { to: "/settings", label: "Settings", icon: Settings },
]; ];
function SidebarLink({ item }: { item: { to: string; label: string; icon: LucideIcon } }) {
const Icon = item.icon;
return (
<NavLink
key={item.to}
to={item.to}
end={item.to === "/"}
className={({ isActive }) =>
`group relative flex h-9 items-center gap-3 rounded-md px-3 text-sm transition-colors ${
isActive
? "bg-accent/10 text-accent dark:bg-accent/15"
: "text-slate-600 hover:bg-slate-100 hover:text-slate-950 dark:text-slate-300 dark:hover:bg-slate-800/80 dark:hover:text-white"
}`
}
>
{({ isActive }) => (
<>
<span className={`absolute left-0 h-5 w-0.5 rounded-r-full ${isActive ? "bg-accent" : "bg-transparent"}`} />
<Icon size={17} className={isActive ? "text-accent" : "text-slate-400 group-hover:text-slate-700 dark:group-hover:text-slate-200"} />
<span className="truncate">{item.label}</span>
</>
)}
</NavLink>
);
}
export function Layout() { export function Layout() {
const navigate = useNavigate(); const navigate = useNavigate();
const { dark, toggle } = useTheme(); const { dark, toggle } = useTheme();
@@ -60,29 +105,31 @@ export function Layout() {
<div className="min-h-screen bg-canvas text-slate-900 dark:text-slate-100"> <div className="min-h-screen bg-canvas text-slate-900 dark:text-slate-100">
<aside className="fixed inset-y-0 left-0 hidden w-64 border-r border-border bg-panel md:block"> <aside className="fixed inset-y-0 left-0 hidden w-64 border-r border-border bg-panel md:block">
<div className="flex h-16 items-center border-b border-border px-5"> <div className="flex h-16 items-center border-b border-border px-5">
<div className="flex items-center gap-3">
<div className="grid h-9 w-9 place-items-center rounded-md bg-accent text-sm font-semibold text-white">NF</div>
<div> <div>
<div className="text-lg font-semibold">NexaFabric</div> <div className="text-base font-semibold leading-5">NexaFabric</div>
<div className="text-xs text-slate-500 dark:text-slate-400">Control Plane</div> <div className="text-xs text-slate-500 dark:text-slate-400">Control Plane</div>
</div> </div>
</div> </div>
<nav className="h-[calc(100vh-4rem)] overflow-y-auto p-3"> </div>
{nav.map((item) => { <nav className="flex h-[calc(100vh-4rem)] flex-col overflow-y-auto p-3">
const Icon = item.icon; <div className="space-y-5">
return ( {navGroups.map((group) => (
<NavLink <section key={group.label}>
key={item.to} <div className="mb-2 px-3 text-[11px] font-semibold uppercase tracking-wider text-slate-400 dark:text-slate-500">{group.label}</div>
to={item.to} <div className="space-y-1">
className={({ isActive }) => {group.items.map((item) => <SidebarLink key={item.to} item={item} />)}
`mb-1 flex h-10 items-center gap-3 rounded-md px-3 text-sm ${ </div>
isActive ? "bg-accent text-white" : "text-slate-600 hover:bg-slate-100 dark:text-slate-300 dark:hover:bg-slate-800" </section>
}` ))}
} </div>
> <div className="mt-auto border-t border-border pt-3">
<Icon size={18} /> <div className="mb-2 px-3 text-[11px] font-semibold uppercase tracking-wider text-slate-400 dark:text-slate-500">System</div>
<span>{item.label}</span> <div className="space-y-1">
</NavLink> {systemNav.map((item) => <SidebarLink key={item.to} item={item} />)}
); </div>
})} </div>
</nav> </nav>
</aside> </aside>
<main className="md:pl-64"> <main className="md:pl-64">