diff --git a/src/apps/officer-web/Screens/Dashboard/Settings/ServerSettings/MaintenanceSection.tsx b/src/apps/officer-web/Screens/Dashboard/Settings/ServerSettings/MaintenanceSection.tsx index 0d6f88e2..581a839f 100644 --- a/src/apps/officer-web/Screens/Dashboard/Settings/ServerSettings/MaintenanceSection.tsx +++ b/src/apps/officer-web/Screens/Dashboard/Settings/ServerSettings/MaintenanceSection.tsx @@ -1,5 +1,5 @@ import { useState } from 'react'; -import { Hammer, Loader2, RotateCw } from 'lucide-react'; +import { Download, Hammer, Loader2, RotateCw } from 'lucide-react'; import { toast } from 'sonner'; import { Button } from '@/components/ui/button'; import { useClient } from 'hooks/useClient'; @@ -31,10 +31,45 @@ export function errorMessage(err: unknown, fallback: string): string { export const MaintenanceSection = () => { const client = useClient(); + const [pulling, setPulling] = useState(false); + const [pulled, setPulled] = useState<{ from: string; to: string; commits: string[]; needs: string[] } | null>(null); const [rebuilding, setRebuilding] = useState(false); const [restarting, setRestarting] = useState(false); const [lastBuild, setLastBuild] = useState(null); + /** + * Pull, and then say what the pull implies rather than acting on it. + * + * Deliberately does not chain into a rebuild or a restart. A pull that touched `bun.lock` means an + * install, which the frozen-lockfile rule exists to keep deliberate; one that touched a schema means + * `db:push` against a live database. Neither is a thing a button should decide, so `needs` is reported + * and the buttons below stay where they are. + */ + const pull = async () => { + setPulling(true); + try { + const res = await client.post<{ + ok: boolean; + updated: boolean; + from?: string; + to?: string; + commits: string[]; + needs: string[]; + }>('/server-settings/pull', {}); + if (!res.updated) { + setPulled(null); + toast.success('Already up to date'); + return; + } + setPulled({ from: res.from ?? '', to: res.to ?? '', commits: res.commits, needs: res.needs }); + toast.success(`Pulled ${res.commits.length} commit${res.commits.length === 1 ? '' : 's'}`); + } catch (err) { + toast.error(errorMessage(err, 'Pull failed')); + } finally { + setPulling(false); + } + }; + const rebuild = async () => { setRebuilding(true); try { @@ -90,6 +125,43 @@ export const MaintenanceSection = () => { return (
+
+
Update the platform
+

+ Pulls this server’s repository. Refuses if anything is uncommitted or the checkout has diverged — it + will never discard local work. +

+

+ Changes nothing that is running. Whatever the update needs is listed here, and stays your call. +

+ {pulled && ( +
+

+ {pulled.from} → {pulled.to} +

+
    + {pulled.commits.slice(0, 5).map((line) => ( +
  • + {line} +
  • + ))} + {pulled.commits.length > 5 && ( +
  • +{pulled.commits.length - 5} more
  • + )} +
+ {pulled.needs.length > 0 && ( +

Needs: {pulled.needs.join(', ')}

+ )} +
+ )} +
+ +
+ +
Rebuild the frontend

@@ -98,7 +170,7 @@ export const MaintenanceSection = () => {

{lastBuild &&

Last build: {lastBuild}

}
- @@ -118,7 +190,7 @@ export const MaintenanceSection = () => {