feat(settings): add panel update management UI
All checks were successful
Deploy to Production / Trigger production update (push) Successful in 10s

Expand the About tab into a full update manager over the existing backend
endpoints: edit the update repository URL, check for updates (showing
changelog/errors), trigger an update with a strong confirmation, and show a
live progress banner with a reset action while an update runs.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-13 22:18:08 +05:00
parent 179cb615f3
commit fb1267c533
4 changed files with 174 additions and 9 deletions

View File

@@ -7,8 +7,32 @@ export const settingsKeys = {
security: ['settings-security'] as const,
channels: ['notification-channels'] as const,
version: ['app-version'] as const,
updateRepository: ['update-repository'] as const,
updateStatus: ['update-status'] as const,
};
export interface CheckUpdateResult {
current_version: string;
latest_version: string | null;
update_available: boolean;
changelog: string | null;
error: string | null;
git_available: boolean;
project_mounted: boolean;
disabled: boolean;
repository_url?: string;
commits_behind?: number;
}
export interface UpdateStatus {
is_updating: boolean;
started_at: string | null;
stage: string | null;
progress: number;
error: string | null;
completed: boolean;
}
interface ProfileResponse {
id: number;
username: string;
@@ -117,6 +141,50 @@ export function useAppVersion() {
export function useCheckUpdates() {
return useMutation({
mutationFn: () => apiClient.get<Record<string, unknown>>('/settings/api/updates/check'),
mutationFn: () => apiClient.get<CheckUpdateResult>('/settings/api/updates/check'),
});
}
export function useUpdateRepository() {
return useQuery({
queryKey: settingsKeys.updateRepository,
queryFn: () => apiClient.get<{ repository_url: string }>('/settings/api/updates/repository'),
});
}
export function useSetUpdateRepository() {
const qc = useQueryClient();
return useMutation({
// The endpoint takes repository_url as a query parameter, not a JSON body.
mutationFn: (repository_url: string) =>
apiClient.put<{ message: string; repository_url: string }>(
`/settings/api/updates/repository?repository_url=${encodeURIComponent(repository_url)}`,
{},
),
onSuccess: () => qc.invalidateQueries({ queryKey: settingsKeys.updateRepository }),
});
}
export function useUpdateStatus(poll = false) {
return useQuery({
queryKey: settingsKeys.updateStatus,
queryFn: () => apiClient.get<UpdateStatus>('/settings/api/updates/status'),
refetchInterval: poll ? 3000 : false,
});
}
export function usePerformUpdate() {
const qc = useQueryClient();
return useMutation({
mutationFn: () => apiClient.post<{ success: boolean; message?: string; error?: string }>('/settings/api/updates/perform', {}),
onSuccess: () => qc.invalidateQueries({ queryKey: settingsKeys.updateStatus }),
});
}
export function useResetUpdate() {
const qc = useQueryClient();
return useMutation({
mutationFn: () => apiClient.post<{ success: boolean; message: string }>('/settings/api/updates/reset', {}),
onSuccess: () => qc.invalidateQueries({ queryKey: settingsKeys.updateStatus }),
});
}