feat: add navigation for remote machines
This commit is contained in:
+11
-5
@@ -27,7 +27,10 @@ import { usePersistentState } from "./hooks/usePersistentState";
|
|||||||
import { useIsMobile } from "./hooks/useIsMobile";
|
import { useIsMobile } from "./hooks/useIsMobile";
|
||||||
import { useServiceInstances } from "./hooks/useServices";
|
import { useServiceInstances } from "./hooks/useServices";
|
||||||
import { useDashboards } from "./hooks/useDashboards";
|
import { useDashboards } from "./hooks/useDashboards";
|
||||||
import { configuredNavEntries } from "./integrations/navEntries";
|
import {
|
||||||
|
configuredNavEntries,
|
||||||
|
remoteMachineNavEntries,
|
||||||
|
} from "./integrations/navEntries";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import {
|
import {
|
||||||
Tooltip,
|
Tooltip,
|
||||||
@@ -97,10 +100,13 @@ function useNavItems() {
|
|||||||
const configuredTypes = new Set(
|
const configuredTypes = new Set(
|
||||||
services.filter((s) => s.enabled).map((s) => s.service_type),
|
services.filter((s) => s.enabled).map((s) => s.service_type),
|
||||||
);
|
);
|
||||||
const serviceEntries = configuredNavEntries(configuredTypes).map((e) => ({
|
const serviceEntries = [
|
||||||
path: e.path,
|
...configuredNavEntries(configuredTypes),
|
||||||
label: e.label,
|
...remoteMachineNavEntries(services),
|
||||||
icon: e.icon,
|
].map((entry) => ({
|
||||||
|
path: entry.path,
|
||||||
|
label: entry.label,
|
||||||
|
icon: entry.icon,
|
||||||
}));
|
}));
|
||||||
const dashboardEntries = dashboards.map((d) => ({
|
const dashboardEntries = dashboards.map((d) => ({
|
||||||
path: `/d/${d.slug}`,
|
path: `/d/${d.slug}`,
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
import { describe, expect, it } from "vitest";
|
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", () => {
|
describe("navEntries", () => {
|
||||||
it("returns no entries when no types are configured", () => {
|
it("returns no entries when no types are configured", () => {
|
||||||
@@ -13,8 +17,33 @@ describe("navEntries", () => {
|
|||||||
expect(entries[0].path).toBe("/services/jellyfin");
|
expect(entries[0].path).toBe("/services/jellyfin");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("does not expose remote machines as a top-level entry", () => {
|
it("creates one top-level entry per enabled remote machine", () => {
|
||||||
expect(configuredNavEntries(new Set(["remote_machine"]))).toEqual([]);
|
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", () => {
|
it("returns all observability entries", () => {
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import {
|
|||||||
GanttChartSquare,
|
GanttChartSquare,
|
||||||
Magnet,
|
Magnet,
|
||||||
Monitor,
|
Monitor,
|
||||||
|
Server,
|
||||||
Users,
|
Users,
|
||||||
type LucideIcon,
|
type LucideIcon,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
@@ -24,6 +25,13 @@ export interface NavEntry {
|
|||||||
path: string;
|
path: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface RemoteMachineNavSource {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
service_type: string;
|
||||||
|
enabled: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Static mapping from service type to its conditional nav entry.
|
* Static mapping from service type to its conditional nav entry.
|
||||||
* Uses the service type's display name. One entry per type.
|
* 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),
|
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)}`,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user