feat: 品牌统一 LokiStack + 全站中英 i18n + 用户批量删除 + 主题定时切换与浅色侧栏

- 品牌: Go 模块/数据文件/二进制/构建部署资产全部 opsx→lokistack;前端 localStorage 命名空间迁移(读旧写新,含按用户 id 前缀迁移);修复 nginx upstream 与 Makefile 引用已改名服务的断裂
- i18n: react-i18next 落地,common + 11 个页面域命名空间全站中英(菜单/顶栏/登录/列表页/表单/确认弹窗/状态徽章);设置页语言即时切换;修复词典自注册导致的循环导入白屏
- 用户管理: 批量删除(POST /users/batch-delete,不删自己/最后管理员,部分失败不阻断)+ 行复选/全选/批量工具条
- 主题: 浅色侧栏(中性灰激活态)、四态模式(浅色/深色/跟随系统/定时切换,按时区判定)、顶栏太阳月亮二态、设置页紧凑分段选择器
- UI: 登录记住用户名、CardActionButton 统一五页卡片操作钮、shadcn Badge 替换手写徽章、语义色修正
- 清理: 移除主机 Web 终端(前后端)、k8s-helm/Helm 遗留代码与字段、README 重写精简
- 修复: 侧栏与日志告警 order 重复、路由标题 i18n key 未翻译、词典双逗号等
This commit is contained in:
opsx committed 2026-10-27 17:11:38 +08:00
1 parent 02d1bd87a7
commit f3733f0bfc
201 files changed
+5438 -2388

No files matched your search

