Frontend: data-driven nav + service-page tab skeleton + stubs (Slice 4)
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).
This commit is contained in:
@@ -0,0 +1,69 @@
|
||||
/**
|
||||
* Service-page content tab stubs.
|
||||
*
|
||||
* Each stub renders a "coming soon" placeholder. Slices 5–9 replace these with
|
||||
* real operational content lifted from the old top-level pages. All stubs accept
|
||||
* an `instance` prop so the real implementations can scope queries by instance.
|
||||
*/
|
||||
import type { ServiceInstance } from "../../types";
|
||||
import { Alert, AlertDescription } from "@/components/ui/alert";
|
||||
|
||||
function Stub({
|
||||
label,
|
||||
instance,
|
||||
}: {
|
||||
label: string;
|
||||
instance: ServiceInstance;
|
||||
}) {
|
||||
return (
|
||||
<Alert>
|
||||
<AlertDescription>
|
||||
{label} for {instance.name} — coming soon.
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
);
|
||||
}
|
||||
|
||||
export function OverviewTab({ instance }: { instance: ServiceInstance }) {
|
||||
return <Stub label="Service overview" instance={instance} />;
|
||||
}
|
||||
|
||||
export function MediaTab({ instance }: { instance: ServiceInstance }) {
|
||||
return <Stub label="Media" instance={instance} />;
|
||||
}
|
||||
|
||||
export function RequestsTab({ instance }: { instance: ServiceInstance }) {
|
||||
return <Stub label="Requests" instance={instance} />;
|
||||
}
|
||||
|
||||
export function FilesTab({ instance }: { instance: ServiceInstance }) {
|
||||
return <Stub label="Files" instance={instance} />;
|
||||
}
|
||||
|
||||
export function ActionsTab({ instance }: { instance: ServiceInstance }) {
|
||||
return <Stub label="Actions" instance={instance} />;
|
||||
}
|
||||
|
||||
export function JobsTab({ instance }: { instance: ServiceInstance }) {
|
||||
return <Stub label="Backup jobs" instance={instance} />;
|
||||
}
|
||||
|
||||
export function UsersTab({ instance }: { instance: ServiceInstance }) {
|
||||
return <Stub label="Users" instance={instance} />;
|
||||
}
|
||||
|
||||
export function MessagingTab({ instance }: { instance: ServiceInstance }) {
|
||||
return <Stub label="Messaging" instance={instance} />;
|
||||
}
|
||||
|
||||
export function AlertsTab({ instance }: { instance: ServiceInstance }) {
|
||||
return <Stub label="Alerts" instance={instance} />;
|
||||
}
|
||||
|
||||
export function LinksTab({ instance }: { instance: ServiceInstance }) {
|
||||
return <Stub label="Links" instance={instance} />;
|
||||
}
|
||||
|
||||
export function MetricsTab({ instance }: { instance: ServiceInstance }) {
|
||||
return <Stub label="Metrics" instance={instance} />;
|
||||
}
|
||||
Reference in New Issue
Block a user