mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-10-12 02:06:37 +08:00
feat(framework): 回灌 OpenFlare 分层、安全与运行时改进
将平台域持久化收敛为 repository 唯一入口,model 去掉 IO。 邮件头写入前清除 CR/LF,防止 header 注入。 httppool 支持可配置 Transport;batchwriter 增加 MinBatchSize/Stats,flush 失败交回批次;任务 PermanentError 作为 SkipRetry 终态。 设置与推送页的确认改为 AlertDialog;axios 去尾斜杠并按 Gin 数组序列化查询参数。 升级共享 Go 依赖(Gin、Asynq、OTel、GORM、Redis 等)。
This commit is contained in:
@@ -37,6 +37,16 @@ import {
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from '@/components/ui/dialog';
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogAction,
|
||||
AlertDialogCancel,
|
||||
AlertDialogContent,
|
||||
AlertDialogDescription,
|
||||
AlertDialogFooter,
|
||||
AlertDialogHeader,
|
||||
AlertDialogTitle,
|
||||
} from '@/components/ui/alert-dialog';
|
||||
import {
|
||||
Breadcrumb,
|
||||
BreadcrumbItem,
|
||||
@@ -53,6 +63,8 @@ import { useLocale, useTranslations } from 'next-intl';
|
||||
import { formatDateTime } from '@/i18n/format';
|
||||
import type { AppLocale } from '@/i18n/config';
|
||||
|
||||
type ConfirmTarget = { id: number; name: string };
|
||||
|
||||
export function AccessTokenMain() {
|
||||
const { user } = useAuth();
|
||||
const queryClient = useQueryClient();
|
||||
@@ -67,6 +79,12 @@ export function AccessTokenMain() {
|
||||
const [copiedId, setCopiedId] = React.useState<number | null>(null);
|
||||
const [newCreatedToken, setNewCreatedToken] =
|
||||
React.useState<CreateTokenResponse | null>(null);
|
||||
const [deleteTarget, setDeleteTarget] = React.useState<ConfirmTarget | null>(
|
||||
null,
|
||||
);
|
||||
const [rotateTarget, setRotateTarget] = React.useState<ConfirmTarget | null>(
|
||||
null,
|
||||
);
|
||||
|
||||
// 获取 Token 列表
|
||||
const accessTokensQuery = useQuery({
|
||||
@@ -98,6 +116,7 @@ export function AccessTokenMain() {
|
||||
const deleteTokenMutation = useMutation({
|
||||
mutationFn: (id: number) => UserService.deleteAccessToken(id),
|
||||
onSuccess: () => {
|
||||
setDeleteTarget(null);
|
||||
void queryClient.invalidateQueries({
|
||||
queryKey: ['user', 'access-tokens'],
|
||||
});
|
||||
@@ -112,6 +131,7 @@ export function AccessTokenMain() {
|
||||
const rotateTokenMutation = useMutation({
|
||||
mutationFn: (id: number) => UserService.rotateAccessToken(id),
|
||||
onSuccess: (data) => {
|
||||
setRotateTarget(null);
|
||||
setNewCreatedToken(data);
|
||||
setViewDialogOpen(true);
|
||||
void queryClient.invalidateQueries({
|
||||
@@ -136,18 +156,6 @@ export function AccessTokenMain() {
|
||||
});
|
||||
};
|
||||
|
||||
const handleDeleteToken = (id: number, name: string) => {
|
||||
if (window.confirm(ta('deleteConfirm', { name }))) {
|
||||
deleteTokenMutation.mutate(id);
|
||||
}
|
||||
};
|
||||
|
||||
const handleRotateToken = (id: number, name: string) => {
|
||||
if (window.confirm(ta('rotateConfirm', { name }))) {
|
||||
rotateTokenMutation.mutate(id);
|
||||
}
|
||||
};
|
||||
|
||||
const handleCopyText = async (text: string, id: number) => {
|
||||
try {
|
||||
await navigator.clipboard.writeText(text);
|
||||
@@ -297,7 +305,9 @@ export function AccessTokenMain() {
|
||||
variant='outline'
|
||||
size='sm'
|
||||
className='text-xs border-dashed text-muted-foreground hover:text-primary hover:bg-primary/5 rounded-lg h-8 px-2.5'
|
||||
onClick={() => handleRotateToken(token.id, token.name)}
|
||||
onClick={() =>
|
||||
setRotateTarget({ id: token.id, name: token.name })
|
||||
}
|
||||
disabled={rotateTokenMutation.isPending}
|
||||
>
|
||||
<RefreshCw
|
||||
@@ -310,7 +320,9 @@ export function AccessTokenMain() {
|
||||
variant='outline'
|
||||
size='sm'
|
||||
className='text-xs border-dashed text-muted-foreground hover:text-rose-500 hover:bg-rose-500/10 hover:border-rose-500/20 rounded-lg h-8 px-2.5'
|
||||
onClick={() => handleDeleteToken(token.id, token.name)}
|
||||
onClick={() =>
|
||||
setDeleteTarget({ id: token.id, name: token.name })
|
||||
}
|
||||
disabled={deleteTokenMutation.isPending}
|
||||
>
|
||||
<Trash2 className='size-3.5 mr-1' />
|
||||
@@ -482,6 +494,64 @@ export function AccessTokenMain() {
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
<AlertDialog
|
||||
open={Boolean(deleteTarget)}
|
||||
onOpenChange={(open) => !open && setDeleteTarget(null)}
|
||||
>
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>{ta('deleteConfirmTitle')}</AlertDialogTitle>
|
||||
<AlertDialogDescription>
|
||||
{ta('deleteConfirm', { name: deleteTarget?.name ?? '' })}
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel disabled={deleteTokenMutation.isPending}>
|
||||
{tCommon('cancel')}
|
||||
</AlertDialogCancel>
|
||||
<AlertDialogAction
|
||||
disabled={deleteTokenMutation.isPending}
|
||||
onClick={() =>
|
||||
deleteTarget && deleteTokenMutation.mutate(deleteTarget.id)
|
||||
}
|
||||
>
|
||||
{deleteTokenMutation.isPending
|
||||
? ta('revoking')
|
||||
: ta('confirmRevoke')}
|
||||
</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
|
||||
<AlertDialog
|
||||
open={Boolean(rotateTarget)}
|
||||
onOpenChange={(open) => !open && setRotateTarget(null)}
|
||||
>
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>{ta('rotateConfirmTitle')}</AlertDialogTitle>
|
||||
<AlertDialogDescription>
|
||||
{ta('rotateConfirm', { name: rotateTarget?.name ?? '' })}
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel disabled={rotateTokenMutation.isPending}>
|
||||
{tCommon('cancel')}
|
||||
</AlertDialogCancel>
|
||||
<AlertDialogAction
|
||||
disabled={rotateTokenMutation.isPending}
|
||||
onClick={() =>
|
||||
rotateTarget && rotateTokenMutation.mutate(rotateTarget.id)
|
||||
}
|
||||
>
|
||||
{rotateTokenMutation.isPending
|
||||
? ta('rotating')
|
||||
: ta('confirmRotate')}
|
||||
</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
</motion.div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user