+82
View File
@@ -0,0 +1,82 @@
// alerts namespace — Log alerts domain (header / rule form & templates / delivery history / Feishu (Lark) notify hints)
export default {
title: 'Alerts',
subtitle:
'Monitor logs with LogQL count rules; push a Feishu (Lark) card when the threshold is exceeded (1-minute cycle)',
checkNow: 'Check Now',
checking: 'Checking...',
create: 'New Rule',
emptyTitle: 'No alert rules yet',
emptyDesc:
'e.g. {namespace=~".+"} |= "FATAL" — any match within 15 minutes pushes a Feishu (Lark) card',
sourceFallback: 'Data source {{id}}',
noChannels: 'No channels linked',
channelSep: ', ',
ruleMeta:
'Fires on more than {{threshold}} matches within {{window}} min · Cooldown {{cooldown}} min',
lastFired: 'Last fired {{time}}',
editAria: 'Edit {{name}}',
deleteAria: 'Delete {{name}}',
historyTitle: 'Delivery history (last 50)',
notifyHintPre: 'Notifications not configured? Go to',
notifyHintPost: 'to configure a Feishu (Lark) Webhook',
editTitle: 'Edit rule · {{name}}',
createTitle: 'New Alert Rule',
templates: 'Quick templates (click to prefill, then adjust as needed)',
saving: 'Saving...',
save: 'Save Changes',
submitCreate: 'Create Rule',
loadFailed: 'Failed to load alert data',
saveFailed: 'Failed to save',
deleteFailed: 'Failed to delete',
checkDone: 'Check completed. See delivery history for results.',
checkFailed: 'Check failed',
toggleFailed: 'Failed to toggle',
confirmDeleteTitle: 'Confirm alert rule deletion',
delivery: {
sent: 'Sent',
cooldown: 'Skipped (cooldown)',
failed: 'Failed',
hits_one: '{{count}} match',
hits_other: '{{count}} matches',
},
tpl: {
nsError: {
label: 'Namespace errors',
desc: 'Error logs in a given namespace (default astralx)',
},
allNsError: {
label: 'All-NS errors > 1000',
desc: 'Total error logs across all namespaces exceed 1000',
},
ingress5xx: {
label: 'Ingress 5xx',
desc: 'ingress-nginx 5xx (requires JSON access logs; the platform can normalize them in one click)',
},
ingress4xx: {
label: 'Ingress 4xx',
desc: 'ingress-nginx 4xx (requires JSON access logs)',
},
fatal: {
label: 'FATAL',
desc: 'Alert on any FATAL log across all namespaces',
},
},
form: {
name: 'Rule name *',
namePlaceholder: 'App fatal errors',
source: 'Data source *',
sourcePlaceholder: 'Select a data source',
query: 'LogQL query *',
queryHint:
'Common: {app="xxx"} |= "FATAL" / {namespace=~".+"} |~ "(?i)error" / {app="x"} |= "OutOfMemoryError"',
channels: 'Channels (multi-select to fan out to multiple alert groups)',
noChannels: 'Create channels first under "Settings → Alert Notifications"',
channelDisabled: '(disabled)',
window: 'Window (min)',
threshold: 'Threshold (matches)',
thresholdHint: '0 = alert on any match',
cooldown: 'Cooldown (min)',
enabled: 'Enable rule',
},
}
+89
View File
@@ -0,0 +1,89 @@
// clusters namespace — cluster management page (English)
// Rich-text values (<code>/<strong>) are rendered via react-i18next <Trans>; keep the tags.
export default {
title: 'Clusters',
description: 'Manage Kubernetes clusters for Loki deployment',
actions: {
add: 'Add Cluster',
detail: 'Details',
editAria: 'Edit cluster {{name}}',
validateAria: 'Validate cluster {{name}}',
deleteAria: 'Delete cluster {{name}}',
},
fields: {
accessType: 'Access Method',
apiServer: 'API Server',
version: 'Version',
nodes: 'Nodes',
unknown: 'Unknown',
},
empty: {
title: 'No clusters yet',
description: 'Add your first Kubernetes cluster to get started',
},
delete: {
warning:
'Delete "{{name}}"? Warning: {{dep}} deployments and {{col}} collector policies still reference this cluster; they will stop working once it is deleted.',
},
toast: {
deleteFailed: 'Failed to delete cluster',
validateFailed: 'Validation failed',
createFailed: 'Failed to create cluster',
},
form: {
addTitle: 'Add Kubernetes Cluster',
editTitle: 'Edit Cluster',
editTitleWithName: 'Edit Cluster · {{name}}',
nameLabel: 'Cluster Name *',
descriptionLabel: 'Description',
descriptionPlaceholder: 'Production logging cluster',
accessTypeLabel: 'Access Method',
kubeconfigLabel: 'Kubeconfig *',
kubeconfigLabelEdit: 'Kubeconfig (leave blank to keep unchanged)',
kubeconfigPlaceholder: 'Paste kubeconfig YAML (API Server is detected automatically)...',
apiServerDetected: 'Detected API Server: {{server}}',
apiServerLabel: 'API Server *',
tokenLabel: 'Token *',
tokenLabelEdit: 'Token (leave blank to keep unchanged)',
tokenPlaceholder: 'Paste Service Account Token...',
tokenInsecureHint:
'Uses insecure mode, for clusters on private networks or with self-signed certificates',
rbacSummary: 'Least-privilege recommendations (click to expand)',
rbacScope:
'Grant permissions only in the <code>log</code> namespace so a leaked Token cannot affect other namespaces; <strong>do not use cluster-admin</strong>.',
rbacScriptIntro:
'Creates the <code>log</code> namespace, ServiceAccount, namespace-scoped Role and a long-lived Token in one shot (run it once in the cluster, then paste the Token into the field below):',
rbacScriptComment1: '# 1) Create in one shot: namespace + SA + namespace-scoped Role + long-lived Token',
rbacScriptComment2: '# 2) Extract the long-lived Token (it does not expire) and paste it into the Token field',
rbacNamespaceNote:
'The Namespace above is created by an administrator; the platform Token itself has no cluster-wide permissions. Only grant the <code>namespaces: create/get</code> ClusterRole if the platform really must create its own namespaces — note that namespace creation cannot be restricted by name, so pre-creating <code>log</code> is safer.',
rbacTokenNote:
'The Token is long-lived but bound to a Role in the <code>log</code> namespace only (it can only touch Pods and workloads there), so a leak is limited to <code>log</code>. The server converts it to a kubeconfig and stores it encrypted with <code>ENCRYPTION_KEY</code>; the database keeps only <code>ENC:</code> ciphertext and the API never returns the plaintext. To rotate it, delete and recreate the Secret — the old Token stops working immediately.',
contextLabel: 'Context (optional)',
contextPlaceholder: 'Leave blank to use the default context',
submitAdd: 'Add Cluster',
submitSave: 'Save Changes',
submittingAdd: 'Adding...',
submittingSave: 'Saving...',
validation: {
nameRequired: 'Cluster name is required',
kubeconfigRequired: 'Kubeconfig content is required',
apiServerRequired: 'API Server address is required',
tokenRequired: 'Token is required',
},
},
detail: {
loadError: 'Failed to load cluster details',
version: 'Version',
unknown: 'Unknown',
currentContext: 'Current Context',
defaultContext: 'Default',
nodesCount: 'Nodes ({{count}})',
nodeReady: 'Ready',
nodeNotReady: 'Not Ready',
nodeIp: 'IP: {{value}}',
nodeOs: 'OS: {{os}}/{{arch}}',
nodeVersion: 'Version: {{version}}',
namespaces: 'Namespaces',
},
}
+131
View File
@@ -0,0 +1,131 @@
// collectors namespace — "Log Collectors" page domain, English copy.
// Registration: the i18n entry (src/i18n/index.ts) is framework-owned; each page-domain
// dictionary registers itself via addResourceBundle when side-effect imported by its
// pages (zh/en each register their own bundle), coexisting with the static common NS.
const collectors = {
page: {
title: 'Log Collectors',
description: 'Configure log collection policies for hosts and clusters; logs are pushed to the linked Loki',
addAction: 'Add Collection Policy',
},
empty: {
title: 'No collection policies yet',
description: 'Create a log collector and link it to a deployed Loki',
},
typeLabel: {
hostPromtail: 'Host Promtail',
k8sDaemonset: 'Promtail DaemonSet',
k8sFluentbitSidecar: 'Fluent Bit Sidecar',
},
field: {
loki: 'Linked Loki',
cluster: 'Cluster',
host: 'Host',
namespace: 'Namespace',
logPath: 'Log Path',
target: 'Target',
unknown: 'Unknown',
},
action: {
install: 'Deploy',
uninstall: 'Uninstall',
editAria: 'Edit collection policy {{name}}',
deleteAria: 'Delete collector {{name}}',
},
toast: {
loadOptionsFailed: 'Failed to load Loki deployments, clusters, or hosts',
installFailed: 'Failed to deploy collector',
uninstallFailed: 'Failed to uninstall collector',
deleteFailed: 'Failed to delete collector',
createFailed: 'Failed to create collector',
saveFailed: 'Failed to save collection policy',
},
deleteConfirm: 'Delete "{{name}}"? The collector will be uninstalled automatically. This cannot be undone.',
form: {
title: 'Add Collection Policy',
name: 'Name',
type: 'Collector Type',
typeOption: {
hostPromtail: 'Host Promtail (SSH)',
k8sDaemonset: 'Kubernetes Promtail DaemonSet',
k8sFluentbitSidecar: 'Kubernetes Fluent Bit Sidecar',
},
lokiDeployment: 'Linked Loki Deployment',
lokiPlaceholder: 'Select a deployed Loki (push target)',
statusParen: ' ({{status}})',
hostParen: ' ({{ip}})',
host: 'Target Host',
hostPlaceholder: 'Select a host',
cluster: 'Target Cluster',
clusterPlaceholder: 'Select a cluster',
namespace: 'Namespace',
nsPickCluster: 'Select a cluster first',
nsLoading: 'Loading namespaces…',
nsPick: 'Select a namespace',
nsEmpty: 'No readable namespaces',
nsLoadFailed: 'Failed to read namespaces',
pickFromCluster: 'Pick from cluster',
manualInput: 'Enter manually',
targetDeployment: 'Target Deployment',
wlPickNamespace: 'Select a namespace first',
wlLoading: 'Loading Deployments…',
wlPick: 'Select a Deployment',
wlEmpty: 'No Deployments in this namespace',
wlLoadFailed: 'Failed to read Deployments',
wlReady: ' ({{ready}}/{{total}} ready)',
logPath: 'Log Path',
logPathHostHint: 'Supports glob wildcards (e.g. /var/log/*.log /var/log/syslog); separate multiple paths with commas',
logPathSidecarHint:
'Enter the file path your app writes to, e.g. /var/log/app/*.log. Existing log volumes are shared automatically; without one, a shared volume is created only in the dedicated logs directory. Regular paths such as /tmp/test.log are not mounted automatically.',
labels: 'Custom Labels (JSON, optional)',
labelsHint: 'When valid JSON, attached as labels to every log line for filtering in Loki/Grafana; leave empty to skip',
submit: 'Create & Deploy',
submitting: 'Creating...',
validation: {
nameRequired: 'Enter a collector name',
lokiRequired: 'Select the Loki deployment to link',
hostRequired: 'Select the target host',
clusterRequired: 'Select the target cluster',
namespaceRequired: 'Select a namespace',
targetDeploymentRequired: 'Enter the target Deployment name',
},
},
edit: {
title: 'Edit Collection Policy',
namespaces: 'Namespaces to Collect',
allNamespaces: 'All namespaces cluster-wide',
selectedCount: '{{count}} selected',
allCheckbox: 'All namespaces (collect cluster-wide)',
nsSearch: 'Search namespaces…',
loading: 'Loading…',
nsListEmpty: 'Namespace list unavailable',
nsNoMatch: 'No matches',
nsLoadFailed: 'Failed to load namespaces',
nsHint:
'Check "All namespaces" to collect cluster-wide; otherwise only the selected namespaces are collected. Generates the <code>kubernetes_sd_configs.namespaces</code> allowlist for Promtail.',
appLabelSource: 'app label source',
appSourcePlaceholder: 'Empty = default (controller-name fallback chain)',
appSourceHint:
'By default falls back through <code>app.kubernetes.io/name → app → controller name → pod name</code>. If your cluster has its own convention label (e.g. <code>k8s.kuboard.cn/name</code>), enter it here and it takes the highest priority; the rest still serve as fallback.',
appSourcePreset: {
default: 'Default: app.kubernetes.io/name → app → controller name → pod name',
controllerName: 'controller_name (ReplicaSet name of the Pod, hash stripped = Deployment name)',
k8sAppName: 'app.kubernetes.io/name (Kubernetes standard label)',
app: 'app (common shorthand)',
kuboard: 'k8s.kuboard.cn/name (Kuboard injected)',
},
appInclude: 'Only collect these apps (optional)',
appIncludePlaceholder: 'Empty = no limit; comma-separated, e.g. cex-spot-api, cex-asset-api',
appIncludeHint:
'When set, Promtail keeps only logs whose app label matches these values (<code>action: keep</code>) — the most direct way to control log volume. Values must match the app resolved by the "app label source" above.',
logPathHint: 'The Sidecar requires the app container\'s shared log volume or the dedicated logs directory. Click "Deploy" after saving to apply the new configuration.',
save: 'Save',
saving: 'Saving…',
validation: {
nameRequired: 'Enter a policy name',
},
},
}
export default collectors
+99
View File
@@ -0,0 +1,99 @@
// common namespace — app shell (menu / top bar / login / shared components / status)
export default {
appName: 'LokiStack',
menu: {
dashboard: 'Dashboard',
wizard: 'Deploy Wizard',
clusters: 'Clusters',
hosts: 'Hosts',
storage: 'Storage',
deployments: 'Deploy Loki',
collectors: 'Log Collectors',
logs: 'Log Query',
alerts: 'Alerts',
settings: 'Settings',
users: 'Users',
login: 'Sign In',
notFound: 'Page Not Found',
},
layout: {
nav: 'Main navigation',
sheetTitle: 'LokiStack Navigation',
collapse: 'Collapse menu',
expand: 'Expand menu',
openNav: 'Open navigation menu',
logout: 'Sign out',
logoutShort: 'Sign out',
toLight: 'Switch to light theme',
toDark: 'Switch to dark theme',
admin: 'Admin',
loading: 'Loading page',
},
login: {
subtitle: 'Loki Log Management Platform',
heading: 'Sign In',
username: 'Username',
usernamePlaceholder: 'Enter username',
password: 'Password',
passwordPlaceholder: 'Enter password',
remember: 'Remember username',
submit: 'Sign In',
submitting: 'Signing in...',
errCredential: 'Invalid username or password',
errConn: 'Cannot reach the backend. Make sure it is running on port 8080.',
errFallback: 'Sign-in failed',
backendDown: 'Backend is not running',
backendDownHint: 'Cannot reach the API service. Start the backend first:',
},
dialog: {
confirmTitle: 'Confirm Deletion',
confirmText: 'Delete',
cancel: 'Cancel',
processing: 'Processing...',
confirmIrreversible: 'Are you sure? This cannot be undone.',
confirmDefault: 'Delete "{{name}}"? This cannot be undone.',
},
error: {
title: 'Failed to load',
hint: 'Check the network or backend service, then retry',
retry: 'Retry',
},
pagination: {
total: '{{count}} items',
summary: '{{count}} items · Page {{page}} of {{pages}}',
},
status: {
active: 'Running',
running: 'Running',
inactive: 'Stopped',
stopped: 'Stopped',
pending: 'Pending',
deploying: 'Deploying',
failed: 'Failed',
error: 'Error',
},
notFound: {
heading: 'Page Not Found',
hint: 'The page does not exist or has been removed',
backHome: 'Back to Home',
},
lang: {
section: 'Language',
zh: '中文',
en: 'English',
},
boundary: {
title: 'Rendering Error',
hint: 'Something went wrong. Try refreshing; if it persists, report the details below.',
unknown: 'Unknown error',
reload: 'Reload Page',
reset: 'Reset Component',
},
spinner: {
default: 'Loading...',
},
password: {
hide: 'Hide password',
show: 'Show password',
},
}
+28
View File
@@ -0,0 +1,28 @@
// dashboard namespace — Dashboard domain (stat cards / quick start / recent deployments / resource lists)
export default {
stat: {
totalDeployments: 'Deployments',
runningCount: '{{count}} running',
clusters: 'Clusters',
connected: 'Connected',
hosts: 'Hosts',
sshReady: 'SSH ready',
status: 'Status',
},
quickStart: {
title: 'Start collecting logs in three steps',
subtitle: 'Target & environment → Storage → Deploy Loki and Grafana, then add a collection strategy',
cta: 'Open Deploy Wizard',
},
recent: {
title: 'Recent Deployments',
viewAll: 'View all',
emptyTitle: 'No deployments yet',
emptyDesc: 'Create your first Loki deployment',
},
list: {
manage: 'Manage',
empty: 'No {{title}} yet',
},
loadFailed: 'Failed to load dashboard data',
}
+124
View File
@@ -0,0 +1,124 @@
// deployments namespace — Loki deployment page copy (page / card / form / retention / logs dialogs)
export default {
page: {
title: 'Deploy Loki',
description: 'Deploy and manage Grafana Loki log aggregation services',
create: 'New Deployment',
emptyTitle: 'No deployments',
emptyDescription: 'Create your first Loki deployment',
// delete confirmation: host-docker removes the container too; other modes only remove the record
deleteDockerDesc:
'Delete "{{name}}"? The Loki container on the host will be removed as well. This cannot be undone.',
deleteRecordDesc:
'Delete "{{name}}"? Deleting the record does not uninstall the Loki resources already deployed on the cluster/host. This cannot be undone.',
errLoadRefs: 'Failed to load cluster/host/storage data',
errDeploy: 'Deployment failed',
errStop: 'Failed to stop',
errRestart: 'Failed to restart',
errDelete: 'Failed to delete the deployment',
},
card: {
mode: 'Mode',
modeK8s: 'Kubernetes',
modeDocker: 'Host Docker',
version: 'Version',
target: 'Target',
targetCluster: 'Cluster: {{name}}',
targetHost: 'Host: {{name}}',
targetUnknown: 'Unknown',
namespace: 'Namespace',
replicas: 'Replicas',
replicasValue: '{{replicas}} · Retain {{days}}d',
retention: 'Retention',
retentionValue: '{{days}}d',
storage: 'Storage',
localStorage: 'Local',
ready: 'Loki ready',
notReady: 'Loki not ready',
runningHint:
'Tip: link Loki under "Log Query → Data Sources" to start querying; for cross-cluster/public access, configure an Ingress domain for Loki first.',
configure: 'Configure',
deploy: 'Deploy',
deploying: 'Deploying...',
stop: 'Stop',
restart: 'Restart',
logs: 'Logs',
deleteAria: 'Delete deployment {{name}}',
},
form: {
title: 'Create Loki Deployment',
name: 'Deployment Name *',
description: 'Description',
descriptionPlaceholder: 'Loki log service for production',
version: 'Version',
mode: 'Deploy Mode',
modeK8s: 'Kubernetes',
modeDocker: 'Host Docker',
cluster: 'Target Cluster *',
clusterPlaceholder: 'Select a cluster',
namespace: 'Namespace *',
host: 'Target Host *',
hostPlaceholder: 'Select a host',
listenIP: 'Listen IP',
listenIPPlaceholder: 'Defaults to the host IP once a host is selected',
listenPort: 'Access Port',
dockerHint: 'Starts a Docker container on the host via SSH; config and data stay on the host.',
storage: 'Storage Config',
storagePlaceholder: 'Select storage (optional)',
localStorage: 'Local storage (default)',
replicas: 'Replicas',
retentionDays: 'Retention (days)',
storageSize: 'Storage Size',
memory: 'Memory',
authEnabled: 'Enable multi-tenant authentication',
create: 'Create Deployment',
creating: 'Creating...',
errCreate: 'Failed to create the deployment',
// zod validation messages (t injected by the schema factory)
nameRequired: 'Enter a deployment name',
versionRequired: 'Select a Loki version',
retentionInvalid: 'Retention days must be an integer no less than 1',
replicasInvalid: 'Replicas must be an integer between 1 and 10',
cpuRequired: 'Enter the CPU quota (e.g. 500m)',
memRequired: 'Enter the memory quota (e.g. 512Mi)',
storageSizeRequired: 'Enter the storage size (e.g. 20Gi)',
clusterRequired: 'Select a target cluster',
hostRequired: 'Select a target host',
portInvalid: 'Port must be between 1 and 65535',
namespaceRequired: 'Enter the namespace',
storageSizeMin: 'Alibaba Cloud ESSD requires at least 20Gi — enter 20Gi or more',
},
retention: {
title: 'Edit Loki Config · {{name}}',
locationLabel: 'Config location: ',
locationHostBinary: '/etc/loki/loki.yaml → limits_config.retention_period',
locationHostDocker: 'Host /etc/lokistack/loki-{{id}}/loki.yaml → limits_config.retention_period',
locationK8s: 'ConfigMap {{namespace}}/loki-config → loki.yaml',
compactorNote: 'Retention is enforced by the Loki compactor; MinIO only stores the log objects.',
liveTitle: 'Full live loki.yaml',
liveHint:
'Read from the deployment target with storage credentials masked; retention days below are edited separately.',
view: 'View Full Config',
refresh: 'Refresh',
loading: 'Loading…',
templateNote: 'Generated by the deployment template',
yamlLabel: 'Retention Config YAML',
yamlHint: 'Edit limits_config.retention_period, e.g. 90d or 2160h. Only this setting is accepted here.',
applyHintRunning:
'Save only updates the deployment record; Save & Apply redeploys Loki and queries may be briefly interrupted.',
applyHintIdle: 'Takes effect on the next deployment.',
cancel: 'Cancel',
saveOnly: 'Save Only',
saveApply: 'Save & Apply',
processing: 'Processing…',
savedApplied: 'Saved {{days}} days — Loki is being redeployed',
yamlRequired: 'Enter the YAML config',
errLoad: 'Failed to read the live config',
errSave: 'Failed to save the Loki config',
},
logs: {
title: 'Deployment Logs - {{name}}',
empty: 'No logs yet...',
errLoad: 'Failed to load deployment logs',
},
}
+81
View File
@@ -0,0 +1,81 @@
// hosts namespace — host management page (English)
export default {
title: 'Hosts',
description: 'Manage SSH hosts for binary-based Loki deployment',
actions: {
add: 'Add Host',
detail: 'Details',
editAria: 'Edit host {{name}}',
validateAria: 'Validate host {{name}}',
deleteAria: 'Delete host {{name}}',
},
fields: {
address: 'Address',
user: 'User',
auth: 'Auth',
authPassword: 'Password',
authKey: 'SSH Key',
os: 'OS',
unknown: 'Unknown',
},
empty: {
title: 'No hosts yet',
description: 'Add your first SSH host to get started',
},
delete: {
warning:
'Delete "{{name}}"? Warning: {{dep}} deployments and {{col}} collector policies still reference this host; they will stop working once it is deleted.',
},
toast: {
deleteFailed: 'Failed to delete host',
validateFailed: 'Validation failed',
createFailed: 'Failed to add host',
},
form: {
addTitle: 'Add SSH Host',
editTitle: 'Edit Host',
editTitleWithName: 'Edit Host · {{name}}',
nameLabel: 'Name *',
hostIpLabel: 'Host IP *',
portLabel: 'Port',
userLabel: 'Username *',
authTypeLabel: 'Authentication',
passwordLabel: 'Password *',
passwordLabelEdit: 'Password (leave blank to keep unchanged)',
passwordPlaceholder: 'SSH login password',
privateKeyLabel: 'Private Key *',
privateKeyLabelEdit: 'Private Key (leave blank to keep unchanged)',
keyPhraseLabel: 'Passphrase (optional)',
keyPhrasePlaceholder: 'Leave blank if no passphrase',
labelsLabel: 'Labels (JSON)',
submitAdd: 'Add Host',
submitSave: 'Save Changes',
submittingAdd: 'Adding...',
submittingSave: 'Saving...',
validation: {
nameRequired: 'Host name is required',
hostIpRequired: 'Host IP is required',
portRequired: 'Port is required',
portNumeric: 'Port must be a number',
portRange: 'Port must be between 1 and 65535',
userRequired: 'Username is required',
passwordRequired: 'SSH password is required',
privateKeyRequired: 'Paste the SSH private key content',
},
},
detail: {
loadError: 'Failed to load host details',
usage: 'Resource Usage',
memory: 'Memory',
disk: 'Disk',
usageDetail: '({{detail}})',
basicInfo: 'Basic Info',
hostname: 'Hostname',
os: 'OS',
arch: 'Architecture',
kernel: 'Kernel',
cpuCores: 'CPU Cores',
uptime: 'Uptime',
unknown: 'Unknown',
},
}
+325
View File
@@ -0,0 +1,325 @@
// logs namespace — Log Query page (/logs) English copy
// Terms left untranslated: LogQL, Live Tail (Tail), Loki, label, stream, Ingress, Pod, Namespace.
export default {
nav: {
aria: 'Log center navigation',
},
tabs: {
query: 'Loki Query',
access: 'Query Access',
sources: 'Data Sources',
descQuery: 'Search by log stream and time range; drag the timeline to narrow the range, and click a log line to view its context.',
descAccess: 'Reuse log scopes with policy groups and attach multiple users at once.',
descSources: 'Configure Loki connections and authentication.',
},
toolbar: {
run: 'Run',
running: 'Running…',
tail: 'Tail',
tailStop: 'Stop Tail',
sourceAria: 'Data source',
sourceEmptyOption: 'No data sources available',
limitLabel: 'Per page',
limitHint: 'Up to 1000 entries per page; paging keeps only the current page. For wide ranges, check the timeline first, then drag to select a peak window.',
rangeInvalid: 'Select a valid start and end time',
queryFailed: 'Query failed',
},
source: {
loadFailed: 'Failed to load data sources',
missingTitle: 'No Loki data source available',
missingBody:
'Link a Loki data source first under "Data Sources": for in-cluster Loki, pick "Cluster Service" (Namespace / Service / port, no Ingress needed); for cross-cluster or public queries, expose Loki via an Ingress domain first, then add it with "Direct URL". Queries are unavailable until a data source is linked.',
goConfigure: 'Go to data sources',
sectionAria: 'Data source management',
heading: 'Loki Data Sources',
hint: 'Tip: when Loki runs in the cluster, add a "Cluster Service" data source directly (no Ingress needed); for cross-cluster / public queries, expose Loki via an Ingress domain first, then link it with "Direct URL". Without a linked data source, the query page cannot be used.',
managedCluster: 'Cluster {{name}}',
managedBadge: 'Platform deployed · managed with the deployment',
add: '+ Add manually',
nameLabel: 'Name',
targetUrl: 'Direct URL',
targetClusterSvc: 'Cluster Service',
addressLabel: 'Loki URL',
clusterLabel: 'Cluster',
clusterPlaceholder: 'Select a cluster…',
namespaceLabel: 'Namespace',
serviceLabel: 'Service',
portLabel: 'Port',
authLabel: 'Authentication',
authNone: 'None',
authBasic: 'Basic Auth',
authBearer: 'Bearer Token',
authHeaders: 'Custom Headers',
usernameLabel: 'Username',
passwordLabel: 'Password',
tokenLabel: 'Token',
headersLabel: 'Custom Headers (JSON object)',
headersInvalid: 'Headers must be a JSON object with string values',
skipTLS: 'Skip TLS verification',
setDefault: 'Set as default',
test: 'Test connection',
save: 'Save data source',
saveFailed: 'Failed to save',
remove: 'Delete',
},
range: {
lastMinutes: 'Last {{count}} minutes',
lastHours: 'Last {{count}} hours',
lastHours_one: 'Last {{count}} hour',
custom: 'Custom time',
title: 'Select time range',
start: 'Start time',
end: 'End time',
apply: 'Apply',
recent: 'Recently used',
},
editor: {
aria: 'LogQL query',
valuesOf: 'Values of {{label}}',
quickTab: 'Quick LogQL',
historyTab: 'History',
historyTabCount: 'History ({{count}})',
escClose: 'Esc to close',
historyEmpty: 'Frequently used LogQL will appear here after you run a query',
},
templates: {
aria: 'Quick query templates',
title: 'Quick LogQL builder',
hint: 'Pick a namespace, then an app, then a pod; app candidates come only from the selected namespace.',
namespace: 'Namespace',
app: 'App',
appWithKey: 'App ({{key}})',
pod: 'Pod',
selectCount: 'Select ({{count}} total)',
noApp: 'No app in this scope',
pickNsFirst: 'Pick a namespace first to narrow down',
noPod: 'No pods under this app',
optional: 'Optional',
scopeLogs: 'Query this scope',
errorLogs: 'Errors (error)',
warnLogs: 'Warnings (warn)',
podLogs: 'Specific pod logs',
ingressLogs: 'Ingress access logs',
scopeHint: 'Pick a namespace, then an app or pod — or choose values in the Label browser',
},
labels: {
browser: 'Label Browser',
selectedCount: '{{count}} selected',
loadingLabels: 'Loading labels…',
noLabels: 'No labels available',
pickHint: 'Select a label on the left, then search its values',
valuesOf: 'Values of {{label}}',
searchAria: 'Search values of {{label}}',
searchPlaceholder: 'Search or type a label value',
scoped: 'Narrow candidates by current query',
loading: 'Loading…',
noValues: 'No matching values',
useAsValue: 'Use "{{value}}" as value',
hint: 'Select namespace first to query all replicas in that Namespace; then app / service_name to narrow to a service (workloads without an app label can rely on namespace alone). Candidates come from logs written to Loki within the current time range; the authorized scope is still enforced server-side.',
},
volume: {
statsLoading: 'Counting logs across the whole time range; you can keep browsing the log page.',
statsError: 'Log volume statistics timed out or failed; the log page still works. Narrow the time range and query again.',
histogramLoading: 'Counting…',
histogramEmpty: 'No log volume data',
timelineAria: 'Log volume timeline',
timelineTitle: 'Log volume timeline',
timelineHint: 'Hover to see counts; press and drag at any height to select a time span; the query runs automatically after selection.',
timelineTotal: '{{count}} logs in range',
timelineBucket: '{{count}} logs',
timelineBucket_one: '{{count}} log',
},
results: {
aria: 'Log results',
title: 'Log Results',
summary: 'Page {{page}} · {{count}} entries',
summary_one: 'Page {{page}} · {{count}} entry',
summaryMore: 'Page {{page}} · {{count}} entries · more available',
summaryMore_one: 'Page {{page}} · {{count}} entry · more available',
viewAria: 'Log display mode',
viewRaw: 'Raw',
viewTable: 'Table',
viewLine: 'Line',
viewStream: 'Stream',
viewPie: 'Pie',
loadingPage: 'Loading log page',
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.',
domainFilter: 'Domain filter',
domainPlaceholder: 'Type or pick a domain',
domainQuery: 'Query in Loki',
domainHint: 'Matches only the request Host recorded in log lines. Showing {{shown}} / {{total}}.',
domainNoMatch: 'No logs match the current domain filter',
filterLabel: 'Filter by this label',
showContext: 'Show 5 lines before and after',
prevPage: 'Previous',
nextPage: 'Next',
pageOf: 'Page {{page}}',
noSourceTitle: 'No queryable data source',
noSourceHint: 'Ask an administrator to add or authorize a data source',
noMatchTitle: 'No matching log entries',
noMatchHint: 'No results under the current time range and filters',
idleTitle: 'Enter a LogQL query and run it',
idleHint: 'You can also build queries quickly in the Label browser',
retry: 'Query again',
},
views: {
noDomain: 'No domain recorded',
otherDomain: 'Other domains',
unknown: 'Unknown',
untagged: 'Untagged',
other: 'Other',
tableLoaded: '{{count}} log entries loaded on this page.',
tableEmpty: 'No logs to display',
thTime: 'Time',
thNsPod: 'namespace / pod',
thLevel: 'Level',
thLog: 'Log',
lineByLoaded: 'Based on currently loaded logs; load more for additional results.',
lineByRange: 'Log volume aggregated over the query time range, from Loki.',
lineGroupAria: 'Line chart grouping',
lineByDomain: 'By domain',
lineByTotal: 'Total',
lineEmpty: 'No time-series data for this query',
lineSeriesTotal: 'Log count',
streamParsed: 'Showing {{count}} parsed entries',
streamByLevel: 'Showing {{count}} loaded entries by level',
streamSuffix: ', based on currently loaded results.',
streamGroupAria: 'Stream chart grouping',
streamByStatus: 'By HTTP status class',
streamEmpty: 'No log streams to display',
pieByAccess: 'Grouping {{count}} parsed entries by access fields',
pieByLabel: 'Grouping {{count}} loaded entries by label',
pieGroupAria: 'Pie chart grouping',
pieDomain: 'Domain (request Host)',
pieStatus: 'HTTP status',
pieMethod: 'Method',
piePath: 'Path',
pieUpstream: 'Upstream service',
pieLevel: 'Level',
pieEmpty: 'No label distribution to display',
},
ingress: {
modesAria: 'Ingress statistics mode',
modeTraffic: 'Endpoints PV / UV',
modeErrors: 'Top 10 errors',
modeSlow: 'Top 10 slow requests',
modeDetail: 'Request details',
summary:
'Based on the {{loaded}} entries loaded on this page, of which {{parsed}} are ingress-nginx access logs. Statistics are recomputed after paging; UV is deduplicated by non-empty client IP.',
statPv: 'PV',
statUv: 'UV (IP)',
statErrors: '4xx / 5xx',
statSlow: 'Slow ≥ {{threshold}}s',
errorClassAria: 'Error status range',
errorAll: 'All 4xx / 5xx',
error5xx: '5xx only',
error4xx: '4xx only',
slowThreshold: 'Slow request threshold',
seconds: '{{count}}s',
slowHint: 'Sorted by request_time from the access log; 408 / 499 / 504 are only timeout hints and do not by themselves prove a backend timeout.',
slowMissing: '{{count}} entries lack a valid duration.',
trafficHint: 'Success rate = 2xx / 3xx requests ÷ PV; query strings are stripped from paths.',
thHost: 'Request Host',
thPath: 'Path',
thSuccess: 'Success rate',
thErrorRate: 'Error rate',
thStatus: 'Status codes',
thSlow: 'Slow',
thMaxLatency: 'Max latency',
thAvgLatency: 'Avg latency',
thTimeout: '408 / 499 / 504',
thClientIp: 'Client IP',
thStatusShort: 'Status',
thLatency: 'Latency',
thTime: 'Time',
empty: 'No matching requests on this page',
noDomain: 'No Host recorded',
},
context: {
title: 'Context of the same log stream',
sameFile: 'Same file',
sameStream: 'Same log stream',
meta: 'Up to 5 entries before and after · ±5 minutes',
file: 'File: {{name}}',
current: 'Current',
currentAria: 'Current log',
beforeAria: 'Logs before',
afterAria: 'Logs after',
beforeTitle: 'Before · {{count}} entries',
beforeTitle_one: 'Before · {{count}} entry',
afterTitle: 'After · {{count}} entries',
afterTitle_one: 'After · {{count}} entry',
beforeEmpty: 'No logs in the previous 5 minutes',
afterEmpty: 'No logs in the following 5 minutes',
loadFailed: 'Failed to load context',
},
access: {
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',
deleteFailed: 'Failed to delete policy group',
sourceRef: 'Data source #{{id}}',
listTitle: 'Query policy groups',
create: 'New',
listHint: 'One log scope can be shared by multiple users; later changes apply to all of them.',
loading: 'Loading…',
empty: 'No policy groups yet',
anyone: 'Anyone',
userCount: '{{count}} users',
userCount_one: '{{count}} user',
editTitle: 'Edit policy group',
createTitle: 'New policy group',
formHint: 'Choose a data source and the allowed log scope, then attach the users who reuse this policy.',
remove: 'Delete',
nameLabel: 'Policy group name',
namePlaceholder: 'e.g. Production agent logs',
sourceLabel: 'Data source',
sourceEmpty: 'Add a data source first',
scopeTitle: 'Allowed log scope',
ownerFallback: 'Policy group',
scopeHint: 'Restrict precisely by Namespace, App, or Pod. Unselected labels are not restricted.',
usersTitle: 'Users',
selectedAnyone: 'Selected: Anyone (anyone)',
selectedCount: 'Selected {{count}}',
anyoneToggle: 'Anyone (anyone)',
anyoneActive: '✓ Anyone (anyone)',
selectFiltered: 'Select filtered',
clear: 'Clear',
usersHint: 'A user can belong to only one policy group per data source; users already in another group are grayed out.',
searchAria: 'Search users',
searchPlaceholder: 'Search by username or nickname',
grouped: 'In a group',
noMatch: 'No matching users',
saving: 'Saving…',
save: 'Save policy group',
deleteTitle: 'Delete query policy group',
deleteDesc: 'After deleting "{{name}}", attached users will lose the data source access granted by this group.',
ruleAria: 'Query scope of {{name}}',
nsLabel: 'Namespace',
appLabel: 'App',
podLabel: 'Pod',
},
select: {
placeholder: 'Search and select',
any: 'Any',
loading: 'Loading…',
noMatch: 'No matches',
},
raw: {
searching: 'Querying…',
count: '{{count}} logs',
count_one: '{{count}} log',
dedupSuffix: ' (deduplicated from {{count}})',
dedup: 'Dedup',
empty: 'No matching logs',
original: 'Raw log',
labelsTitle: 'Labels (click to filter)',
filterValue: 'Filter this value',
excludeValue: 'Exclude this value',
fields: 'Extracted fields',
loadMore: 'Load more',
loadingMore: 'Loading…',
},
}
+141
View File
@@ -0,0 +1,141 @@
// settings namespace — Settings page (general/password/about, notifications, LDAP, image registry), English copy.
// Note: theme & language card titles and ThemeSection copy are owned elsewhere and are not in this dictionary.
export default {
title: 'Settings',
description: 'General settings, alert notifications, LDAP integration, and the default image registry',
tabs: {
general: 'General',
notify: 'Alert Notifications',
ldap: 'LDAP Integration',
registry: 'Default Image Registry',
},
// Action labels shared across sections
actions: {
cancel: 'Cancel',
saving: 'Saving...',
testing: 'Testing...',
},
password: {
title: 'Change Password',
oldLabel: 'Current password',
oldPlaceholder: 'Enter current password',
newLabel: 'New password',
newPlaceholder: 'Enter new password (at least 6 characters)',
confirmLabel: 'Confirm new password',
confirmPlaceholder: 'Re-enter new password',
submit: 'Change password',
submitting: 'Changing...',
errMismatch: 'Passwords do not match',
errTooShort: 'New password must be at least 6 characters',
errFailed: 'Failed to change password',
},
about: {
title: 'About LokiStack',
versionLabel: 'Version',
backendLabel: 'Backend',
frontendLabel: 'Frontend',
k8sLabel: 'Kubernetes Access',
summaryLabel: 'Overview',
backendValue: 'Go + Gin + JSON file storage',
frontendValue: 'React 19 + TypeScript + Tailwind CSS',
k8sValue: 'client-go, direct API Server access',
summaryValue: 'Lightweight Loki log management platform',
},
notify: {
cardTitle: 'Alert Channels — Feishu (Lark)',
create: 'New channel',
// Empty state is assembled as "prefix + link + suffix" (link points to the Alerts page)
emptyBefore: 'No channels yet — create one per alert group (e.g. "App alerts", "Nginx alerts"), then link rules to it on the ',
emptyLink: 'Alerts',
emptyAfter: ' page',
webhookConfigured: 'Webhook configured',
webhookMissing: 'Not configured',
secretConfigured: ' · Signing secret configured',
testAria: 'Test {{name}}',
editAria: 'Edit {{name}}',
deleteAria: 'Delete {{name}}',
editTitle: 'Edit Channel · {{name}}',
createTitle: 'New Notification Channel',
nameLabel: 'Channel name *',
namePlaceholder: 'e.g. App alerts / Nginx alerts',
webhookLabel: 'Webhook URL',
webhookKeepHint: ' (leave blank to keep current)',
webhookSavedPlaceholder: 'Saved — enter a new URL to replace it',
signingLabel: 'Signing secret (optional)',
signingSavedPlaceholder: 'Saved — leave blank to keep current',
signingPlaceholder: 'Feishu (Lark) bot signing secret',
enable: 'Enable channel',
saveChanges: 'Save changes',
createChannel: 'Create channel',
deleteTitle: 'Delete Notification Channel',
testSent: 'Test card sent to "{{name}}"',
errLoad: 'Failed to load notification channels',
errNameRequired: 'Channel name is required',
errWebhookRequired: 'A Webhook URL is required for a new channel',
errSave: 'Save failed',
errSend: 'Send failed',
errToggle: 'Failed to toggle',
errDelete: 'Delete failed',
},
ldap: {
cardTitle: 'LDAP Integration',
headerHint: 'Enables LDAP sign-in and bulk user import',
errLoad: 'Failed to load LDAP configuration',
disabledHint:
'LDAP is disabled — turn it on and fill in the directory details (server / Bind DN / Base DN). After saving, "Import from LDAP" appears on the Users page.',
hostLabel: 'Server address *',
portLabel: 'Port',
ldapsLabel: 'LDAPS',
bindDnLabel: 'Bind DN *',
bindPasswordLabel: 'Bind password *',
bindPasswordSavedPlaceholder: 'Saved — enter a new password to replace it',
bindPasswordPlaceholder: 'Administrator password',
baseDnLabel: 'Base DN *',
userFilterLabel: 'User filter',
usernameLabel: 'Username',
nameLabel: 'Full name',
emailLabel: 'Email',
noEmail: 'No email',
testButton: 'Test connection',
saveButton: 'Save configuration',
errMissingFields: 'Fill in the server address / Bind DN / Base DN first',
errTest: 'Test failed',
savedToast: 'LDAP configuration saved',
},
registry: {
cardTitle: 'Image Registry',
modeOverseasLabel: 'Official (overseas)',
modeOverseasDesc: 'docker.io, for overseas networks',
modeCnLabel: 'China mirror',
modeCnDesc: 'docker.m.daocloud.io mirror, for networks in China',
modeCustomLabel: 'Private registry',
modeCustomDesc: 'Your own registry prefix (e.g. Huawei Cloud SWR)',
errLoad: 'Failed to load image registry configuration',
savedToast: 'Image registry saved. Future deployments and collectors will use the new image addresses.',
customPrefixLabel: 'Private registry prefix *',
customPrefixHint: 'The platform prepends this prefix to image names, e.g. {{example}}/grafana/loki:3.4.0',
imagesLabel: 'Per-service images (repository / tag can be overridden)',
tagFollowVersion: 'Follows deployment version',
repoAria: '{{label}} image repository',
tagAria: '{{label}} image tag',
// The images hint is assembled as "prefix + <code>grafana/loki</code> + suffix"
imagesHintBefore: 'Repository is the image path (e.g. ',
imagesHintAfter: '); when Tag is empty, Loki / Grafana follow the deployment version and Promtail / Fluent Bit use built-in defaults.',
previewTitle: 'Effective image preview',
saveButton: 'Save image registry',
},
themeCard: {
title: 'Theme',
light: 'Light',
dark: 'Dark',
system: 'Follow System',
auto: 'Scheduled',
timezone: 'Timezone',
timezoneAria: 'Theme schedule timezone',
browser: 'Follow browser (local)',
autoHint: 'Light from {{start}}:00 to {{end}}:00 in the selected timezone, dark otherwise; the sun/moon toggle overrides anytime.',
},
prefsCard: {
title: 'Appearance & Language',
},
}
+55
View File
@@ -0,0 +1,55 @@
// storage namespace — storage page copy (page shell / card / form dialog)
export default {
page: {
title: 'Storage',
description: 'Configure object storage backends for Loki',
add: 'Add Storage',
emptyTitle: 'No storage configs',
emptyDescription: 'Add your first Loki storage backend',
// delete confirmation shown while deployments still reference the config
deleteWarning_one:
'Delete "{{name}}"? Warning: {{count}} deployment still uses this storage; its object storage config will stop working.',
deleteWarning_other:
'Delete "{{name}}"? Warning: {{count}} deployments still use this storage; their object storage configs will stop working.',
errDelete: 'Failed to delete the storage config',
errSetDefault: 'Failed to set the default storage',
},
card: {
default: 'Default',
endpoint: 'Endpoint',
pathPrefix: 'Path prefix',
none: 'None',
connection: 'Connection',
insecureHttp: 'HTTP (insecure)',
setDefault: 'Set as default',
editAria: 'Edit storage config {{name}}',
deleteAria: 'Delete storage config {{name}}',
},
form: {
addTitle: 'Add Storage Config',
editTitle: 'Edit Storage Config',
name: 'Name *',
storageType: 'Storage Type',
endpoint: 'Endpoint',
bucket: 'Bucket *',
region: 'Region',
secretPlaceholder: 'Enter the Secret Key',
secretPlaceholderSaved: 'Saved · leave empty to keep',
insecure: 'Insecure (HTTP)',
setDefault: 'Set as default',
autoCreate: 'Auto-create the bucket during testing if missing',
test: 'Test Connection',
testing: 'Testing...',
add: 'Add Storage',
adding: 'Adding...',
save: 'Save Changes',
saving: 'Saving...',
testHintSaved: 'Fill in Bucket and Access Key first (Secret Key is saved — leave it empty)',
testHintMissing: 'Fill in Bucket and Access Key / Secret Key first',
errSubmit: 'Failed to create the storage config',
errTest: 'Connection test failed',
// zod validation messages (t injected by the schema factory)
nameRequired: 'Enter a storage config name',
bucketRequired: 'Enter the bucket name',
},
}
+87
View File
@@ -0,0 +1,87 @@
// users namespace — User management domain (header / role & source badges / create & reset password / LDAP import)
export default {
title: 'User Management',
subtitle: 'Manage platform sign-in accounts (admin only)',
create: 'Create User',
ldapImport: 'Import from LDAP',
emptyTitle: 'No users yet',
emptyDesc: 'Create the first user account',
listTitle: 'Users ({{count}} total)',
currentUser: '(you)',
noNickname: 'No nickname set',
createdAt: 'Created {{date}}',
resetPassword: 'Reset Password',
resetDesc:
'After resetting the password for "{{name}}", all of their sign-in sessions will be invalidated immediately.',
newPassword: 'New password *',
randomPassword: 'Generate random password',
resetting: 'Resetting...',
resetDone: 'Password for "{{name}}" has been reset; all of their sessions are now invalid',
resetFailed: 'Failed to reset password',
deleteAria: 'Delete user {{name}}',
deleteFailed: 'Failed to delete user',
cannotDeleteSelf: 'You cannot delete the account you are currently signed in with',
confirmDeleteTitle: 'Confirm user deletion',
createFailed: 'Failed to create user',
adminOnly: {
title: 'Admin access required',
desc: 'User management is available to admins only. Contact an administrator for account matters.',
},
role: {
admin: 'Admin',
user: 'User',
},
source: {
ldap: 'LDAP import',
local: 'Local account',
},
form: {
username: 'Username *',
usernamePlaceholder: 'Sign-in username',
usernameMin: 'Username must be at least 2 characters',
usernameMax: 'Username must be at most 32 characters',
nickname: 'Nickname',
nicknamePlaceholder: 'Display nickname (optional)',
password: 'Initial password *',
passwordPlaceholder: 'At least 6 characters',
passwordMin: 'Password must be at least 6 characters',
role: 'Role *',
rolePlaceholder: 'Select a role',
submitting: 'Creating...',
},
ldap: {
title: 'Import users from LDAP',
desc: 'Users from the LDAP directory. Imported users sign in with their LDAP password and are assigned the User role.',
added: '{{count}} added',
converted: '{{count}} converted to LDAP',
skipped: '{{count}} skipped',
failedCount: '{{count}} failed',
continue: 'Import more',
done: 'Done',
loading: 'Loading LDAP users...',
summary: '{{total}} users · {{selected}} selected',
errorHintPre: 'Go to',
errorHintPost: 'to check the LDAP configuration and status',
imported: 'Imported',
willConvert: 'Exists locally, will convert to LDAP',
noName: 'No name',
noEmail: 'No email',
empty: 'No users in this directory',
importing: 'Importing...',
importCount_one: 'Import {{count}} user',
importCount_other: 'Import {{count}} users',
importFailed: 'Import failed',
loadFailed: 'Failed to load LDAP users',
},
selectAria: 'Select user {{name}}',
batch: {
selected: '{{count}} selected',
delete: 'Delete Selected',
clear: 'Clear selection',
selectAll: 'Select all',
confirmTitle: 'Confirm Bulk Deletion',
confirmDesc: 'Delete {{count}} selected users? Protected accounts (your own / the last admin) will be skipped. This cannot be undone.',
skippedWarn: '{{count}} skipped: {{reasons}}',
failed: 'Bulk delete failed',
},
}
+69
View File
@@ -0,0 +1,69 @@
// wizard namespace — "Deploy Wizard" page domain, English copy.
// Registration: same as the collectors namespace — each page-domain dictionary registers
// itself via addResourceBundle when side-effect imported (no changes to src/i18n/index.ts).
const wizard = {
page: {
title: 'Deploy Wizard',
description: 'Set up the logging platform in four steps: environment → storage → deploy → collect (steps can be completed in any order)',
loadFailed: 'Failed to load wizard status',
},
steps: {
flow: 'Deployment flow',
doneAria: '{{title}} completed',
todoAria: '{{title}} pending',
env: { title: 'Environment & Target', desc: 'Kubernetes cluster or bare-metal host' },
storage: { title: 'Prepare Storage', desc: 'Object storage or local disk' },
deploy: { title: 'Deploy Loki', desc: 'Built-in log query' },
collect: { title: 'Configure Collection', desc: 'Host files / cluster DaemonSet' },
},
hint: {
env: 'Start by choosing an environment and connecting a cluster or host',
storage: 'Environment ready — object storage is recommended next',
deploy: 'Target and storage ready — create your Loki deployment',
collect: 'Loki is running — add a collection policy to start collecting logs',
all: 'All set: logs are being collected — head to Log Query to analyze them',
},
env: {
k8sTitle: 'Kubernetes Cluster',
k8sDesc: 'Deploy Loki as a StatefulSet via the Kubernetes API; a DaemonSet collects container logs',
hostTitle: 'Bare-Metal Host',
hostDesc: 'Deploy Loki with host Docker and collect specified log files',
ready: 'Ready',
connected: 'Connected: {{names}}',
nameSep: ', ',
k8sHint: 'Connect a cluster via kubeconfig or ServiceAccount token; connectivity is verified automatically on creation',
addCluster: 'Add Cluster',
hostHint: 'Connect a host via SSH key or password (used for deployment and later collector installation)',
addHost: 'Add Host',
pickFirst: '↑ Pick an environment above to get the matching target onboarding form',
},
storage: {
defaultLabel: 'Default storage: ',
none: 'No storage configured — use local MinIO / RustFS, or AWS / Alibaba Cloud object storage',
manage: 'Manage Storage',
add: 'Add Storage',
configure: 'Configure Storage',
hint: 'Object storage (MinIO / RustFS / S3 / OSS) is recommended for more reliable replication and retention. Use "Test Connection" when adding to verify credentials and auto-create missing buckets; skip to use local disk.',
},
deploy: {
meta: '{{mode}} · Namespace {{namespace}} · {{endpoint}}',
endpointPending: 'endpoint available after deployment',
openGrafana: 'Open Grafana ({{url}} · admin/admin)',
openLoki: 'Open Loki',
details: 'Deployment Details',
none: 'No running Loki — deploy to Kubernetes or host Docker',
create: 'Create Deployment',
triggering: 'Triggering deployment…',
deployTriggered: 'Deployment of "{{name}}" triggered — check the deployments page for logs and progress',
},
collect: {
title: 'Log Collection Policies',
activeCount: '{{count}} active',
desc: 'Hosts: specified log file paths · Clusters: all container logs via DaemonSet (deployed into the linked Loki\'s namespace)',
add: 'Add Collection Policy',
manage: 'Manage Collectors',
},
}
export default wizard