project-nomad/admin/inertia/components/updates/AppAutoUpdateSection.tsx

102 lines
4.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import StyledSectionHeader from '~/components/StyledSectionHeader'
import Switch from '~/components/inputs/Switch'
import api from '~/lib/api'
import { useMutation, useQueryClient } from '@tanstack/react-query'
import { useNotifications } from '~/context/NotificationContext'
import { useAppAutoUpdateStatus } from '~/hooks/useAppAutoUpdateStatus'
export default function AppAutoUpdateSection() {
const { addNotification } = useNotifications()
const queryClient = useQueryClient()
const { data: status, isLoading } = useAppAutoUpdateStatus()
const enabled = status?.enabled ?? false
const toggleMutation = useMutation({
mutationFn: (value: boolean) => api.updateSetting('appAutoUpdate.enabled', value),
onSuccess: (_data, value) => {
queryClient.invalidateQueries({ queryKey: ['app-auto-update-status'] })
addNotification({
type: 'success',
message: value ? 'App automatic updates enabled.' : 'App automatic updates disabled.',
})
},
onError: () => {
addNotification({ type: 'error', message: 'Failed to update app auto-update setting.' })
},
})
return (
<>
<StyledSectionHeader title="Automatic App Updates" className="mt-8" />
<div className="bg-surface-primary rounded-lg border shadow-md overflow-hidden mt-6 p-6">
<Switch
checked={enabled}
onChange={(value) => toggleMutation.mutate(value)}
disabled={toggleMutation.isPending || isLoading}
label="Enable Automatic App Updates"
description="Automatically install minor and patch updates for apps you've opted in (toggle each app in Supply Depot). Major versions always require a manual update. Uses the same update window and cool-off period as the core schedule above."
/>
{enabled && status && (
<div className="mt-6 pt-4 border-t border-desert-stone-light text-sm">
<p className="text-desert-stone mb-3">
<span className="font-medium">Update window: </span>
{status.windowStart}{status.windowEnd} (
{status.withinWindow ? 'currently inside' : 'currently outside'}); cool-off{' '}
{status.cooloffHours}h.
{status.lastResult && (
<>
{' '}
<span className="font-medium">Last run: </span>
{status.lastResult}
{status.lastAttemptAt
? ` (${new Date(status.lastAttemptAt).toLocaleString()})`
: ''}
</>
)}
</p>
{status.apps.length === 0 ? (
<p className="text-desert-stone-dark">
No apps are opted in yet. Enable auto-update on individual apps from the Supply Depot.
</p>
) : (
<ul className="space-y-2">
{status.apps.map((app) => (
<li
key={app.service_name}
className="flex items-start justify-between gap-4 rounded-md bg-surface-secondary px-3 py-2"
>
<div>
<p className="font-medium text-text-primary">
{app.friendly_name || app.service_name}
</p>
<p className="text-desert-stone">
{app.current_version}
{app.available_update_version
? `${app.available_update_version}`
: ' (up to date)'}
</p>
{app.auto_disabled_reason && (
<p className="text-desert-red mt-0.5">{app.auto_disabled_reason}</p>
)}
</div>
<span
className={`shrink-0 text-xs font-medium ${
app.eligible ? 'text-desert-green' : 'text-desert-stone'
}`}
>
{app.reason}
</span>
</li>
))}
</ul>
)}
</div>
)}
</div>
</>
)
}