feat: add navigation for remote machines

This commit is contained in:
Developer
2026-07-14 21:06:25 +00:00
parent 37533dd219
commit 4562a9dfca
3 changed files with 68 additions and 8 deletions
+11 -5
View File
@@ -27,7 +27,10 @@ import { usePersistentState } from "./hooks/usePersistentState";
import { useIsMobile } from "./hooks/useIsMobile";
import { useServiceInstances } from "./hooks/useServices";
import { useDashboards } from "./hooks/useDashboards";
import { configuredNavEntries } from "./integrations/navEntries";
import {
configuredNavEntries,
remoteMachineNavEntries,
} from "./integrations/navEntries";
import { Button } from "@/components/ui/button";
import {
Tooltip,
@@ -97,10 +100,13 @@ function useNavItems() {
const configuredTypes = new Set(
services.filter((s) => s.enabled).map((s) => s.service_type),
);
const serviceEntries = configuredNavEntries(configuredTypes).map((e) => ({
path: e.path,
label: e.label,
icon: e.icon,
const serviceEntries = [
...configuredNavEntries(configuredTypes),
...remoteMachineNavEntries(services),
].map((entry) => ({
path: entry.path,
label: entry.label,
icon: entry.icon,
}));
const dashboardEntries = dashboards.map((d) => ({
path: `/d/${d.slug}`,
@@ -1,5 +1,9 @@
import { describe, expect, it } from "vitest";
import { configuredNavEntries, SERVICE_TYPE_NAV_ENTRIES } from "../navEntries";
import {
configuredNavEntries,
remoteMachineNavEntries,
SERVICE_TYPE_NAV_ENTRIES,
} from "../navEntries";
describe("navEntries", () => {
it("returns no entries when no types are configured", () => {
@@ -13,8 +17,33 @@ describe("navEntries", () => {
expect(entries[0].path).toBe("/services/jellyfin");
});
it("does not expose remote machines as a top-level entry", () => {
expect(configuredNavEntries(new Set(["remote_machine"]))).toEqual([]);
it("creates one top-level entry per enabled remote machine", () => {
const entries = remoteMachineNavEntries([
{
id: "storage",
name: "Storage",
service_type: "remote_machine",
enabled: true,
},
{
id: "worker",
name: "Worker",
service_type: "remote_machine",
enabled: true,
},
{
id: "disabled",
name: "Disabled",
service_type: "remote_machine",
enabled: false,
},
]);
expect(entries.map((entry) => entry.label)).toEqual(["Storage", "Worker"]);
expect(entries.map((entry) => entry.path)).toEqual([
"/services/remote_machine/storage",
"/services/remote_machine/worker",
]);
});
it("returns all observability entries", () => {
+25
View File
@@ -12,6 +12,7 @@ import {
GanttChartSquare,
Magnet,
Monitor,
Server,
Users,
type LucideIcon,
} from "lucide-react";
@@ -24,6 +25,13 @@ export interface NavEntry {
path: string;
}
export interface RemoteMachineNavSource {
id: string;
name: string;
service_type: string;
enabled: boolean;
}
/**
* Static mapping from service type to its conditional nav entry.
* Uses the service type's display name. One entry per type.
@@ -77,3 +85,20 @@ export function configuredNavEntries(configuredTypes: Set<string>): NavEntry[] {
configuredTypes.has(e.serviceType),
);
}
/** One direct sidebar entry for each enabled Remote Machine service. */
export function remoteMachineNavEntries(
services: RemoteMachineNavSource[],
): NavEntry[] {
return services
.filter(
(service) => service.enabled && service.service_type === "remote_machine",
)
.sort((left, right) => left.name.localeCompare(right.name))
.map((service) => ({
serviceType: service.service_type,
label: service.name,
icon: Server,
path: `/services/remote_machine/${encodeURIComponent(service.id)}`,
}));
}