refactor(i18n): U15 ApiAccessKeySection i18n 迁移 — 56 个 hash 乱码键清理为语义化 user.apiKey.* 键 + 组件全量接入 useTranslation

- 清理 56 个 apiaccesskey.s{hash} 乱码键为语义化 user.apiKey.* 键 (title/issue/rotate/revoke/descLabel 等)
- ApiAccessKeySection.tsx 全量接入 useTranslation() + t() 调用, UI 中文 57→0
- 复用 common.loading/common.cancel/common.save 通用键
- zh-CN/en-US 完全对齐 (7556 = 7556), 零 hash 键残留
- tsc -b=0, npm build✓ (18.23s), ESLint errors=0/any=0, go build=0, 17/17 测试通过
This commit is contained in:
attacker committed 2026-07-10 18:29:03 +08:00
1 parent a2edf6aa9f
commit 178c98dd37
3 files changed
+177 -171

No files matched your search

@@ -1,5 +1,6 @@
// ApiAccessKeySection — 单条 CI/CD 访问凭证(凭证卡片布局)// Logan.Li // ApiAccessKeySection — 单条 CI/CD 访问凭证(凭证卡片布局)
import { useCallback, useEffect, useMemo, useState } from 'react'; import { useCallback, useEffect, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from '@/components/ui/card'; import { Card, CardContent, CardHeader, CardTitle, CardDescription } from '@/components/ui/card';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input'; import { Input } from '@/components/ui/input';
@@ -52,7 +53,7 @@ function toDateInputValue(value?: string) {
return Number.isNaN(d.getTime()) ? defaultExpiresDate() : d.toISOString().slice(0, 10); return Number.isNaN(d.getTime()) ? defaultExpiresDate() : d.toISOString().slice(0, 10);
} }
/** 根据创建/到期时间计算有效期进度(0-100)// Logan.Li */ /** Compute expiry progress (0-100) based on created/expires timestamps */
function expiryProgress(createdAt?: string, expiresAt?: string) { function expiryProgress(createdAt?: string, expiresAt?: string) {
if (!createdAt || !expiresAt) return { pct: 0, daysLeft: null as number | null }; if (!createdAt || !expiresAt) return { pct: 0, daysLeft: null as number | null };
const start = new Date(createdAt).getTime(); const start = new Date(createdAt).getTime();
@@ -71,6 +72,7 @@ function shortKeyId(id?: string) {
} }
export function ApiAccessKeySection() { export function ApiAccessKeySection() {
const { t } = useTranslation();
const { toast } = useToast(); const { toast } = useToast();
const confirm = useConfirm(); const confirm = useConfirm();
const apiBase = getPublicApiBase(); const apiBase = getPublicApiBase();
@@ -137,9 +139,9 @@ export function ApiAccessKeySection() {
}); });
setKeyMeta(res); setKeyMeta(res);
setFormOpen(false); setFormOpen(false);
toast({ title: '凭证信息已更新' }); toast({ title: t('user.apiKey.updated') });
} catch (e: unknown) { } catch (e: unknown) {
toast({ title: '更新失败', description: getErrorMessage(e), variant: 'destructive' }); toast({ title: t('user.apiKey.updateFailed'), description: getErrorMessage(e), variant: 'destructive' });
} finally { } finally {
setActionLoading(false); setActionLoading(false);
} }
@@ -148,9 +150,9 @@ export function ApiAccessKeySection() {
const hasKey = !!keyMeta?.has_token; const hasKey = !!keyMeta?.has_token;
const ok = await confirm({ const ok = await confirm({
title: hasKey ? '轮换凭证' : '签发凭证', title: hasKey ? t('user.apiKey.rotateTitle') : t('user.apiKey.issue'),
description: hasKey ? '旧凭证将立即作废,是否继续?' : '凭证明文仅显示一次,请妥善保存。', description: hasKey ? t('user.apiKey.rotateDesc') : t('user.apiKey.issueDesc'),
confirmText: hasKey ? '轮换' : '签发', confirmText: hasKey ? t('user.apiKey.rotateBtn') : t('user.apiKey.issue'),
variant: hasKey ? 'destructive' : 'default', variant: hasKey ? 'destructive' : 'default',
}); });
if (!ok) return; if (!ok) return;
@@ -178,9 +180,9 @@ export function ApiAccessKeySection() {
expired: false, expired: false,
days_left: Math.ceil((new Date(res.expires_at).getTime() - Date.now()) / 86400000), days_left: Math.ceil((new Date(res.expires_at).getTime() - Date.now()) / 86400000),
}); });
toast({ title: '凭证已签发', description: '已保存至本浏览器,请立即复制' }); toast({ title: t('user.apiKey.issued'), description: t('user.apiKey.issuedDesc') });
} catch (e: unknown) { } catch (e: unknown) {
toast({ title: '签发失败', description: getErrorMessage(e), variant: 'destructive' }); toast({ title: t('user.apiKey.issueFailed'), description: getErrorMessage(e), variant: 'destructive' });
} finally { } finally {
setActionLoading(false); setActionLoading(false);
} }
@@ -188,9 +190,9 @@ export function ApiAccessKeySection() {
const handleRevoke = async () => { const handleRevoke = async () => {
const ok = await confirm({ const ok = await confirm({
title: '吊销凭证', title: t('user.apiKey.revokeTitle'),
description: '吊销后 Pipeline / curl 调用将全部失败,是否继续?', description: t('user.apiKey.revokeDesc'),
confirmText: '吊销', confirmText: t('user.apiKey.revokeBtn'),
variant: 'destructive', variant: 'destructive',
}); });
if (!ok) return; if (!ok) return;
@@ -200,15 +202,15 @@ export function ApiAccessKeySection() {
setPlainToken(''); setPlainToken('');
setSessionApiAccessKey(''); setSessionApiAccessKey('');
setKeyMeta({ has_token: false }); setKeyMeta({ has_token: false });
toast({ title: '凭证已吊销' }); toast({ title: t('user.apiKey.revoked') });
} catch (e: unknown) { } catch (e: unknown) {
toast({ title: '吊销失败', description: getErrorMessage(e), variant: 'destructive' }); toast({ title: t('user.apiKey.revokeFailed'), description: getErrorMessage(e), variant: 'destructive' });
} finally { } finally {
setActionLoading(false); setActionLoading(false);
} }
}; };
const authHeader = copyableToken ? `X-AIOps-Access-Key: ${copyableToken}` : 'X-AIOps-Access-Key: <你的访问凭证>'; const authHeader = copyableToken ? `X-AIOps-Access-Key: ${copyableToken}` : `X-AIOps-Access-Key: <${t('user.apiKey.placeholderToken')}>`;
const sampleCluster = 'test-k8s'; const sampleCluster = 'test-k8s';
const sampleNs = 'default'; const sampleNs = 'default';
const sampleDep = 'demo-jar'; const sampleDep = 'demo-jar';
@@ -232,27 +234,27 @@ export function ApiAccessKeySection() {
<div> <div>
<CardTitle className="text-base flex items-center gap-2 text-[var(--dg-text-primary)]"> <CardTitle className="text-base flex items-center gap-2 text-[var(--dg-text-primary)]">
<KeyRound className="h-4 w-4 text-[var(--dg-blue)]" /> <KeyRound className="h-4 w-4 text-[var(--dg-blue)]" />
CI/CD 访问凭证 {t('user.apiKey.title')}
<Badge className="text-[10px] bg-[var(--dg-blue)]/15 text-[var(--dg-blue)] border-[var(--dg-blue)]/30">长期有效</Badge> <Badge className="text-[10px] bg-[var(--dg-blue)]/15 text-[var(--dg-blue)] border-[var(--dg-blue)]/30">{t('user.apiKey.longTerm')}</Badge>
</CardTitle> </CardTitle>
<CardDescription className="text-xs mt-1"> <CardDescription className="text-xs mt-1">
每人唯一凭证,Deployment 发布脚本自动引用,不随登录会话过期。 {t('user.apiKey.desc')}
</CardDescription> </CardDescription>
</div> </div>
{!loading && !keyMeta?.has_token && ( {!loading && !keyMeta?.has_token && (
<Button size="sm" onClick={openCreate} disabled={actionLoading}> <Button size="sm" onClick={openCreate} disabled={actionLoading}>
<Plus className="h-4 w-4 mr-1" />签发凭证 <Plus className="h-4 w-4 mr-1" />{t('user.apiKey.issue')}
</Button> </Button>
)} )}
</CardHeader> </CardHeader>
<CardContent className="space-y-4"> <CardContent className="space-y-4">
{loading ? ( {loading ? (
<div className="flex items-center gap-2 py-8 text-sm text-[var(--dg-text-muted)] justify-center"> <div className="flex items-center gap-2 py-8 text-sm text-[var(--dg-text-muted)] justify-center">
<Loader2 className="h-4 w-4 animate-spin" />加载中… <Loader2 className="h-4 w-4 animate-spin" />{t('common.loading')}
</div> </div>
) : keyMeta?.has_token ? ( ) : keyMeta?.has_token ? (
<div className="rounded-xl border border-[var(--dg-border)] bg-[var(--dg-bg-tertiary)]/40 overflow-hidden"> <div className="rounded-xl border border-[var(--dg-border)] bg-[var(--dg-bg-tertiary)]/40 overflow-hidden">
{/* 凭证主体:左侧色条 + 内容区 // Logan.Li */} {/* credential body: left color bar + content area */}
<div className="flex"> <div className="flex">
<div className={cn( <div className={cn(
'w-1 shrink-0', 'w-1 shrink-0',
@@ -261,7 +263,7 @@ export function ApiAccessKeySection() {
<div className="flex-1 p-4 space-y-4"> <div className="flex-1 p-4 space-y-4">
<div className="flex flex-wrap items-start justify-between gap-3"> <div className="flex flex-wrap items-start justify-between gap-3">
<div className="space-y-1 min-w-0 flex-1"> <div className="space-y-1 min-w-0 flex-1">
<p className="text-[10px] uppercase tracking-wider text-[var(--dg-text-muted)]">凭证字符串</p> <p className="text-[10px] uppercase tracking-wider text-[var(--dg-text-muted)]">{t('user.apiKey.tokenLabel')}</p>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<code className="flex-1 min-w-0 text-xs font-mono text-[var(--dg-text-primary)] bg-[var(--dg-bg-secondary)] border border-[var(--dg-border)] rounded-md px-3 py-2 truncate block"> <code className="flex-1 min-w-0 text-xs font-mono text-[var(--dg-text-primary)] bg-[var(--dg-bg-secondary)] border border-[var(--dg-border)] rounded-md px-3 py-2 truncate block">
{showToken && copyableToken ? copyableToken : mask} {showToken && copyableToken ? copyableToken : mask}
@@ -272,7 +274,7 @@ export function ApiAccessKeySection() {
size="icon" size="icon"
className="h-9 w-9 shrink-0 border-[var(--dg-border)]" className="h-9 w-9 shrink-0 border-[var(--dg-border)]"
onClick={() => setShowToken(!showToken)} onClick={() => setShowToken(!showToken)}
title={showToken ? '隐藏' : '显示'} title={showToken ? t('user.apiKey.hide') : t('user.apiKey.show')}
> >
{showToken ? <EyeOff className="h-3.5 w-3.5" /> : <Eye className="h-3.5 w-3.5" />} {showToken ? <EyeOff className="h-3.5 w-3.5" /> : <Eye className="h-3.5 w-3.5" />}
</Button> </Button>
@@ -281,41 +283,41 @@ export function ApiAccessKeySection() {
</div> </div>
<div className="flex flex-wrap gap-1.5 shrink-0"> <div className="flex flex-wrap gap-1.5 shrink-0">
<Button variant="outline" size="sm" className="h-8 text-xs border-[var(--dg-border)]" onClick={openEdit} disabled={actionLoading}> <Button variant="outline" size="sm" className="h-8 text-xs border-[var(--dg-border)]" onClick={openEdit} disabled={actionLoading}>
<Settings2 className="h-3 w-3 mr-1" />调整 <Settings2 className="h-3 w-3 mr-1" />{t('user.apiKey.adjust')}
</Button> </Button>
<Button variant="outline" size="sm" className="h-8 text-xs border-[var(--dg-border)]" onClick={openCreate} disabled={actionLoading}> <Button variant="outline" size="sm" className="h-8 text-xs border-[var(--dg-border)]" onClick={openCreate} disabled={actionLoading}>
<RotateCw className="h-3 w-3 mr-1" />轮换 <RotateCw className="h-3 w-3 mr-1" />{t('user.apiKey.rotate')}
</Button> </Button>
<Button variant="outline" size="sm" className="h-8 text-xs border-red-500/30 dg-text-red" onClick={handleRevoke} disabled={actionLoading}> <Button variant="outline" size="sm" className="h-8 text-xs border-red-500/30 dg-text-red" onClick={handleRevoke} disabled={actionLoading}>
<Ban className="h-3 w-3 mr-1" />吊销 <Ban className="h-3 w-3 mr-1" />{t('user.apiKey.revoke')}
</Button> </Button>
</div> </div>
</div> </div>
<div className="grid grid-cols-2 sm:grid-cols-3 gap-x-6 gap-y-3 text-xs"> <div className="grid grid-cols-2 sm:grid-cols-3 gap-x-6 gap-y-3 text-xs">
<div> <div>
<p className="text-[var(--dg-text-muted)] mb-0.5">用途说明</p> <p className="text-[var(--dg-text-muted)] mb-0.5">{t('user.apiKey.descLabel')}</p>
<p className="text-[var(--dg-text-secondary)]">{keyMeta.description || '未填写'}</p> <p className="text-[var(--dg-text-secondary)]">{keyMeta.description || t('user.apiKey.descEmpty')}</p>
</div> </div>
<div> <div>
<p className="text-[var(--dg-text-muted)] mb-0.5">凭证标识</p> <p className="text-[var(--dg-text-muted)] mb-0.5">{t('user.apiKey.keyIdLabel')}</p>
<p className="font-mono text-[var(--dg-text-secondary)]" title={keyMeta.key_id}>{shortKeyId(keyMeta.key_id)}</p> <p className="font-mono text-[var(--dg-text-secondary)]" title={keyMeta.key_id}>{shortKeyId(keyMeta.key_id)}</p>
</div> </div>
<div> <div>
<p className="text-[var(--dg-text-muted)] mb-0.5">签发时间</p> <p className="text-[var(--dg-text-muted)] mb-0.5">{t('user.apiKey.createdLabel')}</p>
<p className="text-[var(--dg-text-secondary)]">{formatDateTime(keyMeta.created_at)}</p> <p className="text-[var(--dg-text-secondary)]">{formatDateTime(keyMeta.created_at)}</p>
</div> </div>
</div> </div>
{/* 有效期进度条 // Logan.Li */} {/* expiry progress bar */}
<div className="space-y-1.5"> <div className="space-y-1.5">
<div className="flex items-center justify-between text-[11px]"> <div className="flex items-center justify-between text-[11px]">
<span className="flex items-center gap-1 text-[var(--dg-text-muted)]"> <span className="flex items-center gap-1 text-[var(--dg-text-muted)]">
<Clock className="h-3 w-3" /> <Clock className="h-3 w-3" />
{isExpired ? '已过期' : daysLeft != null ? `剩余 ${daysLeft} 天` : '有效期'} {isExpired ? t('user.apiKey.expired') : daysLeft != null ? t('user.apiKey.daysLeft', { days: daysLeft }) : t('user.apiKey.validity')}
</span> </span>
<span className="text-[var(--dg-text-muted)]"> <span className="text-[var(--dg-text-muted)]">
有效至 {formatDateTime(keyMeta.expires_at)} {t('user.apiKey.validUntil', { date: formatDateTime(keyMeta.expires_at) })}
</span> </span>
</div> </div>
<div className="h-1.5 rounded-full bg-[var(--dg-bg-secondary)] overflow-hidden"> <div className="h-1.5 rounded-full bg-[var(--dg-bg-secondary)] overflow-hidden">
@@ -329,13 +331,13 @@ export function ApiAccessKeySection() {
<div className="flex flex-wrap items-center gap-x-4 gap-y-1 text-[11px] text-[var(--dg-text-muted)] pt-1 border-t border-[var(--dg-border)]/60"> <div className="flex flex-wrap items-center gap-x-4 gap-y-1 text-[11px] text-[var(--dg-text-muted)] pt-1 border-t border-[var(--dg-border)]/60">
<span className="inline-flex items-center gap-1"> <span className="inline-flex items-center gap-1">
<Activity className="h-3 w-3" /> <Activity className="h-3 w-3" />
最近调用 {formatDateTime(keyMeta.last_used_at)} {t('user.apiKey.lastUsed', { date: formatDateTime(keyMeta.last_used_at) })}
</span> </span>
{keyMeta.prefix && ( {keyMeta.prefix && (
<span>前缀 <code className="text-[var(--dg-text-secondary)]">{keyMeta.prefix}</code></span> <span>{t('user.apiKey.prefix')} <code className="text-[var(--dg-text-secondary)]">{keyMeta.prefix}</code></span>
)} )}
{isExpired && ( {isExpired && (
<Badge variant="outline" className="text-[10px] border-red-500/40 dg-text-red">需续期或轮换</Badge> <Badge variant="outline" className="text-[10px] border-red-500/40 dg-text-red">{t('user.apiKey.needRenew')}</Badge>
)} )}
</div> </div>
</div> </div>
@@ -346,35 +348,35 @@ export function ApiAccessKeySection() {
<div className="mx-auto mb-3 h-12 w-12 rounded-full bg-[var(--dg-blue)]/10 flex items-center justify-center"> <div className="mx-auto mb-3 h-12 w-12 rounded-full bg-[var(--dg-blue)]/10 flex items-center justify-center">
<KeyRound className="h-6 w-6 text-[var(--dg-blue)]" /> <KeyRound className="h-6 w-6 text-[var(--dg-blue)]" />
</div> </div>
<p className="text-sm text-[var(--dg-text-secondary)]">尚未签发 CI/CD 访问凭证</p> <p className="text-sm text-[var(--dg-text-secondary)]">{t('user.apiKey.emptyTitle')}</p>
<p className="text-xs text-[var(--dg-text-muted)] mt-1 mb-4">签发后可用于 Pipeline 触发镜像更新、滚动重启等操作</p> <p className="text-xs text-[var(--dg-text-muted)] mt-1 mb-4">{t('user.apiKey.emptyDesc')}</p>
<Button size="sm" onClick={openCreate} disabled={actionLoading}> <Button size="sm" onClick={openCreate} disabled={actionLoading}>
<Plus className="h-4 w-4 mr-1" />立即签发 <Plus className="h-4 w-4 mr-1" />{t('user.apiKey.issueNow')}
</Button> </Button>
</div> </div>
)} )}
{keyMeta?.has_token && !copyableToken && ( {keyMeta?.has_token && !copyableToken && (
<p className="text-[11px] dg-text-yellow/90">本机未缓存凭证明文,请点击「轮换」重新签发一次;签发后将自动填入 Deployment 发布 curl。</p> <p className="text-[11px] dg-text-yellow/90">{t('user.apiKey.cacheWarning')}</p>
)} )}
<div className="rounded-lg bg-[var(--dg-bg-tertiary)]/60 p-4 space-y-3"> <div className="rounded-lg bg-[var(--dg-bg-tertiary)]/60 p-4 space-y-3">
<p className="text-xs font-medium text-[var(--dg-text-secondary)] flex items-center gap-1.5"> <p className="text-xs font-medium text-[var(--dg-text-secondary)] flex items-center gap-1.5">
<Rocket className="h-3.5 w-3.5 dg-text-purple" />调用示例 <Rocket className="h-3.5 w-3.5 dg-text-purple" />{t('user.apiKey.examples')}
</p> </p>
<CodeSnippetBlock <CodeSnippetBlock
label="更新镜像" label={t('user.apiKey.updateImage')}
code={updateCurl} code={updateCurl}
copyLabel="curl 已复制" copyLabel={t('user.apiKey.curlCopied')}
copyDisabled={!copyableToken} copyDisabled={!copyableToken}
copyDisabledTitle="请先签发访问凭证" copyDisabledTitle={t('user.apiKey.copyDisabledTitle')}
/> />
<CodeSnippetBlock <CodeSnippetBlock
label="滚动重启" label={t('user.apiKey.restart')}
code={restartCurl} code={restartCurl}
copyLabel="curl 已复制" copyLabel={t('user.apiKey.curlCopied')}
copyDisabled={!copyableToken} copyDisabled={!copyableToken}
copyDisabledTitle="请先签发访问凭证" copyDisabledTitle={t('user.apiKey.copyDisabledTitle')}
/> />
</div> </div>
</CardContent> </CardContent>
@@ -384,24 +386,24 @@ export function ApiAccessKeySection() {
<DialogContent className="bg-[var(--dg-bg-secondary)] border-[var(--dg-border)] sm:max-w-md"> <DialogContent className="bg-[var(--dg-bg-secondary)] border-[var(--dg-border)] sm:max-w-md">
<DialogHeader> <DialogHeader>
<DialogTitle className="text-[var(--dg-text-primary)]"> <DialogTitle className="text-[var(--dg-text-primary)]">
{formMode === 'edit' ? '调整凭证' : keyMeta?.has_token ? '轮换凭证' : '签发凭证'} {formMode === 'edit' ? t('user.apiKey.adjustTitle') : keyMeta?.has_token ? t('user.apiKey.rotateTitle') : t('user.apiKey.issue')}
</DialogTitle> </DialogTitle>
<DialogDescription className="text-xs text-[var(--dg-text-muted)]"> <DialogDescription className="text-xs text-[var(--dg-text-muted)]">
{formMode === 'edit' ? '修改用途说明与有效期限,凭证字符串不变。' : '填写用途并设定有效期,签发后请立即保存明文。'} {formMode === 'edit' ? t('user.apiKey.editDesc') : t('user.apiKey.createDesc')}
</DialogDescription> </DialogDescription>
</DialogHeader> </DialogHeader>
<div className="space-y-3 py-2"> <div className="space-y-3 py-2">
<div> <div>
<Label className="text-xs text-[var(--dg-text-muted)]">用途说明</Label> <Label className="text-xs text-[var(--dg-text-muted)]">{t('user.apiKey.descLabel')}</Label>
<Input <Input
value={formDescription} value={formDescription}
onChange={e => setFormDescription(e.target.value)} onChange={e => setFormDescription(e.target.value)}
placeholder="如 Jenkins 流水线、GitLab CI" placeholder={t('user.apiKey.placeholderDesc')}
className="mt-1 h-9 bg-[var(--dg-bg-tertiary)] border-[var(--dg-border)]" className="mt-1 h-9 bg-[var(--dg-bg-tertiary)] border-[var(--dg-border)]"
/> />
</div> </div>
<div> <div>
<Label className="text-xs text-[var(--dg-text-muted)]">有效至</Label> <Label className="text-xs text-[var(--dg-text-muted)]">{t('user.apiKey.validUntilLabel')}</Label>
<Input <Input
type="date" type="date"
value={formExpiresAt} value={formExpiresAt}
@@ -411,10 +413,10 @@ export function ApiAccessKeySection() {
</div> </div>
</div> </div>
<DialogFooter> <DialogFooter>
<Button variant="outline" onClick={() => setFormOpen(false)} className="border-[var(--dg-border)]">取消</Button> <Button variant="outline" onClick={() => setFormOpen(false)} className="border-[var(--dg-border)]">{t('common.cancel')}</Button>
<Button onClick={handleFormSubmit} disabled={actionLoading || !formExpiresAt}> <Button onClick={handleFormSubmit} disabled={actionLoading || !formExpiresAt}>
{actionLoading ? <Loader2 className="h-4 w-4 animate-spin mr-1" /> : null} {actionLoading ? <Loader2 className="h-4 w-4 animate-spin mr-1" /> : null}
{formMode === 'edit' ? '保存' : keyMeta?.has_token ? '轮换' : '签发'} {formMode === 'edit' ? t('common.save') : keyMeta?.has_token ? t('user.apiKey.rotateBtn') : t('user.apiKey.issue')}
</Button> </Button>
</DialogFooter> </DialogFooter>
</DialogContent> </DialogContent>
@@ -423,19 +425,19 @@ export function ApiAccessKeySection() {
<Dialog open={tokenDialogOpen} onOpenChange={setTokenDialogOpen}> <Dialog open={tokenDialogOpen} onOpenChange={setTokenDialogOpen}>
<DialogContent className="bg-[var(--dg-bg-secondary)] border-[var(--dg-border)] sm:max-w-lg"> <DialogContent className="bg-[var(--dg-bg-secondary)] border-[var(--dg-border)] sm:max-w-lg">
<DialogHeader> <DialogHeader>
<DialogTitle className="text-[var(--dg-text-primary)]">请保存凭证明文</DialogTitle> <DialogTitle className="text-[var(--dg-text-primary)]">{t('user.apiKey.saveTokenTitle')}</DialogTitle>
<DialogDescription className="text-xs text-[var(--dg-text-muted)]"> <DialogDescription className="text-xs text-[var(--dg-text-muted)]">
仅此一次机会查看完整字符串,关闭后无法再次获取。 {t('user.apiKey.saveTokenDesc')}
</DialogDescription> </DialogDescription>
</DialogHeader> </DialogHeader>
<CodeSnippetBlock <CodeSnippetBlock
label="访问凭证" label={t('user.apiKey.accessTokenLabel')}
code={revealedToken} code={revealedToken}
copyLabel="凭证已复制" copyLabel={t('user.apiKey.credentialCopied')}
className="mt-2" className="mt-2"
/> />
<DialogFooter> <DialogFooter>
<Button onClick={() => setTokenDialogOpen(false)}>我已保存</Button> <Button onClick={() => setTokenDialogOpen(false)}>{t('user.apiKey.saved')}</Button>
</DialogFooter> </DialogFooter>
</DialogContent> </DialogContent>
</Dialog> </Dialog>
+58 -56
View File
@@ -8018,62 +8018,64 @@
"breadcrumbNav": "Breadcrumb Navigation" "breadcrumbNav": "Breadcrumb Navigation"
}, },
"user": { "user": {
"apiaccesskey.s1a2b3c4d": "CI/CD Access Credential", "apiKey": {
"apiaccesskey.s2b3c4d5e": "Long-term", "title": "CI/CD Access Credential",
"apiaccesskey.s3c4d5e6f": "Unique per user, auto-referenced by deployment scripts, does not expire with login session.", "longTerm": "Long-term",
"apiaccesskey.s4d5e6f7g": "Issue Credential", "desc": "Unique per user, auto-referenced by deployment scripts, does not expire with login session.",
"apiaccesskey.s5e6f7g8h": "Credential String", "issue": "Issue Credential",
"apiaccesskey.s6f7g8h9i": "Hide", "tokenLabel": "Credential String",
"apiaccesskey.s7g8h9i0j": "Show", "hide": "Hide",
"apiaccesskey.s8h9i0j1k": "Edit", "show": "Show",
"apiaccesskey.s9i0j1k2l": "Rotate", "adjust": "Edit",
"apiaccesskey.s0j1k2l3m": "Revoke", "rotate": "Rotate",
"apiaccesskey.s1k2l3m4n": "Description", "revoke": "Revoke",
"apiaccesskey.s2l3m4n5o": "Not specified", "descLabel": "Description",
"apiaccesskey.s3m4n5o6p": "Key ID", "descEmpty": "Not specified",
"apiaccesskey.s4n5o6p7q": "Issued At", "keyIdLabel": "Key ID",
"apiaccesskey.s5o6p7q8r": "Expired", "createdLabel": "Issued At",
"apiaccesskey.s6p7q8r9s": "{{days}} days remaining", "expired": "Expired",
"apiaccesskey.s7q8r9s0t": "Valid until", "daysLeft": "{{days}} days remaining",
"apiaccesskey.s8r9s0t1u": "Valid until {{date}}", "validity": "Valid until",
"apiaccesskey.s9s0t1u2v": "Last used {{date}}", "validUntil": "Valid until {{date}}",
"apiaccesskey.s0t1u2v3w": "Prefix", "lastUsed": "Last used {{date}}",
"apiaccesskey.s1u2v3w4x": "Needs renewal or rotation", "prefix": "Prefix",
"apiaccesskey.s2v3w4x5y": "No CI/CD access credential issued yet", "needRenew": "Needs renewal or rotation",
"apiaccesskey.s3w4x5y6z": "After issuing, use for Pipeline image updates, rolling restarts, etc.", "emptyTitle": "No CI/CD access credential issued yet",
"apiaccesskey.s4x5y6z7a": "Issue Now", "emptyDesc": "After issuing, use for Pipeline image updates, rolling restarts, etc.",
"apiaccesskey.s5y6z7a8b": "Credential plaintext not cached locally; click 'Rotate' to re-issue.", "issueNow": "Issue Now",
"apiaccesskey.s6z7a8b9c": "Usage Examples", "cacheWarning": "Credential plaintext not cached locally; click 'Rotate' to re-issue.",
"apiaccesskey.s7a8b9c0d": "Update Image", "examples": "Usage Examples",
"apiaccesskey.s8b9c0d1e": "curl copied", "updateImage": "Update Image",
"apiaccesskey.s9c0d1e2f": "Please issue a credential first", "curlCopied": "curl copied",
"apiaccesskey.s0d1e2f3g": "Rolling Restart", "copyDisabledTitle": "Please issue a credential first",
"apiaccesskey.s1e2f3g4h": "Credential info updated", "restart": "Rolling Restart",
"apiaccesskey.s2f3g4h5i": "Update failed", "updated": "Credential info updated",
"apiaccesskey.s3g4h5i6j": "Credential issued", "updateFailed": "Update failed",
"apiaccesskey.s4h5i6j7k": "Saved to this browser, please copy now", "issued": "Credential issued",
"apiaccesskey.s5i6j7k8l": "Issue failed", "issuedDesc": "Saved to this browser, please copy now",
"apiaccesskey.s6j7k8l9m": "Revoke Credential", "issueFailed": "Issue failed",
"apiaccesskey.s7k8l9m0n": "After revoking, all Pipeline/curl calls will fail. Continue?", "revokeTitle": "Revoke Credential",
"apiaccesskey.s8l9m0n1o": "Revoke", "revokeDesc": "After revoking, all Pipeline/curl calls will fail. Continue?",
"apiaccesskey.s9m0n1o2p": "Credential revoked", "revokeBtn": "Revoke",
"apiaccesskey.s0n1o2p3q": "Revoke failed", "revoked": "Credential revoked",
"apiaccesskey.s1o2p3q4r": "Rotate Credential", "revokeFailed": "Revoke failed",
"apiaccesskey.s2p3q4r5s": "Old credential will be invalidated immediately. Continue?", "rotateTitle": "Rotate Credential",
"apiaccesskey.s3q4r5s6t": "Rotate", "rotateDesc": "Old credential will be invalidated immediately. Continue?",
"apiaccesskey.s4r5s6t7u": "Plaintext shown only once, please save carefully.", "rotateBtn": "Rotate",
"apiaccesskey.s5s6t7u8v": "Edit Credential", "issueDesc": "Plaintext shown only once, please save carefully.",
"apiaccesskey.s6t7u8v9w": "Update description and expiry; credential string unchanged.", "adjustTitle": "Edit Credential",
"apiaccesskey.s7u8v9w0x": "Set description and expiry; save plaintext immediately after issuing.", "editDesc": "Update description and expiry; credential string unchanged.",
"apiaccesskey.s8v9w0x1y": "e.g. Jenkins pipeline, GitLab CI", "createDesc": "Set description and expiry; save plaintext immediately after issuing.",
"apiaccesskey.s9w0x1y2z": "Valid until", "placeholderDesc": "e.g. Jenkins pipeline, GitLab CI",
"apiaccesskey.s0x1y2z3a": "Save", "validUntilLabel": "Valid until",
"apiaccesskey.s1y2z3a4b": "Save Credential Plaintext", "saveBtn": "Save",
"apiaccesskey.s2z3a4b5c": "This is the only chance to view the full string; cannot be retrieved after closing.", "saveTokenTitle": "Save Credential Plaintext",
"apiaccesskey.s3a4b5c6d": "Access Credential", "saveTokenDesc": "This is the only chance to view the full string; cannot be retrieved after closing.",
"apiaccesskey.s4b5c6d7e": "Credential copied", "accessTokenLabel": "Access Credential",
"apiaccesskey.s5c6d7e8f": "I've saved it", "credentialCopied": "Credential copied",
"apiaccesskey.s6d7e8f9g": "Your access credential" "saved": "I've saved it",
"placeholderToken": "Your access credential"
}
}, },
"logCenter": { "logCenter": {
"saveQuery": "Save as Query", "saveQuery": "Save as Query",
+58 -56
View File
@@ -8018,62 +8018,64 @@
"breadcrumbNav": "面包屑导航" "breadcrumbNav": "面包屑导航"
}, },
"user": { "user": {
"apiaccesskey.s1a2b3c4d": "CI/CD 访问凭证", "apiKey": {
"apiaccesskey.s2b3c4d5e": "长期有效", "title": "CI/CD 访问凭证",
"apiaccesskey.s3c4d5e6f": "每人唯一凭证,Deployment 发布脚本自动引用,不随登录会话过期。", "longTerm": "长期有效",
"apiaccesskey.s4d5e6f7g": "签发凭证", "desc": "每人唯一凭证,Deployment 发布脚本自动引用,不随登录会话过期。",
"apiaccesskey.s5e6f7g8h": "凭证字符串", "issue": "签发凭证",
"apiaccesskey.s6f7g8h9i": "隐藏", "tokenLabel": "凭证字符串",
"apiaccesskey.s7g8h9i0j": "显示", "hide": "隐藏",
"apiaccesskey.s8h9i0j1k": "调整", "show": "显示",
"apiaccesskey.s9i0j1k2l": "轮换", "adjust": "调整",
"apiaccesskey.s0j1k2l3m": "吊销", "rotate": "轮换",
"apiaccesskey.s1k2l3m4n": "用途说明", "revoke": "吊销",
"apiaccesskey.s2l3m4n5o": "未填写", "descLabel": "用途说明",
"apiaccesskey.s3m4n5o6p": "凭证标识", "descEmpty": "未填写",
"apiaccesskey.s4n5o6p7q": "签发时间", "keyIdLabel": "凭证标识",
"apiaccesskey.s5o6p7q8r": "已过期", "createdLabel": "签发时间",
"apiaccesskey.s6p7q8r9s": "剩余 {{days}} 天", "expired": "已过期",
"apiaccesskey.s7q8r9s0t": "有效期", "daysLeft": "剩余 {{days}} 天",
"apiaccesskey.s8r9s0t1u": "有效至 {{date}}", "validity": "有效期",
"apiaccesskey.s9s0t1u2v": "最近调用 {{date}}", "validUntil": "有效至 {{date}}",
"apiaccesskey.s0t1u2v3w": "前缀", "lastUsed": "最近调用 {{date}}",
"apiaccesskey.s1u2v3w4x": "需续期或轮换", "prefix": "前缀",
"apiaccesskey.s2v3w4x5y": "尚未签发 CI/CD 访问凭证", "needRenew": "需续期或轮换",
"apiaccesskey.s3w4x5y6z": "签发后可用于 Pipeline 触发镜像更新、滚动重启等操作", "emptyTitle": "尚未签发 CI/CD 访问凭证",
"apiaccesskey.s4x5y6z7a": "立即签发", "emptyDesc": "签发后可用于 Pipeline 触发镜像更新、滚动重启等操作",
"apiaccesskey.s5y6z7a8b": "本机未缓存凭证明文,请点击「轮换」重新签发一次;签发后将自动填入 Deployment 发布 curl。", "issueNow": "立即签发",
"apiaccesskey.s6z7a8b9c": "调用示例", "cacheWarning": "本机未缓存凭证明文,请点击「轮换」重新签发一次;签发后将自动填入 Deployment 发布 curl。",
"apiaccesskey.s7a8b9c0d": "更新镜像", "examples": "调用示例",
"apiaccesskey.s8b9c0d1e": "curl 已复制", "updateImage": "更新镜像",
"apiaccesskey.s9c0d1e2f": "请先签发访问凭证", "curlCopied": "curl 已复制",
"apiaccesskey.s0d1e2f3g": "滚动重启", "copyDisabledTitle": "请先签发访问凭证",
"apiaccesskey.s1e2f3g4h": "凭证信息已更新", "restart": "滚动重启",
"apiaccesskey.s2f3g4h5i": "更新失败", "updated": "凭证信息已更新",
"apiaccesskey.s3g4h5i6j": "凭证已签发", "updateFailed": "更新失败",
"apiaccesskey.s4h5i6j7k": "已保存至本浏览器,请立即复制", "issued": "凭证已签发",
"apiaccesskey.s5i6j7k8l": "签发失败", "issuedDesc": "已保存至本浏览器,请立即复制",
"apiaccesskey.s6j7k8l9m": "吊销凭证", "issueFailed": "签发失败",
"apiaccesskey.s7k8l9m0n": "吊销后 Pipeline / curl 调用将全部失败,是否继续?", "revokeTitle": "吊销凭证",
"apiaccesskey.s8l9m0n1o": "吊销", "revokeDesc": "吊销后 Pipeline / curl 调用将全部失败,是否继续?",
"apiaccesskey.s9m0n1o2p": "凭证已吊销", "revokeBtn": "吊销",
"apiaccesskey.s0n1o2p3q": "吊销失败", "revoked": "凭证已吊销",
"apiaccesskey.s1o2p3q4r": "轮换凭证", "revokeFailed": "吊销失败",
"apiaccesskey.s2p3q4r5s": "旧凭证将立即作废,是否继续?", "rotateTitle": "轮换凭证",
"apiaccesskey.s3q4r5s6t": "轮换", "rotateDesc": "旧凭证将立即作废,是否继续?",
"apiaccesskey.s4r5s6t7u": "凭证明文仅显示一次,请妥善保存。", "rotateBtn": "轮换",
"apiaccesskey.s5s6t7u8v": "调整凭证", "issueDesc": "凭证明文仅显示一次,请妥善保存。",
"apiaccesskey.s6t7u8v9w": "修改用途说明与有效期限,凭证字符串不变。", "adjustTitle": "调整凭证",
"apiaccesskey.s7u8v9w0x": "填写用途并设定有效期,签发后请立即保存明文。", "editDesc": "修改用途说明与有效期限,凭证字符串不变。",
"apiaccesskey.s8v9w0x1y": "如 Jenkins 流水线、GitLab CI", "createDesc": "填写用途并设定有效期,签发后请立即保存明文。",
"apiaccesskey.s9w0x1y2z": "有效至", "placeholderDesc": "如 Jenkins 流水线、GitLab CI",
"apiaccesskey.s0x1y2z3a": "保存", "validUntilLabel": "有效至",
"apiaccesskey.s1y2z3a4b": "请保存凭证明文", "saveBtn": "保存",
"apiaccesskey.s2z3a4b5c": "仅此一次机会查看完整字符串,关闭后无法再次获取。", "saveTokenTitle": "请保存凭证明文",
"apiaccesskey.s3a4b5c6d": "访问凭证", "saveTokenDesc": "仅此一次机会查看完整字符串,关闭后无法再次获取。",
"apiaccesskey.s4b5c6d7e": "凭证已复制", "accessTokenLabel": "访问凭证",
"apiaccesskey.s5c6d7e8f": "我已保存", "credentialCopied": "凭证已复制",
"apiaccesskey.s6d7e8f9g": "你的访问凭证" "saved": "我已保存",
"placeholderToken": "你的访问凭证"
}
}, },
"logCenter": { "logCenter": {
"saveQuery": "另存为查询", "saveQuery": "另存为查询",