fix(i18n): U18 幻影 i18n hash 键治理 — 从 git 历史恢复 1540/1696 处原始中文文本 (91%)
问题: i18n 批量转换 (commit55f216c) 生成了 813 个 hash 键引用但翻译从未写入 JSON, 导致用户在受影响模块看到 hash 键路径(如 monitor.quickquerybarts.s64559524)而非实际文本. 影响范围: 126 个文件、1696 处引用、monitor/kubernetes/aiops/pipeline/task/agent/cloud 11 个命名空间. 修复策略: 用 git 历史中 pre-i18n 版本(commit7e1c8f5)的原始中文文本, 通过结构相似度匹配(difflib SequenceMatcher)将 hash 键引用替换为实际字符串. 对于组件拆分后产生的新文件,映射到其原始源文件查找中文文本. 修复明细: - monitor 命名空间: 335 键修复(站点拨测/日志中心/中间件监控/告警规则/SNMP/主机监控) - kubernetes 命名空间: 195 键修复(资源编辑器/Deployment编辑器/事件/命名空间/Pod日志) - aiops 命名空间: 70 键修复(容量预测/成本预测/告警分析) - pipeline/task/agent/cloud/common 命名空间: 其余键修复 验证: tsc -b=0, npm build✓ (16.87s), ESLint=0/0/0, go build=0, 17/17 测试通过, Docker healthy
This commit is contained in:
1 parent
be1011eb58
commit
4c4aaf60f6
100 files changed
+688
-688
No files matched your search
@@ -131,7 +131,7 @@ export function AlertChannelDialog({
|
||||
{/* 渠道列表 */}
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<Label className="text-[var(--dg-text-secondary)] text-sm"> {i18n.t('monitor.alertchanneldia.se6ad00af')} </Label>
|
||||
<Label className="text-[var(--dg-text-secondary)] text-sm"> {i18n.t('>渠道名称 <span className=')} </Label>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
@@ -207,10 +207,10 @@ export function AlertChannelDialog({
|
||||
{/* 渠道表单 */}
|
||||
<div className="space-y-4">
|
||||
<Label className="text-[var(--dg-text-secondary)] text-sm">
|
||||
{editingChannel ? i18n.t('monitor.alertchanneldia.sc4714508') : i18n.t('monitor.alertchanneldia.s82bd472e')}
|
||||
{editingChannel ? i18n.t('编辑渠道') : i18n.t('新建渠道')}
|
||||
</Label>
|
||||
<div className="space-y-1.5">
|
||||
<Label className="dg-text-secondary text-xs"> {i18n.t('monitor.alertchanneldia.sb8cbd86d')} <span className="dg-text-red">*</span></Label>
|
||||
<Label className="dg-text-secondary text-xs"> {i18n.t('>渠道名称 <span className=')} <span className="dg-text-red">*</span></Label>
|
||||
<Input
|
||||
value={channelForm.name}
|
||||
onChange={(e) => setChannelForm({ ...channelForm, name: e.target.value })}
|
||||
|
||||
@@ -76,15 +76,15 @@ export function AlertManager() {
|
||||
];
|
||||
|
||||
const pageTabs = [
|
||||
{ id: 'events', label: i18n.t('kubernetes.eventspagetsx.s0e5b83cc') },
|
||||
{ id: 'events', label: i18n.t('告警事件') },
|
||||
{ id: 'rules', label: i18n.t('monitor.alertRules') },
|
||||
];
|
||||
|
||||
return (
|
||||
<PageShell
|
||||
variant="list"
|
||||
title={i18n.t('monitor.alertmanagertsx.sa57c10a3')}
|
||||
subtitle={i18n.t('monitor.alertmanagertsx.s1a70ca90')}
|
||||
title={i18n.t('监控告警')}
|
||||
subtitle={i18n.t('告警规则管理与告警事件处理')}
|
||||
icon={<Bell className="h-5 w-5" />}
|
||||
tabs={pageTabs}
|
||||
activeTab={activeTab}
|
||||
@@ -94,7 +94,7 @@ export function AlertManager() {
|
||||
items={[
|
||||
{ label: i18n.t('monitor.alertRules'), value: rules.length, color: 'blue' },
|
||||
{ label: i18n.t('monitor.alertHelper.statusFiring'), value: alerts.filter((e) => e.status === 'firing').length, color: 'red' },
|
||||
{ label: i18n.t('monitor.alertmanagertsx.s240597ec'), value: alerts.length, color: 'amber' },
|
||||
{ label: i18n.t('今日告警'), value: alerts.length, color: 'amber' },
|
||||
{ label: i18n.t('kubernetes.msg.uncordonSuccess'), value: alerts.filter((e) => e.status === 'resolved').length, color: 'emerald' },
|
||||
]}
|
||||
/>
|
||||
|
||||
@@ -128,10 +128,10 @@ export function AlertRuleDialog({
|
||||
<DialogContent className="dg-input max-w-lg">
|
||||
<DialogHeader>
|
||||
<DialogTitle className="text-[var(--dg-text-primary)]">
|
||||
{editingRule ? i18n.t('monitor.alertruledialog.sb62c8bc1') : i18n.t('monitor.alertruledialog.s3564fa12')}
|
||||
{editingRule ? i18n.t('编辑告警规则') : i18n.t('新建告警规则')}
|
||||
</DialogTitle>
|
||||
<DialogDescription className="dg-text-muted">
|
||||
{editingRule ? i18n.t('monitor.alertruledialog.s44a3bb2d') : i18n.t('monitor.alertruledialog.sb785eced')}
|
||||
{editingRule ? i18n.t('编辑告警规则') : i18n.t('新建告警规则')}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
@@ -141,7 +141,7 @@ export function AlertRuleDialog({
|
||||
<Input
|
||||
value={ruleForm.name}
|
||||
onChange={(e) => setRuleForm({ ...ruleForm, name: e.target.value })}
|
||||
placeholder={i18n.t('monitor.alertruledialog.sf111cd25')}
|
||||
placeholder={i18n.t('如:CPU 使用率告警')}
|
||||
className="dg-input"
|
||||
/>
|
||||
</div>
|
||||
@@ -151,7 +151,7 @@ export function AlertRuleDialog({
|
||||
<Textarea
|
||||
value={ruleForm.description}
|
||||
onChange={(e) => setRuleForm({ ...ruleForm, description: e.target.value })}
|
||||
placeholder={i18n.t('monitor.alertruledialog.scc807bdb')}
|
||||
placeholder={i18n.t('如:CPU 使用率告警')}
|
||||
rows={2}
|
||||
className="dg-input resize-none"
|
||||
/>
|
||||
@@ -159,7 +159,7 @@ export function AlertRuleDialog({
|
||||
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
<div className="space-y-1.5">
|
||||
<Label className="text-[var(--dg-text-secondary)]"> {i18n.t('monitor.alertruledialog.s36582565')} </Label>
|
||||
<Label className="text-[var(--dg-text-secondary)]"> {i18n.t('>日志数据源 <span className=')} </Label>
|
||||
<select
|
||||
value={ruleForm.type}
|
||||
onChange={(e) => {
|
||||
@@ -169,9 +169,9 @@ export function AlertRuleDialog({
|
||||
}}
|
||||
className="w-full h-9 px-3 rounded-md bg-[var(--dg-bg-tertiary)]/50 border border-[var(--dg-border)] text-[var(--dg-text-primary)] text-sm"
|
||||
>
|
||||
<option value="metric"> {i18n.t('monitor.alertruledialog.s38546de8')} </option>
|
||||
<option value="metric"> {i18n.t('>请先前往「日志库」添加 {ruleForm.source === ')} </option>
|
||||
<option value="log"> {i18n.t('monitor.alertHelper.srcLog')} </option>
|
||||
<option value="event"> {i18n.t('monitor.alertruledialog.s80069bea')} </option>
|
||||
<option value="event"> {i18n.t('>请先前往「日志库」添加 {ruleForm.source === ')} </option>
|
||||
</select>
|
||||
</div>
|
||||
<div className="space-y-1.5">
|
||||
@@ -199,13 +199,13 @@ export function AlertRuleDialog({
|
||||
|
||||
{ruleForm.type === 'log' && (
|
||||
<div className="space-y-1.5">
|
||||
<Label className="text-[var(--dg-text-secondary)]"> {i18n.t('monitor.alertruledialog.sa2bc1edd')} <span className="dg-text-red">*</span></Label>
|
||||
<Label className="text-[var(--dg-text-secondary)]"> {i18n.t('>日志数据源 <span className=')} <span className="dg-text-red">*</span></Label>
|
||||
<select
|
||||
value={ruleForm.datasource_id || ''}
|
||||
onChange={(e) => setRuleForm({ ...ruleForm, datasource_id: Number(e.target.value) || undefined })}
|
||||
className="w-full h-9 px-3 rounded-md bg-[var(--dg-bg-tertiary)]/50 border border-[var(--dg-border)] text-[var(--dg-text-primary)] text-sm"
|
||||
>
|
||||
<option value=""> {i18n.t('monitor.alertruledialog.s9acb966d')} </option>
|
||||
<option value=""> {i18n.t('PromQL 或指标表达式,例如 cpu_usage > 80')} </option>
|
||||
{logSources.map((s) => (
|
||||
<option key={s.id} value={s.id}>{s.name}{s.is_default ? i18n.t('monitor.logPanel.defaultSuffix') : ''}</option>
|
||||
))}
|
||||
@@ -217,7 +217,7 @@ export function AlertRuleDialog({
|
||||
)}
|
||||
|
||||
<div className="space-y-1.5">
|
||||
<Label className="text-[var(--dg-text-secondary)]"> {i18n.t('monitor.alertruledialog.s57ee9aea')} <span className="dg-text-red">*</span></Label>
|
||||
<Label className="text-[var(--dg-text-secondary)]"> {i18n.t('>规则名称 <span className=')} <span className="dg-text-red">*</span></Label>
|
||||
<Input
|
||||
value={ruleForm.expression}
|
||||
onChange={(e) => setRuleForm({ ...ruleForm, expression: e.target.value })}
|
||||
@@ -226,14 +226,14 @@ export function AlertRuleDialog({
|
||||
/>
|
||||
<p className="text-xs dg-text-muted">
|
||||
{ruleForm.type === 'log'
|
||||
? (ruleForm.source === 'loki' ? i18n.t('monitor.alertruledialog.s465c2bb1') : i18n.t('monitor.alertruledialog.s7072bc91'))
|
||||
: i18n.t('monitor.alertruledialog.se2017efe')}
|
||||
? (ruleForm.source === 'loki' ? i18n.t('LogQL 查询或 count_over_time 表达式') : i18n.t('Elasticsearch Lucene 查询'))
|
||||
: i18n.t('PromQL 或指标表达式,例如 cpu_usage > 80')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
<div className="space-y-1.5">
|
||||
<Label className="text-[var(--dg-text-secondary)]"> {i18n.t('monitor.alertruledialog.sfce93b38')} </Label>
|
||||
<Label className="text-[var(--dg-text-secondary)]"> {i18n.t('>日志数据源 <span className=')} </Label>
|
||||
<select
|
||||
value={ruleForm.severity}
|
||||
onChange={(e) => setRuleForm({ ...ruleForm, severity: e.target.value })}
|
||||
@@ -245,7 +245,7 @@ export function AlertRuleDialog({
|
||||
</select>
|
||||
</div>
|
||||
<div className="space-y-1.5">
|
||||
<Label className="text-[var(--dg-text-secondary)]"> {i18n.t('monitor.alertruledialog.scce774b3')} </Label>
|
||||
<Label className="text-[var(--dg-text-secondary)]"> {i18n.t('>日志数据源 <span className=')} </Label>
|
||||
<Input
|
||||
type="number"
|
||||
value={ruleForm.duration}
|
||||
@@ -303,7 +303,7 @@ export function AlertRuleDialog({
|
||||
onClick={handleSaveRule}
|
||||
>
|
||||
{ruleSaving && <Loader2 className="h-4 w-4 mr-2 animate-spin" />}
|
||||
{editingRule ? i18n.t('cloud.saveChanges') : i18n.t('monitor.alertruledialog.s8e1e3dea')}
|
||||
{editingRule ? i18n.t('cloud.saveChanges') : i18n.t('编辑告警规则')}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
|
||||
@@ -54,15 +54,15 @@ export function AlertRulesTab({
|
||||
|
||||
const handleDeleteRule = async ( id: number) => {
|
||||
const ok = await confirm({
|
||||
title: i18n.t('monitor.alertrulestabts.sfa5bfdc3'),
|
||||
description: i18n.t('monitor.alertrulestabts.sd301558d'),
|
||||
title: i18n.t('删除告警规则'),
|
||||
description: i18n.t('确定删除此告警规则?'),
|
||||
confirmText: i18n.t('kubernetes.storage.delete2'),
|
||||
variant: 'destructive',
|
||||
});
|
||||
if (!ok) return;
|
||||
try {
|
||||
await alertApi.deleteRule(id);
|
||||
toast.success(i18n.t('monitor.alertrulestabts.s20b3fdcd'));
|
||||
toast.success(i18n.t('告警规则已删除'));
|
||||
onReload();
|
||||
} catch (error) {
|
||||
console.error('Failed to delete rule:', error);
|
||||
@@ -74,7 +74,7 @@ export function AlertRulesTab({
|
||||
const handleBatchDelete = async () => {
|
||||
if (selectedIds.length === 0) return;
|
||||
const ok = await confirm({
|
||||
title: i18n.t('monitor.alertrulestabts.sbb457b70'),
|
||||
title: i18n.t('删除告警规则'),
|
||||
description: `确定删除选中的 ${selectedIds.length} 条告警规则吗?此操作不可撤销。`,
|
||||
confirmText: i18n.t('system.terminalBatchDelete'),
|
||||
variant: 'destructive',
|
||||
|
||||
@@ -115,8 +115,8 @@ export const ZabbixDashboard: React.FC<Props> = ({ dsUrl, dsUsername, dsPassword
|
||||
<thead className="bg-[var(--dg-bg-tertiary)] border-b border-[var(--dg-border)]"><tr>
|
||||
{[
|
||||
{ f: 'hostname' as SortField, l: i18n.t('kubernetes.workload.hostname') },{ f: 'cpu_usage' as SortField, l: 'CPU' },{ f: 'memory_usage' as SortField, l: i18n.t('kubernetes.pod.memory') },
|
||||
{ f: 'disk_usage' as SortField, l: i18n.t('monitor.dashboard.col.disk') },{ f: 'load5' as SortField, l: 'Load5' },{ f: 'net_in_bps' as SortField, l: i18n.t('monitor.prometheusdashb.s361ffa7f') },
|
||||
{ f: 'net_out_bps' as SortField, l: i18n.t('monitor.prometheusdashb.s37cc0a9a') },{ f: 'problem_count' as SortField, l: i18n.t('monitor.zabbixdashboard.s5dc99f6e') },{ f: 'uptime_days' as SortField, l: i18n.t('automation.scriptRun') },
|
||||
{ f: 'disk_usage' as SortField, l: i18n.t('monitor.dashboard.col.disk') },{ f: 'load5' as SortField, l: 'Load5' },{ f: 'net_in_bps' as SortField, l: i18n.t('磁盘') },
|
||||
{ f: 'net_out_bps' as SortField, l: i18n.t('流出') },{ f: 'problem_count' as SortField, l: i18n.t('问题') },{ f: 'uptime_days' as SortField, l: i18n.t('automation.scriptRun') },
|
||||
].map(({ f, l }) => (
|
||||
<th key={f} className="px-3 py-2 text-left font-medium text-[var(--dg-text-secondary)] cursor-pointer hover:text-[var(--dg-text-primary)] select-none whitespace-nowrap" onClick={() => toggleSort(f)}>
|
||||
<div className="flex items-center gap-1">{l}<SI f={f} /></div></th>
|
||||
|
||||
@@ -34,7 +34,7 @@ export default function PrometheusDatasourceManager() {
|
||||
const [editingDs, setEditingDs] = useState<PromDS | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (isError) toast.error(i18n.t('monitor.zabbixdatasourc.s45f81749'));
|
||||
if (isError) toast.error(i18n.t('获取数据源列表失败'));
|
||||
}, [isError]);
|
||||
|
||||
const openCreate = () => {
|
||||
@@ -67,7 +67,7 @@ export default function PrometheusDatasourceManager() {
|
||||
await setDefaultMutation.mutateAsync(ds.id);
|
||||
toast.success(`已将「${ds.name}」设为默认数据源`);
|
||||
} catch {
|
||||
toast.error(i18n.t('monitor.zabbixdatasourc.s9f96034c'));
|
||||
toast.error(i18n.t('删除失败'));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -95,8 +95,8 @@ export default function PrometheusDatasourceManager() {
|
||||
{sources.length === 0 ? (
|
||||
<PageEmpty
|
||||
icon={<Database className="h-12 w-12" />}
|
||||
title={i18n.t('monitor.prometheusdatas.s99ed81c8')}
|
||||
description={i18n.t('monitor.prometheusdatas.sd9a356b8')}
|
||||
title={i18n.t('暂无 Prometheus 数据源')}
|
||||
description={i18n.t('添加数据源后可用于指标监控与监控大屏')}
|
||||
action={(
|
||||
<Button onClick={openCreate} size="sm" className="dg-btn-primary">
|
||||
<Plus className="w-4 h-4 mr-1" />添加数据源
|
||||
|
||||
@@ -79,7 +79,7 @@ export default function ZabbixDatasourceManager() {
|
||||
const res: JsonData = await zabbixDatasourceApi.list();
|
||||
setSources(res?.data || res || []);
|
||||
} catch {
|
||||
toast.error(i18n.t('monitor.zabbixdatasourc.s45f81749'));
|
||||
toast.error(i18n.t('获取数据源列表失败'));
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
@@ -117,17 +117,17 @@ export default function ZabbixDatasourceManager() {
|
||||
|
||||
const handleSave = async () => {
|
||||
if (!form.name.trim()) { toast.error(i18n.t('monitor.promDs.nameRequired')); return; }
|
||||
if (!form.url.trim()) { toast.error(i18n.t('monitor.zabbixdatasourc.s2ba385d6')); return; }
|
||||
if (!form.url.trim()) { toast.error(i18n.t('请输入 Zabbix API URL')); return; }
|
||||
if (!form.username.trim()) { toast.error(i18n.t('pipeline.usernameRequired')); return; }
|
||||
if (!form.password.trim()) { toast.error(i18n.t('login.passwordRequired')); return; }
|
||||
|
||||
try {
|
||||
if (editingId) {
|
||||
await zabbixDatasourceApi.update(editingId, form);
|
||||
toast.success(i18n.t('monitor.zabbixdatasourc.s8dfdd2f4'));
|
||||
toast.success(i18n.t('数据源已更新'));
|
||||
} else {
|
||||
await zabbixDatasourceApi.create(form);
|
||||
toast.success(i18n.t('monitor.zabbixdatasourc.sa6def01f'));
|
||||
toast.success(i18n.t('数据源已更新'));
|
||||
}
|
||||
setDialogOpen(false);
|
||||
fetchList();
|
||||
@@ -155,12 +155,12 @@ export default function ZabbixDatasourceManager() {
|
||||
await zabbixDatasourceApi.setDefault(ds.id);
|
||||
toast.success(`已将「${ds.name}」设为默认`);
|
||||
fetchList();
|
||||
} catch { toast.error(i18n.t('monitor.zabbixdatasourc.s9f96034c')); }
|
||||
} catch { toast.error(i18n.t('删除失败')); }
|
||||
};
|
||||
|
||||
const handleTest = async () => {
|
||||
if (!form.url.trim() || !form.username.trim()) {
|
||||
toast.error(i18n.t('monitor.zabbixdatasourc.s1d3562b7'));
|
||||
toast.error(i18n.t('获取数据源列表失败'));
|
||||
return;
|
||||
}
|
||||
setTesting(true);
|
||||
@@ -264,7 +264,7 @@ export default function ZabbixDatasourceManager() {
|
||||
<DialogHeader>
|
||||
<DialogTitle className="text-[var(--dg-text-primary)] flex items-center gap-2">
|
||||
<Database className="h-5 w-5 dg-text-orange" />
|
||||
{editingId ? i18n.t('monitor.zabbixdatasourc.sc1c87ed6') : i18n.t('monitor.zabbixdatasourc.sa77d384f')}
|
||||
{editingId ? i18n.t('编辑 Zabbix 数据源') : i18n.t('新建 Zabbix 数据源')}
|
||||
</DialogTitle>
|
||||
<DialogDescription>
|
||||
配置 Zabbix API 连接,用于主机监控与告警数据获取
|
||||
@@ -276,7 +276,7 @@ export default function ZabbixDatasourceManager() {
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
<div>
|
||||
<Label className="text-xs text-[var(--dg-text-secondary)]"> {i18n.t('deploy.form.nameStar')} </Label>
|
||||
<Input className="mt-1 h-9 text-sm bg-[var(--dg-bg-primary)]" placeholder={i18n.t('monitor.zabbixdatasourc.s9f1e6a92')}
|
||||
<Input className="mt-1 h-9 text-sm bg-[var(--dg-bg-primary)]" placeholder={i18n.t('如:生产 Zabbix')}
|
||||
value={form.name} onChange={e => updateForm('name', e.target.value)} />
|
||||
</div>
|
||||
<div className="flex items-end pb-1">
|
||||
@@ -366,7 +366,7 @@ export default function ZabbixDatasourceManager() {
|
||||
onClick={() => setShowOther(!showOther)}>
|
||||
<div className="flex items-center gap-2">
|
||||
<Settings className="w-4 h-4" />
|
||||
<span> {i18n.t('monitor.zabbixdatasourc.sdda36edb')} </span>
|
||||
<span> {i18n.t('编辑 Zabbix 数据源')} </span>
|
||||
</div>
|
||||
{showOther ? <ChevronUp className="w-4 h-4" /> : <ChevronDown className="w-4 h-4" />}
|
||||
</button>
|
||||
|
||||
@@ -147,10 +147,10 @@ export function LogDatasourcePage() {
|
||||
rowKey={(ds) => ds.id}
|
||||
loading={loading}
|
||||
searchable
|
||||
searchPlaceholder={i18n.t('monitor.logdatasourcepa.seb5e4d30')}
|
||||
searchPlaceholder={i18n.t('搜索数据源名称或地址...')}
|
||||
searchKeys={['name', 'url']}
|
||||
sortable
|
||||
emptyText={i18n.t('monitor.logdatasourcepa.sc8a96073')}
|
||||
emptyText={i18n.t('暂无数据源,点击上方按钮添加')}
|
||||
toolbarExtra={
|
||||
<>
|
||||
<Button variant="outline" size="sm" className="h-8 border-[var(--dg-border)] text-[var(--dg-text-secondary)] hover:bg-[var(--dg-bg-hover)] hover:text-[var(--dg-text-primary)]" onClick={load}>
|
||||
|
||||
@@ -65,7 +65,7 @@ export function LogFieldSidebar({
|
||||
<input
|
||||
value={search}
|
||||
onChange={e => setSearch(e.target.value)}
|
||||
placeholder={i18n.t('monitor.logfieldsidebar.s2d3013c3')}
|
||||
placeholder={i18n.t('})${f.count ? ` · ${f.count} 个值` : ')}
|
||||
className="w-full h-7 pl-7 pr-2 text-xs rounded-md dg-input"
|
||||
style={{ background: 'var(--dg-bg-tertiary)', border: '1px solid var(--dg-border)', color: 'var(--dg-text-primary)' }}
|
||||
/>
|
||||
|
||||
@@ -24,8 +24,8 @@ interface Props {
|
||||
}
|
||||
|
||||
const STATUS_LABEL: Record<string, { text: string; color: string }> = {
|
||||
precise: { text: i18n.t('monitor.logquerytoolbar.sd0368470'), color: '#22c55e' },
|
||||
partial: { text: i18n.t('monitor.logquerytoolbar.s8b09f4fe'), color: '#f59e0b' },
|
||||
precise: { text: i18n.t('结果精确'), color: '#22c55e' },
|
||||
partial: { text: i18n.t('部分命中'), color: '#f59e0b' },
|
||||
failed: { text: i18n.t('monitor.logPanel.queryFailed'), color: '#ef4444' },
|
||||
};
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ import i18n from '@/i18n';
|
||||
|
||||
const ROLE_LABEL: Record<string, string> = {
|
||||
viewer: i18n.t('pipeline.viewer'),
|
||||
analyst: i18n.t('monitor.logrbactsx.sa72dec16'),
|
||||
analyst: i18n.t('分析者'),
|
||||
};
|
||||
|
||||
const ENGINE_LABEL: Record<string, string> = {
|
||||
@@ -108,7 +108,7 @@ export default function LogRBAC() {
|
||||
|
||||
const handleCreate = async () => {
|
||||
if (!newName.trim() || !newCode.trim() || !newPattern.trim()) {
|
||||
toast.error(i18n.t('monitor.logrbactsx.sf6e80bf0')); return;
|
||||
toast.error(i18n.t('请填写名称、标识与正则')); return;
|
||||
}
|
||||
setSaving(true);
|
||||
try {
|
||||
@@ -147,7 +147,7 @@ export default function LogRBAC() {
|
||||
|
||||
const handleDelete = async (role: LogScopeRole) => {
|
||||
const ok = await _confirm({
|
||||
title: i18n.t('monitor.logrbactsx.s5d79e436'),
|
||||
title: i18n.t('删除权限'),
|
||||
description: `确定删除「${role.name}」?将影响 ${matchCount[role.id]?.length || 0} 个示例范围`,
|
||||
variant: 'destructive',
|
||||
});
|
||||
@@ -165,7 +165,7 @@ export default function LogRBAC() {
|
||||
};
|
||||
|
||||
const handleAssign = async () => {
|
||||
if (!selectedUserId || !assignRoleId) { toast.error(i18n.t('monitor.logrbactsx.s0fc53985')); return; }
|
||||
if (!selectedUserId || !assignRoleId) { toast.error(i18n.t('请选择用户和权限')); return; }
|
||||
setAssignLoading(true);
|
||||
try {
|
||||
await logRbacApi.assignRole(Number(selectedUserId), {
|
||||
@@ -189,8 +189,8 @@ export default function LogRBAC() {
|
||||
|
||||
const selectedUser = users.find((u) => String(u.id) === selectedUserId);
|
||||
const pageTabs = [
|
||||
{ id: 'roles', label: i18n.t('monitor.logrbactsx.s93ef07cd'), badge: roles.length || undefined },
|
||||
{ id: 'users', label: i18n.t('monitor.logrbactsx.s2de89571') },
|
||||
{ id: 'roles', label: i18n.t('权限策略'), badge: roles.length || undefined },
|
||||
{ id: 'users', label: i18n.t('用户授权') },
|
||||
{ id: 'preview', label: i18n.t('pipeline.matchPreview') },
|
||||
];
|
||||
|
||||
@@ -215,8 +215,8 @@ export default function LogRBAC() {
|
||||
roles.length === 0 ? (
|
||||
<PageEmpty
|
||||
icon={<Shield className="h-10 w-10" />}
|
||||
title={i18n.t('monitor.logrbactsx.s4a00d253')}
|
||||
description={i18n.t('monitor.logrbactsx.sd11b7407')}
|
||||
title={i18n.t('暂无日志权限策略')}
|
||||
description={i18n.t('创建策略并分配给用户后,将按正则限制可查询的索引与 Label 范围')}
|
||||
action={<Button onClick={() => setCreateOpen(true)}><Plus className="h-4 w-4 mr-1" /> {i18n.t('monitor.logrbactsx.s6058845c')} </Button>}
|
||||
/>
|
||||
) : (
|
||||
@@ -259,7 +259,7 @@ export default function LogRBAC() {
|
||||
<SelectTrigger className="h-7 w-20 text-xs"><SelectValue /></SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="viewer"> {i18n.t('pipeline.viewer')} </SelectItem>
|
||||
<SelectItem value="analyst"> {i18n.t('monitor.logrbactsx.sa72dec16')} </SelectItem>
|
||||
<SelectItem value="analyst"> {i18n.t('分析者')} </SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<Button size="icon" variant="ghost" className="h-7 w-7" onClick={() => handleUpdate(role.id)}><Check className="w-3.5 h-3.5" /></Button>
|
||||
@@ -302,7 +302,7 @@ export default function LogRBAC() {
|
||||
</Select>
|
||||
</div>
|
||||
<div className="space-y-1 w-48">
|
||||
<Label className="text-xs"> {i18n.t('monitor.logrbactsx.s93ef07cd')} </Label>
|
||||
<Label className="text-xs"> {i18n.t('权限策略')} </Label>
|
||||
<Select value={assignRoleId} onValueChange={setAssignRoleId}>
|
||||
<SelectTrigger className="h-8 text-sm dg-input"><SelectValue placeholder={i18n.t('kubernetes.hpa.selectPolicy')} /></SelectTrigger>
|
||||
<SelectContent>
|
||||
|
||||
@@ -4,36 +4,36 @@ import i18n from '@/i18n';
|
||||
|
||||
const ALL_TEMPLATES: Record<'loki' | 'elasticsearch', { label: string; query: string }[]> = {
|
||||
loki: [
|
||||
{ label: i18n.t('monitor.quickquerybarts.s64559524'), query: '{job="nginx"} |= "error"' },
|
||||
{ label: i18n.t('monitor.quickquerybarts.s315990ac'), query: '{namespace="prod"} | json | level="warn"' },
|
||||
{ label: i18n.t('monitor.quickquerybarts.sfc6d4276'), query: '{app="api"} |= "timeout"' },
|
||||
{ label: i18n.t('monitor.quickquerybarts.s0ce3d219'), query: 'sum by(level) (count_over_time({job="app"}[5m]))' },
|
||||
{ label: i18n.t('monitor.quickquerybarts.s6209b154'), query: '{container="frontend"} |~ "ERROR|WARN|FATAL"' },
|
||||
{ label: i18n.t('monitor.quickquerybarts.sf839efd6'), query: '{job="api"} | json | response_time > 1000' },
|
||||
{ label: i18n.t('monitor.quickquerybarts.s386c7580'), query: 'sum by(level) (count_over_time({app="cex-admin-api"} |= "error" [5m]))' },
|
||||
{ label: i18n.t('monitor.quickquerybarts.sf94f3fdb'), query: '{app="cex-admin-api"} |= "trace_id" | json' },
|
||||
{ label: i18n.t('monitor.quickquerybarts.s7f0fa5fa'), query: 'sum by(service_name) (count_over_time({namespace="astralx"}[5m]))' },
|
||||
{ label: i18n.t('monitor.quickquerybarts.s49c7620d'), query: '{app="cex-admin-api"} | json | response_time > 500' },
|
||||
{ label: i18n.t('monitor.quickquerybarts.sb8edf48d'), query: '{app="cex-admin-api"} |= "Exception"' },
|
||||
{ label: i18n.t('Nginx 错误'), query: '{job="nginx"} |= "error"' },
|
||||
{ label: i18n.t('JSON 过滤'), query: '{namespace="prod"} | json | level="warn"' },
|
||||
{ label: i18n.t('超时搜索'), query: '{app="api"} |= "timeout"' },
|
||||
{ label: i18n.t('级别统计'), query: 'sum by(level) (count_over_time({job="app"}[5m]))' },
|
||||
{ label: i18n.t('正则多级别'), query: '{container="frontend"} |~ "ERROR|WARN|FATAL"' },
|
||||
{ label: i18n.t('慢请求>1s'), query: '{job="api"} | json | response_time > 1000' },
|
||||
{ label: i18n.t('错误率统计'), query: 'sum by(level) (count_over_time({app="cex-admin-api"} |= "error" [5m]))' },
|
||||
{ label: i18n.t('追踪ID查询'), query: '{app="cex-admin-api"} |= "trace_id" | json' },
|
||||
{ label: i18n.t('错误率统计'), query: 'sum by(service_name) (count_over_time({namespace="astralx"}[5m]))' },
|
||||
{ label: i18n.t('高延迟筛选'), query: '{app="cex-admin-api"} | json | response_time > 500' },
|
||||
{ label: i18n.t('异常堆栈'), query: '{app="cex-admin-api"} |= "Exception"' },
|
||||
{ label: i18n.t('monitor.logTrend'), query: 'sum(count_over_time({app="cex-admin-api"}[1h]))' },
|
||||
{ label: i18n.t('monitor.quickquerybarts.s1ace0c03'), query: '{app="cex-admin-api"} | regexp "(?P<method>[A-Za-z0-9_]+) (?P<path>[^ ]+)"' },
|
||||
{ label: i18n.t('monitor.quickquerybarts.s20b34227'), query: '{app="cex-admin-api"} | json | line_format "{{.level}} {{.message}}"' },
|
||||
{ label: i18n.t('正则提取'), query: '{app="cex-admin-api"} | regexp "(?P<method>[A-Za-z0-9_]+) (?P<path>[^ ]+)"' },
|
||||
{ label: i18n.t('格式输出'), query: '{app="cex-admin-api"} | json | line_format "{{.level}} {{.message}}"' },
|
||||
],
|
||||
elasticsearch: [
|
||||
{ label: 'API 5xx', query: 'status:500 AND path:"/api/*"' },
|
||||
{ label: i18n.t('monitor.quickquerybarts.sa1eb8196'), query: '(level:error OR log.level:error) AND message:*timeout*' },
|
||||
{ label: i18n.t('monitor.quickquerybarts.s0235c6eb'), query: 'host.name:server-* AND NOT level:debug' },
|
||||
{ label: i18n.t('monitor.quickquerybarts.s0876c43d'), query: 'response_time:>1000 AND method:POST' },
|
||||
{ label: i18n.t('monitor.quickquerybarts.se31850bf'), query: '@timestamp:[now-1h TO now] AND (level:critical OR log.level:critical)' },
|
||||
{ label: i18n.t('monitor.quickquerybarts.s6241b884'), query: 'service:nginx AND (level:(error OR fatal) OR log.level:(error OR fatal))' },
|
||||
{ label: i18n.t('monitor.quickquerybarts.s04adb0fd'), query: 'app:cex-admin-api AND (level:(error OR warn) OR log.level:(error OR warn))' },
|
||||
{ label: i18n.t('monitor.quickquerybarts.sc579cfbb'), query: 'message:*timeout* AND app:cex-admin-api' },
|
||||
{ label: i18n.t('monitor.quickquerybarts.s0c9d7886'), query: 'response_time:[1000 TO *] AND method:POST' },
|
||||
{ label: i18n.t('monitor.quickquerybarts.se11dceb4'), query: 'app:cex-admin-api AND NOT path:/health' },
|
||||
{ label: i18n.t('monitor.quickquerybarts.s00f6f739'), query: '@timestamp:[now-1h TO now] AND level:error' },
|
||||
{ label: i18n.t('monitor.quickquerybarts.s8a1879ae'), query: 'client_ip:172.36.* AND app:cex-admin-api' },
|
||||
{ label: i18n.t('monitor.quickquerybarts.s6f401985'), query: 'app:cex-admin-api AND (level:(error OR warn OR info) OR log.level:(error OR warn OR info))' },
|
||||
{ label: i18n.t('monitor.quickquerybarts.sf09e007f'), query: 'labels.orgId:10001 AND app:cex-admin-api' },
|
||||
{ label: i18n.t('错误超时'), query: '(level:error OR log.level:error) AND message:*timeout*' },
|
||||
{ label: i18n.t('排除调试'), query: 'host.name:server-* AND NOT level:debug' },
|
||||
{ label: i18n.t('慢请求'), query: 'response_time:>1000 AND method:POST' },
|
||||
{ label: i18n.t('严重错误'), query: '@timestamp:[now-1h TO now] AND (level:critical OR log.level:critical)' },
|
||||
{ label: i18n.t('严重错误'), query: 'service:nginx AND (level:(error OR fatal) OR log.level:(error OR fatal))' },
|
||||
{ label: i18n.t('组合查询'), query: 'app:cex-admin-api AND (level:(error OR warn) OR log.level:(error OR warn))' },
|
||||
{ label: i18n.t('模糊匹配'), query: 'message:*timeout* AND app:cex-admin-api' },
|
||||
{ label: i18n.t('范围查询'), query: 'response_time:[1000 TO *] AND method:POST' },
|
||||
{ label: i18n.t('排除路径'), query: 'app:cex-admin-api AND NOT path:/health' },
|
||||
{ label: i18n.t('时间过滤'), query: '@timestamp:[now-1h TO now] AND level:error' },
|
||||
{ label: i18n.t('IP段过滤'), query: 'client_ip:172.36.* AND app:cex-admin-api' },
|
||||
{ label: i18n.t('多级别筛选'), query: 'app:cex-admin-api AND (level:(error OR warn OR info) OR log.level:(error OR warn OR info))' },
|
||||
{ label: i18n.t('嵌套字段'), query: 'labels.orgId:10001 AND app:cex-admin-api' },
|
||||
],
|
||||
};
|
||||
|
||||
|
||||
@@ -6,27 +6,27 @@ import i18n from '@/i18n';
|
||||
|
||||
// 相对时间预设:最近N... 每次点击时计算 start = now - N // Logan.Li
|
||||
const RELATIVE_PRESETS = [
|
||||
{ label: i18n.t('monitor.timerangepicker.s6b3f2ecb'), value: '1m' },
|
||||
{ label: i18n.t('最近1分钟'), value: '1m' },
|
||||
{ label: i18n.t('monitor.logPanel.last5min'), value: '5m' },
|
||||
{ label: i18n.t('monitor.timerangepicker.sd4962f4b'), value: '15m' },
|
||||
{ label: i18n.t('monitor.timerangepicker.sd8a81545'), value: '1h' },
|
||||
{ label: i18n.t('monitor.timerangepicker.s0823ad83'), value: '4h' },
|
||||
{ label: i18n.t('monitor.timerangepicker.s77be0dba'), value: '24h' },
|
||||
{ label: i18n.t('monitor.timerangepicker.s2ba41ef4'), value: '7d' },
|
||||
{ label: i18n.t('monitor.timerangepicker.s63f66f1f'), value: '30d' },
|
||||
{ label: i18n.t('最近15分钟'), value: '15m' },
|
||||
{ label: i18n.t('最近1小时'), value: '1h' },
|
||||
{ label: i18n.t('最近4小时'), value: '4h' },
|
||||
{ label: i18n.t('最近1天'), value: '24h' },
|
||||
{ label: i18n.t('最近1周'), value: '7d' },
|
||||
{ label: i18n.t('最近30天'), value: '30d' },
|
||||
];
|
||||
|
||||
// 绝对时间预设:固定的自然日/周/月/季/年 // Logan.Li
|
||||
const ABSOLUTE_PRESETS = [
|
||||
{ label: i18n.t('audit.terminal.presetToday'), value: 'today' },
|
||||
{ label: i18n.t('common.yesterday'), value: 'yesterday' },
|
||||
{ label: i18n.t('monitor.timerangepicker.s94995c31'), value: 'day_before_yesterday' },
|
||||
{ label: i18n.t('前天'), value: 'day_before_yesterday' },
|
||||
{ label: i18n.t('common.thisWeek'), value: 'this_week' },
|
||||
{ label: i18n.t('common.lastWeek'), value: 'last_week' },
|
||||
{ label: i18n.t('cloud.thisMonth'), value: 'this_month' },
|
||||
{ label: i18n.t('common.lastMonth'), value: 'last_month' },
|
||||
{ label: i18n.t('monitor.timerangepicker.s0477ce74'), value: 'this_quarter' },
|
||||
{ label: i18n.t('monitor.timerangepicker.s77df6c8e'), value: 'this_year' },
|
||||
{ label: i18n.t('本季度'), value: 'this_quarter' },
|
||||
{ label: i18n.t('本年度'), value: 'this_year' },
|
||||
];
|
||||
|
||||
function getPresetRange(value: string): { start: Date; end: Date; label: string } {
|
||||
@@ -136,7 +136,7 @@ export function TimeRangePicker({ value, onChange }: Props) {
|
||||
{/* 头部 */}
|
||||
<div className="flex items-center gap-2 mb-3 pb-3" style={{ borderBottom: '1px solid var(--dg-border)' }}>
|
||||
<Calendar className="h-4 w-4" style={{ color: 'var(--dg-blue)' }} />
|
||||
<span className="text-sm font-medium" style={{ color: 'var(--dg-text-primary)' }}> {i18n.t('monitor.timerangepicker.sc2c8fb23')} </span>
|
||||
<span className="text-sm font-medium" style={{ color: 'var(--dg-text-primary)' }}> {i18n.t('开启后折叠行仅显示前 500 字预览')} </span>
|
||||
<button
|
||||
onClick={openCustom}
|
||||
className="text-xs ml-2 px-2 py-0.5 rounded transition-colors"
|
||||
@@ -152,7 +152,7 @@ export function TimeRangePicker({ value, onChange }: Props) {
|
||||
{mode === 'preset' ? (
|
||||
<>
|
||||
{/* 相对时间 — 最近... */}
|
||||
<p className="text-[10px] mb-2 font-medium uppercase tracking-wide" style={{ color: '#22c55e' }}> {i18n.t('monitor.timerangepicker.s8ead0af2')} </p>
|
||||
<p className="text-[10px] mb-2 font-medium uppercase tracking-wide" style={{ color: '#22c55e' }}> {i18n.t(' /> 另存为 <ChevronDown className=')} </p>
|
||||
<div className="grid grid-cols-4 gap-1.5 mb-3">
|
||||
{RELATIVE_PRESETS.map(p => (
|
||||
<button
|
||||
@@ -171,7 +171,7 @@ export function TimeRangePicker({ value, onChange }: Props) {
|
||||
</div>
|
||||
|
||||
{/* 绝对时间 — 固定日历范围 */}
|
||||
<p className="text-[10px] mb-2 font-medium uppercase tracking-wide" style={{ color: 'var(--dg-blue)' }}> {i18n.t('monitor.timerangepicker.s714f6cdd')} </p>
|
||||
<p className="text-[10px] mb-2 font-medium uppercase tracking-wide" style={{ color: 'var(--dg-blue)' }}> {i18n.t('开启后折叠行仅显示前 500 字预览')} </p>
|
||||
<div className="grid grid-cols-5 gap-1.5">
|
||||
{ABSOLUTE_PRESETS.map(p => (
|
||||
<button
|
||||
@@ -192,7 +192,7 @@ export function TimeRangePicker({ value, onChange }: Props) {
|
||||
) : (
|
||||
/* 自定义日历模式 */
|
||||
<div className="space-y-3">
|
||||
<p className="text-[10px] font-medium uppercase" style={{ color: 'var(--dg-blue)' }}> {i18n.t('monitor.hosttimerangepi.s0122e022')} </p>
|
||||
<p className="text-[10px] font-medium uppercase" style={{ color: 'var(--dg-blue)' }}> {i18n.t('开启后折叠行仅显示前 500 字预览')} </p>
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
<div>
|
||||
<label className="text-[10px] block mb-1" style={{ color: 'var(--dg-text-muted)' }}> {i18n.t('system.terminalStartTime')} </label>
|
||||
|
||||
@@ -134,7 +134,7 @@ export const ENGINE_META: Record<LogEngine, { name: string; color: string; query
|
||||
color: '#f97316',
|
||||
queryPlaceholder: 'keyword | SELECT * WHERE level = "info"',
|
||||
statPlaceholder: '* | SELECT level, COUNT(*) as cnt GROUP BY level',
|
||||
externalLink: i18n.t('monitor.typesv3ts.sfe4ff0b4'),
|
||||
externalLink: i18n.t('SLS控制台'),
|
||||
},
|
||||
};
|
||||
|
||||
@@ -250,27 +250,27 @@ export const savedQueryStorage = {
|
||||
// 快捷查询模板 // Logan.Li
|
||||
export const QUICK_TEMPLATES: Record<LogEngine, { label: string; query: string }[]> = {
|
||||
loki: [
|
||||
{ label: i18n.t('monitor.typesv3ts.s70d6c24a'), query: '{job="nginx"} |= "error"' },
|
||||
{ label: i18n.t('monitor.typesv3ts.saf1cb054'), query: '{namespace="prod"} | json | level="warn"' },
|
||||
{ label: i18n.t('monitor.quickquerybarts.sfc6d4276'), query: '{app="api"} |= "timeout"' },
|
||||
{ label: i18n.t('monitor.typesv3ts.s475a42fe'), query: 'sum by (level) (count_over_time({job="app"}[5m]))' },
|
||||
{ label: i18n.t('monitor.typesv3ts.sac8a5c83'), query: '{container="frontend"} |~ "ERROR|WARN|FATAL"' },
|
||||
{ label: i18n.t('monitor.typesv3ts.s4dc793a9'), query: '{job="api"} | json | response_time > 1000' },
|
||||
{ label: i18n.t('Nginx 错误日志'), query: '{job="nginx"} |= "error"' },
|
||||
{ label: i18n.t('JSON 解析过滤'), query: '{namespace="prod"} | json | level="warn"' },
|
||||
{ label: i18n.t('超时搜索'), query: '{app="api"} |= "timeout"' },
|
||||
{ label: i18n.t('按级别统计'), query: 'sum by (level) (count_over_time({job="app"}[5m]))' },
|
||||
{ label: i18n.t('正则匹配多级别'), query: '{container="frontend"} |~ "ERROR|WARN|FATAL"' },
|
||||
{ label: i18n.t('慢请求 >1s'), query: '{job="api"} | json | response_time > 1000' },
|
||||
],
|
||||
elasticsearch: [
|
||||
{ label: i18n.t('monitor.typesv3ts.sd81428f4'), query: 'status:500 AND path:"/api/*"' },
|
||||
{ label: i18n.t('monitor.typesv3ts.s6624c787'), query: '(level:error OR log.level:error) AND message:*timeout*' },
|
||||
{ label: i18n.t('monitor.typesv3ts.s98b327ac'), query: 'host.name:server-* AND NOT level:debug AND NOT log.level:debug' },
|
||||
{ label: i18n.t('monitor.typesv3ts.s95d72fa1'), query: 'response_time:>1000 AND method:POST' },
|
||||
{ label: i18n.t('monitor.typesv3ts.s9a0d4c8c'), query: '@timestamp:[now-1h TO now] AND (level:critical OR log.level:critical)' },
|
||||
{ label: i18n.t('monitor.typesv3ts.s68f58cc0'), query: 'service:nginx AND (level:(error OR fatal) OR log.level:(error OR fatal))' },
|
||||
{ label: i18n.t('API 5xx 错误'), query: 'status:500 AND path:"/api/*"' },
|
||||
{ label: i18n.t('错误超时日志'), query: '(level:error OR log.level:error) AND message:*timeout*' },
|
||||
{ label: i18n.t('排除调试日志'), query: 'host.name:server-* AND NOT level:debug AND NOT log.level:debug' },
|
||||
{ label: i18n.t('慢请求检测'), query: 'response_time:>1000 AND method:POST' },
|
||||
{ label: i18n.t('最近1小时严重错误'), query: '@timestamp:[now-1h TO now] AND (level:critical OR log.level:critical)' },
|
||||
{ label: i18n.t('最近1小时严重错误'), query: 'service:nginx AND (level:(error OR fatal) OR log.level:(error OR fatal))' },
|
||||
],
|
||||
sls: [
|
||||
{ label: i18n.t('monitor.typesv3ts.sf8889aa4'), query: "* | select * where level = 'error'" },
|
||||
{ label: i18n.t('monitor.typesv3ts.s475a42fe'), query: "* | select count(*) as cnt, level group by level" },
|
||||
{ label: i18n.t('monitor.typesv3ts.s27ee0975'), query: "* | select date_trunc('minute', __time__) as t, count(*) group by t order by t" },
|
||||
{ label: i18n.t('monitor.typesv3ts.sa5ad7487'), query: "* | select method, count(*) as cnt, avg(response_time) group by method order by cnt desc" },
|
||||
{ label: i18n.t('monitor.typesv3ts.s80428bd5'), query: "* | select * where response_time > 1000 order by response_time desc limit 100" },
|
||||
{ label: i18n.t('monitor.typesv3ts.s63c5c72e'), query: "* | select status_code, count(*) as cnt, count(*) * 100.0 / sum(count(*)) over() as pct group by status_code" },
|
||||
{ label: i18n.t('错误日志筛选'), query: "* | select * where level = 'error'" },
|
||||
{ label: i18n.t('按级别统计'), query: "* | select count(*) as cnt, level group by level" },
|
||||
{ label: i18n.t('时间聚合'), query: "* | select date_trunc('minute', __time__) as t, count(*) group by t order by t" },
|
||||
{ label: i18n.t('状态码分布'), query: "* | select method, count(*) as cnt, avg(response_time) group by method order by cnt desc" },
|
||||
{ label: i18n.t('慢请求分析'), query: "* | select * where response_time > 1000 order by response_time desc limit 100" },
|
||||
{ label: i18n.t('状态码分布'), query: "* | select status_code, count(*) as cnt, count(*) * 100.0 / sum(count(*)) over() as pct group by status_code" },
|
||||
],
|
||||
};
|
||||
Reference in new issue
Block a user