diff --git a/frontend/src/i18n/locales/en/logs.ts b/frontend/src/i18n/locales/en/logs.ts index 76dd98f..06aed87 100644 --- a/frontend/src/i18n/locales/en/logs.ts +++ b/frontend/src/i18n/locales/en/logs.ts @@ -146,6 +146,7 @@ export default { loadingQuery: 'Querying Loki', loadingWait: 'Waiting for {{seconds}}s. Wide-range queries may take a while.', pagingHint: 'Logs are paged newest to oldest; charts show the current page, and the top timeline covers the whole query range.', + domainAll: 'All domains', domainFilter: 'Domain filter', domainPlaceholder: 'Type or pick a domain', domainQuery: 'Query in Loki', @@ -256,6 +257,7 @@ export default { loadFailed: 'Failed to load context', }, access: { + labelsUnlimited: 'Unlimited · all namespaces', loadFailed: 'Failed to load query policies', validate: 'Fill in the policy group name, choose a data source, and select "Anyone" or at least one user', saveFailed: 'Failed to save policy group', diff --git a/frontend/src/i18n/locales/zh/logs.ts b/frontend/src/i18n/locales/zh/logs.ts index 0fbd7c1..62978bc 100644 --- a/frontend/src/i18n/locales/zh/logs.ts +++ b/frontend/src/i18n/locales/zh/logs.ts @@ -141,6 +141,7 @@ export default { loadingQuery: '正在查询 Loki', loadingWait: '已等待 {{seconds}} 秒。大范围查询可能需要较长时间。', pagingHint: '日志按时间从新到旧翻页;图表展示当前页,顶部时间线展示整个查询范围。', + domainAll: '全部域名', domainFilter: '域名筛选', domainPlaceholder: '输入或选择域名', domainQuery: '在 Loki 中查询', @@ -249,6 +250,7 @@ export default { loadFailed: '读取上下文失败', }, access: { + labelsUnlimited: '不限 · 可查询全部命名空间', loadFailed: '加载查询策略失败', validate: '请填写策略组名称,选择数据源,并选择「所有人」或至少一位用户', saveFailed: '保存策略组失败', diff --git a/frontend/src/pages/logs/components/LogResultsPanel.tsx b/frontend/src/pages/logs/components/LogResultsPanel.tsx index 615790c..8306f81 100644 --- a/frontend/src/pages/logs/components/LogResultsPanel.tsx +++ b/frontend/src/pages/logs/components/LogResultsPanel.tsx @@ -2,7 +2,7 @@ import { useEffect, useMemo, useState } from 'react' import { Loader2 } from 'lucide-react' import { useTranslation } from 'react-i18next' import { Button } from '@/components/ui/button' -import { Input } from '@/components/ui/input' +import { SearchableValueSelect } from '@/components/SearchableValueSelect' import type { LogEntry } from '@/api/logs' import { parseIngressAccessLog } from '@/lib/ingress-access-log' import { applyLabelFilter } from '../query-utils' @@ -50,9 +50,19 @@ export function LogResultsPanel({ entries, searched, hasSource, pageIndex, hasNe } {pageIndex > 0 &&

{t('results.pagingHint')}

} {domains.length > 0 &&
- - setDomainFilter(event.target.value)} placeholder={t('results.domainPlaceholder')} /> - {domains.map(domain => + {t('results.domainFilter')} + {t('results.domainHint', { shown: visibleEntries.length, total: entries.length })}
} diff --git a/frontend/src/pages/logs/components/PolicyGroupsPage.tsx b/frontend/src/pages/logs/components/PolicyGroupsPage.tsx index a851f3f..7933153 100644 --- a/frontend/src/pages/logs/components/PolicyGroupsPage.tsx +++ b/frontend/src/pages/logs/components/PolicyGroupsPage.tsx @@ -1,7 +1,10 @@ import { useEffect, useState } from 'react' +import { Plus, UsersRound } from 'lucide-react' import { toast } from 'sonner' import { useTranslation } from 'react-i18next' import { Button } from '@/components/ui/button' +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select' +import { Checkbox } from '@/components/ui/checkbox' import { Input } from '@/components/ui/input' import { ConfirmDialog } from '@/components/ConfirmDialog' import { ErrorState } from '@/components/ErrorState' @@ -84,20 +87,49 @@ export function PolicyGroupsPage({ sources }: { sources: LogSource[] }) { return

{form.id ? t('access.editTitle') : t('access.createTitle')}

{t('access.formHint')}

{form.id && }
-
+

{t('access.scopeTitle')}

setForm(current => ({ ...current, labels }))} />

{t('access.scopeHint')}

-

{t('access.usersTitle')} {form.userIds.includes(ANYONE) ? t('access.selectedAnyone') : t('access.selectedCount', { count: form.userIds.length })}

{t('access.usersHint')}

setFilter(event.target.value)} placeholder={t('access.searchPlaceholder')} />
{visibleUsers.map(user => )}{visibleUsers.length === 0 &&

{t('access.noMatch')}

}
+

{t('access.usersTitle')} {form.userIds.includes(ANYONE) ? t('access.selectedAnyone') : t('access.selectedCount', { count: form.userIds.length })}

{t('access.usersHint')}

setFilter(event.target.value)} placeholder={t('access.searchPlaceholder')} />
{visibleUsers.map(user => ( + + ))}{visibleUsers.length === 0 &&

{t('access.noMatch')}

}
{ if (!open) setDeleteTarget(null) }} name={deleteTarget?.name} title={t('access.deleteTitle')} description={t('access.deleteDesc', { name: deleteTarget?.name ?? '' })} loading={saving} onConfirm={() => void remove()} /> diff --git a/frontend/src/pages/logs/components/SourcesManager.tsx b/frontend/src/pages/logs/components/SourcesManager.tsx index b8b9bbc..669ef28 100644 --- a/frontend/src/pages/logs/components/SourcesManager.tsx +++ b/frontend/src/pages/logs/components/SourcesManager.tsx @@ -4,6 +4,7 @@ import { toast } from 'sonner' import { Database, Globe, Loader2, Lock, Pencil, Plus, Server, Trash2, Zap } from 'lucide-react' import { Badge } from '@/components/ui/badge' import { Button } from '@/components/ui/button' +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select' import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card' import { Checkbox } from '@/components/ui/checkbox' import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog' @@ -180,10 +181,17 @@ export const SourcesManager = memo(function SourcesManager({ sources, onSaved }: <>