feat(notification): manage push applications

This commit is contained in:
2026-09-10 20:09:06 +08:00
parent 573b0c4bc7
commit 1243ed6c92
7 changed files with 140 additions and 0 deletions
+38
View File
@@ -0,0 +1,38 @@
import { useState } from 'react'
import { createFileRoute } from '@tanstack/react-router'
import { useQuery, useQueryClient } from '@tanstack/react-query'
import type { ColumnDef } from '@tanstack/react-table'
import { toast } from 'sonner'
import { deletePushApp, fetchPushApps, verifyPushApp } from '@/api/modules/push-apps'
import type { PushApp } from '@/api/types'
import { PERM, requirePermission } from '@/auth/permissions'
import { useCan } from '@/auth/store'
import { ConfirmDialog } from '@/components/confirm-dialog'
import { DataTable } from '@/components/data-table/data-table'
import { PageHeader } from '@/components/page-header'
import { Badge } from '@/components/ui/badge'
import { Button } from '@/components/ui/button'
import { PushAppEditSheet } from '@/features/notification/push-app-edit-sheet'
import { handleApiError } from '@/lib/errors'
export const Route = createFileRoute('/_authed/push-apps')({ beforeLoad: () => requirePermission(PERM.NOTIFICATION_READ), component: PushAppsPage })
function PushAppsPage() {
const canManage = useCan(PERM.PUSH_APP_MANAGE)
const queryClient = useQueryClient()
const query = useQuery({ queryKey: ['push-apps'], queryFn: fetchPushApps })
const [editing, setEditing] = useState<PushApp | null>(null)
const [creating, setCreating] = useState(false)
const refresh = () => { void queryClient.invalidateQueries({ queryKey: ['push-apps'] }) }
const verify = async (app: PushApp) => { try { const response = await verifyPushApp(app.id); const failed = Object.entries(response.data.checks).filter(([, ok]) => !ok).map(([key]) => key); response.data.ok ? toast.success('凭证验证通过') : toast.error(`验证未通过:${failed.join(', ')}`) } catch (error) { handleApiError(error) } }
const remove = async (app: PushApp) => { try { await deletePushApp(app.id); toast.success('推送应用已删除'); refresh() } catch (error) { handleApiError(error) } }
const columns: ColumnDef<PushApp>[] = [
{ header: '应用', cell: ({ row }) => <div><div className="font-medium">{row.original.label ?? row.original.app_key}</div><code className="text-xs text-muted-foreground">{row.original.app_key}</code></div> },
{ header: '通道', cell: ({ row }) => <Badge variant="secondary">{row.original.provider.toUpperCase()}</Badge> },
{ header: '平台', accessorKey: 'platform' },
{ header: '凭证', cell: ({ row }) => row.original.credentials_present ? <Badge className="bg-emerald-500/12 text-emerald-700"></Badge> : <Badge variant="destructive"></Badge> },
{ header: '状态', cell: ({ row }) => row.original.is_active ? '启用' : '停用' },
{ header: '操作', cell: ({ row }) => <div className="flex gap-1.5">{canManage && <Button size="sm" variant="ghost" onClick={() => void verify(row.original)}></Button>}{canManage && row.original.provider !== 'mipush' && <Button size="sm" variant="outline" onClick={() => setEditing(row.original)}></Button>}{canManage && <ConfirmDialog trigger={<Button size="sm" variant="destructive"></Button>} title={`删除 ${row.original.app_key}/${row.original.provider}`} confirmLabel="删除" destructive onConfirm={() => remove(row.original)} />}</div> },
]
return <div><PageHeader title="推送应用" description="管理每个应用和推送通道的凭证与路由" actions={canManage ? <Button onClick={() => setCreating(true)}></Button> : undefined} /><PushAppEditSheet app={editing} creating={creating} onOpenChange={(open) => { if (!open) { setEditing(null); setCreating(false) } }} onSaved={refresh} /><DataTable columns={columns} data={query.data?.data ?? []} loading={query.isLoading} /></div>
}