Add maintenance cleanup for revoked and orphaned configs
Two new maintenance actions remove VpnConfig records (with their traffic samples) from the database: all revoked configs, and configs whose inbound or node no longer exists. 3x-ui is not touched; both write an AuditLog entry. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
1bc86dc426
commit
2c20fb0427
12 files changed
+367
-1
No files matched your search
@@ -15,6 +15,14 @@ export function deleteDisabledApps() {
|
||||
return apiRequest<MaintenanceCleanupResponseDto>('/admin/maintenance/apps/disabled', { method: 'DELETE' })
|
||||
}
|
||||
|
||||
export function deleteRevokedConfigs() {
|
||||
return apiRequest<MaintenanceCleanupResponseDto>('/admin/maintenance/configs/revoked', { method: 'DELETE' })
|
||||
}
|
||||
|
||||
export function deleteOrphanedConfigs() {
|
||||
return apiRequest<MaintenanceCleanupResponseDto>('/admin/maintenance/configs/orphaned', { method: 'DELETE' })
|
||||
}
|
||||
|
||||
export function factoryReset() {
|
||||
return apiRequest<void>('/admin/maintenance/factory-reset', { method: 'DELETE' })
|
||||
}
|
||||
@@ -6,7 +6,13 @@ import { toast } from '@/shared/ui/toast-store'
|
||||
import { Button } from '@/shared/ui/button'
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/shared/ui/card'
|
||||
import { Input } from '@/shared/ui/input'
|
||||
import { deleteClosedTickets, deleteDisabledApps, deleteOldAuditLogs } from '@/features/admin/maintenance/api'
|
||||
import {
|
||||
deleteClosedTickets,
|
||||
deleteDisabledApps,
|
||||
deleteOldAuditLogs,
|
||||
deleteOrphanedConfigs,
|
||||
deleteRevokedConfigs,
|
||||
} from '@/features/admin/maintenance/api'
|
||||
import { FactoryResetDialog } from '@/features/admin/maintenance/FactoryResetDialog'
|
||||
|
||||
export const Route = createFileRoute('/admin/maintenance')({ component: AdminMaintenancePage })
|
||||
@@ -45,6 +51,23 @@ function AdminMaintenancePage() {
|
||||
onError: () => toast.error(t('auth.genericError')),
|
||||
})
|
||||
|
||||
const onConfigsDeleted = async (data: { deletedCount: number }) => {
|
||||
toast.success(t('admin.maintenance.configs.deleted', { count: data.deletedCount }))
|
||||
await queryClient.invalidateQueries({ queryKey: ['admin-configs'] })
|
||||
}
|
||||
|
||||
const deleteRevokedConfigsMutation = useMutation({
|
||||
mutationFn: deleteRevokedConfigs,
|
||||
onSuccess: onConfigsDeleted,
|
||||
onError: () => toast.error(t('auth.genericError')),
|
||||
})
|
||||
|
||||
const deleteOrphanedConfigsMutation = useMutation({
|
||||
mutationFn: deleteOrphanedConfigs,
|
||||
onSuccess: onConfigsDeleted,
|
||||
onError: () => toast.error(t('auth.genericError')),
|
||||
})
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-4">
|
||||
<Card>
|
||||
@@ -120,6 +143,35 @@ function AdminMaintenancePage() {
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="text-base">{t('admin.maintenance.configs.title')}</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="flex flex-col gap-3">
|
||||
<p className="text-sm text-muted-foreground">{t('admin.maintenance.configs.description')}</p>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
disabled={deleteRevokedConfigsMutation.isPending}
|
||||
onClick={() => {
|
||||
if (confirm(t('admin.maintenance.configs.revokedConfirm'))) deleteRevokedConfigsMutation.mutate()
|
||||
}}
|
||||
>
|
||||
{t('admin.maintenance.configs.revokedAction')}
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
disabled={deleteOrphanedConfigsMutation.isPending}
|
||||
onClick={() => {
|
||||
if (confirm(t('admin.maintenance.configs.orphanedConfirm'))) deleteOrphanedConfigsMutation.mutate()
|
||||
}}
|
||||
>
|
||||
{t('admin.maintenance.configs.orphanedAction')}
|
||||
</Button>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<details className="group rounded-md border border-red-900/50">
|
||||
<summary className="cursor-pointer select-none px-4 py-3 text-sm font-medium text-red-400">
|
||||
{t('admin.maintenance.factoryReset.dangerZone')}
|
||||
|
||||
@@ -586,6 +586,17 @@ const resources = {
|
||||
confirm: 'Удалить все отключённые приложения? Действие необратимо.',
|
||||
deleted: 'Удалено приложений: {{count}}.',
|
||||
},
|
||||
configs: {
|
||||
title: 'Конфиги',
|
||||
description:
|
||||
'Удалить из базы отозванные конфиги или конфиги, чей инбаунд/нода уже удалены (в списке — «? · ?»). Вместе с конфигами удаляется история трафика. Действие необратимо.',
|
||||
revokedAction: 'Удалить все отозванные конфиги',
|
||||
revokedConfirm: 'Удалить все отозванные конфиги? Действие необратимо.',
|
||||
orphanedAction: 'Удалить все конфиги без ноды',
|
||||
orphanedConfirm:
|
||||
'Удалить все конфиги, чей инбаунд или нода удалены, в любом статусе? Действие необратимо.',
|
||||
deleted: 'Удалено конфигов: {{count}}.',
|
||||
},
|
||||
factoryReset: {
|
||||
dangerZone: 'Опасная зона',
|
||||
title: 'Полный сброс панели',
|
||||
@@ -1205,6 +1216,17 @@ const resources = {
|
||||
confirm: 'Delete all disabled apps? This cannot be undone.',
|
||||
deleted: 'Deleted apps: {{count}}.',
|
||||
},
|
||||
configs: {
|
||||
title: 'Configs',
|
||||
description:
|
||||
'Delete revoked configs, or configs whose inbound/node no longer exists (shown as "? · ?"), from the database along with their traffic history. This cannot be undone.',
|
||||
revokedAction: 'Delete all revoked configs',
|
||||
revokedConfirm: 'Delete all revoked configs? This cannot be undone.',
|
||||
orphanedAction: 'Delete all configs without a node',
|
||||
orphanedConfirm:
|
||||
'Delete all configs whose inbound or node was deleted, in any status? This cannot be undone.',
|
||||
deleted: 'Deleted configs: {{count}}.',
|
||||
},
|
||||
factoryReset: {
|
||||
dangerZone: 'Danger zone',
|
||||
title: 'Full panel reset',
|
||||
|
||||
Reference in new issue
Block a user