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:
1 parent
02d1bd87a7
commit
f3733f0bfc
201 files changed
+5438
-2388
No files matched your search
@@ -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',
|
||||
},
|
||||
}
|
||||
@@ -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',
|
||||
},
|
||||
}
|
||||
@@ -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
|
||||
@@ -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',
|
||||
},
|
||||
}
|
||||
@@ -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',
|
||||
}
|
||||
@@ -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',
|
||||
},
|
||||
}
|
||||
@@ -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',
|
||||
},
|
||||
}
|
||||
@@ -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…',
|
||||
},
|
||||
}
|
||||
@@ -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',
|
||||
},
|
||||
}
|
||||
@@ -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',
|
||||
},
|
||||
}
|
||||
@@ -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',
|
||||
},
|
||||
}
|
||||
@@ -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
|
||||
Reference in new issue
Block a user