01527ae4f0
Combine both branches into a single coherent branch: - Full mobile responsive parity (useIsMobile, MobileCardRow, SheetForm, .mobile-touch-target, mobile cards, SheetForm forms, 44px targets, dirty-state confirm, TablePagination, refetchIntervalInBackground). - Full services-as-hub IA (data-driven nav, service-page tab skeleton, new service types, Authentik directory + messaging, named dashboards, legacy routes 404, Observability split, Jellyseerr absorbed). Enhancement: service tabs now use mobile-parity primitives: - MediaTab: MobileCardRow below md (title/size/HDR/library/year) + TablePagination; DataTable at md+ (desktop branch preserved). - FilesTab: MobileCardRow below md (name/type/size/modified) + handleRowClick; DataTable at md+. - ServicePage: SheetForm branch below md (open-on-mount, sticky header + save bar, cancel navigates back to /services, dirty-state guard). - Dashboard: single-column + section anchors below md (from mobile-parity) + empty-state CTA (from services-hub). - App.tsx: useIsMobile() replaces inline matchMedia (from mobile-parity) + data-driven useNavItems (from services-hub). - Backup tables (BackupAlerts/Jobs/Runs) already have MobileCardRow from mobile-parity; JobsTab inherits mobile behavior through its sub-components. Conflict resolutions: - Backend: entirely from services-hub (mobile didn't touch it). - Deleted pages (Media/FileBrowser/Actions/Users/UsersPage/Applications/ ObservabilityPage/BackupsPage + hooks/useUsers + tests): kept deleted (services-hub deleted them; content moved into service tabs). - New service-tabs/*: from services-hub, enhanced with mobile patterns. - App.tsx: services-hub's data-driven nav + mobile-parity's useIsMobile. - Dashboard.tsx: merged (services-hub CTA + mobile-parity sections/anchors). - ServicePage.tsx: services-hub's tab skeleton + mobile-parity's SheetForm. - Primitives (useIsMobile/mobile-card/sheet-form/etc.): from mobile-parity. 117 frontend tests pass (mobile-parity's 122 - 5 deleted page tests + services-hub's new tab/dashboard tests); 271 backend tests pass; lint/ build green both sides.
88 lines
2.9 KiB
TypeScript
88 lines
2.9 KiB
TypeScript
/**
|
|
* JobsTab — operational content for the backups service page.
|
|
*
|
|
* Lifted from the old top-level `components/BackupsPage.tsx`. The three
|
|
* sub-tables (Jobs / Runs / Alerts) and their hooks are preserved verbatim.
|
|
*
|
|
* NOTE: the backup hooks currently query globally (no service_id filter).
|
|
* The backend gained `service_id` attribution in Slice 3, but the hooks don't
|
|
* yet accept a serviceId param. This tab shows ALL backups data for now;
|
|
* per-instance scoping by `instance.id` is a follow-up once the hooks gain the
|
|
* parameter.
|
|
*/
|
|
import { useState } from "react";
|
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
|
import {
|
|
useAcknowledgeAlert,
|
|
useBackupAlerts,
|
|
useBackupJobs,
|
|
useBackupRuns,
|
|
} from "../../hooks/useBackups";
|
|
import BackupAlertsTable from "../../components/BackupAlertsTable";
|
|
import BackupJobsTable from "../../components/BackupJobsTable";
|
|
import BackupRunsTable from "../../components/BackupRunsTable";
|
|
import type { ServiceInstance } from "../../types";
|
|
|
|
export function JobsTab({ instance }: { instance: ServiceInstance }) {
|
|
// instance.id is not yet used — backup hooks query globally (see file
|
|
// docstring). Per-instance scoping is a follow-up.
|
|
void instance;
|
|
const [tab, setTab] = useState("jobs");
|
|
const { data: jobsData, isLoading: jobsLoading } = useBackupJobs();
|
|
const { data: runsData, isLoading: runsLoading } = useBackupRuns();
|
|
const { data: alertsData, isLoading: alertsLoading } = useBackupAlerts(
|
|
undefined,
|
|
false,
|
|
);
|
|
const acknowledgeMutation = useAcknowledgeAlert();
|
|
|
|
// Build a map of latest runs per job
|
|
const latestRuns = new Map();
|
|
if (runsData) {
|
|
for (const run of runsData) {
|
|
const existing = latestRuns.get(run.job_id);
|
|
if (!existing || run.started_at > existing.started_at) {
|
|
latestRuns.set(run.job_id, run);
|
|
}
|
|
}
|
|
}
|
|
|
|
const alertsLabel = alertsData ? `Alerts (${alertsData.length})` : "Alerts";
|
|
|
|
return (
|
|
<div className="space-y-4">
|
|
<Tabs value={tab} onValueChange={setTab}>
|
|
<TabsList>
|
|
<TabsTrigger value="jobs">Jobs</TabsTrigger>
|
|
<TabsTrigger value="runs">Runs</TabsTrigger>
|
|
<TabsTrigger value="alerts">{alertsLabel}</TabsTrigger>
|
|
</TabsList>
|
|
<TabsContent value="jobs">
|
|
{jobsLoading ? (
|
|
<p className="text-sm text-muted-foreground">Loading jobs…</p>
|
|
) : (
|
|
<BackupJobsTable jobs={jobsData ?? []} latestRuns={latestRuns} />
|
|
)}
|
|
</TabsContent>
|
|
<TabsContent value="runs">
|
|
{runsLoading ? (
|
|
<p className="text-sm text-muted-foreground">Loading runs…</p>
|
|
) : (
|
|
<BackupRunsTable runs={runsData ?? []} />
|
|
)}
|
|
</TabsContent>
|
|
<TabsContent value="alerts">
|
|
{alertsLoading ? (
|
|
<p className="text-sm text-muted-foreground">Loading alerts…</p>
|
|
) : (
|
|
<BackupAlertsTable
|
|
alerts={alertsData ?? []}
|
|
onAcknowledge={(id) => acknowledgeMutation.mutate(id)}
|
|
/>
|
|
)}
|
|
</TabsContent>
|
|
</Tabs>
|
|
</div>
|
|
);
|
|
}
|