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')} />
-
+ {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 }:
<>