caf6c226ff
The IA shell lands. The static navItems array is replaced by useNavItems(),
which combines useServiceInstances (enabled instances) + useDashboards to
build the nav in spec order: Main Dashboard, named dashboards, conditional
service-type entries (one per configured type; ssh_tasks contributes Files
+ Actions, nextcloud contributes none), Services, Settings.
Legacy top-level routes (/media, /files, /actions, /users, /observability,
/backups, /monitoring, /applications) are removed; a NotFoundPage catch-all
returns 404 (R4.7).
ServicePage is refactored to a tab skeleton: Overview | type-specific
content tabs | Widgets | Config. serviceContentTabs(type) returns the
per-type set (jellyfin=Media+Requests, ssh_tasks=Files+Actions, backups=Jobs,
authentik=Users+Messaging, alertmanager=Alerts, grafana=Links,
prometheus=Metrics, nextcloud=none). Content tabs are stubs ('coming soon');
real content migrates in slices 5-9. Widgets + Config tabs preserve the
existing widget-list and config/secrets editing verbatim.
ServiceTypePage resolves /services/:type (no id) by redirecting to the
first enabled instance; empty state when none.
Instance switcher (Select) appears when >1 ENABLED sibling of the same
type exists (R3.1).
Empty states: Dashboard shows an 'Add a service' CTA when no instances
exist; ServicesPage already had a strong empty state.
Fixes from Slice 4 review:
- B1 (blocker): secret editing regressed because buildInput() hardcoded
secrets:{} after the ConfigBody lift orphaned draftSecrets. Lifted
draftSecrets to the parent ServicePage; buildInput now sends only the
non-blank typed drafts ('leave blank to keep' semantics restored).
- S1: switcher trigger keys off enabled siblings, not total.
New: navEntries.ts + test, dashboards api/hook, service-tabs/ stubs +
index, ServiceTypePage, ServicePage tab skeleton + ConfigBody lift,
Dashboard empty-state CTA, ServicePage tab/switcher/secret-save tests.
Note: this branch is based on main (mobile-responsive-parity is unmerged);
the mobile SheetForm on ServicePage will be re-added when content tabs
get real content (slices 5-9). 84 tests pass (+1 secret-save guard);
lint/build green.
Refs openspec/changes/services-as-hub-ia/ (spec R1-R4/R9, tasks slice 4).
69 lines
1.7 KiB
TypeScript
69 lines
1.7 KiB
TypeScript
/**
|
|
* Per-type content-tab descriptors for the service page skeleton.
|
|
*
|
|
* Each entry names a tab and its component. The service page renders
|
|
* `[Overview, ...contentTabs(type), Widgets, Config]`.
|
|
*/
|
|
import type { ComponentType } from "react";
|
|
import type { ServiceInstance } from "../../types";
|
|
import {
|
|
ActionsTab,
|
|
AlertsTab,
|
|
FilesTab,
|
|
JobsTab,
|
|
LinksTab,
|
|
MediaTab,
|
|
MessagingTab,
|
|
MetricsTab,
|
|
OverviewTab,
|
|
RequestsTab,
|
|
UsersTab,
|
|
} from "./stubs";
|
|
|
|
export type ServiceTabComponent = ComponentType<{ instance: ServiceInstance }>;
|
|
|
|
export interface ContentTab {
|
|
label: string;
|
|
Component: ServiceTabComponent;
|
|
}
|
|
|
|
/** Overview tab (shared across all service types). */
|
|
export const OVERVIEW_TAB: ContentTab = {
|
|
label: "Overview",
|
|
Component: OverviewTab,
|
|
};
|
|
|
|
/**
|
|
* Returns the type-specific content tabs for a service type.
|
|
* Types with no operational content return `[]` (only Overview + Widgets + Config).
|
|
*/
|
|
export function serviceContentTabs(serviceType: string): ContentTab[] {
|
|
switch (serviceType) {
|
|
case "jellyfin":
|
|
return [
|
|
{ label: "Media", Component: MediaTab },
|
|
{ label: "Requests", Component: RequestsTab },
|
|
];
|
|
case "ssh_tasks":
|
|
return [
|
|
{ label: "Files", Component: FilesTab },
|
|
{ label: "Actions", Component: ActionsTab },
|
|
];
|
|
case "backups":
|
|
return [{ label: "Jobs", Component: JobsTab }];
|
|
case "authentik":
|
|
return [
|
|
{ label: "Users", Component: UsersTab },
|
|
{ label: "Messaging", Component: MessagingTab },
|
|
];
|
|
case "alertmanager":
|
|
return [{ label: "Alerts", Component: AlertsTab }];
|
|
case "grafana":
|
|
return [{ label: "Links", Component: LinksTab }];
|
|
case "prometheus":
|
|
return [{ label: "Metrics", Component: MetricsTab }];
|
|
default:
|
|
return [];
|
|
}
|
|
}
|