/*
Copyright (C) 2023-2026 QuantumNous
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see .
For commercial licensing, please contact support@quantumnous.com
*/
import { useQuery } from '@tanstack/react-query'
import { ListChecks, RefreshCw } from 'lucide-react'
import { useTranslation } from 'react-i18next'
import { ErrorState } from '@/components/error-state'
import { Badge } from '@/components/ui/badge'
import { Button } from '@/components/ui/button'
import { Progress } from '@/components/ui/progress'
import { Skeleton } from '@/components/ui/skeleton'
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from '@/components/ui/table'
import { listSystemTasks } from '@/features/system-settings/api'
import type {
SystemTask,
SystemTaskStatus,
} from '@/features/system-settings/types'
import { toIntlLocale } from '@/i18n/languages'
import { formatTimestampRelative, formatTimestampToDate } from '@/lib/format'
import { cn } from '@/lib/utils'
const TASK_LIMIT = 20
const ACTIVE_POLL_INTERVAL_MS = 8000
const STATUS_VARIANT: Record = {
pending: 'secondary',
running: 'secondary',
succeeded: 'secondary',
failed: 'destructive',
}
const STATUS_CLASS_NAME: Record = {
pending:
'bg-amber-50 text-amber-700 dark:bg-amber-500/15 dark:text-amber-300',
running:
'bg-sky-50 text-sky-700 dark:bg-sky-500/15 dark:text-sky-300 [&_span]:bg-sky-500',
succeeded:
'bg-emerald-50 text-emerald-700 dark:bg-emerald-500/15 dark:text-emerald-300',
failed: '',
}
const STATUS_DOT_CLASS_NAME: Record = {
pending: 'bg-amber-500',
running: 'bg-sky-500',
succeeded: 'bg-emerald-500',
failed: 'bg-destructive',
}
const PROGRESS_BAR_CLASS_NAME: Record = {
pending: '[&_[data-slot=progress-indicator]]:bg-amber-500',
running: '[&_[data-slot=progress-indicator]]:bg-sky-500',
succeeded: '[&_[data-slot=progress-indicator]]:bg-emerald-500',
failed: '[&_[data-slot=progress-indicator]]:bg-destructive',
}
// Maps backend system task type constants to i18n source keys. Unknown/future
// types fall back to their raw identifier so the panel never shows blank.
const TYPE_LABEL: Record = {
log_cleanup: 'Log cleanup',
channel_test: 'Batch channel test',
model_update: 'Batch upstream model update',
midjourney_poll: 'Drawing task polling',
async_task_poll: 'Async task polling',
}
const TYPE_DISPLAY_ID: Record = {
midjourney_poll: 'drawing_task_poll',
}
function isActiveStatus(status: SystemTaskStatus) {
return status === 'pending' || status === 'running'
}
function getProgress(task: SystemTask): number | null {
const progress = (task.state as { progress?: unknown } | undefined)?.progress
if (typeof progress !== 'number' || Number.isNaN(progress)) return null
return Math.min(100, Math.max(0, progress))
}
type SystemTasksTableProps = {
tasks: SystemTask[]
}
function SystemTasksTable(props: SystemTasksTableProps) {
const { t, i18n } = useTranslation()
return (
{t(
'Recent maintenance tasks running across instances and their execution status.'
)}
{hasActiveTasks
? t('Auto-refreshing every {{seconds}}s', {
seconds: ACTIVE_POLL_INTERVAL_MS / 1000,
})
: t('Live refresh pauses when no task is running')}