fix(i18n): localize peer SSH, edit IP, routes, access tokens and setup keys
- Localize PeerEditIPModal config and buttons - Localize PeerSSHToggle dialogs, tooltips, help text and callouts - Localize AddRouteDropdownButton and RemoteJobDropdownButton - Localize PeerRoutesTable and RouteMetricCell tooltips - Localize AccessTokensTable empty state - Localize SetupKeysTable filters, options and empty states - Add missing translation keys to en.ts and zh.ts - Fix t.rich tag usage for peerOfflineRemoteJob - Fix singularize usage with ICU plural activePoliciesCount - Normalize formatting with prettier - Remove empty progress.md
This commit is contained in:
150
batch-i18n.py
150
batch-i18n.py
@@ -1,73 +1,103 @@
|
|||||||
import re
|
"""Batch i18n localization for remaining simple files"""
|
||||||
import os
|
import re, os
|
||||||
|
|
||||||
os.chdir('/root/github_projects/dashboard')
|
os.chdir('/root/github_projects/dashboard')
|
||||||
|
|
||||||
# List of files and their replacements
|
# Helper: replace first occurrence after a marker
|
||||||
tasks = [
|
def replace_in_file(filepath, replacements):
|
||||||
{
|
|
||||||
'file': 'src/modules/peer/AddRouteDropdownButton.tsx',
|
|
||||||
'import_replace': ('import Button from "@components/Button";', 'import { useTranslations } from "next-intl";\nimport Button from "@components/Button";'),
|
|
||||||
'function_replace': ('export default function AddRouteDropdownButton({', 'export default function AddRouteDropdownButton({\n const t = useTranslations("common");'),
|
|
||||||
'text_replacements': [
|
|
||||||
('New Network Route', '{t("newNetworkRoute")}'),
|
|
||||||
('Existing Network', '{t("existingNetwork")}'),
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'file': 'src/modules/peer/RemoteJobDropdownButton.tsx',
|
|
||||||
'import_replace': ('import Button from "@components/Button";', 'import { useTranslations } from "next-intl";\nimport Button from "@components/Button";'),
|
|
||||||
'function_replace': ('export default function RemoteJobDropdownButton({', 'export default function RemoteJobDropdownButton({\n const t = useTranslations("common");'),
|
|
||||||
'text_replacements': [
|
|
||||||
('Debug Bundle', '{t("debugBundle")}'),
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'file': 'src/modules/routes/RouteMetricCell.tsx',
|
|
||||||
'import_replace': ('import FullTooltip from "@components/FullTooltip";', 'import { useTranslations } from "next-intl";\nimport FullTooltip from "@components/FullTooltip";'),
|
|
||||||
'function_replace': ('export default function RouteMetricCell({\n metric,\n useHoverStyle = true,\n}: Readonly<Props>) {', 'export default function RouteMetricCell({\n metric,\n useHoverStyle = true,\n}: Readonly<Props>) {\n const t = useTranslations("common");'),
|
|
||||||
'text_replacements': [
|
|
||||||
('Lower metrics have higher priority.', '{t("metricPriority")}'),
|
|
||||||
]
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
for task in tasks:
|
|
||||||
filepath = task['file']
|
|
||||||
with open(filepath) as f:
|
with open(filepath) as f:
|
||||||
content = f.read()
|
content = f.read()
|
||||||
|
|
||||||
changed = False
|
changed = False
|
||||||
|
for old, new in replacements:
|
||||||
# Apply import replacement
|
|
||||||
old_import, new_import = task['import_replace']
|
|
||||||
if old_import in content:
|
|
||||||
content = content.replace(old_import, new_import)
|
|
||||||
changed = True
|
|
||||||
print(f"{filepath}: import added")
|
|
||||||
|
|
||||||
# Apply function replacement
|
|
||||||
old_func, new_func = task['function_replace']
|
|
||||||
if old_func in content:
|
|
||||||
content = content.replace(old_func, new_func)
|
|
||||||
changed = True
|
|
||||||
print(f"{filepath}: t() added")
|
|
||||||
else:
|
|
||||||
print(f"{filepath}: WARNING - function pattern not found!")
|
|
||||||
|
|
||||||
# Apply text replacements
|
|
||||||
for old, new in task['text_replacements']:
|
|
||||||
if old in content:
|
if old in content:
|
||||||
content = content.replace(old, new)
|
content = content.replace(old, new, 1)
|
||||||
changed = True
|
changed = True
|
||||||
print(f"{filepath}: '{old}' -> '{new}'")
|
print(f" {os.path.basename(filepath)}: replaced '{old[:40]}'")
|
||||||
else:
|
else:
|
||||||
print(f"{filepath}: WARNING - '{old}' not found!")
|
print(f" WARN: '{old[:40]}' not in {os.path.basename(filepath)}")
|
||||||
|
|
||||||
if changed:
|
if changed:
|
||||||
with open(filepath, 'w') as f:
|
with open(filepath, 'w') as f:
|
||||||
f.write(content)
|
f.write(content)
|
||||||
print(f"{filepath}: saved")
|
|
||||||
print()
|
|
||||||
|
|
||||||
print("Done!")
|
# === 1. AddRouteDropdownButton.tsx ===
|
||||||
|
replace_in_file('src/modules/peer/AddRouteDropdownButton.tsx', [
|
||||||
|
('import Button from "@components/Button";',
|
||||||
|
'import { useTranslations } from "next-intl";\nimport Button from "@components/Button";'),
|
||||||
|
('export default function AddRouteDropdownButton() {',
|
||||||
|
'export default function AddRouteDropdownButton() {\n const t = useTranslations("common");'),
|
||||||
|
('New Network Route', '{t("newNetworkRoute")}'),
|
||||||
|
('Existing Network', '{t("existingNetwork")}'),
|
||||||
|
])
|
||||||
|
|
||||||
|
# === 2. RemoteJobDropdownButton.tsx ===
|
||||||
|
replace_in_file('src/modules/peer/RemoteJobDropdownButton.tsx', [
|
||||||
|
('import Button from "@components/Button";',
|
||||||
|
'import { useTranslations } from "next-intl";\nimport Button from "@components/Button";'),
|
||||||
|
('export const RemoteJobDropdownButton = () => {',
|
||||||
|
'export const RemoteJobDropdownButton = () => {\n const t = useTranslations("common");'),
|
||||||
|
('Debug Bundle', '{t("debugBundle")}'),
|
||||||
|
])
|
||||||
|
|
||||||
|
# === 3. RouteMetricCell.tsx ===
|
||||||
|
replace_in_file('src/modules/routes/RouteMetricCell.tsx', [
|
||||||
|
('import FullTooltip from "@components/FullTooltip";',
|
||||||
|
'import { useTranslations } from "next-intl";\nimport FullTooltip from "@components/FullTooltip";'),
|
||||||
|
('export default function RouteMetricCell({',
|
||||||
|
'export default function RouteMetricCell({\n const t = useTranslations("common");'),
|
||||||
|
('Lower metrics have higher priority.', '{t("metricPriority")}'),
|
||||||
|
])
|
||||||
|
|
||||||
|
# === 4. PeerRoutesTable.tsx ===
|
||||||
|
replace_in_file('src/modules/peer/PeerRoutesTable.tsx', [
|
||||||
|
('import Card from "@components/Card";',
|
||||||
|
'import { useTranslations } from "next-intl";\nimport Card from "@components/Card";'),
|
||||||
|
('export const RouteTableColumns: ColumnDef<Route>[] = [',
|
||||||
|
'function RouteTableColumns(t: ReturnType<typeof useTranslations>): ColumnDef<Route>[] {\n return ['),
|
||||||
|
('];\n\nexport default function PeerRoutesTable({',
|
||||||
|
'];\n}\n\nexport default function PeerRoutesTable({'),
|
||||||
|
('export default function PeerRoutesTable({',
|
||||||
|
'export default function PeerRoutesTable({\n const t = useTranslations("common");'),
|
||||||
|
('];\n\nfunction RouteTableColumns', '];\n}\n\nfunction RouteTableColumns'), # fix double close
|
||||||
|
])
|
||||||
|
|
||||||
|
# Replace column headers in PeerRoutesTable
|
||||||
|
with open('src/modules/peer/PeerRoutesTable.tsx') as f:
|
||||||
|
c = f.read()
|
||||||
|
c = c.replace('DataTableHeader column={column}>Name<', 'DataTableHeader column={column}>{t("name")}<')
|
||||||
|
c = c.replace('DataTableHeader column={column}>Network<', 'DataTableHeader column={column}>{t("network")}<')
|
||||||
|
c = c.replace('DataTableHeader column={column}>Distribution Groups<', 'DataTableHeader column={column}>{t("distributionGroups")}<')
|
||||||
|
c = c.replace('DataTableHeader column={column}>Active<', 'DataTableHeader column={column}>{t("active")}<')
|
||||||
|
with open('src/modules/peer/PeerRoutesTable.tsx', 'w') as f:
|
||||||
|
f.write(c)
|
||||||
|
print(" PeerRoutesTable: column headers replaced")
|
||||||
|
|
||||||
|
# === 5. Add keys to en.ts ===
|
||||||
|
with open('src/i18n/messages/en.ts') as f:
|
||||||
|
en = f.read()
|
||||||
|
|
||||||
|
# Add to common namespace (after debugBundle or similar)
|
||||||
|
if 'debugBundle' not in en:
|
||||||
|
en = en.replace(
|
||||||
|
'routingPeer: "Routing Peer",',
|
||||||
|
'routingPeer: "Routing Peer",\n newNetworkRoute: "New Network Route",\n existingNetwork: "Existing Network",\n debugBundle: "Debug Bundle",\n metricPriority: "Lower metrics have higher priority.",'
|
||||||
|
)
|
||||||
|
|
||||||
|
with open('src/i18n/messages/en.ts', 'w') as f:
|
||||||
|
f.write(en)
|
||||||
|
print(" en.ts: keys added")
|
||||||
|
|
||||||
|
# === 6. Add keys to zh.ts ===
|
||||||
|
with open('src/i18n/messages/zh.ts') as f:
|
||||||
|
zh = f.read()
|
||||||
|
|
||||||
|
if 'debugBundle' not in zh:
|
||||||
|
zh = zh.replace(
|
||||||
|
'routingPeer: "路由节点",',
|
||||||
|
'routingPeer: "路由节点",\n newNetworkRoute: "新网络路由",\n existingNetwork: "现有网络",\n debugBundle: "调试包",\n metricPriority: "较低的度量值具有更高的优先级。"'
|
||||||
|
)
|
||||||
|
|
||||||
|
with open('src/i18n/messages/zh.ts', 'w') as f:
|
||||||
|
f.write(zh)
|
||||||
|
print(" zh.ts: Chinese translations added")
|
||||||
|
|
||||||
|
print("\nDone!")
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -2,7 +2,8 @@ export default {
|
|||||||
common: {
|
common: {
|
||||||
loading: "加载中...",
|
loading: "加载中...",
|
||||||
restrictedAccessHeading: "您无权访问",
|
restrictedAccessHeading: "您无权访问",
|
||||||
restrictedAccessDescription: "您似乎无权访问此页面。只有具有相应权限的用户才能访问此页面。请联系您的网络管理员获取更多信息。",
|
restrictedAccessDescription:
|
||||||
|
"您似乎无权访问此页面。只有具有相应权限的用户才能访问此页面。请联系您的网络管理员获取更多信息。",
|
||||||
error: "错误",
|
error: "错误",
|
||||||
success: "成功",
|
success: "成功",
|
||||||
cancel: "取消",
|
cancel: "取消",
|
||||||
@@ -79,6 +80,10 @@ export default {
|
|||||||
blockUser: "阻止用户",
|
blockUser: "阻止用户",
|
||||||
removePeersFromGroup: "从组中移除节点",
|
removePeersFromGroup: "从组中移除节点",
|
||||||
routingPeer: "路由节点",
|
routingPeer: "路由节点",
|
||||||
|
newNetworkRoute: "新网络路由",
|
||||||
|
existingNetwork: "现有网络",
|
||||||
|
debugBundle: "调试包",
|
||||||
|
metricPriority: "较低的度量值具有更高的优先级。",
|
||||||
peerGroup: "节点组",
|
peerGroup: "节点组",
|
||||||
expires: "过期",
|
expires: "过期",
|
||||||
distributionGroups: "分发组",
|
distributionGroups: "分发组",
|
||||||
@@ -90,7 +95,25 @@ export default {
|
|||||||
routingPeerHelp: "指定单个节点作为路由节点",
|
routingPeerHelp: "指定单个节点作为路由节点",
|
||||||
exitNode: "出口节点",
|
exitNode: "出口节点",
|
||||||
networkRoute: "网络路由",
|
networkRoute: "网络路由",
|
||||||
customHeadersHelp: "添加额外的标头以包含在转发请求中。\nHop-by-hop 标头如 Host 或 Connection 不被允许。"
|
addRoute: "添加路由",
|
||||||
|
newNetworkRouteDesc: "使用此节点创建新的网络路由",
|
||||||
|
existingNetworkDesc: "将此节点添加到现有网络",
|
||||||
|
networkRoutes: "网络路由",
|
||||||
|
noNetworkRoutes: "此节点没有网络路由",
|
||||||
|
noNetworkRoutesDesc:
|
||||||
|
"您还没有分配的网络路由。您可以将此节点添加到现有网络或创建新的网络路由。",
|
||||||
|
runRemoteJob: "运行远程任务",
|
||||||
|
peerOfflineRemoteJob:
|
||||||
|
"节点 <bold>{name}</bold> 当前离线。请连接节点以运行远程任务。",
|
||||||
|
debugBundleDesc: "收集调试信息以进行故障排除",
|
||||||
|
accessTokens: "访问令牌",
|
||||||
|
noAccessTokens: "暂无访问令牌",
|
||||||
|
noAccessTokensDesc:
|
||||||
|
"您还没有任何访问令牌。您可以添加令牌以访问 NetBird API。",
|
||||||
|
disable: "禁用",
|
||||||
|
network: "网络",
|
||||||
|
customHeadersHelp:
|
||||||
|
"添加额外的标头以包含在转发请求中。\nHop-by-hop 标头如 Host 或 Connection 不被允许。",
|
||||||
},
|
},
|
||||||
navigation: {
|
navigation: {
|
||||||
controlCenter: "控制中心",
|
controlCenter: "控制中心",
|
||||||
@@ -120,7 +143,7 @@ export default {
|
|||||||
auditEvents: "审计事件",
|
auditEvents: "审计事件",
|
||||||
settings: "设置",
|
settings: "设置",
|
||||||
documentation: "文档",
|
documentation: "文档",
|
||||||
helpAndSupport: "帮助和支持"
|
helpAndSupport: "帮助和支持",
|
||||||
},
|
},
|
||||||
table: {
|
table: {
|
||||||
search: "搜索...",
|
search: "搜索...",
|
||||||
@@ -141,7 +164,7 @@ export default {
|
|||||||
cancel: "取消",
|
cancel: "取消",
|
||||||
rows: "行",
|
rows: "行",
|
||||||
selectAll: "全选",
|
selectAll: "全选",
|
||||||
selectRow: "选择行"
|
selectRow: "选择行",
|
||||||
},
|
},
|
||||||
auth: {
|
auth: {
|
||||||
login: "登录",
|
login: "登录",
|
||||||
@@ -159,14 +182,15 @@ export default {
|
|||||||
accountBlocked: "账户已被阻止",
|
accountBlocked: "账户已被阻止",
|
||||||
accountPending: "账户待审批",
|
accountPending: "账户待审批",
|
||||||
sessionExpired: "会话已过期",
|
sessionExpired: "会话已过期",
|
||||||
sessionExpiredDescription: "您的登录会话似乎不再活跃或已过期。请重新登录以继续使用应用。",
|
sessionExpiredDescription:
|
||||||
|
"您的登录会话似乎不再活跃或已过期。请重新登录以继续使用应用。",
|
||||||
loginRequired: "需要登录",
|
loginRequired: "需要登录",
|
||||||
unauthorized: "未授权",
|
unauthorized: "未授权",
|
||||||
forbidden: "禁止访问",
|
forbidden: "禁止访问",
|
||||||
accessDenied: "访问被拒绝",
|
accessDenied: "访问被拒绝",
|
||||||
tooManyRequests: "请求过多",
|
tooManyRequests: "请求过多",
|
||||||
tryAgainLater: "请稍后再试",
|
tryAgainLater: "请稍后再试",
|
||||||
contactAdmin: "请联系管理员"
|
contactAdmin: "请联系管理员",
|
||||||
},
|
},
|
||||||
errors: {
|
errors: {
|
||||||
generic: "发生错误",
|
generic: "发生错误",
|
||||||
@@ -177,7 +201,7 @@ export default {
|
|||||||
validationError: "验证错误",
|
validationError: "验证错误",
|
||||||
permissionDenied: "权限被拒绝",
|
permissionDenied: "权限被拒绝",
|
||||||
resourceNotFound: "资源未找到",
|
resourceNotFound: "资源未找到",
|
||||||
serviceUnavailable: "服务不可用"
|
serviceUnavailable: "服务不可用",
|
||||||
},
|
},
|
||||||
peers: {
|
peers: {
|
||||||
title: "节点",
|
title: "节点",
|
||||||
@@ -219,12 +243,14 @@ export default {
|
|||||||
pendingApprovals: "待审批",
|
pendingApprovals: "待审批",
|
||||||
noPeers: "暂无节点",
|
noPeers: "暂无节点",
|
||||||
noAccessiblePeersTitle: "此节点没有可访问的节点",
|
noAccessiblePeersTitle: "此节点没有可访问的节点",
|
||||||
noAccessiblePeersDescription: "向您的网络添加更多节点,或检查访问控制策略。",
|
noAccessiblePeersDescription:
|
||||||
|
"向您的网络添加更多节点,或检查访问控制策略。",
|
||||||
searchPlaceholder: "按名称或 IP 搜索节点...",
|
searchPlaceholder: "按名称或 IP 搜索节点...",
|
||||||
selectPeer: "选择一个节点...",
|
selectPeer: "选择一个节点...",
|
||||||
noPeersAvailable: "没有可选的节点。",
|
noPeersAvailable: "没有可选的节点。",
|
||||||
noPeersMatching: "没有匹配的节点。",
|
noPeersMatching: "没有匹配的节点。",
|
||||||
updateRequired: "请将 NetBird 更新到 v0.36.6 或更高版本,才能将此节点用作路由节点。",
|
updateRequired:
|
||||||
|
"请将 NetBird 更新到 v0.36.6 或更高版本,才能将此节点用作路由节点。",
|
||||||
serialNumber: "序列号",
|
serialNumber: "序列号",
|
||||||
loginExpiration: "会话过期",
|
loginExpiration: "会话过期",
|
||||||
enableLoginExpiration: "启用人会话过期",
|
enableLoginExpiration: "启用人会话过期",
|
||||||
@@ -235,9 +261,25 @@ export default {
|
|||||||
enableSSH: "启用 SSH 访问",
|
enableSSH: "启用 SSH 访问",
|
||||||
disableSSH: "禁用 SSH 访问",
|
disableSSH: "禁用 SSH 访问",
|
||||||
disableSSHConfirmation: "禁用 SSH 访问?",
|
disableSSHConfirmation: "禁用 SSH 访问?",
|
||||||
disableSSHDescription: "从 NetBird v0.61.0 开始,一旦 SSH 访问被禁用,将无法再从控制台重新启用。您需要创建一个明确的访问控制策略并更新 NetBird 客户端以恢复 SSH 功能。",
|
disableSSHDescription:
|
||||||
|
"从 NetBird v0.61.0 开始,一旦 SSH 访问被禁用,将无法再从控制台重新启用。您需要创建一个明确的访问控制策略并更新 NetBird 客户端以恢复 SSH 功能。",
|
||||||
sshLearnMore: "了解更多",
|
sshLearnMore: "了解更多",
|
||||||
browserPeerTooltip: "显示由 NetBird 浏览器客户端创建的临时节点。这些节点是临时的,将在一段时间后自动删除。",
|
cancel: "取消",
|
||||||
|
changesTakeEffect: "更改将在节点下次更新时生效。",
|
||||||
|
sshAccess: "SSH 访问",
|
||||||
|
sshAccessHelp: "在此节点上启用 SSH 服务器,以通过安全 shell 访问计算机。",
|
||||||
|
sshSetupHelp:
|
||||||
|
"设置 SSH 并创建明确的访问控制策略,定义哪些用户可以通过 SSH 访问此计算机的特定本地用户名。",
|
||||||
|
sshOldVersionWarning:
|
||||||
|
"您已配置 SSH 访问,但您的客户端运行的是旧版 NetBird。请将 NetBird 客户端更新到 v.0.61.0+ 以允许 SSH 连接。",
|
||||||
|
sshServerNotEnabled:
|
||||||
|
"您已配置 SSH 访问策略,但此客户端上未启用 SSH 服务器。启用 SSH 服务器以允许 SSH 连接。",
|
||||||
|
sshNeedsPolicy:
|
||||||
|
"您的 SSH 服务器已启用,但从 NetBird v0.61.0 开始,SSH 需要明确的访问控制策略。请创建 SSH 访问控制策略以允许 SSH 连接。",
|
||||||
|
createSSHPolicy: "创建 SSH 策略",
|
||||||
|
activePoliciesCount: "{count, plural, other {# 个活跃策略}}",
|
||||||
|
browserPeerTooltip:
|
||||||
|
"显示由 NetBird 浏览器客户端创建的临时节点。这些节点是临时的,将在一段时间后自动删除。",
|
||||||
connectTooltipOffline: "只有节点在线时才能通过 SSH 或 RDP 连接。",
|
connectTooltipOffline: "只有节点在线时才能通过 SSH 或 RDP 连接。",
|
||||||
expirationDisabledTooltip: "通过 setup-key 添加的所有节点都会禁用过期。",
|
expirationDisabledTooltip: "通过 setup-key 添加的所有节点都会禁用过期。",
|
||||||
justNow: "刚刚",
|
justNow: "刚刚",
|
||||||
@@ -255,12 +297,14 @@ export default {
|
|||||||
groupsAssigning: "正在更新所选节点的组...",
|
groupsAssigning: "正在更新所选节点的组...",
|
||||||
assigningGroups: "正在分配组...",
|
assigningGroups: "正在分配组...",
|
||||||
groupsAssignedSuccess: "组已成功分配",
|
groupsAssignedSuccess: "组已成功分配",
|
||||||
assignGroupsDescription: "将以下组分配给所选节点。除非选择覆盖,否则将保留先前分配的组。",
|
assignGroupsDescription:
|
||||||
|
"将以下组分配给所选节点。除非选择覆盖,否则将保留先前分配的组。",
|
||||||
overwriteGroups: "覆盖现有组",
|
overwriteGroups: "覆盖现有组",
|
||||||
overwriteGroupsHelp: "使用所选组覆盖节点的现有组。先前分配的组将被移除。",
|
overwriteGroupsHelp: "使用所选组覆盖节点的现有组。先前分配的组将被移除。",
|
||||||
overwrite: "覆盖",
|
overwrite: "覆盖",
|
||||||
overwriteGroupsConfirm: "覆盖现有组?",
|
overwriteGroupsConfirm: "覆盖现有组?",
|
||||||
overwriteGroupsConfirmDescription: "确定要覆盖所选 {count} 个节点的现有组吗?此操作无法撤销。",
|
overwriteGroupsConfirmDescription:
|
||||||
|
"确定要覆盖所选 {count} 个节点的现有组吗?此操作无法撤销。",
|
||||||
addGroups: "添加组",
|
addGroups: "添加组",
|
||||||
assignedGroups: "已分配的组",
|
assignedGroups: "已分配的组",
|
||||||
groupsSaved: "节点的组已成功保存",
|
groupsSaved: "节点的组已成功保存",
|
||||||
@@ -279,10 +323,11 @@ export default {
|
|||||||
windows: "Windows",
|
windows: "Windows",
|
||||||
macos: "macOS",
|
macos: "macOS",
|
||||||
android: "Android",
|
android: "Android",
|
||||||
ios: "iOS"
|
ios: "iOS",
|
||||||
},
|
},
|
||||||
updateAvailable: "有新版本可用",
|
updateAvailable: "有新版本可用",
|
||||||
updateDescription: "NetBird 有新版本可用。请更新客户端以获取最新功能和错误修复。",
|
updateDescription:
|
||||||
|
"NetBird 有新版本可用。请更新客户端以获取最新功能和错误修复。",
|
||||||
downloadChangelog: "下载与更新日志",
|
downloadChangelog: "下载与更新日志",
|
||||||
dnsLabelCopied: "DNS 标签已复制到剪贴板",
|
dnsLabelCopied: "DNS 标签已复制到剪贴板",
|
||||||
ipCopied: "IP 地址已复制到剪贴板",
|
ipCopied: "IP 地址已复制到剪贴板",
|
||||||
@@ -297,7 +342,8 @@ export default {
|
|||||||
domain: "域名",
|
domain: "域名",
|
||||||
region: "地区",
|
region: "地区",
|
||||||
regionCopied: "地区已复制到剪贴板",
|
regionCopied: "地区已复制到剪贴板",
|
||||||
peerNotFoundDescription: "您尝试访问的节点不存在。可能已被删除,或您没有查看权限。请验证 URL 或返回控制台。",
|
peerNotFoundDescription:
|
||||||
|
"您尝试访问的节点不存在。可能已被删除,或您没有查看权限。请验证 URL 或返回控制台。",
|
||||||
tabOverview: "概览",
|
tabOverview: "概览",
|
||||||
tabNetworkRoutes: "网络路由",
|
tabNetworkRoutes: "网络路由",
|
||||||
tabAccessiblePeers: "可访问节点",
|
tabAccessiblePeers: "可访问节点",
|
||||||
@@ -307,6 +353,14 @@ export default {
|
|||||||
assignGroups: "分配组",
|
assignGroups: "分配组",
|
||||||
peerSaved: "节点已成功保存",
|
peerSaved: "节点已成功保存",
|
||||||
peerSaving: "正在保存节点...",
|
peerSaving: "正在保存节点...",
|
||||||
|
editPeerIPAddress: "编辑节点 IP 地址",
|
||||||
|
updatePeerIPDescription: "更新此节点的 NetBird IP 地址。",
|
||||||
|
editPeerIPPlaceholder: "例如:100.64.0.15",
|
||||||
|
editPeerIPErrorMessage: "请输入有效的 IP,例如:100.64.0.15",
|
||||||
|
editPeerIPv6Address: "编辑节点 IPv6 地址",
|
||||||
|
updatePeerIPv6Description: "更新此节点的 NetBird IPv6 地址。",
|
||||||
|
editPeerIPv6Placeholder: "例如:fd00:1234::1",
|
||||||
|
editPeerIPv6ErrorMessage: "请输入有效的 IPv6 地址,例如:fd00:1234::1",
|
||||||
remoteAccess: "远程访问",
|
remoteAccess: "远程访问",
|
||||||
remoteAccessDescription: "通过 SSH 或 RDP 直接连接到此节点。",
|
remoteAccessDescription: "通过 SSH 或 RDP 直接连接到此节点。",
|
||||||
domainName: "域名",
|
domainName: "域名",
|
||||||
@@ -320,40 +374,50 @@ export default {
|
|||||||
peerIpv6Updated: "NetBird 节点 IPv6 已成功更新",
|
peerIpv6Updated: "NetBird 节点 IPv6 已成功更新",
|
||||||
peerIpv6Updating: "正在更新节点 IPv6...",
|
peerIpv6Updating: "正在更新节点 IPv6...",
|
||||||
noServicesForPeer: "此节点未配置服务",
|
noServicesForPeer: "此节点未配置服务",
|
||||||
addServicesDescription: "将您的服务添加到此节点,并通过 NetBird 反向代理安全地暴露它们",
|
addServicesDescription:
|
||||||
|
"将您的服务添加到此节点,并通过 NetBird 反向代理安全地暴露它们",
|
||||||
editPeerName: "编辑节点名称",
|
editPeerName: "编辑节点名称",
|
||||||
editPeerNameDescription: "为您的节点设置一个易于识别的名称。",
|
editPeerNameDescription: "为您的节点设置一个易于识别的名称。",
|
||||||
peerNamePlaceholder: "例如:AWS 服务器",
|
peerNamePlaceholder: "例如:AWS 服务器",
|
||||||
domainNamePreview: "域名预览",
|
domainNamePreview: "域名预览",
|
||||||
domainNamePreviewHelp: "如果域名已存在,我们会添加一个递增数字后缀。",
|
domainNamePreviewHelp: "如果域名已存在,我们会添加一个递增数字后缀。",
|
||||||
userDevicesDescription: "笔记本电脑、手机和其他由用户操作的私人设备,通常在用户使用 SSO 登录时添加。",
|
userDevicesDescription:
|
||||||
|
"笔记本电脑、手机和其他由用户操作的私人设备,通常在用户使用 SSO 登录时添加。",
|
||||||
learnMore: "了解更多",
|
learnMore: "了解更多",
|
||||||
addNewDeviceTitle: "添加新设备到您的网络",
|
addNewDeviceTitle: "添加新设备到您的网络",
|
||||||
addNewDeviceDescription: "首先,安装 NetBird 并使用您的电子邮件账户登录。之后您应该已连接。如有其他问题,请查看我们的",
|
addNewDeviceDescription:
|
||||||
|
"首先,安装 NetBird 并使用您的电子邮件账户登录。之后您应该已连接。如有其他问题,请查看我们的",
|
||||||
installationGuide: "安装指南",
|
installationGuide: "安装指南",
|
||||||
serversDescription: "服务器、虚拟机、自治代理和其他无用户的无人值守机器,通常使用安装密钥注册。",
|
serversDescription:
|
||||||
addNewServerTitle: "添加新服务器到您的网络",
|
"服务器、虚拟机、自治代理和其他无用户的无人值守机器,通常使用安装密钥注册。",
|
||||||
addNewServerDescription: "首先,在服务器上安装 NetBird 并使用安装密钥注册。如有其他问题,请查看我们的",
|
addNewServerTitle: "添加新服务器到您的网络",
|
||||||
saveGroups: "保存组",
|
addNewServerDescription:
|
||||||
|
"首先,在服务器上安装 NetBird 并使用安装密钥注册。如有其他问题,请查看我们的",
|
||||||
|
saveGroups: "保存组",
|
||||||
sessionExpiration: "会话过期",
|
sessionExpiration: "会话过期",
|
||||||
sessionExpirationDescription: "启用后,要求 SSO 登录的节点在会话过期后必须重新身份验证。",
|
sessionExpirationDescription:
|
||||||
inactivityExpirationDescription: "启用后,要求用户在与管理界面断开连接 10 分钟后重新身份验证。",
|
"启用后,要求 SSO 登录的节点在会话过期后必须重新身份验证。",
|
||||||
|
inactivityExpirationDescription:
|
||||||
|
"启用后,要求用户在与管理界面断开连接 10 分钟后重新身份验证。",
|
||||||
setupKeyPeerExpirationDisabled: "此设置对使用安装密钥添加的所有节点禁用。",
|
setupKeyPeerExpirationDisabled: "此设置对使用安装密钥添加的所有节点禁用。",
|
||||||
noPermissionToUpdateSetting: "您没有更新此设置所需的权限。",
|
noPermissionToUpdateSetting: "您没有更新此设置所需的权限。",
|
||||||
globalSettingDisabled: "全局设置 {setting} 当前已禁用。请启用全局设置以便能够按节点单独切换。",
|
globalSettingDisabled:
|
||||||
|
"全局设置 {setting} 当前已禁用。请启用全局设置以便能够按节点单独切换。",
|
||||||
goToSettings: "前往设置",
|
goToSettings: "前往设置",
|
||||||
expirationUpdateSuccess: "过期时间已成功更新",
|
expirationUpdateSuccess: "过期时间已成功更新",
|
||||||
expirationUpdating: "正在更新设置...",
|
expirationUpdating: "正在更新设置...",
|
||||||
peerSessionExpiration: "节点会话过期",
|
peerSessionExpiration: "节点会话过期",
|
||||||
requireLoginAfterDisconnect: "断开连接后要求重新登录",
|
requireLoginAfterDisconnect: "断开连接后要求重新登录",
|
||||||
getStarted: "开始使用 NetBird",
|
getStarted: "开始使用 NetBird",
|
||||||
getStartedDescription: "看起来您还没有任何连接的设备。\n开始使用,向您的网络中添加一台设备。",
|
getStartedDescription:
|
||||||
|
"看起来您还没有任何连接的设备。\n开始使用,向您的网络中添加一台设备。",
|
||||||
learnMoreInOur: "在我们的",
|
learnMoreInOur: "在我们的",
|
||||||
gettingStartedGuide: "入门指南",
|
gettingStartedGuide: "入门指南",
|
||||||
userPeersDescription: "查看此用户注册的所有节点。",
|
userPeersDescription: "查看此用户注册的所有节点。",
|
||||||
accessiblePeersDesc: "此节点可以连接到 NetBird 网络中的以下节点。",
|
accessiblePeersDesc: "此节点可以连接到 NetBird 网络中的以下节点。",
|
||||||
networkRoutesDesc: "无需在每个资源上安装 NetBird 即可访问其他网络。",
|
networkRoutesDesc: "无需在每个资源上安装 NetBird 即可访问其他网络。",
|
||||||
remoteJobsDesc: "远程触发此节点上的操作,如调试包或其他任务,无需 CLI 访问。"
|
remoteJobsDesc:
|
||||||
|
"远程触发此节点上的操作,如调试包或其他任务,无需 CLI 访问。",
|
||||||
},
|
},
|
||||||
policies: {
|
policies: {
|
||||||
title: "策略",
|
title: "策略",
|
||||||
@@ -403,50 +467,63 @@ saveGroups: "保存组",
|
|||||||
portsPlaceholder: "例如:443",
|
portsPlaceholder: "例如:443",
|
||||||
addPolicy: "添加策略",
|
addPolicy: "添加策略",
|
||||||
createNewPolicy: "创建新策略",
|
createNewPolicy: "创建新策略",
|
||||||
createNewPolicyDescription: "看起来您还没有任何策略。策略可以按特定协议和端口允许连接。",
|
createNewPolicyDescription:
|
||||||
|
"看起来您还没有任何策略。策略可以按特定协议和端口允许连接。",
|
||||||
noPoliciesForGroup: "此组尚未在任何策略中使用",
|
noPoliciesForGroup: "此组尚未在任何策略中使用",
|
||||||
noPoliciesForGroupDescription: "将组作为策略中的源或目标分配,以在此处查看其列表。",
|
noPoliciesForGroupDescription:
|
||||||
temporaryPoliciesTooltip: "显示由 NetBird 浏览器客户端创建的临时策略。这些策略是临时的,将在一段时间后自动删除。",
|
"将组作为策略中的源或目标分配,以在此处查看其列表。",
|
||||||
|
temporaryPoliciesTooltip:
|
||||||
|
"显示由 NetBird 浏览器客户端创建的临时策略。这些策略是临时的,将在一段时间后自动删除。",
|
||||||
learnMoreAbout: "了解更多关于",
|
learnMoreAbout: "了解更多关于",
|
||||||
accessControls: "访问控制",
|
accessControls: "访问控制",
|
||||||
policyActions: "策略操作",
|
policyActions: "策略操作",
|
||||||
policyEnabledSuccess: "策略已成功启用",
|
policyEnabledSuccess: "策略已成功启用",
|
||||||
policyDisabledSuccess: "策略已成功禁用",
|
policyDisabledSuccess: "策略已成功禁用",
|
||||||
confirmDeleteTitle: "删除 '{name}'?",
|
confirmDeleteTitle: "删除 '{name}'?",
|
||||||
confirmDeleteDescription: "确定要删除此访问控制策略吗?此操作无法撤销。",
|
confirmDeleteDescription: "确定要删除此访问控制策略吗?此操作无法撤销。",
|
||||||
updatePolicy: "更新访问控制策略",
|
updatePolicy: "更新访问控制策略",
|
||||||
modalDescription: "使用此策略限制对资源组的访问。",
|
modalDescription: "使用此策略限制对资源组的访问。",
|
||||||
tabPolicy: "策略",
|
tabPolicy: "策略",
|
||||||
tabNameDescription: "名称与描述",
|
tabNameDescription: "名称与描述",
|
||||||
protocolHelp: "仅允许指定的网络协议。要更改流量方向和端口,请选择 TCP 或 UDP 协议。",
|
protocolHelp:
|
||||||
selectProtocol: "选择协议...",
|
"仅允许指定的网络协议。要更改流量方向和端口,请选择 TCP 或 UDP 协议。",
|
||||||
netbirdSshHelp: "为 SSH 专用策略选择 NetBird SSH 以进行细粒度访问控制,或使用端口 22 的 TCP 进行基本网络级 SSH 访问",
|
selectProtocol: "选择协议...",
|
||||||
sourceHelp: "通常是一组用户设备(例如开发人员、市场人员)或点对点连接中将访问目标的单个设备。",
|
netbirdSshHelp:
|
||||||
selectSource: "选择源...",
|
"为 SSH 专用策略选择 NetBird SSH 以进行细粒度访问控制,或使用端口 22 的 TCP 进行基本网络级 SSH 访问",
|
||||||
destinationHelp: "通常是一组节点或资源(例如服务器、数据库、内部服务),将由源访问。也可以是单个节点或资源。",
|
sourceHelp:
|
||||||
selectDestination: "选择目标...",
|
"通常是一组用户设备(例如开发人员、市场人员)或点对点连接中将访问目标的单个设备。",
|
||||||
resourcesBidirectionalWarning: "某些目标组包含资源。资源仅支持入站流量,无法发起连接。",
|
selectSource: "选择源...",
|
||||||
sshResourceWarning: "SSH 访问仅适用于节点,不适用于路由资源。请确保您的目标组包含用于 SSH 连接的节点。",
|
destinationHelp:
|
||||||
sshAccess: "SSH 访问",
|
"通常是一组节点或资源(例如服务器、数据库、内部服务),将由源访问。也可以是单个节点或资源。",
|
||||||
sshAccessHelp: "选择'完全访问'以允许任何本地用户进行 SSH,或选择'受限访问'以指定每个组允许使用的本地用户。",
|
selectDestination: "选择目标...",
|
||||||
ports: "端口",
|
resourcesBidirectionalWarning:
|
||||||
portsHelp: "仅允许对指定端口的网络流量和访问。选择 1 到 65535 之间的端口或端口范围。",
|
"某些目标组包含资源。资源仅支持入站流量,无法发起连接。",
|
||||||
enablePolicy: "启用策略",
|
sshResourceWarning:
|
||||||
enablePolicyHelp: "使用此开关启用或禁用策略。",
|
"SSH 访问仅适用于节点,不适用于路由资源。请确保您的目标组包含用于 SSH 连接的节点。",
|
||||||
ruleName: "规则名称",
|
sshAccess: "SSH 访问",
|
||||||
ruleNameHelp: "为策略设置一个易于识别的名称。",
|
sshAccessHelp:
|
||||||
ruleNamePlaceholder: "例如:开发人员到服务器",
|
"选择'完全访问'以允许任何本地用户进行 SSH,或选择'受限访问'以指定每个组允许使用的本地用户。",
|
||||||
policyDescriptionLabel: "描述(可选)",
|
ports: "端口",
|
||||||
policyDescriptionHelp: "写一个简短的描述为此策略添加更多上下文。",
|
portsHelp:
|
||||||
policyDescriptionPlaceholder: "例如:允许开发人员访问服务器,并允许服务器访问开发人员。",
|
"仅允许对指定端口的网络流量和访问。选择 1 到 65535 之间的端口或端口范围。",
|
||||||
accessControlDescription: "创建规则以管理网络中的访问,并定义节点可以连接的内容。",
|
enablePolicy: "启用策略",
|
||||||
|
enablePolicyHelp: "使用此开关启用或禁用策略。",
|
||||||
|
ruleName: "规则名称",
|
||||||
|
ruleNameHelp: "为策略设置一个易于识别的名称。",
|
||||||
|
ruleNamePlaceholder: "例如:开发人员到服务器",
|
||||||
|
policyDescriptionLabel: "描述(可选)",
|
||||||
|
policyDescriptionHelp: "写一个简短的描述为此策略添加更多上下文。",
|
||||||
|
policyDescriptionPlaceholder:
|
||||||
|
"例如:允许开发人员访问服务器,并允许服务器访问开发人员。",
|
||||||
|
accessControlDescription:
|
||||||
|
"创建规则以管理网络中的访问,并定义节点可以连接的内容。",
|
||||||
policyCreated: "策略 '{name}' 创建成功",
|
policyCreated: "策略 '{name}' 创建成功",
|
||||||
policyUpdated: "策略 '{name}' 更新成功",
|
policyUpdated: "策略 '{name}' 更新成功",
|
||||||
policyDeleted: "策略 '{name}' 已删除",
|
policyDeleted: "策略 '{name}' 已删除",
|
||||||
policyEnableLoading: "正在启用策略...",
|
policyEnableLoading: "正在启用策略...",
|
||||||
policyDisableLoading: "正在禁用策略...",
|
policyDisableLoading: "正在禁用策略...",
|
||||||
policySaveLoading: "正在保存策略...",
|
policySaveLoading: "正在保存策略...",
|
||||||
policyDeleteLoading: "正在删除策略..."
|
policyDeleteLoading: "正在删除策略...",
|
||||||
},
|
},
|
||||||
groups: {
|
groups: {
|
||||||
title: "组",
|
title: "组",
|
||||||
@@ -490,11 +567,11 @@ accessControlDescription: "创建规则以管理网络中的访问,并定义
|
|||||||
unused: "未使用",
|
unused: "未使用",
|
||||||
usage: "使用情况",
|
usage: "使用情况",
|
||||||
inUse: "正在使用",
|
inUse: "正在使用",
|
||||||
nameservers: "名称服务器",
|
nameservers: "名称服务器",
|
||||||
zones: "区域",
|
zones: "区域",
|
||||||
routes: "路由",
|
routes: "路由",
|
||||||
setupKeys: "安装密钥",
|
setupKeys: "安装密钥",
|
||||||
viewDetails: "查看详情",
|
viewDetails: "查看详情",
|
||||||
rename: "重命名",
|
rename: "重命名",
|
||||||
groupsDescription: "将节点、用户和资源组织到组中以管理访问。",
|
groupsDescription: "将节点、用户和资源组织到组中以管理访问。",
|
||||||
allGroups: "所有组",
|
allGroups: "所有组",
|
||||||
@@ -507,7 +584,7 @@ viewDetails: "查看详情",
|
|||||||
renameDisabledIdP: "此组由 IdP 颁发,无法重命名。",
|
renameDisabledIdP: "此组由 IdP 颁发,无法重命名。",
|
||||||
deleteDisabledIdP: "此组由 IdP 颁发,无法删除。",
|
deleteDisabledIdP: "此组由 IdP 颁发,无法删除。",
|
||||||
deleteDisabledInUse: "请先移除此组的依赖关系后再删除。",
|
deleteDisabledInUse: "请先移除此组的依赖关系后再删除。",
|
||||||
assignedGroups: "已分配的组"
|
assignedGroups: "已分配的组",
|
||||||
},
|
},
|
||||||
users: {
|
users: {
|
||||||
title: "用户",
|
title: "用户",
|
||||||
@@ -564,7 +641,7 @@ viewDetails: "查看详情",
|
|||||||
approving: "正在批准用户...",
|
approving: "正在批准用户...",
|
||||||
userRejected: "用户 {name} 已拒绝",
|
userRejected: "用户 {name} 已拒绝",
|
||||||
rejecting: "正在拒绝用户...",
|
rejecting: "正在拒绝用户...",
|
||||||
copyUserId: "复制用户 ID",
|
copyUserId: "复制用户 ID",
|
||||||
copyUserIdSuccess: "用户 ID 已复制到剪贴板",
|
copyUserIdSuccess: "用户 ID 已复制到剪贴板",
|
||||||
networkAdmin: "网络管理员",
|
networkAdmin: "网络管理员",
|
||||||
billingAdmin: "账单管理员",
|
billingAdmin: "账单管理员",
|
||||||
@@ -573,13 +650,15 @@ copyUserId: "复制用户 ID",
|
|||||||
lastLoginOn: "最后登录于",
|
lastLoginOn: "最后登录于",
|
||||||
showInvites: "显示邀请",
|
showInvites: "显示邀请",
|
||||||
addNewUsers: "添加新用户",
|
addNewUsers: "添加新用户",
|
||||||
addNewUsersDescription: "看起来您还没有任何用户。开始使用,邀请用户加入您的账户。",
|
addNewUsersDescription:
|
||||||
|
"看起来您还没有任何用户。开始使用,邀请用户加入您的账户。",
|
||||||
addUser: "添加用户",
|
addUser: "添加用户",
|
||||||
localAuthDisabled: "本地身份验证已禁用。请使用您的 IdP 进行身份验证。",
|
localAuthDisabled: "本地身份验证已禁用。请使用您的 IdP 进行身份验证。",
|
||||||
team: "团队",
|
team: "团队",
|
||||||
usersPageDescription: "管理用户及其权限。同域名电子邮件用户在首次登录时会自动添加。",
|
usersPageDescription:
|
||||||
|
"管理用户及其权限。同域名电子邮件用户在首次登录时会自动添加。",
|
||||||
expiresIn: "过期时间",
|
expiresIn: "过期时间",
|
||||||
expiresInHelp: "邀请过期前的天数。"
|
expiresInHelp: "邀请过期前的天数。",
|
||||||
},
|
},
|
||||||
serviceUsers: {
|
serviceUsers: {
|
||||||
title: "服务用户",
|
title: "服务用户",
|
||||||
@@ -608,15 +687,16 @@ localAuthDisabled: "本地身份验证已禁用。请使用您的 IdP 进行身
|
|||||||
userDeleted: "服务用户 '{name}' 已删除",
|
userDeleted: "服务用户 '{name}' 已删除",
|
||||||
createLoading: "正在创建服务用户...",
|
createLoading: "正在创建服务用户...",
|
||||||
updateLoading: "正在更新服务用户...",
|
updateLoading: "正在更新服务用户...",
|
||||||
deleteLoading: "正在删除服务用户...",
|
deleteLoading: "正在删除服务用户...",
|
||||||
serviceUsersDescription: "使用服务用户创建 API 令牌,避免丢失自动化访问。",
|
serviceUsersDescription: "使用服务用户创建 API 令牌,避免丢失自动化访问。",
|
||||||
serviceUsersEmptyDescription: "看起来您还没有任何服务用户。开始使用,创建一个服务用户。",
|
serviceUsersEmptyDescription:
|
||||||
|
"看起来您还没有任何服务用户。开始使用,创建一个服务用户。",
|
||||||
blocked: "已阻止",
|
blocked: "已阻止",
|
||||||
accessTokens: "访问令牌",
|
accessTokens: "访问令牌",
|
||||||
accessTokensDescription: "访问令牌提供对 NetBird API 的访问权限。",
|
accessTokensDescription: "访问令牌提供对 NetBird API 的访问权限。",
|
||||||
tokenName: "名称",
|
tokenName: "名称",
|
||||||
tokenNameHelp: "为令牌设置一个易于识别的名称",
|
tokenNameHelp: "为令牌设置一个易于识别的名称",
|
||||||
tokenExpiresIn: "过期时间"
|
tokenExpiresIn: "过期时间",
|
||||||
},
|
},
|
||||||
settings: {
|
settings: {
|
||||||
title: "设置",
|
title: "设置",
|
||||||
@@ -642,7 +722,7 @@ serviceUsersDescription: "使用服务用户创建 API 令牌,避免丢失自
|
|||||||
confirmPassword: "确认密码",
|
confirmPassword: "确认密码",
|
||||||
twoFactor: "两步验证",
|
twoFactor: "两步验证",
|
||||||
enable2FA: "启用两步验证",
|
enable2FA: "启用两步验证",
|
||||||
disable2FA: "禁用两步验证",
|
disable2FA: "禁用两步验证",
|
||||||
authentication: "身份验证",
|
authentication: "身份验证",
|
||||||
setupKeys: "安装密钥",
|
setupKeys: "安装密钥",
|
||||||
identityProviders: "身份提供者",
|
identityProviders: "身份提供者",
|
||||||
@@ -659,7 +739,8 @@ disable2FA: "禁用两步验证",
|
|||||||
deleteAccountSuccess: "NetBird 账户已成功删除。",
|
deleteAccountSuccess: "NetBird 账户已成功删除。",
|
||||||
deleteAccountLoading: "正在删除账户...",
|
deleteAccountLoading: "正在删除账户...",
|
||||||
deleteAccountCardTitle: "删除 NetBird 账户",
|
deleteAccountCardTitle: "删除 NetBird 账户",
|
||||||
deleteAccountWarning: "在继续删除 NetBird 账户之前,请注意此操作不可撤销。一旦您的账户被删除,您将永久失去对所有关联数据的访问权限,包括您的节点、用户、组、策略和路由。",
|
deleteAccountWarning:
|
||||||
|
"在继续删除 NetBird 账户之前,请注意此操作不可撤销。一旦您的账户被删除,您将永久失去对所有关联数据的访问权限,包括您的节点、用户、组、策略和路由。",
|
||||||
deleteAccountButton: "删除账户",
|
deleteAccountButton: "删除账户",
|
||||||
endpointUrls: "端点 URL",
|
endpointUrls: "端点 URL",
|
||||||
endpointUrlsHelp: "将这些添加到您的身份提供者配置中",
|
endpointUrlsHelp: "将这些添加到您的身份提供者配置中",
|
||||||
@@ -674,65 +755,83 @@ disable2FA: "禁用两步验证",
|
|||||||
beta: "Beta",
|
beta: "Beta",
|
||||||
selectInterval: "选择间隔...",
|
selectInterval: "选择间隔...",
|
||||||
userApprovalRequired: "需要用户审批",
|
userApprovalRequired: "需要用户审批",
|
||||||
userApprovalHelp: "对通过域名匹配加入的新用户要求手动审批。用户在被审批前将被阻止。",
|
userApprovalHelp:
|
||||||
|
"对通过域名匹配加入的新用户要求手动审批。用户在被审批前将被阻止。",
|
||||||
enableLocalMFA: "启用本地多因素认证",
|
enableLocalMFA: "启用本地多因素认证",
|
||||||
localMfaHelp: "对使用本地凭据认证的用户要求多因素认证。",
|
localMfaHelp: "对使用本地凭据认证的用户要求多因素认证。",
|
||||||
peerSessionExpiration: "节点会话过期",
|
peerSessionExpiration: "节点会话过期",
|
||||||
peerSessionExpirationHelp: "对通过 SSO 注册的节点请求定期重新认证。",
|
peerSessionExpirationHelp: "对通过 SSO 注册的节点请求定期重新认证。",
|
||||||
sessionExpiration: "会话过期",
|
sessionExpiration: "会话过期",
|
||||||
sessionExpirationHelp: "使用 SSO 登录添加的每个节点在此时间后需要重新认证。",
|
sessionExpirationHelp:
|
||||||
|
"使用 SSO 登录添加的每个节点在此时间后需要重新认证。",
|
||||||
requireLoginAfterDisconnect: "断开连接后要求重新登录",
|
requireLoginAfterDisconnect: "断开连接后要求重新登录",
|
||||||
requireLoginHelp: "启用后,用户从管理界面断开连接 10 分钟后将需要重新认证。",
|
requireLoginHelp:
|
||||||
|
"启用后,用户从管理界面断开连接 10 分钟后将需要重新认证。",
|
||||||
automaticUpdates: "自动更新",
|
automaticUpdates: "自动更新",
|
||||||
automaticUpdatesHelp: "配置 NetBird 客户端如何接收更新通知。启用后,用户将被提示安装所选版本。这至少需要 NetBird v0.61.0。",
|
automaticUpdatesHelp:
|
||||||
|
"配置 NetBird 客户端如何接收更新通知。启用后,用户将被提示安装所选版本。这至少需要 NetBird v0.61.0。",
|
||||||
versionCustomPrefix: "版本",
|
versionCustomPrefix: "版本",
|
||||||
versionCustomPlaceholder: "例如:0.52.2",
|
versionCustomPlaceholder: "例如:0.52.2",
|
||||||
forceAutomaticUpdates: "强制自动更新",
|
forceAutomaticUpdates: "强制自动更新",
|
||||||
forceAutomaticUpdatesHelp: "启用后,更新将在后台自动安装,无需用户交互。",
|
forceAutomaticUpdatesHelp: "启用后,更新将在后台自动安装,无需用户交互。",
|
||||||
automaticUpdatesWarning: "启用自动更新将在更新过程中重启 NetBird 客户端,可能暂时中断活动连接。在生产环境中请谨慎使用。",
|
automaticUpdatesWarning:
|
||||||
|
"启用自动更新将在更新过程中重启 NetBird 客户端,可能暂时中断活动连接。在生产环境中请谨慎使用。",
|
||||||
exposeServicesFromCli: "通过 CLI 暴露服务",
|
exposeServicesFromCli: "通过 CLI 暴露服务",
|
||||||
exposeServicesFromCliHelp: "允许节点通过 NetBird 反向代理使用 CLI 暴露本地服务。这至少需要 NetBird v0.66.0。",
|
exposeServicesFromCliHelp:
|
||||||
|
"允许节点通过 NetBird 反向代理使用 CLI 暴露本地服务。这至少需要 NetBird v0.66.0。",
|
||||||
enablePeerExpose: "启用节点暴露",
|
enablePeerExpose: "启用节点暴露",
|
||||||
enablePeerExposeHelp: "启用后,节点可以通过公共 URL 暴露本地 HTTP 服务。",
|
enablePeerExposeHelp: "启用后,节点可以通过公共 URL 暴露本地 HTTP 服务。",
|
||||||
allowedPeerGroups: "允许的节点组",
|
allowedPeerGroups: "允许的节点组",
|
||||||
allowedPeerGroupsHelp: "选择允许暴露服务的节点组。至少需要一个组。",
|
allowedPeerGroupsHelp: "选择允许暴露服务的节点组。至少需要一个组。",
|
||||||
selectPeerGroups: "选择节点组...",
|
selectPeerGroups: "选择节点组...",
|
||||||
experimental: "实验性",
|
experimental: "实验性",
|
||||||
experimentalHelp: "惰性连接是一个实验性功能。功能和行为可能会发展。NetBird 不再维护始终在线的连接,而是根据活动或信令按需激活它们。",
|
experimentalHelp:
|
||||||
|
"惰性连接是一个实验性功能。功能和行为可能会发展。NetBird 不再维护始终在线的连接,而是根据活动或信令按需激活它们。",
|
||||||
enableLazyConnections: "启用惰性连接",
|
enableLazyConnections: "启用惰性连接",
|
||||||
enableLazyConnectionsHelp: "惰性连接根据活动或信令按需激活,而不是维护始终在线的连接。",
|
enableLazyConnectionsHelp:
|
||||||
|
"惰性连接根据活动或信令按需激活,而不是维护始终在线的连接。",
|
||||||
enableGroupPropagation: "启用用户组传播",
|
enableGroupPropagation: "启用用户组传播",
|
||||||
groupPropagationHelp: "允许从用户的自动组向节点传播组,共享成员资格信息。",
|
groupPropagationHelp: "允许从用户的自动组向节点传播组,共享成员资格信息。",
|
||||||
enableJwtGroupSync: "启用 JWT 组同步",
|
enableJwtGroupSync: "启用 JWT 组同步",
|
||||||
jwtGroupSyncHelp: "从 JWT 声明中提取并同步组与用户的自动组,根据令牌自动创建组。",
|
jwtGroupSyncHelp:
|
||||||
|
"从 JWT 声明中提取并同步组与用户的自动组,根据令牌自动创建组。",
|
||||||
jwtClaim: "JWT 声明",
|
jwtClaim: "JWT 声明",
|
||||||
jwtClaimHelp: "指定用于提取组名称的 JWT 声明,例如 roles 或 groups,以添加到账户组(此声明应包含组名称列表)。",
|
jwtClaimHelp:
|
||||||
|
"指定用于提取组名称的 JWT 声明,例如 roles 或 groups,以添加到账户组(此声明应包含组名称列表)。",
|
||||||
jwtClaimPlaceholder: "例如:roles",
|
jwtClaimPlaceholder: "例如:roles",
|
||||||
jwtAllowGroups: "JWT 允许的组",
|
jwtAllowGroups: "JWT 允许的组",
|
||||||
jwtAllowGroupsHelp: "限制指定组名称对 NetBird 的访问,例如 NetBird 用户。要使用这些组,您需要先在您的 IdP 中配置它们。",
|
jwtAllowGroupsHelp:
|
||||||
|
"限制指定组名称对 NetBird 的访问,例如 NetBird 用户。要使用这些组,您需要先在您的 IdP 中配置它们。",
|
||||||
addGroupPlaceholder: "添加组并按 Enter",
|
addGroupPlaceholder: "添加组并按 Enter",
|
||||||
jwtGroupAccessWarning: "为防止失去访问权限,请确保您是该组的成员。",
|
jwtGroupAccessWarning: "为防止失去访问权限,请确保您是该组的成员。",
|
||||||
dnsDomain: "DNS 域名",
|
dnsDomain: "DNS 域名",
|
||||||
dnsDomainHelp: "为您的网络指定自定义节点 DNS 域名。此域名不应指向已在其他地方使用的域名,以避免覆盖 DNS 结果。",
|
dnsDomainHelp:
|
||||||
|
"为您的网络指定自定义节点 DNS 域名。此域名不应指向已在其他地方使用的域名,以避免覆盖 DNS 结果。",
|
||||||
dnsDomainHostedPlaceholder: "netbird.cloud",
|
dnsDomainHostedPlaceholder: "netbird.cloud",
|
||||||
dnsDomainSelfhostedPlaceholder: "netbird.selfhosted",
|
dnsDomainSelfhostedPlaceholder: "netbird.selfhosted",
|
||||||
networkRange: "网络范围",
|
networkRange: "网络范围",
|
||||||
networkRangeHelp: "以 CIDR 格式为您的网络指定自定义 IPv4 范围。更改后将重新分配所有节点 IP。",
|
networkRangeHelp:
|
||||||
|
"以 CIDR 格式为您的网络指定自定义 IPv4 范围。更改后将重新分配所有节点 IP。",
|
||||||
networkRangePlaceholder: "例如 100.64.0.0/16",
|
networkRangePlaceholder: "例如 100.64.0.0/16",
|
||||||
ipv6NetworkRange: "IPv6 网络范围",
|
ipv6NetworkRange: "IPv6 网络范围",
|
||||||
ipv6NetworkRangeHelp: "以 CIDR 格式为您的网络指定自定义 IPv6 范围。更改后将重新分配所有节点 IPv6 地址。",
|
ipv6NetworkRangeHelp:
|
||||||
|
"以 CIDR 格式为您的网络指定自定义 IPv6 范围。更改后将重新分配所有节点 IPv6 地址。",
|
||||||
ipv6NetworkRangePlaceholder: "例如 fd00:1234:5678::/64",
|
ipv6NetworkRangePlaceholder: "例如 fd00:1234:5678::/64",
|
||||||
ipv6EnabledGroups: "IPv6 启用组",
|
ipv6EnabledGroups: "IPv6 启用组",
|
||||||
ipv6EnabledGroupsHelp: "所选组中的节点将接收 IPv6 覆盖地址(双栈)。删除所有组以禁用 IPv6。更改将在保存时应用并重启受影响的客户端。",
|
ipv6EnabledGroupsHelp:
|
||||||
|
"所选组中的节点将接收 IPv6 覆盖地址(双栈)。删除所有组以禁用 IPv6。更改将在保存时应用并重启受影响的客户端。",
|
||||||
selectIpv6Groups: "选择启用 IPv6 的组...",
|
selectIpv6Groups: "选择启用 IPv6 的组...",
|
||||||
ipv6PrefixLengthError: "前缀长度必须介于 /48 和 /112 之间",
|
ipv6PrefixLengthError: "前缀长度必须介于 /48 和 /112 之间",
|
||||||
ipv6FormatError: "请输入有效的 IPv6 CIDR 范围,例如 fd00:1234::/64",
|
ipv6FormatError: "请输入有效的 IPv6 CIDR 范围,例如 fd00:1234::/64",
|
||||||
enableDnsWildcardRouting: "启用 DNS 通配符路由",
|
enableDnsWildcardRouting: "启用 DNS 通配符路由",
|
||||||
dnsWildcardRoutingHelp: "允许使用 DNS 通配符进行路由。这需要 NetBird 客户端 v0.35 或更高版本。更改仅在重启客户端后生效。",
|
dnsWildcardRoutingHelp:
|
||||||
|
"允许使用 DNS 通配符进行路由。这需要 NetBird 客户端 v0.35 或更高版本。更改仅在重启客户端后生效。",
|
||||||
configureIdpDescription: "为您的网络配置用于用户身份验证的身份提供者。",
|
configureIdpDescription: "为您的网络配置用于用户身份验证的身份提供者。",
|
||||||
setupKeysDescription: "安装密钥是预身份验证密钥,允许在您的网络中注册新机器。",
|
setupKeysDescription:
|
||||||
|
"安装密钥是预身份验证密钥,允许在您的网络中注册新机器。",
|
||||||
restrictDashboard: "限制普通用户访问仪表板",
|
restrictDashboard: "限制普通用户访问仪表板",
|
||||||
restrictDashboardHelp: "对仪表板的访问将受到限制,普通用户将无法查看任何节点。",
|
restrictDashboardHelp:
|
||||||
|
"对仪表板的访问将受到限制,普通用户将无法查看任何节点。",
|
||||||
name: "名称",
|
name: "名称",
|
||||||
type: "类型",
|
type: "类型",
|
||||||
idpProviderType: "提供商类型",
|
idpProviderType: "提供商类型",
|
||||||
@@ -742,7 +841,7 @@ disable2FA: "禁用两步验证",
|
|||||||
idpClientId: "客户端 ID",
|
idpClientId: "客户端 ID",
|
||||||
idpClientSecret: "客户端密钥",
|
idpClientSecret: "客户端密钥",
|
||||||
idpIssuerUrl: "Issuer URL",
|
idpIssuerUrl: "Issuer URL",
|
||||||
idpIssuerUrlHelp: "此提供商的 OIDC issuer URL"
|
idpIssuerUrlHelp: "此提供商的 OIDC issuer URL",
|
||||||
},
|
},
|
||||||
reverseProxy: {
|
reverseProxy: {
|
||||||
title: "反向代理",
|
title: "反向代理",
|
||||||
@@ -786,8 +885,10 @@ disable2FA: "禁用两步验证",
|
|||||||
accessLogsDescription: "查看反向代理服务的访问日志",
|
accessLogsDescription: "查看反向代理服务的访问日志",
|
||||||
noAccessLogs: "暂无访问日志",
|
noAccessLogs: "暂无访问日志",
|
||||||
servicesDescription: "通过 NetBird 的反向代理安全地暴露服务。",
|
servicesDescription: "通过 NetBird 的反向代理安全地暴露服务。",
|
||||||
betaNoticeCloud: "NetBird 的反向代理目前处于测试阶段,在此期间免费使用。功能、特性和定价可能在上线时发生变化。",
|
betaNoticeCloud:
|
||||||
betaNoticeSelfHosted: "NetBird 的反向代理目前处于测试阶段。功能、特性和定价可能在上线时发生变化。",
|
"NetBird 的反向代理目前处于测试阶段,在此期间免费使用。功能、特性和定价可能在上线时发生变化。",
|
||||||
|
betaNoticeSelfHosted:
|
||||||
|
"NetBird 的反向代理目前处于测试阶段。功能、特性和定价可能在上线时发生变化。",
|
||||||
saveChanges: "保存更改",
|
saveChanges: "保存更改",
|
||||||
addServiceBtn: "添加服务",
|
addServiceBtn: "添加服务",
|
||||||
editServiceBtn: "编辑服务",
|
editServiceBtn: "编辑服务",
|
||||||
@@ -800,7 +901,8 @@ disable2FA: "禁用两步验证",
|
|||||||
advancedSettings: "高级设置",
|
advancedSettings: "高级设置",
|
||||||
netBirdOnlyAccess: "仅 NetBird 访问",
|
netBirdOnlyAccess: "仅 NetBird 访问",
|
||||||
netBirdOnlyAccessDescription: "仅所选 NetBird 组内的已连接节点可访问。",
|
netBirdOnlyAccessDescription: "仅所选 NetBird 组内的已连接节点可访问。",
|
||||||
netBirdOnlyAccessTooltip: "仅 NetBird 访问需要至少一个已连接嵌入式代理(netbird proxy)的代理集群。所选集群没有嵌入式代理。请连接一个嵌入式代理以启用此选项。",
|
netBirdOnlyAccessTooltip:
|
||||||
|
"仅 NetBird 访问需要至少一个已连接嵌入式代理(netbird proxy)的代理集群。所选集群没有嵌入式代理。请连接一个嵌入式代理以启用此选项。",
|
||||||
sso: "SSO(单点登录)",
|
sso: "SSO(单点登录)",
|
||||||
ssoDescription: "要求用户通过 SSO 身份验证才能访问此服务。",
|
ssoDescription: "要求用户通过 SSO 身份验证才能访问此服务。",
|
||||||
password: "密码",
|
password: "密码",
|
||||||
@@ -809,9 +911,11 @@ disable2FA: "禁用两步验证",
|
|||||||
pinCodeDescription: "要求输入数字 PIN 码才能访问此服务。",
|
pinCodeDescription: "要求输入数字 PIN 码才能访问此服务。",
|
||||||
httpHeaders: "HTTP 请求头",
|
httpHeaders: "HTTP 请求头",
|
||||||
httpHeadersDescription: "要求特定 HTTP 请求头才能访问此服务。",
|
httpHeadersDescription: "要求特定 HTTP 请求头才能访问此服务。",
|
||||||
netBirdOnlyServiceNotice: "此服务仅可通过 NetBird 访问。默认会应用允许 NetBird 网络范围的规则。您在此处添加的规则会叠加在默认规则之上。",
|
netBirdOnlyServiceNotice:
|
||||||
|
"此服务仅可通过 NetBird 访问。默认会应用允许 NetBird 网络范围的规则。您在此处添加的规则会叠加在默认规则之上。",
|
||||||
preserveClientSourceIp: "保留客户端源 IP",
|
preserveClientSourceIp: "保留客户端源 IP",
|
||||||
preserveClientSourceIpHelp: "使用 PROXY Protocol v2 将流量转发到后端时,保留客户端源 IP 地址。",
|
preserveClientSourceIpHelp:
|
||||||
|
"使用 PROXY Protocol v2 将流量转发到后端时,保留客户端源 IP 地址。",
|
||||||
sessionIdleTimeout: "会话空闲超时",
|
sessionIdleTimeout: "会话空闲超时",
|
||||||
sessionIdleTimeoutHelp: "空闲超过此时间后关闭 UDP 会话。留空表示无超时。",
|
sessionIdleTimeoutHelp: "空闲超过此时间后关闭 UDP 会话。留空表示无超时。",
|
||||||
connectionTimeout: "连接超时",
|
connectionTimeout: "连接超时",
|
||||||
@@ -820,23 +924,28 @@ disable2FA: "禁用两步验证",
|
|||||||
passHostHeader: "传递 Host 请求头",
|
passHostHeader: "传递 Host 请求头",
|
||||||
passHostHeaderHelp: "将原始 Host 请求头转发到后端,而不是改写为目标地址。",
|
passHostHeaderHelp: "将原始 Host 请求头转发到后端,而不是改写为目标地址。",
|
||||||
rewriteRedirects: "重写重定向",
|
rewriteRedirects: "重写重定向",
|
||||||
rewriteRedirectsHelp: "重写后端响应中的 Location 请求头,使用公共域名而不是内部后端地址。",
|
rewriteRedirectsHelp:
|
||||||
|
"重写后端响应中的 Location 请求头,使用公共域名而不是内部后端地址。",
|
||||||
directUpstream: "直连上游",
|
directUpstream: "直连上游",
|
||||||
directUpstreamHelp: "从代理主机直接拨号上游目标,而不是通过 WireGuard 隧道。当上游在无 WireGuard 连接的情况下可达时打开。",
|
directUpstreamHelp:
|
||||||
directUpstreamHelpCluster: "对代理集群目标必需且锁定开启:集群没有可回退的 WireGuard 端点。",
|
"从代理主机直接拨号上游目标,而不是通过 WireGuard 隧道。当上游在无 WireGuard 连接的情况下可达时打开。",
|
||||||
directUpstreamTooltip: "直连上游仅在至少一个已连接嵌入式代理(netbird proxy)的集群上可配置。所选集群没有嵌入式代理。",
|
directUpstreamHelpCluster:
|
||||||
|
"对代理集群目标必需且锁定开启:集群没有可回退的 WireGuard 端点。",
|
||||||
|
directUpstreamTooltip:
|
||||||
|
"直连上游仅在至少一个已连接嵌入式代理(netbird proxy)的集群上可配置。所选集群没有嵌入式代理。",
|
||||||
learnMoreServices: "服务",
|
learnMoreServices: "服务",
|
||||||
learnMoreAuthentication: "身份验证",
|
learnMoreAuthentication: "身份验证",
|
||||||
learnMoreAccessControl: "访问控制",
|
learnMoreAccessControl: "访问控制",
|
||||||
learnMoreSettings: "设置",
|
learnMoreSettings: "设置",
|
||||||
noProtectionTitle: "未配置保护",
|
noProtectionTitle: "未配置保护",
|
||||||
noProtectionDescription: "此服务未配置身份验证或访问控制规则。它将对互联网上的所有人公开可访问。确定要继续吗?",
|
noProtectionDescription:
|
||||||
|
"此服务未配置身份验证或访问控制规则。它将对互联网上的所有人公开可访问。确定要继续吗?",
|
||||||
httpsService: "HTTPS 服务",
|
httpsService: "HTTPS 服务",
|
||||||
tlsPassthrough: "TLS 直通",
|
tlsPassthrough: "TLS 直通",
|
||||||
tcpService: "TCP 服务",
|
tcpService: "TCP 服务",
|
||||||
udpService: "UDP 服务",
|
udpService: "UDP 服务",
|
||||||
forwardTrafficDesc: "将流量直接转发到您的后端服务。",
|
forwardTrafficDesc: "将流量直接转发到您的后端服务。",
|
||||||
exposeServicesDesc: "通过 NetBird 的反向代理安全地暴露服务。"
|
exposeServicesDesc: "通过 NetBird 的反向代理安全地暴露服务。",
|
||||||
},
|
},
|
||||||
dns: {
|
dns: {
|
||||||
title: "DNS",
|
title: "DNS",
|
||||||
@@ -898,7 +1007,8 @@ disable2FA: "禁用两步验证",
|
|||||||
distributionGroupsLabel: "分配组",
|
distributionGroupsLabel: "分配组",
|
||||||
zoneGroupsHelp: "将此区域及其记录广播给属于以下组的节点。",
|
zoneGroupsHelp: "将此区域及其记录广播给属于以下组的节点。",
|
||||||
enableSearchDomains: "启用搜索域名",
|
enableSearchDomains: "启用搜索域名",
|
||||||
searchDomainHelpZone: "例如,'server.company.internal' 将可通过'server'访问",
|
searchDomainHelpZone:
|
||||||
|
"例如,'server.company.internal' 将可通过'server'访问",
|
||||||
enableDNSZone: "启用 DNS 区域",
|
enableDNSZone: "启用 DNS 区域",
|
||||||
enableDisableDNSZone: "使用此开关启用或禁用 DNS 区域。",
|
enableDisableDNSZone: "使用此开关启用或禁用 DNS 区域。",
|
||||||
addDNSZone: "添加 DNS 区域",
|
addDNSZone: "添加 DNS 区域",
|
||||||
@@ -925,12 +1035,13 @@ disable2FA: "禁用两步验证",
|
|||||||
dnsZones: "DNS 区域",
|
dnsZones: "DNS 区域",
|
||||||
dns: "DNS",
|
dns: "DNS",
|
||||||
recordTypeAAAA: "AAAA",
|
recordTypeAAAA: "AAAA",
|
||||||
recordTypeCNAME: "CNAME"
|
recordTypeCNAME: "CNAME",
|
||||||
},
|
},
|
||||||
networks: {
|
networks: {
|
||||||
title: "网络",
|
title: "网络",
|
||||||
description: "管理组织的网络和路由",
|
description: "管理组织的网络和路由",
|
||||||
pageDescription: "无需在每台机器上安装 NetBird,即可访问 LAN 和 VPC 中的内部资源。",
|
pageDescription:
|
||||||
|
"无需在每台机器上安装 NetBird,即可访问 LAN 和 VPC 中的内部资源。",
|
||||||
networkName: "网络名称",
|
networkName: "网络名称",
|
||||||
networkNamePlaceholder: "例如:工程网络",
|
networkNamePlaceholder: "例如:工程网络",
|
||||||
createNetwork: "创建网络",
|
createNetwork: "创建网络",
|
||||||
@@ -957,9 +1068,11 @@ disable2FA: "禁用两步验证",
|
|||||||
addRoutingPeer: "添加路由节点",
|
addRoutingPeer: "添加路由节点",
|
||||||
removeRoutingPeer: "移除路由节点",
|
removeRoutingPeer: "移除路由节点",
|
||||||
networkRoutes: "网络路由",
|
networkRoutes: "网络路由",
|
||||||
routesDescription: "访问其他网络,如局域网和 VPC,无需在每个资源上安装 NetBird。",
|
routesDescription:
|
||||||
|
"访问其他网络,如局域网和 VPC,无需在每个资源上安装 NetBird。",
|
||||||
learnMoreAbout: "了解更多关于",
|
learnMoreAbout: "了解更多关于",
|
||||||
newNetworksRecommendation: "我们建议使用新的网络概念来更轻松地可视化和管理对资源的访问。",
|
newNetworksRecommendation:
|
||||||
|
"我们建议使用新的网络概念来更轻松地可视化和管理对资源的访问。",
|
||||||
goToNetworks: "前往网络",
|
goToNetworks: "前往网络",
|
||||||
createRoute: "创建路由",
|
createRoute: "创建路由",
|
||||||
editRoute: "编辑路由",
|
editRoute: "编辑路由",
|
||||||
@@ -987,20 +1100,23 @@ disable2FA: "禁用两步验证",
|
|||||||
resourceDescriptionHelp: "写一个简短的描述为此资源添加更多上下文。",
|
resourceDescriptionHelp: "写一个简短的描述为此资源添加更多上下文。",
|
||||||
resourceDescriptionPlaceholder: "例如:生产环境、开发环境",
|
resourceDescriptionPlaceholder: "例如:生产环境、开发环境",
|
||||||
resourceGroupsLabel: "资源组",
|
resourceGroupsLabel: "资源组",
|
||||||
resourceGroupsHelp: "将此资源添加到组(例如数据库、Web 服务器)中,并在访问策略中引用该组以简化管理。",
|
resourceGroupsHelp:
|
||||||
|
"将此资源添加到组(例如数据库、Web 服务器)中,并在访问策略中引用该组以简化管理。",
|
||||||
resourceGroupsPlaceholder: "添加或选择资源组...",
|
resourceGroupsPlaceholder: "添加或选择资源组...",
|
||||||
accessControl: "访问控制",
|
accessControl: "访问控制",
|
||||||
resourceTab: "资源",
|
resourceTab: "资源",
|
||||||
optionalSettings: "可选设置",
|
optionalSettings: "可选设置",
|
||||||
accessControlPolicies: "访问控制策略",
|
accessControlPolicies: "访问控制策略",
|
||||||
accessControlPoliciesHelp: "定义允许访问此资源的源组。您还可以限制对特定协议和端口的访问。如果没有策略,将无法访问此资源。",
|
accessControlPoliciesHelp:
|
||||||
|
"定义允许访问此资源的源组。您还可以限制对特定协议和端口的访问。如果没有策略,将无法访问此资源。",
|
||||||
routeType: "路由类型",
|
routeType: "路由类型",
|
||||||
routeTypeHelp: "选择路由类型以添加网络范围或域名列表。",
|
routeTypeHelp: "选择路由类型以添加网络范围或域名列表。",
|
||||||
routeTypeNetworkRange: "网络范围",
|
routeTypeNetworkRange: "网络范围",
|
||||||
routeTypeDomains: "域名",
|
routeTypeDomains: "域名",
|
||||||
networkRange: "网络范围",
|
networkRange: "网络范围",
|
||||||
networkRangeHelp: "添加私有 IPv4 或 IPv6 地址或范围",
|
networkRangeHelp: "添加私有 IPv4 或 IPv6 地址或范围",
|
||||||
networkRangePlaceholder: "例如:172.16.0.1, 172.16.0.0/16, 2001:db8::1 或 2001:db8::/64",
|
networkRangePlaceholder:
|
||||||
|
"例如:172.16.0.1, 172.16.0.0/16, 2001:db8::1 或 2001:db8::/64",
|
||||||
domains: "域名",
|
domains: "域名",
|
||||||
distributionGroups: "分发组",
|
distributionGroups: "分发组",
|
||||||
networkIdentifier: "网络标识符",
|
networkIdentifier: "网络标识符",
|
||||||
@@ -1009,7 +1125,7 @@ disable2FA: "禁用两步验证",
|
|||||||
metricPlaceholder: "输入度量值 (1-9999)",
|
metricPlaceholder: "输入度量值 (1-9999)",
|
||||||
additionalSettings: "其他设置",
|
additionalSettings: "其他设置",
|
||||||
accessControlGroups: "访问控制组",
|
accessControlGroups: "访问控制组",
|
||||||
autoApply: "自动应用"
|
autoApply: "自动应用",
|
||||||
},
|
},
|
||||||
postureChecks: {
|
postureChecks: {
|
||||||
title: "姿态检查",
|
title: "姿态检查",
|
||||||
@@ -1044,14 +1160,17 @@ disable2FA: "禁用两步验证",
|
|||||||
postureCheckNameHelp: "为姿态检查设置一个易于识别的名称。",
|
postureCheckNameHelp: "为姿态检查设置一个易于识别的名称。",
|
||||||
postureCheckNamePlaceholder: "例如:NetBird 版本 > 0.25.0",
|
postureCheckNamePlaceholder: "例如:NetBird 版本 > 0.25.0",
|
||||||
postureCheckDescriptionHelp: "写一个简短的描述为此策略添加更多上下文。",
|
postureCheckDescriptionHelp: "写一个简短的描述为此策略添加更多上下文。",
|
||||||
postureCheckDescriptionPlaceholder: "例如:检查 NetBird 版本是否大于 0.25.0",
|
postureCheckDescriptionPlaceholder:
|
||||||
|
"例如:检查 NetBird 版本是否大于 0.25.0",
|
||||||
netBirdClientVersion: "NetBird 客户端版本",
|
netBirdClientVersion: "NetBird 客户端版本",
|
||||||
netBirdClientVersionHelp: "根据特定的 NetBird 客户端版本限制对节点的访问。",
|
netBirdClientVersionHelp: "根据特定的 NetBird 客户端版本限制对节点的访问。",
|
||||||
netBirdClientVersionCheck: "客户端版本检查",
|
netBirdClientVersionCheck: "客户端版本检查",
|
||||||
minimumRequiredVersion: "最低所需版本",
|
minimumRequiredVersion: "最低所需版本",
|
||||||
minimumRequiredVersionHelp: "仅具有指定最低 NetBird 客户端版本的对等节点才能访问网络。",
|
minimumRequiredVersionHelp:
|
||||||
|
"仅具有指定最低 NetBird 客户端版本的对等节点才能访问网络。",
|
||||||
minimumRequiredVersionPlaceholder: "例如:0.25.0",
|
minimumRequiredVersionPlaceholder: "例如:0.25.0",
|
||||||
minimumRequiredVersionError: "请输入有效版本,例如:0.2, 0.2.0, 0.2.0-alpha.1",
|
minimumRequiredVersionError:
|
||||||
|
"请输入有效版本,例如:0.2, 0.2.0, 0.2.0-alpha.1",
|
||||||
countryAndRegion: "国家与地区",
|
countryAndRegion: "国家与地区",
|
||||||
countryAndRegionHelp: "根据国家或地区限制网络中的访问。",
|
countryAndRegionHelp: "根据国家或地区限制网络中的访问。",
|
||||||
countryAndRegionCheck: "国家与地区检查",
|
countryAndRegionCheck: "国家与地区检查",
|
||||||
@@ -1075,7 +1194,8 @@ disable2FA: "禁用两步验证",
|
|||||||
processHelp: "根据对等节点上正在运行的进程限制网络中的访问。",
|
processHelp: "根据对等节点上正在运行的进程限制网络中的访问。",
|
||||||
processCheck: "进程检查",
|
processCheck: "进程检查",
|
||||||
processes: "进程",
|
processes: "进程",
|
||||||
processesHelp: "添加进程的可执行文件路径。您可以为 Linux、macOS 和 Windows 定义路径。仅当进程在系统上运行时,对等节点才允许连接。",
|
processesHelp:
|
||||||
|
"添加进程的可执行文件路径。您可以为 Linux、macOS 和 Windows 定义路径。仅当进程在系统上运行时,对等节点才允许连接。",
|
||||||
addProcess: "添加进程",
|
addProcess: "添加进程",
|
||||||
linuxPathPlaceholder: "/usr/local/bin/netbird",
|
linuxPathPlaceholder: "/usr/local/bin/netbird",
|
||||||
macPathPlaceholder: "/Applications/NetBird.app/Contents/MacOS/netbird",
|
macPathPlaceholder: "/Applications/NetBird.app/Contents/MacOS/netbird",
|
||||||
@@ -1091,9 +1211,10 @@ disable2FA: "禁用两步验证",
|
|||||||
validCidr: "请输入有效的 CIDR,例如:192.168.1.0/24",
|
validCidr: "请输入有效的 CIDR,例如:192.168.1.0/24",
|
||||||
cidrPlaceholder: "例如:172.16.0.0/16",
|
cidrPlaceholder: "例如:172.16.0.0/16",
|
||||||
noChecks: "您还没有添加任何态势检查",
|
noChecks: "您还没有添加任何态势检查",
|
||||||
noChecksDescription: "添加各种态势检查以进一步限制网络中的访问。例如,仅允许具有特定 NetBird 客户端版本、操作系统或位置的客户端连接。",
|
noChecksDescription:
|
||||||
|
"添加各种态势检查以进一步限制网络中的访问。例如,仅允许具有特定 NetBird 客户端版本、操作系统或位置的客户端连接。",
|
||||||
browseChecks: "浏览检查",
|
browseChecks: "浏览检查",
|
||||||
newPostureCheck: "新建态势检查"
|
newPostureCheck: "新建态势检查",
|
||||||
},
|
},
|
||||||
setupKeys: {
|
setupKeys: {
|
||||||
title: "安装密钥",
|
title: "安装密钥",
|
||||||
@@ -1134,13 +1255,27 @@ disable2FA: "禁用两步验证",
|
|||||||
expiresIn: "过期时间",
|
expiresIn: "过期时间",
|
||||||
expiresInHelp: "密钥过期前的天数。",
|
expiresInHelp: "密钥过期前的天数。",
|
||||||
expiresInHelpEmpty: "留空表示永不过期。",
|
expiresInHelpEmpty: "留空表示永不过期。",
|
||||||
expiresInSuffix: "天"
|
expiresInSuffix: "天",
|
||||||
|
all: "全部",
|
||||||
|
oneOff: "一次性",
|
||||||
|
reusable: "可重复使用",
|
||||||
|
status: "状态",
|
||||||
|
groupNotUsedTitle: "此组尚未在任何安装密钥中使用",
|
||||||
|
groupNotUsedDescription:
|
||||||
|
"在创建新的安装密钥时分配此组,以便在此处查看它们。",
|
||||||
|
getStartedDescription:
|
||||||
|
"添加安装密钥以在您的网络中注册新设备。该密钥在初始设置期间将设备链接到您的账户。",
|
||||||
|
learnMore: "了解更多关于",
|
||||||
|
groups: "组",
|
||||||
|
usage: "使用情况",
|
||||||
|
lastUsedOn: "上次使用于",
|
||||||
},
|
},
|
||||||
activity: {
|
activity: {
|
||||||
title: "活动",
|
title: "活动",
|
||||||
description: "查看审计事件和活动日志",
|
description: "查看审计事件和活动日志",
|
||||||
auditEvents: "审计事件",
|
auditEvents: "审计事件",
|
||||||
auditEventsDescription: "审计整个网络中的配置变更、访问策略更新以及节点注册和登录事件。",
|
auditEventsDescription:
|
||||||
|
"审计整个网络中的配置变更、访问策略更新以及节点注册和登录事件。",
|
||||||
noEvents: "暂无事件",
|
noEvents: "暂无事件",
|
||||||
searchPlaceholder: "搜索事件...",
|
searchPlaceholder: "搜索事件...",
|
||||||
searchByAuditNameUserPeerMeta: "按审计名称、用户、节点、元数据搜索...",
|
searchByAuditNameUserPeerMeta: "按审计名称、用户、节点、元数据搜索...",
|
||||||
@@ -1150,7 +1285,7 @@ disable2FA: "禁用两步验证",
|
|||||||
timestamp: "时间戳",
|
timestamp: "时间戳",
|
||||||
ipAddress: "IP 地址",
|
ipAddress: "IP 地址",
|
||||||
details: "详情",
|
details: "详情",
|
||||||
code: "代码"
|
code: "代码",
|
||||||
},
|
},
|
||||||
controlCenter: {
|
controlCenter: {
|
||||||
title: "控制中心",
|
title: "控制中心",
|
||||||
@@ -1161,10 +1296,10 @@ disable2FA: "禁用两步验证",
|
|||||||
totalGroups: "组总数",
|
totalGroups: "组总数",
|
||||||
totalUsers: "用户总数",
|
totalUsers: "用户总数",
|
||||||
totalNetworks: "网络总数",
|
totalNetworks: "网络总数",
|
||||||
networkOverview: "网络概览"
|
networkOverview: "网络概览",
|
||||||
},
|
},
|
||||||
onboarding: {
|
onboarding: {
|
||||||
title: "开始使用 NetBird",
|
title: "开始使用 NetBird",
|
||||||
addResource: "添加您的第一个资源"
|
addResource: "添加您的第一个资源",
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -23,7 +23,9 @@ type Props = {
|
|||||||
user: User;
|
user: User;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function AccessTokensTableColumns(t: ReturnType<typeof useTranslations>): ColumnDef<AccessToken>[] {
|
export function AccessTokensTableColumns(
|
||||||
|
t: ReturnType<typeof useTranslations>,
|
||||||
|
): ColumnDef<AccessToken>[] {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
accessorKey: "name",
|
accessorKey: "name",
|
||||||
@@ -39,7 +41,9 @@ export function AccessTokensTableColumns(t: ReturnType<typeof useTranslations>):
|
|||||||
{
|
{
|
||||||
accessorKey: "expiration_date",
|
accessorKey: "expiration_date",
|
||||||
header: ({ column }) => {
|
header: ({ column }) => {
|
||||||
return <DataTableHeader column={column}>{t("expires")}</DataTableHeader>;
|
return (
|
||||||
|
<DataTableHeader column={column}>{t("expires")}</DataTableHeader>
|
||||||
|
);
|
||||||
},
|
},
|
||||||
cell: ({ row }) => (
|
cell: ({ row }) => (
|
||||||
<ExpirationDateRow date={row.original.expiration_date} />
|
<ExpirationDateRow date={row.original.expiration_date} />
|
||||||
@@ -48,7 +52,9 @@ export function AccessTokensTableColumns(t: ReturnType<typeof useTranslations>):
|
|||||||
{
|
{
|
||||||
accessorKey: "last_used",
|
accessorKey: "last_used",
|
||||||
header: ({ column }) => {
|
header: ({ column }) => {
|
||||||
return <DataTableHeader column={column}>{t("lastUsed")}</DataTableHeader>;
|
return (
|
||||||
|
<DataTableHeader column={column}>{t("lastUsed")}</DataTableHeader>
|
||||||
|
);
|
||||||
},
|
},
|
||||||
sortingFn: "datetime",
|
sortingFn: "datetime",
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
@@ -64,7 +70,7 @@ export function AccessTokensTableColumns(t: ReturnType<typeof useTranslations>):
|
|||||||
header: "",
|
header: "",
|
||||||
cell: ({ row }) => <AccessTokenActionCell access_token={row.original} />,
|
cell: ({ row }) => <AccessTokenActionCell access_token={row.original} />,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function AccessTokensTable({ user }: Readonly<Props>) {
|
export default function AccessTokensTable({ user }: Readonly<Props>) {
|
||||||
@@ -92,7 +98,7 @@ export default function AccessTokensTable({ user }: Readonly<Props>) {
|
|||||||
<Card className={"mt-5 w-full"}>
|
<Card className={"mt-5 w-full"}>
|
||||||
{tokens && tokens.length > 0 ? (
|
{tokens && tokens.length > 0 ? (
|
||||||
<DataTable
|
<DataTable
|
||||||
text={"Access Tokens"}
|
text={t("accessTokens")}
|
||||||
tableClassName={"mt-0"}
|
tableClassName={"mt-0"}
|
||||||
minimal={true}
|
minimal={true}
|
||||||
showSearchAndFilters={false}
|
showSearchAndFilters={false}
|
||||||
@@ -106,10 +112,8 @@ export default function AccessTokensTable({ user }: Readonly<Props>) {
|
|||||||
<div className={"bg-nb-gray-950 overflow-hidden"}>
|
<div className={"bg-nb-gray-950 overflow-hidden"}>
|
||||||
<NoResults
|
<NoResults
|
||||||
className={"py-3"}
|
className={"py-3"}
|
||||||
title={"No access tokens"}
|
title={t("noAccessTokens")}
|
||||||
description={
|
description={t("noAccessTokensDesc")}
|
||||||
"You don't have any access tokens yet. You can add a token to access the NetBird API."
|
|
||||||
}
|
|
||||||
icon={<IconApi size={20} className={"fill-nb-gray-300"} />}
|
icon={<IconApi size={20} className={"fill-nb-gray-300"} />}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { useTranslations } from "next-intl";
|
||||||
import Button from "@components/Button";
|
import Button from "@components/Button";
|
||||||
import {
|
import {
|
||||||
DropdownMenu,
|
DropdownMenu,
|
||||||
@@ -18,6 +19,7 @@ import { RouteModalContent } from "@/modules/routes/RouteModal";
|
|||||||
export default function AddRouteDropdownButton() {
|
export default function AddRouteDropdownButton() {
|
||||||
const [modal, setModal] = useState(false);
|
const [modal, setModal] = useState(false);
|
||||||
const [existingNetworkModal, setExistingNetworkModal] = useState(false);
|
const [existingNetworkModal, setExistingNetworkModal] = useState(false);
|
||||||
|
const t = useTranslations("common");
|
||||||
const { peer } = usePeer();
|
const { peer } = usePeer();
|
||||||
const { permission } = usePermissions();
|
const { permission } = usePermissions();
|
||||||
|
|
||||||
@@ -44,7 +46,7 @@ export default function AddRouteDropdownButton() {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Button variant={"primary"}>
|
<Button variant={"primary"}>
|
||||||
Add Route
|
{t("addRoute")}
|
||||||
<ChevronDown size={16} />
|
<ChevronDown size={16} />
|
||||||
</Button>
|
</Button>
|
||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
@@ -61,10 +63,10 @@ export default function AddRouteDropdownButton() {
|
|||||||
size={"small"}
|
size={"small"}
|
||||||
/>
|
/>
|
||||||
<div className={"flex flex-col text-left"}>
|
<div className={"flex flex-col text-left"}>
|
||||||
<div className={"text-left text-white"}>New Network Route</div>
|
<div className={"text-left text-white"}>
|
||||||
<div className={"text-xs"}>
|
{t("newNetworkRoute")}
|
||||||
Create a new network route with this peer
|
|
||||||
</div>
|
</div>
|
||||||
|
<div className={"text-xs"}>{t("newNetworkRouteDesc")}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
@@ -83,10 +85,10 @@ export default function AddRouteDropdownButton() {
|
|||||||
size={"small"}
|
size={"small"}
|
||||||
/>
|
/>
|
||||||
<div className={"flex flex-col text-left"}>
|
<div className={"flex flex-col text-left"}>
|
||||||
<div className={"text-left text-white"}>Existing Network</div>
|
<div className={"text-left text-white"}>
|
||||||
<div className={"text-xs"}>
|
{t("existingNetwork")}
|
||||||
Add this peer to an existing network
|
|
||||||
</div>
|
</div>
|
||||||
|
<div className={"text-xs"}>{t("existingNetworkDesc")}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
|
|||||||
@@ -23,35 +23,6 @@ interface PeerEditIPModalProps {
|
|||||||
version: IPVersion;
|
version: IPVersion;
|
||||||
}
|
}
|
||||||
|
|
||||||
const config: Record<
|
|
||||||
IPVersion,
|
|
||||||
{
|
|
||||||
title: string;
|
|
||||||
description: string;
|
|
||||||
placeholder: string;
|
|
||||||
errorMessage: string;
|
|
||||||
validate: (ip: string) => boolean;
|
|
||||||
}
|
|
||||||
> = {
|
|
||||||
v4: {
|
|
||||||
title: "Edit Peer IP Address",
|
|
||||||
description: "Update the NetBird IP address for this peer.",
|
|
||||||
placeholder: "e.g., 100.64.0.15",
|
|
||||||
errorMessage: "Please enter a valid IP, e.g., 100.64.0.15",
|
|
||||||
validate: (ip: string) =>
|
|
||||||
/^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/.test(
|
|
||||||
ip,
|
|
||||||
),
|
|
||||||
},
|
|
||||||
v6: {
|
|
||||||
title: "Edit Peer IPv6 Address",
|
|
||||||
description: "Update the NetBird IPv6 address for this peer.",
|
|
||||||
placeholder: "e.g., fd00:1234::1",
|
|
||||||
errorMessage: "Please enter a valid IPv6 address, e.g., fd00:1234::1",
|
|
||||||
validate: (ip: string) => cidr.isValidAddress(ip) && ip.includes(":"),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export function PeerEditIPModal({
|
export function PeerEditIPModal({
|
||||||
open,
|
open,
|
||||||
onOpenChange,
|
onOpenChange,
|
||||||
@@ -60,6 +31,42 @@ export function PeerEditIPModal({
|
|||||||
version,
|
version,
|
||||||
}: Readonly<PeerEditIPModalProps>) {
|
}: Readonly<PeerEditIPModalProps>) {
|
||||||
const t = useTranslations("peers");
|
const t = useTranslations("peers");
|
||||||
|
const tc = useTranslations("common");
|
||||||
|
|
||||||
|
const config = useMemo<
|
||||||
|
Record<
|
||||||
|
IPVersion,
|
||||||
|
{
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
placeholder: string;
|
||||||
|
errorMessage: string;
|
||||||
|
validate: (ip: string) => boolean;
|
||||||
|
}
|
||||||
|
>
|
||||||
|
>(
|
||||||
|
() => ({
|
||||||
|
v4: {
|
||||||
|
title: t("editPeerIPAddress"),
|
||||||
|
description: t("updatePeerIPDescription"),
|
||||||
|
placeholder: t("editPeerIPPlaceholder"),
|
||||||
|
errorMessage: t("editPeerIPErrorMessage"),
|
||||||
|
validate: (ip: string) =>
|
||||||
|
/^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/.test(
|
||||||
|
ip,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
v6: {
|
||||||
|
title: t("editPeerIPv6Address"),
|
||||||
|
description: t("updatePeerIPv6Description"),
|
||||||
|
placeholder: t("editPeerIPv6Placeholder"),
|
||||||
|
errorMessage: t("editPeerIPv6ErrorMessage"),
|
||||||
|
validate: (ip: string) => cidr.isValidAddress(ip) && ip.includes(":"),
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
[t, version],
|
||||||
|
);
|
||||||
|
|
||||||
const { title, description, placeholder, errorMessage, validate } =
|
const { title, description, placeholder, errorMessage, validate } =
|
||||||
config[version];
|
config[version];
|
||||||
const [ip, setIP] = useState(currentIP);
|
const [ip, setIP] = useState(currentIP);
|
||||||
@@ -99,7 +106,7 @@ export function PeerEditIPModal({
|
|||||||
<div className={"flex gap-3 w-full justify-end"}>
|
<div className={"flex gap-3 w-full justify-end"}>
|
||||||
<ModalClose asChild={true}>
|
<ModalClose asChild={true}>
|
||||||
<Button variant={"secondary"} className={"w-full"}>
|
<Button variant={"secondary"} className={"w-full"}>
|
||||||
{t("cancel")}
|
{tc("cancel")}
|
||||||
</Button>
|
</Button>
|
||||||
</ModalClose>
|
</ModalClose>
|
||||||
|
|
||||||
@@ -109,7 +116,7 @@ export function PeerEditIPModal({
|
|||||||
onClick={() => onSave(trim(ip))}
|
onClick={() => onSave(trim(ip))}
|
||||||
disabled={isDisabled}
|
disabled={isDisabled}
|
||||||
>
|
>
|
||||||
Save
|
{tc("save")}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</ModalFooter>
|
</ModalFooter>
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { useTranslations } from "next-intl";
|
||||||
import Card from "@components/Card";
|
import Card from "@components/Card";
|
||||||
import { DataTable } from "@components/table/DataTable";
|
import { DataTable } from "@components/table/DataTable";
|
||||||
import DataTableHeader from "@components/table/DataTableHeader";
|
import DataTableHeader from "@components/table/DataTableHeader";
|
||||||
@@ -20,11 +21,14 @@ type Props = {
|
|||||||
peer: Peer;
|
peer: Peer;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const RouteTableColumns: ColumnDef<Route>[] = [
|
function RouteTableColumns(
|
||||||
|
t: ReturnType<typeof useTranslations>,
|
||||||
|
): ColumnDef<Route>[] {
|
||||||
|
return [
|
||||||
{
|
{
|
||||||
accessorKey: "network_id",
|
accessorKey: "network_id",
|
||||||
header: ({ column }) => {
|
header: ({ column }) => {
|
||||||
return <DataTableHeader column={column}>Name</DataTableHeader>;
|
return <DataTableHeader column={column}>{t("name")}</DataTableHeader>;
|
||||||
},
|
},
|
||||||
sortingFn: "text",
|
sortingFn: "text",
|
||||||
cell: ({ row }) => <PeerRouteNameCell route={row.original} />,
|
cell: ({ row }) => <PeerRouteNameCell route={row.original} />,
|
||||||
@@ -32,7 +36,9 @@ export const RouteTableColumns: ColumnDef<Route>[] = [
|
|||||||
{
|
{
|
||||||
accessorKey: "network",
|
accessorKey: "network",
|
||||||
header: ({ column }) => {
|
header: ({ column }) => {
|
||||||
return <DataTableHeader column={column}>Network</DataTableHeader>;
|
return (
|
||||||
|
<DataTableHeader column={column}>{t("network")}</DataTableHeader>
|
||||||
|
);
|
||||||
},
|
},
|
||||||
cell: ({ row }) => (
|
cell: ({ row }) => (
|
||||||
<GroupedRouteNetworkRangeCell
|
<GroupedRouteNetworkRangeCell
|
||||||
@@ -46,7 +52,9 @@ export const RouteTableColumns: ColumnDef<Route>[] = [
|
|||||||
accessorFn: (r) => r.groups?.length,
|
accessorFn: (r) => r.groups?.length,
|
||||||
header: ({ column }) => {
|
header: ({ column }) => {
|
||||||
return (
|
return (
|
||||||
<DataTableHeader column={column}>Distribution Groups</DataTableHeader>
|
<DataTableHeader column={column}>
|
||||||
|
{t("distributionGroups")}
|
||||||
|
</DataTableHeader>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
cell: ({ row }) => <RouteDistributionGroupsCell route={row.original} />,
|
cell: ({ row }) => <RouteDistributionGroupsCell route={row.original} />,
|
||||||
@@ -56,7 +64,7 @@ export const RouteTableColumns: ColumnDef<Route>[] = [
|
|||||||
accessorKey: "enabled",
|
accessorKey: "enabled",
|
||||||
sortingFn: "basic",
|
sortingFn: "basic",
|
||||||
header: ({ column }) => (
|
header: ({ column }) => (
|
||||||
<DataTableHeader column={column}>Active</DataTableHeader>
|
<DataTableHeader column={column}>{t("active")}</DataTableHeader>
|
||||||
),
|
),
|
||||||
cell: ({ row }) => <PeerRouteActiveCell route={row.original} />,
|
cell: ({ row }) => <PeerRouteActiveCell route={row.original} />,
|
||||||
},
|
},
|
||||||
@@ -65,13 +73,15 @@ export const RouteTableColumns: ColumnDef<Route>[] = [
|
|||||||
header: "",
|
header: "",
|
||||||
cell: ({ row }) => <PeerRouteActionCell route={row.original} />,
|
cell: ({ row }) => <PeerRouteActionCell route={row.original} />,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
}
|
||||||
|
|
||||||
export default function PeerRoutesTable({
|
export default function PeerRoutesTable({
|
||||||
peerRoutes,
|
peerRoutes,
|
||||||
isLoading,
|
isLoading,
|
||||||
peer,
|
peer,
|
||||||
}: Props) {
|
}: Props) {
|
||||||
|
const t = useTranslations("common");
|
||||||
// Default sorting state of the table
|
// Default sorting state of the table
|
||||||
const [sorting, setSorting] = useState<SortingState>([
|
const [sorting, setSorting] = useState<SortingState>([
|
||||||
{
|
{
|
||||||
@@ -87,15 +97,13 @@ export default function PeerRoutesTable({
|
|||||||
wrapperProps={{
|
wrapperProps={{
|
||||||
className: cn("w-full"),
|
className: cn("w-full"),
|
||||||
}}
|
}}
|
||||||
text={"Network Routes"}
|
text={t("networkRoutes")}
|
||||||
tableClassName={"mt-0"}
|
tableClassName={"mt-0"}
|
||||||
getStartedCard={
|
getStartedCard={
|
||||||
<NoResults
|
<NoResults
|
||||||
className={"py-4"}
|
className={"py-4"}
|
||||||
title={"This peer has no network routes"}
|
title={t("noNetworkRoutes")}
|
||||||
description={
|
description={t("noNetworkRoutesDesc")}
|
||||||
"You don't have any assigned network routes yet. You can add this peer to an existing network or create a new network route."
|
|
||||||
}
|
|
||||||
icon={
|
icon={
|
||||||
<NetworkRoutesIcon size={20} className={"fill-nb-gray-300"} />
|
<NetworkRoutesIcon size={20} className={"fill-nb-gray-300"} />
|
||||||
}
|
}
|
||||||
@@ -107,7 +115,7 @@ export default function PeerRoutesTable({
|
|||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
sorting={sorting}
|
sorting={sorting}
|
||||||
setSorting={setSorting}
|
setSorting={setSorting}
|
||||||
columns={RouteTableColumns}
|
columns={RouteTableColumns(t)}
|
||||||
data={peerRoutes}
|
data={peerRoutes}
|
||||||
paginationPaddingClassName={"px-0 pt-8"}
|
paginationPaddingClassName={"px-0 pt-8"}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ import { Group } from "@/interfaces/Group";
|
|||||||
import { orderBy } from "lodash";
|
import { orderBy } from "lodash";
|
||||||
import CircleIcon from "@/assets/icons/CircleIcon";
|
import CircleIcon from "@/assets/icons/CircleIcon";
|
||||||
import Badge from "@components/Badge";
|
import Badge from "@components/Badge";
|
||||||
import { cn, singularize } from "@utils/helpers";
|
import { cn } from "@utils/helpers";
|
||||||
import { Modal } from "@components/modal/Modal";
|
import { Modal } from "@components/modal/Modal";
|
||||||
import { AccessControlModalContent } from "@/modules/access-control/AccessControlModal";
|
import { AccessControlModalContent } from "@/modules/access-control/AccessControlModal";
|
||||||
import PoliciesProvider from "@/contexts/PoliciesProvider";
|
import PoliciesProvider from "@/contexts/PoliciesProvider";
|
||||||
@@ -37,6 +37,7 @@ import { isNetbirdSSHProtocolSupported } from "@utils/version";
|
|||||||
|
|
||||||
export const PeerSSHToggle = () => {
|
export const PeerSSHToggle = () => {
|
||||||
const t = useTranslations("peers");
|
const t = useTranslations("peers");
|
||||||
|
const tc = useTranslations("common");
|
||||||
const { permission } = usePermissions();
|
const { permission } = usePermissions();
|
||||||
const { peer, toggleSSH, setSSHInstructionsModal } = usePeer();
|
const { peer, toggleSSH, setSSHInstructionsModal } = usePeer();
|
||||||
const { data: policies } = useFetchApi<Policy[]>(
|
const { data: policies } = useFetchApi<Policy[]>(
|
||||||
@@ -82,25 +83,22 @@ export const PeerSSHToggle = () => {
|
|||||||
|
|
||||||
const disableDashboardSSH = async () => {
|
const disableDashboardSSH = async () => {
|
||||||
const choice = await confirm({
|
const choice = await confirm({
|
||||||
title: `Disable SSH Access?`,
|
title: t("disableSSHConfirmation"),
|
||||||
description: (
|
description: (
|
||||||
<div>
|
<div>
|
||||||
Starting from NetBird v0.61.0, once SSH access is disabled, you cannot
|
{t("disableSSHDescription")}{" "}
|
||||||
re-enable it again from the dashboard. You'll need to create an
|
|
||||||
explicit access control policy and update your NetBird client to
|
|
||||||
restore SSH functionality.{" "}
|
|
||||||
<InlineLink
|
<InlineLink
|
||||||
href={"https://docs.netbird.io/manage/peers/ssh"}
|
href={"https://docs.netbird.io/manage/peers/ssh"}
|
||||||
target={"_blank"}
|
target={"_blank"}
|
||||||
onClick={(e) => e.stopPropagation()}
|
onClick={(e) => e.stopPropagation()}
|
||||||
>
|
>
|
||||||
Learn more
|
{t("sshLearnMore")}
|
||||||
<ExternalLinkIcon size={12} />
|
<ExternalLinkIcon size={12} />
|
||||||
</InlineLink>
|
</InlineLink>
|
||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
confirmText: "Disable",
|
confirmText: tc("disable"),
|
||||||
cancelText: "Cancel",
|
cancelText: tc("cancel"),
|
||||||
type: "warning",
|
type: "warning",
|
||||||
maxWidthClass: "max-w-xl",
|
maxWidthClass: "max-w-xl",
|
||||||
});
|
});
|
||||||
@@ -114,9 +112,7 @@ export const PeerSSHToggle = () => {
|
|||||||
content={
|
content={
|
||||||
<div className={"flex gap-2 items-center !text-nb-gray-300 text-xs"}>
|
<div className={"flex gap-2 items-center !text-nb-gray-300 text-xs"}>
|
||||||
<LockIcon size={14} />
|
<LockIcon size={14} />
|
||||||
<span>
|
<span>{t("noPermissionToUpdateSetting")}</span>
|
||||||
{`You don't have the required permissions to update this setting.`}
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
interactive={false}
|
interactive={false}
|
||||||
@@ -135,9 +131,7 @@ export const PeerSSHToggle = () => {
|
|||||||
{t("sshAccess")}
|
{t("sshAccess")}
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
helpText={
|
helpText={t("sshAccessHelp")}
|
||||||
"Enable the SSH server on this peer to access the machine via an secure shell."
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
</FullTooltip>
|
</FullTooltip>
|
||||||
<PeerSSHPolicyInfo peer={peer} />
|
<PeerSSHPolicyInfo peer={peer} />
|
||||||
@@ -148,10 +142,7 @@ export const PeerSSHToggle = () => {
|
|||||||
<Label>{t("sshAccess")}</Label>
|
<Label>{t("sshAccess")}</Label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<HelpText>
|
<HelpText>{t("sshSetupHelp")}</HelpText>
|
||||||
Set up SSH and create an explicit access control policy defining which
|
|
||||||
users can access specific local usernames of this machine via SSH.
|
|
||||||
</HelpText>
|
|
||||||
|
|
||||||
{!isNetbirdSSHProtocolSupported(peer.version) &&
|
{!isNetbirdSSHProtocolSupported(peer.version) &&
|
||||||
enabledPolicies?.length > 0 &&
|
enabledPolicies?.length > 0 &&
|
||||||
@@ -166,9 +157,7 @@ export const PeerSSHToggle = () => {
|
|||||||
}
|
}
|
||||||
className="my-3"
|
className="my-3"
|
||||||
>
|
>
|
||||||
You have SSH access configured but your client runs on an older
|
{t("sshOldVersionWarning")}
|
||||||
NetBird version. Please update your NetBird client to v.0.61.0+ in
|
|
||||||
order to allow SSH connections.
|
|
||||||
</Callout>
|
</Callout>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@@ -183,9 +172,7 @@ export const PeerSSHToggle = () => {
|
|||||||
}
|
}
|
||||||
className="my-3"
|
className="my-3"
|
||||||
>
|
>
|
||||||
You have an SSH access policy configured, but the SSH server
|
{t("sshServerNotEnabled")}
|
||||||
isn't enabled on this client. Enable the SSH server to allow SSH
|
|
||||||
connections.
|
|
||||||
</Callout>
|
</Callout>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@@ -200,9 +187,7 @@ export const PeerSSHToggle = () => {
|
|||||||
}
|
}
|
||||||
className="my-3"
|
className="my-3"
|
||||||
>
|
>
|
||||||
Your SSH server is enabled, but starting from NetBird v0.61.0, SSH
|
{t("sshNeedsPolicy")}
|
||||||
requires an explicit access control policy. Please create an SSH
|
|
||||||
access control policy in order to allow SSH connections.
|
|
||||||
</Callout>
|
</Callout>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@@ -214,7 +199,7 @@ export const PeerSSHToggle = () => {
|
|||||||
disabled={!permission?.policies.create}
|
disabled={!permission?.policies.create}
|
||||||
>
|
>
|
||||||
<CirclePlusIcon size={14} />
|
<CirclePlusIcon size={14} />
|
||||||
Create SSH Policy
|
{t("createSSHPolicy")}
|
||||||
</Button>
|
</Button>
|
||||||
) : (
|
) : (
|
||||||
<Button
|
<Button
|
||||||
@@ -300,11 +285,9 @@ export const PeerSSHToggle = () => {
|
|||||||
/>
|
/>
|
||||||
<div>
|
<div>
|
||||||
<span className={"font-medium text-xs"}>
|
<span className={"font-medium text-xs"}>
|
||||||
{singularize(
|
{t("activePoliciesCount", {
|
||||||
"Active Policies",
|
count: enabledPolicies?.length,
|
||||||
enabledPolicies?.length,
|
})}
|
||||||
true,
|
|
||||||
)}
|
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</Badge>
|
</Badge>
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { useTranslations } from "next-intl";
|
||||||
import Button from "@components/Button";
|
import Button from "@components/Button";
|
||||||
import {
|
import {
|
||||||
DropdownMenu,
|
DropdownMenu,
|
||||||
@@ -16,6 +17,7 @@ import { CreateDebugJobModalContent } from "../jobs/CreateDebugJobModal";
|
|||||||
|
|
||||||
export const RemoteJobDropdownButton = () => {
|
export const RemoteJobDropdownButton = () => {
|
||||||
const [modal, setModal] = useState(false);
|
const [modal, setModal] = useState(false);
|
||||||
|
const t = useTranslations("common");
|
||||||
const { peer } = usePeer();
|
const { peer } = usePeer();
|
||||||
const { permission } = usePermissions();
|
const { permission } = usePermissions();
|
||||||
const isConnected = peer?.connected;
|
const isConnected = peer?.connected;
|
||||||
@@ -39,7 +41,7 @@ export const RemoteJobDropdownButton = () => {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Button variant={"primary"} disabled={disabled}>
|
<Button variant={"primary"} disabled={disabled}>
|
||||||
Run Remote Job
|
{t("runRemoteJob")}
|
||||||
<ChevronDown size={16} />
|
<ChevronDown size={16} />
|
||||||
</Button>
|
</Button>
|
||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
@@ -52,10 +54,12 @@ export const RemoteJobDropdownButton = () => {
|
|||||||
}
|
}
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
Peer{" "}
|
{t.rich("peerOfflineRemoteJob", {
|
||||||
<span className={"text-white font-medium"}>{peer.name}</span>{" "}
|
name: peer.name,
|
||||||
is currently offline. Please connect the peer to run remote
|
bold: (chunks) => (
|
||||||
jobs.
|
<span className={"text-white font-medium"}>{chunks}</span>
|
||||||
|
),
|
||||||
|
})}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<DropdownMenuSeparator />
|
<DropdownMenuSeparator />
|
||||||
@@ -73,10 +77,8 @@ export const RemoteJobDropdownButton = () => {
|
|||||||
size={"small"}
|
size={"small"}
|
||||||
/>
|
/>
|
||||||
<div className={"flex flex-col text-left"}>
|
<div className={"flex flex-col text-left"}>
|
||||||
<div className={"text-left text-white"}>Debug Bundle</div>
|
<div className={"text-left text-white"}>{t("debugBundle")}</div>
|
||||||
<div className={"text-xs"}>
|
<div className={"text-xs"}>{t("debugBundleDesc")}</div>
|
||||||
Collect debug information for troubleshooting
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { useTranslations } from "next-intl";
|
||||||
import FullTooltip from "@components/FullTooltip";
|
import FullTooltip from "@components/FullTooltip";
|
||||||
import { ArrowUpDown, InfoIcon } from "lucide-react";
|
import { ArrowUpDown, InfoIcon } from "lucide-react";
|
||||||
|
|
||||||
@@ -9,13 +10,14 @@ export default function RouteMetricCell({
|
|||||||
metric,
|
metric,
|
||||||
useHoverStyle = true,
|
useHoverStyle = true,
|
||||||
}: Readonly<Props>) {
|
}: Readonly<Props>) {
|
||||||
|
const t = useTranslations("common");
|
||||||
return (
|
return (
|
||||||
<FullTooltip
|
<FullTooltip
|
||||||
hoverButton={useHoverStyle}
|
hoverButton={useHoverStyle}
|
||||||
isAction={true}
|
isAction={true}
|
||||||
content={
|
content={
|
||||||
<div className={"text-xs max-w-xs flex gap-2 items-center"}>
|
<div className={"text-xs max-w-xs flex gap-2 items-center"}>
|
||||||
<div>Lower metrics have higher priority.</div>
|
<div>{t("metricPriority")}</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -44,12 +44,16 @@ import SetupKeyModal from "@/modules/setup-keys/SetupKeyModal";
|
|||||||
import SetupKeyNameCell from "@/modules/setup-keys/SetupKeyNameCell";
|
import SetupKeyNameCell from "@/modules/setup-keys/SetupKeyNameCell";
|
||||||
import SetupKeyUsageCell from "@/modules/setup-keys/SetupKeyUsageCell";
|
import SetupKeyUsageCell from "@/modules/setup-keys/SetupKeyUsageCell";
|
||||||
|
|
||||||
export function SetupKeysTableColumns(t: ReturnType<typeof useTranslations>): ColumnDef<SetupKey>[] {
|
export function SetupKeysTableColumns(
|
||||||
|
t: ReturnType<typeof useTranslations>,
|
||||||
|
): ColumnDef<SetupKey>[] {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
accessorKey: "name",
|
accessorKey: "name",
|
||||||
header: ({ column }) => {
|
header: ({ column }) => {
|
||||||
return <DataTableHeader column={column}>{t("nameAndKey")}</DataTableHeader>;
|
return (
|
||||||
|
<DataTableHeader column={column}>{t("nameAndKey")}</DataTableHeader>
|
||||||
|
);
|
||||||
},
|
},
|
||||||
sortingFn: "text",
|
sortingFn: "text",
|
||||||
cell: ({ row }) => (
|
cell: ({ row }) => (
|
||||||
@@ -88,11 +92,13 @@ export function SetupKeysTableColumns(t: ReturnType<typeof useTranslations>): Co
|
|||||||
{
|
{
|
||||||
accessorKey: "last_used",
|
accessorKey: "last_used",
|
||||||
header: ({ column }) => {
|
header: ({ column }) => {
|
||||||
return <DataTableHeader column={column}>{t("lastUsed")}</DataTableHeader>;
|
return (
|
||||||
|
<DataTableHeader column={column}>{t("lastUsed")}</DataTableHeader>
|
||||||
|
);
|
||||||
},
|
},
|
||||||
sortingFn: "datetime",
|
sortingFn: "datetime",
|
||||||
cell: ({ row }) => (
|
cell: ({ row }) => (
|
||||||
<LastTimeRow date={row.original.last_used} text={"Last used on"} />
|
<LastTimeRow date={row.original.last_used} text={t("lastUsedOn")} />
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -117,7 +123,9 @@ export function SetupKeysTableColumns(t: ReturnType<typeof useTranslations>): Co
|
|||||||
{
|
{
|
||||||
accessorKey: "expires",
|
accessorKey: "expires",
|
||||||
header: ({ column }) => {
|
header: ({ column }) => {
|
||||||
return <DataTableHeader column={column}>{t("expires")}</DataTableHeader>;
|
return (
|
||||||
|
<DataTableHeader column={column}>{t("expires")}</DataTableHeader>
|
||||||
|
);
|
||||||
},
|
},
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
let expires = dayjs(row.original.expires);
|
let expires = dayjs(row.original.expires);
|
||||||
@@ -136,7 +144,7 @@ export function SetupKeysTableColumns(t: ReturnType<typeof useTranslations>): Co
|
|||||||
return <SetupKeyActionCell setupKey={row.original} />;
|
return <SetupKeyActionCell setupKey={row.original} />;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
@@ -185,10 +193,8 @@ export default function SetupKeysTable({
|
|||||||
// only offers groups that actually appear in the table.
|
// only offers groups that actually appear in the table.
|
||||||
const tableGroups = useMemo<Group[]>(
|
const tableGroups = useMemo<Group[]>(
|
||||||
() =>
|
() =>
|
||||||
(uniqBy(
|
(uniqBy(setupKeys?.flatMap((k) => k.groups || []), "name") as Group[]) ||
|
||||||
setupKeys?.flatMap((k) => k.groups || []),
|
[],
|
||||||
"name",
|
|
||||||
) as Group[]) || [],
|
|
||||||
[setupKeys],
|
[setupKeys],
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -197,27 +203,27 @@ export default function SetupKeysTable({
|
|||||||
// re-route it through the consolidated filter UI.
|
// re-route it through the consolidated filter UI.
|
||||||
const statusOptions = useMemo<RadioOption<boolean | undefined>[]>(
|
const statusOptions = useMemo<RadioOption<boolean | undefined>[]>(
|
||||||
() => [
|
() => [
|
||||||
{ value: undefined, label: "All", dotClass: "bg-nb-gray-500" },
|
{ value: undefined, label: t("all"), dotClass: "bg-nb-gray-500" },
|
||||||
{ value: true, label: "Valid", dotClass: "bg-green-500" },
|
{ value: true, label: t("active"), dotClass: "bg-green-500" },
|
||||||
{ value: false, label: "Expired", dotClass: "bg-nb-gray-700" },
|
{ value: false, label: t("expired"), dotClass: "bg-nb-gray-700" },
|
||||||
],
|
],
|
||||||
[],
|
[t],
|
||||||
);
|
);
|
||||||
|
|
||||||
const usageOptions = useMemo<RadioOption<string | undefined>[]>(
|
const usageOptions = useMemo<RadioOption<string | undefined>[]>(
|
||||||
() => [
|
() => [
|
||||||
{ value: undefined, label: "All" },
|
{ value: undefined, label: t("all") },
|
||||||
{ value: "one-off", label: "One-off" },
|
{ value: "one-off", label: t("oneOff") },
|
||||||
{ value: "reusable", label: "Reusable" },
|
{ value: "reusable", label: t("reusable") },
|
||||||
],
|
],
|
||||||
[],
|
[t],
|
||||||
);
|
);
|
||||||
|
|
||||||
const filterDefs = useMemo<TableFilterDef[]>(
|
const filterDefs = useMemo<TableFilterDef[]>(
|
||||||
() => [
|
() => [
|
||||||
{
|
{
|
||||||
id: "valid",
|
id: "valid",
|
||||||
label: "Status",
|
label: t("status"),
|
||||||
renderPicker: (p) => (
|
renderPicker: (p) => (
|
||||||
<RadioPicker
|
<RadioPicker
|
||||||
value={p.value as boolean | undefined}
|
value={p.value as boolean | undefined}
|
||||||
@@ -231,7 +237,7 @@ export default function SetupKeysTable({
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "type",
|
id: "type",
|
||||||
label: "Usage",
|
label: t("usage"),
|
||||||
renderPicker: (p) => (
|
renderPicker: (p) => (
|
||||||
<RadioPicker
|
<RadioPicker
|
||||||
value={p.value as string | undefined}
|
value={p.value as string | undefined}
|
||||||
@@ -245,7 +251,7 @@ export default function SetupKeysTable({
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "group_names",
|
id: "group_names",
|
||||||
label: "Groups",
|
label: t("groups"),
|
||||||
renderPicker: (p) => (
|
renderPicker: (p) => (
|
||||||
<GroupsPicker
|
<GroupsPicker
|
||||||
value={p.value as string[] | undefined}
|
value={p.value as string[] | undefined}
|
||||||
@@ -257,7 +263,7 @@ export default function SetupKeysTable({
|
|||||||
formatChip: (v) => formatGroupsChip(v as string[] | undefined),
|
formatChip: (v) => formatGroupsChip(v as string[] | undefined),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
[statusOptions, usageOptions, tableGroups],
|
[statusOptions, usageOptions, tableGroups, t],
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -273,14 +279,14 @@ export default function SetupKeysTable({
|
|||||||
inset={false}
|
inset={false}
|
||||||
minimal={isGroupPage}
|
minimal={isGroupPage}
|
||||||
keepStateInLocalStorage={!isGroupPage}
|
keepStateInLocalStorage={!isGroupPage}
|
||||||
text={"Setup Keys"}
|
text={t("title")}
|
||||||
sorting={sorting}
|
sorting={sorting}
|
||||||
setSorting={setSorting}
|
setSorting={setSorting}
|
||||||
initialPageSize={25}
|
initialPageSize={25}
|
||||||
showResetFilterButton={false}
|
showResetFilterButton={false}
|
||||||
columns={SetupKeysTableColumns(t)}
|
columns={SetupKeysTableColumns(t)}
|
||||||
data={setupKeys}
|
data={setupKeys}
|
||||||
searchPlaceholder={"Search by name, type or group..."}
|
searchPlaceholder={t("searchPlaceholder")}
|
||||||
columnVisibility={{
|
columnVisibility={{
|
||||||
valid: false,
|
valid: false,
|
||||||
group_strings: false,
|
group_strings: false,
|
||||||
@@ -295,10 +301,8 @@ export default function SetupKeysTable({
|
|||||||
<NoResults
|
<NoResults
|
||||||
icon={<SetupKeysIcon className={"fill-nb-gray-200"} size={20} />}
|
icon={<SetupKeysIcon className={"fill-nb-gray-200"} size={20} />}
|
||||||
className={"py-4"}
|
className={"py-4"}
|
||||||
title={"This group is not used within any setup keys yet"}
|
title={t("groupNotUsedTitle")}
|
||||||
description={
|
description={t("groupNotUsedDescription")}
|
||||||
"Assign this group when creating a new setup key to see them listed here."
|
|
||||||
}
|
|
||||||
>
|
>
|
||||||
<Button
|
<Button
|
||||||
variant={"primary"}
|
variant={"primary"}
|
||||||
@@ -307,7 +311,7 @@ export default function SetupKeysTable({
|
|||||||
disabled={!permission.setup_keys.create}
|
disabled={!permission.setup_keys.create}
|
||||||
>
|
>
|
||||||
<PlusCircle size={16} />
|
<PlusCircle size={16} />
|
||||||
Create Key
|
{t("createSetupKey")}
|
||||||
</Button>
|
</Button>
|
||||||
</NoResults>
|
</NoResults>
|
||||||
) : (
|
) : (
|
||||||
@@ -321,10 +325,8 @@ export default function SetupKeysTable({
|
|||||||
size={"large"}
|
size={"large"}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
title={"Create Setup Key"}
|
title={t("createSetupKey")}
|
||||||
description={
|
description={t("getStartedDescription")}
|
||||||
"Add a setup key to register new machines in your network. The key links machines to your account during initial setup."
|
|
||||||
}
|
|
||||||
button={
|
button={
|
||||||
<Button
|
<Button
|
||||||
variant={"primary"}
|
variant={"primary"}
|
||||||
@@ -334,19 +336,19 @@ export default function SetupKeysTable({
|
|||||||
data-testid="open-create-setup-key"
|
data-testid="open-create-setup-key"
|
||||||
>
|
>
|
||||||
<PlusCircle size={16} />
|
<PlusCircle size={16} />
|
||||||
Create Key
|
{t("createSetupKey")}
|
||||||
</Button>
|
</Button>
|
||||||
}
|
}
|
||||||
learnMore={
|
learnMore={
|
||||||
<>
|
<>
|
||||||
Learn more about
|
{t("learnMore")}
|
||||||
<InlineLink
|
<InlineLink
|
||||||
href={
|
href={
|
||||||
"https://docs.netbird.io/how-to/register-machines-using-setup-keys"
|
"https://docs.netbird.io/how-to/register-machines-using-setup-keys"
|
||||||
}
|
}
|
||||||
target={"_blank"}
|
target={"_blank"}
|
||||||
>
|
>
|
||||||
Setup Keys
|
{t("title")}
|
||||||
<ExternalLinkIcon size={12} />
|
<ExternalLinkIcon size={12} />
|
||||||
</InlineLink>
|
</InlineLink>
|
||||||
</>
|
</>
|
||||||
@@ -365,7 +367,7 @@ export default function SetupKeysTable({
|
|||||||
data-testid="open-create-setup-key"
|
data-testid="open-create-setup-key"
|
||||||
>
|
>
|
||||||
<PlusCircle size={16} />
|
<PlusCircle size={16} />
|
||||||
Create Key
|
{t("createSetupKey")}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
|
|||||||
Reference in New Issue
Block a user