fix(i18n): localize RouteModal, SetupKeyModal, CreateAccessTokenModal

- RouteModal: routeType, networkRange, domains, distributionGroups, metric
- SetupKeyModal: createTitle, nameHelp, usageLimitHelp, expiresIn
- CreateAccessTokenModal: tokenName, tokenNameHelp, tokenExpiresIn
- Add corresponding keys to en.ts and zh.ts
This commit is contained in:
sakuradairong
2026-06-23 21:38:18 +08:00
parent 312c32f6ea
commit dc9c9d9735
21 changed files with 1466 additions and 45 deletions

View File

@@ -0,0 +1,85 @@
---
name: gitnexus-cli
description: "Use when the user needs to run GitNexus CLI commands like analyze/index a repo, check status, clean the index, generate a wiki, or list indexed repos. Examples: \"Index this repo\", \"Reanalyze the codebase\", \"Generate a wiki\""
---
# GitNexus CLI Commands
Commands below use `node .gitnexus/run.cjs <command>` — the project-local runner `gitnexus analyze` drops next to the index. It auto-selects an available runner at call time (global `gitnexus`, else `pnpm dlx`, else `npx`), so no package-manager assumption and no global install is required.
> **Not analyzed yet, or `node .gitnexus/run.cjs` reports `Cannot find module`** (the gitignored runner is absent — e.g. a fresh clone or `git clean`)? (Re)generate it with `npx gitnexus analyze` from the project root. On **npm 11.x**, if `npx` crashes during install (`node.target is null`), install once with `npm i -g gitnexus` (then `gitnexus analyze`) or use `pnpm --allow-build=@ladybugdb/core --allow-build=gitnexus --allow-build=tree-sitter dlx gitnexus@latest analyze`. See [#1939](https://github.com/abhigyanpatwari/GitNexus/issues/1939).
## Commands
### analyze — Build or refresh the index
```bash
node .gitnexus/run.cjs analyze
```
Run from the project root. This parses all source files, builds the knowledge graph, writes it to `.gitnexus/`, and generates CLAUDE.md / AGENTS.md context files.
| Flag | Effect |
| -------------- | ---------------------------------------------------------------- |
| `--force` | Force full re-index even if up to date |
| `--embeddings` | Enable embedding generation for semantic search (off by default) |
| `--drop-embeddings` | Drop existing embeddings on rebuild. By default, an `analyze` without `--embeddings` preserves them. |
**When to run:** First time in a project, after major code changes, or when `gitnexus://repo/{name}/context` reports the index is stale. In Claude Code, a PostToolUse hook detects staleness after `git commit` and `git merge` and notifies the agent to run `analyze` — the hook does not run analyze itself, to avoid blocking the agent for up to 120s and risking KuzuDB corruption on timeout.
### status — Check index freshness
```bash
node .gitnexus/run.cjs status
```
Shows whether the current repo has a GitNexus index, when it was last updated, and symbol/relationship counts. Use this to check if re-indexing is needed.
### clean — Delete the index
```bash
node .gitnexus/run.cjs clean
```
Deletes the `.gitnexus/` directory and unregisters the repo from the global registry. Use before re-indexing if the index is corrupt or after removing GitNexus from a project.
| Flag | Effect |
| --------- | ------------------------------------------------- |
| `--force` | Skip confirmation prompt |
| `--all` | Clean all indexed repos, not just the current one |
### wiki — Generate documentation from the graph
```bash
node .gitnexus/run.cjs wiki
```
Generates repository documentation from the knowledge graph using an LLM. Requires an API key (saved to `~/.gitnexus/config.json` on first use).
| Flag | Effect |
| ------------------- | ----------------------------------------- |
| `--force` | Force full regeneration |
| `--model <model>` | LLM model (default: minimax/minimax-m2.5) |
| `--base-url <url>` | LLM API base URL |
| `--api-key <key>` | LLM API key |
| `--concurrency <n>` | Parallel LLM calls (default: 3) |
| `--gist` | Publish wiki as a public GitHub Gist |
### list — Show all indexed repos
```bash
node .gitnexus/run.cjs list
```
Lists all repositories registered in `~/.gitnexus/registry.json`. The MCP `list_repos` tool provides the same information.
## After Indexing
1. **Read `gitnexus://repo/{name}/context`** to verify the index loaded
2. Use the other GitNexus skills (`exploring`, `debugging`, `impact-analysis`, `refactoring`) for your task
## Troubleshooting
- **"Not inside a git repository"**: Run from a directory inside a git repo
- **Index is stale after re-analyzing**: Restart Claude Code to reload the MCP server
- **Embeddings slow**: Omit `--embeddings` (it's off by default) or set `OPENAI_API_KEY` for faster API-based embedding

View File

@@ -0,0 +1,89 @@
---
name: gitnexus-debugging
description: "Use when the user is debugging a bug, tracing an error, or asking why something fails. Examples: \"Why is X failing?\", \"Where does this error come from?\", \"Trace this bug\""
---
# Debugging with GitNexus
## When to Use
- "Why is this function failing?"
- "Trace where this error comes from"
- "Who calls this method?"
- "This endpoint returns 500"
- Investigating bugs, errors, or unexpected behavior
## Workflow
```
1. query({query: "<error or symptom>"}) → Find related execution flows
2. context({name: "<suspect>"}) → See callers/callees/processes
3. READ gitnexus://repo/{name}/process/{name} → Trace execution flow
4. cypher({query: "MATCH path..."}) → Custom traces if needed
```
> If "Index is stale" → run `node .gitnexus/run.cjs analyze` in terminal.
## Checklist
```
- [ ] Understand the symptom (error message, unexpected behavior)
- [ ] query for error text or related code
- [ ] Identify the suspect function from returned processes
- [ ] context to see callers and callees
- [ ] Trace execution flow via process resource if applicable
- [ ] cypher for custom call chain traces if needed
- [ ] Read source files to confirm root cause
```
## Debugging Patterns
| Symptom | GitNexus Approach |
| -------------------- | ---------------------------------------------------------- |
| Error message | `query` for error text → `context` on throw sites |
| Wrong return value | `context` on the function → trace callees for data flow |
| Intermittent failure | `context` → look for external calls, async deps |
| Performance issue | `context` → find symbols with many callers (hot paths) |
| Recent regression | `detect_changes` to see what your changes affect |
## Tools
**query** — find code related to error:
```
query({query: "payment validation error"})
→ Processes: CheckoutFlow, ErrorHandling
→ Symbols: validatePayment, handlePaymentError, PaymentException
```
**context** — full context for a suspect:
```
context({name: "validatePayment"})
→ Incoming calls: processCheckout, webhookHandler
→ Outgoing calls: verifyCard, fetchRates (external API!)
→ Processes: CheckoutFlow (step 3/7)
```
**cypher** — custom call chain traces:
```cypher
MATCH path = (a)-[:CodeRelation {type: 'CALLS'}*1..2]->(b:Function {name: "validatePayment"})
RETURN [n IN nodes(path) | n.name] AS chain
```
## Example: "Payment endpoint returns 500 intermittently"
```
1. query({query: "payment error handling"})
→ Processes: CheckoutFlow, ErrorHandling
→ Symbols: validatePayment, handlePaymentError
2. context({name: "validatePayment"})
→ Outgoing calls: verifyCard, fetchRates (external API!)
3. READ gitnexus://repo/my-app/process/CheckoutFlow
→ Step 3: validatePayment → calls fetchRates (external)
4. Root cause: fetchRates calls external API without proper timeout
```

View File

@@ -0,0 +1,78 @@
---
name: gitnexus-exploring
description: "Use when the user asks how code works, wants to understand architecture, trace execution flows, or explore unfamiliar parts of the codebase. Examples: \"How does X work?\", \"What calls this function?\", \"Show me the auth flow\""
---
# Exploring Codebases with GitNexus
## When to Use
- "How does authentication work?"
- "What's the project structure?"
- "Show me the main components"
- "Where is the database logic?"
- Understanding code you haven't seen before
## Workflow
```
1. READ gitnexus://repos → Discover indexed repos
2. READ gitnexus://repo/{name}/context → Codebase overview, check staleness
3. query({query: "<what you want to understand>"}) → Find related execution flows
4. context({name: "<symbol>"}) → Deep dive on specific symbol
5. READ gitnexus://repo/{name}/process/{name} → Trace full execution flow
```
> If step 2 says "Index is stale" → run `node .gitnexus/run.cjs analyze` in terminal.
## Checklist
```
- [ ] READ gitnexus://repo/{name}/context
- [ ] query for the concept you want to understand
- [ ] Review returned processes (execution flows)
- [ ] context on key symbols for callers/callees
- [ ] READ process resource for full execution traces
- [ ] Read source files for implementation details
```
## Resources
| Resource | What you get |
| --------------------------------------- | ------------------------------------------------------- |
| `gitnexus://repo/{name}/context` | Stats, staleness warning (~150 tokens) |
| `gitnexus://repo/{name}/clusters` | All functional areas with cohesion scores (~300 tokens) |
| `gitnexus://repo/{name}/cluster/{name}` | Area members with file paths (~500 tokens) |
| `gitnexus://repo/{name}/process/{name}` | Step-by-step execution trace (~200 tokens) |
## Tools
**query** — find execution flows related to a concept:
```
query({query: "payment processing"})
→ Processes: CheckoutFlow, RefundFlow, WebhookHandler
→ Symbols grouped by flow with file locations
```
**context** — 360-degree view of a symbol:
```
context({name: "validateUser"})
→ Incoming calls: loginHandler, apiMiddleware
→ Outgoing calls: checkToken, getUserById
→ Processes: LoginFlow (step 2/5), TokenRefresh (step 1/3)
```
## Example: "How does payment processing work?"
```
1. READ gitnexus://repo/my-app/context → 918 symbols, 45 processes
2. query({query: "payment processing"})
→ CheckoutFlow: processPayment → validateCard → chargeStripe
→ RefundFlow: initiateRefund → calculateRefund → processRefund
3. context({name: "processPayment"})
→ Incoming: checkoutHandler, webhookHandler
→ Outgoing: validateCard, chargeStripe, saveTransaction
4. Read src/payments/processor.ts for implementation details
```

View File

@@ -0,0 +1,64 @@
---
name: gitnexus-guide
description: "Use when the user asks about GitNexus itself — available tools, how to query the knowledge graph, MCP resources, graph schema, or workflow reference. Examples: \"What GitNexus tools are available?\", \"How do I use GitNexus?\""
---
# GitNexus Guide
Quick reference for all GitNexus MCP tools, resources, and the knowledge graph schema.
## Always Start Here
For any task involving code understanding, debugging, impact analysis, or refactoring:
1. **Read `gitnexus://repo/{name}/context`** — codebase overview + check index freshness
2. **Match your task to a skill below** and **read that skill file**
3. **Follow the skill's workflow and checklist**
> If step 1 warns the index is stale, run `node .gitnexus/run.cjs analyze` in the terminal first.
## Skills
| Task | Skill to read |
| -------------------------------------------- | ------------------- |
| Understand architecture / "How does X work?" | `gitnexus-exploring` |
| Blast radius / "What breaks if I change X?" | `gitnexus-impact-analysis` |
| Trace bugs / "Why is X failing?" | `gitnexus-debugging` |
| Rename / extract / split / refactor | `gitnexus-refactoring` |
| Tools, resources, schema reference | `gitnexus-guide` (this file) |
| Index, status, clean, wiki CLI commands | `gitnexus-cli` |
## Tools Reference
| Tool | What it gives you |
| ---------------- | ------------------------------------------------------------------------ |
| `query` | Process-grouped code intelligence — execution flows related to a concept |
| `context` | 360-degree symbol view — categorized refs, processes it participates in |
| `impact` | Symbol blast radius — what breaks at depth 1/2/3 with confidence |
| `detect_changes` | Git-diff impact — what do your current changes affect |
| `rename` | Multi-file coordinated rename with confidence-tagged edits |
| `cypher` | Raw graph queries (read `gitnexus://repo/{name}/schema` first) |
| `list_repos` | Discover indexed repos |
## Resources Reference
Lightweight reads (~100-500 tokens) for navigation:
| Resource | Content |
| ---------------------------------------------- | ----------------------------------------- |
| `gitnexus://repo/{name}/context` | Stats, staleness check |
| `gitnexus://repo/{name}/clusters` | All functional areas with cohesion scores |
| `gitnexus://repo/{name}/cluster/{clusterName}` | Area members |
| `gitnexus://repo/{name}/processes` | All execution flows |
| `gitnexus://repo/{name}/process/{processName}` | Step-by-step trace |
| `gitnexus://repo/{name}/schema` | Graph schema for Cypher |
## Graph Schema
**Nodes:** File, Function, Class, Interface, Method, Community, Process
**Edges (via CodeRelation.type):** CALLS, IMPORTS, EXTENDS, IMPLEMENTS, DEFINES, MEMBER_OF, STEP_IN_PROCESS
```cypher
MATCH (caller)-[:CodeRelation {type: 'CALLS'}]->(f:Function {name: "myFunc"})
RETURN caller.name, caller.filePath
```

View File

@@ -0,0 +1,97 @@
---
name: gitnexus-impact-analysis
description: "Use when the user wants to know what will break if they change something, or needs safety analysis before editing code. Examples: \"Is it safe to change X?\", \"What depends on this?\", \"What will break?\""
---
# Impact Analysis with GitNexus
## When to Use
- "Is it safe to change this function?"
- "What will break if I modify X?"
- "Show me the blast radius"
- "Who uses this code?"
- Before making non-trivial code changes
- Before committing — to understand what your changes affect
## Workflow
```
1. impact({target: "X", direction: "upstream"}) → What depends on this
2. READ gitnexus://repo/{name}/processes → Check affected execution flows
3. detect_changes() → Map current git changes to affected flows
4. Assess risk and report to user
```
> If "Index is stale" → run `node .gitnexus/run.cjs analyze` in terminal.
## Checklist
```
- [ ] impact({target, direction: "upstream"}) to find dependents
- [ ] Review d=1 items first (these WILL BREAK)
- [ ] Check high-confidence (>0.8) dependencies
- [ ] READ processes to check affected execution flows
- [ ] detect_changes() for pre-commit check
- [ ] Assess risk level and report to user
```
## Understanding Output
| Depth | Risk Level | Meaning |
| ----- | ---------------- | ------------------------ |
| d=1 | **WILL BREAK** | Direct callers/importers |
| d=2 | LIKELY AFFECTED | Indirect dependencies |
| d=3 | MAY NEED TESTING | Transitive effects |
## Risk Assessment
| Affected | Risk |
| ------------------------------ | -------- |
| <5 symbols, few processes | LOW |
| 5-15 symbols, 2-5 processes | MEDIUM |
| >15 symbols or many processes | HIGH |
| Critical path (auth, payments) | CRITICAL |
## Tools
**impact** — the primary tool for symbol blast radius:
```
impact({
target: "validateUser",
direction: "upstream",
minConfidence: 0.8,
maxDepth: 3
})
→ d=1 (WILL BREAK):
- loginHandler (src/auth/login.ts:42) [CALLS, 100%]
- apiMiddleware (src/api/middleware.ts:15) [CALLS, 100%]
→ d=2 (LIKELY AFFECTED):
- authRouter (src/routes/auth.ts:22) [CALLS, 95%]
```
**detect_changes** — git-diff based impact analysis:
```
detect_changes({scope: "staged"})
→ Changed: 5 symbols in 3 files
→ Affected: LoginFlow, TokenRefresh, APIMiddlewarePipeline
→ Risk: MEDIUM
```
## Example: "What breaks if I change validateUser?"
```
1. impact({target: "validateUser", direction: "upstream"})
→ d=1: loginHandler, apiMiddleware (WILL BREAK)
→ d=2: authRouter, sessionManager (LIKELY AFFECTED)
2. READ gitnexus://repo/my-app/processes
→ LoginFlow and TokenRefresh touch validateUser
3. Risk: 2 direct callers, 2 processes = MEDIUM
```

View File

@@ -0,0 +1,121 @@
---
name: gitnexus-refactoring
description: "Use when the user wants to rename, extract, split, move, or restructure code safely. Examples: \"Rename this function\", \"Extract this into a module\", \"Refactor this class\", \"Move this to a separate file\""
---
# Refactoring with GitNexus
## When to Use
- "Rename this function safely"
- "Extract this into a module"
- "Split this service"
- "Move this to a new file"
- Any task involving renaming, extracting, splitting, or restructuring code
## Workflow
```
1. impact({target: "X", direction: "upstream"}) → Map all dependents
2. query({query: "X"}) → Find execution flows involving X
3. context({name: "X"}) → See all incoming/outgoing refs
4. Plan update order: interfaces → implementations → callers → tests
```
> If "Index is stale" → run `node .gitnexus/run.cjs analyze` in terminal.
## Checklists
### Rename Symbol
```
- [ ] rename({symbol_name: "oldName", new_name: "newName", dry_run: true}) — preview all edits
- [ ] Review graph edits (high confidence) and ast_search edits (review carefully)
- [ ] If satisfied: rename({..., dry_run: false}) — apply edits
- [ ] detect_changes() — verify only expected files changed
- [ ] Run tests for affected processes
```
### Extract Module
```
- [ ] context({name: target}) — see all incoming/outgoing refs
- [ ] impact({target, direction: "upstream"}) — find all external callers
- [ ] Define new module interface
- [ ] Extract code, update imports
- [ ] detect_changes() — verify affected scope
- [ ] Run tests for affected processes
```
### Split Function/Service
```
- [ ] context({name: target}) — understand all callees
- [ ] Group callees by responsibility
- [ ] impact({target, direction: "upstream"}) — map callers to update
- [ ] Create new functions/services
- [ ] Update callers
- [ ] detect_changes() — verify affected scope
- [ ] Run tests for affected processes
```
## Tools
**rename** — automated multi-file rename:
```
rename({symbol_name: "validateUser", new_name: "authenticateUser", dry_run: true})
→ 12 edits across 8 files
→ 10 graph edits (high confidence), 2 ast_search edits (review)
→ Changes: [{file_path, edits: [{line, old_text, new_text, confidence}]}]
```
**impact** — map all dependents first:
```
impact({target: "validateUser", direction: "upstream"})
→ d=1: loginHandler, apiMiddleware, testUtils
→ Affected Processes: LoginFlow, TokenRefresh
```
**detect_changes** — verify your changes after refactoring:
```
detect_changes({scope: "all"})
→ Changed: 8 files, 12 symbols
→ Affected processes: LoginFlow, TokenRefresh
→ Risk: MEDIUM
```
**cypher** — custom reference queries:
```cypher
MATCH (caller)-[:CodeRelation {type: 'CALLS'}]->(f:Function {name: "validateUser"})
RETURN caller.name, caller.filePath ORDER BY caller.filePath
```
## Risk Rules
| Risk Factor | Mitigation |
| ------------------- | ----------------------------------------- |
| Many callers (>5) | Use rename for automated updates |
| Cross-area refs | Use detect_changes after to verify scope |
| String/dynamic refs | query to find them |
| External/public API | Version and deprecate properly |
## Example: Rename `validateUser` to `authenticateUser`
```
1. rename({symbol_name: "validateUser", new_name: "authenticateUser", dry_run: true})
→ 12 edits: 10 graph (safe), 2 ast_search (review)
→ Files: validator.ts, login.ts, middleware.ts, config.json...
2. Review ast_search edits (config.json: dynamic reference!)
3. rename({symbol_name: "validateUser", new_name: "authenticateUser", dry_run: false})
→ Applied 12 edits across 8 files
4. detect_changes({scope: "all"})
→ Affected: LoginFlow, TokenRefresh
→ Risk: MEDIUM — run tests for these flows
```

377
AGENTS.md Normal file
View File

@@ -0,0 +1,377 @@
# 仓库指南
## 项目概述
NetBird Dashboard 是 NetBird 管理服务的 Web 界面。这是一个 Next.js 应用程序,为 NetBird 网络提供网络管理、对等节点监控、访问控制和配置功能。
**在线版本:** https://app.netbird.io/
**源代码:** https://github.com/netbirdio/dashboard
## 架构与数据流
### 技术栈
- **框架:** Next.js 13+ 使用 App Router
- **语言:** TypeScript
- **样式:** Tailwind CSS + shadcn/ui 组件
- **状态管理:** React Context + SWR 用于服务器状态
- **认证:** OIDC 通过 @axa-fr/react-oidc
- **国际化:** next-intl
- **测试:** Cypress (E2E)
- **部署:** Docker + Nginx
### 高级结构
```
src/
├── app/ # Next.js App Router 页面
│ ├── (dashboard)/ # 主仪表板路由(分组布局)
│ ├── (remote-access)/ # 远程访问路由
│ ├── install/ # 安装向导
│ ├── invite/ # 用户邀请流程
│ └── setup/ # 初始设置流程
├── assets/ # 静态资源(图标、图片、字体)
├── auth/ # OIDC 认证组件
├── components/ # 共享 UI 组件(基于 shadcn/ui
├── contexts/ # React Context 提供者
├── hooks/ # 自定义 React 钩子
├── i18n/ # 国际化配置和消息
├── interfaces/ # TypeScript 类型定义
├── layouts/ # 布局组件
├── modules/ # 功能模块(领域特定)
└── utils/ # 工具函数
```
### 数据流
1. **认证:** OIDC 提供者处理认证 → 令牌存储在内存中
2. **API 调用:** `useFetchApi` 钩子 → SWR → OIDC 请求 → 管理 API
3. **状态:** 服务器状态通过 SWR 缓存UI 状态通过 React Context
4. **渲染:** 默认使用服务器组件,需要时使用客户端组件
## 关键目录
### `src/app/` - 页面和路由
- 使用 Next.js App Router 和路由分组
- `(dashboard)/` 包含主要应用页面和共享布局
- 每个路由有 `page.tsx` 和可选的 `layout.tsx`
- 通过 `error/page.tsx` 实现错误边界
### `src/modules/` - 功能模块
按功能组织的领域特定组件:
- `peers/` - 对等节点管理组件
- `networks/` - 网络配置
- `access-control/` - ACL 策略
- `dns/` - DNS 管理
- `routes/` - 网络路由
- `users/` - 用户管理
- `groups/` - 分组管理
- `setup-keys/` - 设置密钥管理
- `activity/` - 活动日志
- `settings/` - 账户设置
### `src/components/` - 共享 UI 组件
基于 shadcn/ui 构建,具有自定义变体:
- `Input.tsx` - 带验证的表单输入
- `Select.tsx` - 下拉选择
- `Dialog.tsx` - 模态对话框
- `Table.tsx` - 数据表格
- `Button.tsx` - 操作按钮
- `Badge.tsx` - 状态徽章
- `Tooltip.tsx` - 信息提示
### `src/contexts/` - 状态提供者
全局状态的 React Context 提供者:
- `ApplicationProvider.tsx` - 应用级配置
- `PeersProvider.tsx` - 对等节点数据
- `GroupsProvider.tsx` - 分组数据
- `RoutesProvider.tsx` - 路由数据
- `PoliciesProvider.tsx` - ACL 策略
- `PermissionsProvider.tsx` - 用户权限
- `GlobalThemeProvider.tsx` - 主题管理
- `LocaleProvider.tsx` - 语言/区域设置
### `src/hooks/` - 自定义钩子
可复用的 React 钩子:
- `useLocalStorage.tsx` - 持久化本地存储
- `useDebounce.tsx` - 防抖值
- `useSearch.ts` - 搜索功能
- `useCopyToClipboard.ts` - 剪贴板操作
- `useElementSize.ts` - DOM 元素尺寸
- `useIntersectionObserver.ts` - 可见性检测
### `src/interfaces/` - 类型定义
领域模型的 TypeScript 接口:
- `Peer.ts` - 网络对等节点
- `Group.ts` - 对等节点分组
- `Route.ts` - 网络路由
- `Nameserver.ts` - DNS 名称服务器
- `Account.ts` - 用户账户
- `SetupKey.ts` - 设置密钥
- `AccessToken.ts` - API 访问令牌
### `src/utils/` - 工具函数
辅助函数:
- `api.tsx` - 集成 SWR 的 API 客户端
- `helpers.ts` - 通用工具cn, randomString 等)
- `config.ts` - 配置加载器
- `ip.ts` - IP 地址工具
- `wireguard.ts` - WireGuard 辅助函数
- `version.ts` - 版本比较
## 开发命令
```bash
# 安装依赖
npm install
# 启动开发服务器(端口 3000
npm run dev
# 使用 Turbopack 启动(更快)
npm run turbo
# 构建生产版本
npm run build
# 启动生产服务器
npm start
# 运行代码检查
npm run lint
# 打开 Cypress 测试运行器
npm run cypress:open
# 复制 OIDC 服务工作者(认证必需)
npm run copy
npm run copytrusted
```
## 代码规范和常见模式
### 组件模式
```tsx
// 使用 shadcn/ui 和 class-variance-authority 实现变体
import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "@utils/helpers";
const buttonVariants = cva("base-classes", {
variants: {
variant: {
default: "default-classes",
destructive: "destructive-classes",
},
},
});
interface ButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof buttonVariants> {}
export function Button({ className, variant, ...props }: ButtonProps) {
return (
<button
className={cn(buttonVariants({ variant }), className)}
{...props}
/>
);
}
```
### Context 提供者模式
```tsx
import React, { useMemo } from "react";
import useFetchApi from "@utils/api";
const DataContext = React.createContext({} as DataType);
export default function DataProvider({ children }: { children: React.ReactNode }) {
const { data, isLoading } = useFetchApi<Data[]>("/endpoint");
const value = useMemo(() => ({ data, isLoading }), [data, isLoading]);
return (
<DataContext.Provider value={value}>
{children}
</DataContext.Provider>
);
}
export const useData = () => React.useContext(DataContext);
```
### API 钩子模式
```tsx
import useFetchApi from "@utils/api";
// GET 请求使用 SWR
const { data, isLoading, error } = useFetchApi<Data[]>("/endpoint");
// POST/PUT/DELETE 请求
const { mutate } = useFetchApi("/endpoint", { method: "POST" });
```
### 样式模式
```tsx
import { cn } from "@utils/helpers";
// 合并 Tailwind 类
<div className={cn(
"base-classes",
condition && "conditional-classes",
className
)} />
```
### 导入顺序
`simple-import-sort` ESLint 插件强制执行:
1. 副作用导入 (`import "polyfill"`)
2. 外部包 (`import React from "react"`)
3. 内部别名 (`import { Button } from "@/components"`)
4. 相对导入 (`import { useData } from "./context"`)
### 文件命名
- **组件:** PascalCase (`PeerTable.tsx`, `GroupSelector.tsx`)
- **钩子:** camelCase 带 `use` 前缀 (`useLocalStorage.tsx`)
- **工具函数:** camelCase (`helpers.ts`, `api.tsx`)
- **接口:** PascalCase (`Peer.ts`, `Group.ts`)
- **页面:** `page.tsx`Next.js App Router 要求)
- **布局:** `layout.tsx`Next.js App Router 要求)
## 重要文件
### 入口点
- `src/app/layout.tsx` - 根布局(提供者、字体、元数据)
- `src/app/(dashboard)/layout.tsx` - 仪表板布局(导航、认证)
- `src/app/page.tsx` - 首页重定向
### 配置文件
- `next.config.js` - Next.js 配置
- `tailwind.config.ts` - Tailwind CSS 配置
- `components.json` - shadcn/ui 配置
- `config.json` - 应用配置API 端点、认证)
- `.eslintrc.json` - ESLint 规则
- `tsconfig.json` - TypeScript 配置
### 关键工具
- `src/utils/api.tsx` - API 客户端SWR + OIDC
- `src/utils/config.ts` - 配置加载器
- `src/utils/helpers.ts` - 共享工具
- `src/auth/OIDCProvider.tsx` - 认证提供者
## 运行时/工具偏好
### 必需环境
- Node.js 18+(推荐 LTS
- npm包管理器
### 本地开发设置
1. 克隆仓库
2. 创建 `.local-config.json` 覆盖 `config.json` 中的值
3. 运行 `npm install`
4. 运行 `npm run copy`(复制 OIDC 服务工作者)
5. 运行 `npm run dev`
### Docker 部署
```bash
docker run -d --name netbird-dashboard \
-p 80:80 \
-e AUTH0_DOMAIN=<domain> \
-e AUTH0_CLIENT_ID=<client-id> \
-e AUTH0_AUDIENCE=<audience> \
-e NETBIRD_MGMT_API_ENDPOINT=<api-url> \
netbirdio/dashboard:main
```
### 配置
- `config.json` - 默认配置
- `.local-config.json` - 本地覆盖(已忽略)
- Docker 部署的环境变量
## 测试与质量保证
### E2E 测试Cypress
```bash
# 打开 Cypress UI
npm run cypress:open
# 无头运行测试
npx cypress run
```
**测试位置:** `cypress/e2e/`
**支持文件:** `cypress/support/`
**测试数据:** `cypress/fixtures/`
### 代码检查
```bash
npm run lint
```
ESLint 配置:
- `next/core-web-vitals` - Next.js 最佳实践
- `prettier` - 代码格式化
- `simple-import-sort` - 导入排序
### 类型检查
TypeScript 严格模式已启用。运行 `npx tsc --noEmit` 检查类型。
## 模块上下文
参见 `docs/contexts/` 获取特定模块的详细文档:
- `peers.md` - 对等节点管理模块
- `networks.md` - 网络配置模块
- `access-control.md` - ACL 策略模块
- `dns.md` - DNS 管理模块
- `api-client.md` - API 客户端模式
- `authentication.md` - OIDC 认证流程
## 常见问题与注意事项
### OIDC 服务工作者
安装后必须运行 `npm run copy` 将 OIDC 服务工作者复制到 `public/`
### 本地配置
创建 `.local-config.json` 覆盖 `config.json` 中的本地开发值。
### 静态导出
应用在 Next.js 配置中使用 `output: "export"` - 运行时无服务器端渲染。
### 暗黑模式
主题通过 `GlobalThemeProvider` 管理。使用 Tailwind 暗黑模式类。
### API 端点
所有 API 调用通过 `src/utils/api.tsx`,它处理:
- OIDC 令牌注入
- 令牌刷新
- 错误处理
- SWR 缓存
## 快速参考
### 添加新页面
1. 创建 `src/app/(dashboard)/new-page/page.tsx`
2.`src/layouts/Navigation.tsx` 中添加导航
3. 如需要,在 `src/contexts/` 中创建上下文提供者
4.`src/interfaces/` 中添加类型
### 添加新组件
1.`src/components/`(共享)或 `src/modules/<feature>/`(功能特定)中创建
2. 遵循 shadcn/ui 模式并实现变体
3. 从组件文件导出
### 添加新 API 端点
1. 在组件或上下文中使用 `useFetchApi` 钩子
2.`src/interfaces/` 中添加 TypeScript 接口
3. 如果数据在组件间共享,创建上下文提供者
### 添加新模块
1.`src/modules/<feature>/` 中创建目录
2. 为该功能添加组件
3.`src/contexts/` 中创建上下文提供者
4.`src/app/(dashboard)/<feature>/` 中添加页面
5. 更新导航
---
*本文档由 AI 自动生成。随着代码库的发展而更新。*

43
CLAUDE.md Normal file
View File

@@ -0,0 +1,43 @@
<!-- gitnexus:start -->
# GitNexus — Code Intelligence
This project is indexed by GitNexus as **dashboard** (4993 symbols, 15422 relationships, 300 execution flows). Use the GitNexus MCP tools to understand code, assess impact, and navigate safely.
> Index stale? Run `node .gitnexus/run.cjs analyze` from the project root — it auto-selects an available runner. No `.gitnexus/run.cjs` yet? `npx gitnexus analyze` (npm 11 crash → `npm i -g gitnexus`; #1939).
## Always Do
- **MUST run impact analysis before editing any symbol.** Before modifying a function, class, or method, run `impact({target: "symbolName", direction: "upstream"})` and report the blast radius (direct callers, affected processes, risk level) to the user.
- **MUST run `detect_changes()` before committing** to verify your changes only affect expected symbols and execution flows. For regression review, compare against the default branch: `detect_changes({scope: "compare", base_ref: "main"})`.
- **MUST warn the user** if impact analysis returns HIGH or CRITICAL risk before proceeding with edits.
- When exploring unfamiliar code, use `query({query: "concept"})` to find execution flows instead of grepping. It returns process-grouped results ranked by relevance.
- When you need full context on a specific symbol — callers, callees, which execution flows it participates in — use `context({name: "symbolName"})`.
## Never Do
- NEVER edit a function, class, or method without first running `impact` on it.
- NEVER ignore HIGH or CRITICAL risk warnings from impact analysis.
- NEVER rename symbols with find-and-replace — use `rename` which understands the call graph.
- NEVER commit changes without running `detect_changes()` to check affected scope.
## Resources
| Resource | Use for |
|----------|---------|
| `gitnexus://repo/dashboard/context` | Codebase overview, check index freshness |
| `gitnexus://repo/dashboard/clusters` | All functional areas |
| `gitnexus://repo/dashboard/processes` | All execution flows |
| `gitnexus://repo/dashboard/process/{name}` | Step-by-step execution trace |
## CLI
| Task | Read this skill file |
|------|---------------------|
| Understand architecture / "How does X work?" | `.claude/skills/gitnexus/gitnexus-exploring/SKILL.md` |
| Blast radius / "What breaks if I change X?" | `.claude/skills/gitnexus/gitnexus-impact-analysis/SKILL.md` |
| Trace bugs / "Why is X failing?" | `.claude/skills/gitnexus/gitnexus-debugging/SKILL.md` |
| Rename / extract / split / refactor | `.claude/skills/gitnexus/gitnexus-refactoring/SKILL.md` |
| Tools, resources, schema reference | `.claude/skills/gitnexus/gitnexus-guide/SKILL.md` |
| Index, status, clean, wiki CLI commands | `.claude/skills/gitnexus/gitnexus-cli/SKILL.md` |
<!-- gitnexus:end -->

View File

@@ -0,0 +1,59 @@
# 访问控制模块
## 功能
管理访问控制策略ACL- 定义哪些对等节点可以通信。
## API 接口
- `GET /api/policies` - 列出策略
- `GET /api/policies/:id` - 获取策略详情
- `POST /api/policies` - 创建策略
- `PUT /api/policies/:id` - 更新策略
- `DELETE /api/policies/:id` - 删除策略
## 关键类型
```typescript
interface Policy {
id: string;
name: string;
description: string;
enabled: boolean;
rules: PolicyRule[];
// ... 更多字段
}
interface PolicyRule {
name: string;
sources: Group[];
destinations: Group[];
// ... 更多字段
}
```
## 文件路径
- `src/modules/access-control/` - UI 组件
- `src/contexts/PoliciesProvider.tsx` - 数据提供者
- `src/app/(dashboard)/access-control/page.tsx` - 页面组件
## 组件
- 策略列表表格
- 策略编辑器
- 规则配置
## 使用方法
```tsx
import { usePolicies } from "@/contexts/PoliciesProvider";
function MyComponent() {
const { policies, isLoading } = usePolicies();
// ...
}
```
## 命令
- 页面:`/access-control`
## 注意事项
- 策略按顺序评估
- 禁用的策略会被跳过
- 规则引用分组,而不是单个对等节点
- 可以向策略添加姿态检查

View File

@@ -0,0 +1,97 @@
# API Client
## Purpose
Centralized API client with SWR integration and OIDC authentication.
## File Path
- `src/utils/api.tsx`
## Key Exports
```typescript
// Main hook for API calls
export default function useFetchApi<T>(
url: string,
options?: RequestOptions
): SWRResponse<T, ErrorResponse>;
// Request options
type RequestOptions = {
key?: string;
signal?: AbortSignal;
origin?: string;
globalParams?: Params;
ignoreGlobalParams?: boolean;
refreshInterval?: number;
blob?: boolean;
shouldRetryOnError?: boolean;
};
// Error response type
export type ErrorResponse = {
code: number;
message: string;
};
// Query params type
export type Params = Record<string, string | number | boolean>;
```
## Usage Patterns
### GET Request
```tsx
const { data, isLoading, error } = useFetchApi<Peer[]>("/peers");
```
### GET with Refresh
```tsx
const { data } = useFetchApi<Peer[]>("/peers", {
refreshInterval: 5000, // Poll every 5 seconds
});
```
### POST/PUT/DELETE
```tsx
const { mutate } = useFetchApi("/peers", { method: "POST" });
// Or use direct apiRequest function
```
### Custom Key
```tsx
const { data } = useFetchApi("/peers", {
key: "my-custom-key",
});
```
## Implementation Details
### Authentication
- Uses OIDC tokens from `@axa-fr/react-oidc`
- Automatically injects Authorization header
- Handles token refresh
### Caching
- Uses SWR for caching and revalidation
- Global config in `ApplicationProvider`
- Supports refresh intervals
### Error Handling
- Returns structured `ErrorResponse`
- Integrates with `ErrorBoundary`
- Configurable retry behavior
### Configuration
- Base URL from `config.json` (`apiOrigin`)
- Can override with `origin` option
- Global params merged from `ApplicationContext`
## Dependencies
- `swr` - Data fetching and caching
- `@axa-fr/react-oidc` - OIDC authentication
- `react-jwt` - JWT token handling
## Gotchas
- Requires OIDC provider to be initialized
- Tokens stored in memory (not localStorage)
- Global params applied to all requests unless `ignoreGlobalParams: true`
- Use `shouldRetryOnError: false` to disable automatic retries

View File

@@ -0,0 +1,93 @@
# Authentication
## Purpose
OIDC-based authentication using Auth0 or compatible providers.
## File Paths
- `src/auth/OIDCProvider.tsx` - Main OIDC provider
- `src/auth/SecureProvider.tsx` - Protected route wrapper
- `src/auth/OIDCError.tsx` - Error display
- `src/auth/SessionLost.tsx` - Session lost handling
## Key Dependencies
- `@axa-fr/react-oidc` - OIDC client library
## Configuration
Configuration in `config.json`:
```json
{
"auth0Domain": "your-tenant.auth0.com",
"auth0ClientId": "your-client-id",
"auth0Audience": "your-audience",
"auth0Authority": "https://your-tenant.auth0.com",
"auth0RedirectUri": "http://localhost:3000",
"authScope": "openid profile email"
}
```
## Environment Variables (Docker)
- `AUTH0_DOMAIN` - Auth0 tenant domain
- `AUTH0_CLIENT_ID` - Auth0 application client ID
- `AUTH0_AUDIENCE` - API audience identifier
## Usage
### Protected Routes
```tsx
import SecureProvider from "@/auth/SecureProvider";
export default function Layout({ children }) {
return <SecureProvider>{children}</SecureProvider>;
}
```
### Access User Info
```tsx
import { useOidc, useOidcAccessToken, useOidcIdToken } from "@axa-fr/react-oidc";
function MyComponent() {
const { isAuthenticated, login, logout } = useOidc();
const { oidcAccessToken } = useOidcAccessToken();
const { oidcIdToken } = useOidcIdToken();
// ...
}
```
## Flow
1. User visits protected route
2. `SecureProvider` checks authentication
3. If not authenticated, redirects to Auth0 login
4. After login, Auth0 redirects back with tokens
5. Tokens stored in memory (not localStorage)
6. API calls use access token for authorization
## Service Worker
OIDC service worker must be copied to `public/`:
```bash
npm run copy
npm run copytrusted
```
This enables:
- Token refresh without page reload
- Secure token storage
- Silent authentication
## Local Development
Create `.local-config.json`:
```json
{
"auth0Domain": "localhost",
"auth0ClientId": "test-client-id",
"auth0Audience": "test-audience",
"auth0Authority": "http://localhost:9999",
"auth0RedirectUri": "http://localhost:3000"
}
```
## Gotchas
- Service worker file must be in `public/` directory
- Tokens are in-memory only (cleared on page refresh)
- CORS must be configured on Auth0 for local development
- Redirect URI must match Auth0 configuration exactly
- Silent authentication requires HTTPS in production

46
docs/contexts/dns.md Normal file
View File

@@ -0,0 +1,46 @@
# DNS 模块
## 功能
管理网络的 DNS 名称服务器和 DNS 设置。
## API 接口
- `GET /api/nameservers` - 列出名称服务器
- `GET /api/nameservers/:id` - 获取名称服务器详情
- `POST /api/nameservers` - 创建名称服务器
- `PUT /api/nameservers/:id` - 更新名称服务器
- `DELETE /api/nameservers/:id` - 删除名称服务器
## 关键类型
```typescript
interface Nameserver {
id: string;
name: string;
ip: string;
port: number;
groups: Group[];
// ... 更多字段
}
```
## 文件路径
- `src/modules/dns/` - UI 组件
- `src/interfaces/Nameserver.ts` - 类型定义
- `src/app/(dashboard)/dns/page.tsx` - 页面组件
## 组件
- 名称服务器列表表格
- 名称服务器编辑器
- 分组分配
## 使用方法
```tsx
// 通过自定义钩子或上下文访问
```
## 命令
- 页面:`/dns`
## 注意事项
- 名称服务器分配给分组
- 分组决定哪些对等节点使用哪些名称服务器
- 可以配置默认名称服务器

53
docs/contexts/networks.md Normal file
View File

@@ -0,0 +1,53 @@
# 网络模块
## 功能
配置和管理网络设置、路由和网络级策略。
## API 接口
- `GET /api/networks` - 列出网络
- `GET /api/networks/:id` - 获取网络详情
- `POST /api/networks` - 创建网络
- `PUT /api/networks/:id` - 更新网络
- `DELETE /api/networks/:id` - 删除网络
## 关键类型
```typescript
interface Network {
id: string;
name: string;
description: string;
// ... 更多字段
}
```
## 文件路径
- `src/modules/networks/` - UI 组件
- `src/contexts/RoutesProvider.tsx` - 路由数据
- `src/contexts/GroupRouteProvider.tsx` - 分组路由
- `src/interfaces/Network.ts` - 类型定义
- `src/interfaces/Route.ts` - 路由类型
- `src/app/(dashboard)/networks/page.tsx` - 网络页面
- `src/app/(dashboard)/network-routes/page.tsx` - 路由页面
## 组件
- 网络列表组件在 `src/modules/networks/`
- 路由管理在 `src/modules/routes/`
## 使用方法
```tsx
import { useRoutes } from "@/contexts/RoutesProvider";
function MyComponent() {
const { routes, isLoading } = useRoutes();
// ...
}
```
## 命令
- 网络:`/networks`
- 路由:`/network-routes`
## 注意事项
- 网络按账户范围划分
- 路由定义对等节点之间的流量流向
- 分组路由允许将路由分配给对等节点分组

61
docs/contexts/peers.md Normal file
View File

@@ -0,0 +1,61 @@
# 对等节点模块
## 功能
管理网络对等节点 - 查看、配置和监控已连接的设备。
## API 接口
- `GET /api/peers` - 列出所有对等节点
- `GET /api/peers/:id` - 获取对等节点详情
- `PUT /api/peers/:id` - 更新对等节点
- `DELETE /api/peers/:id` - 删除对等节点
## 关键类型
```typescript
interface Peer {
id: string;
name: string;
ip: string;
connected: boolean;
last_seen: string;
os: OperatingSystem;
version: string;
groups: Group[];
// ... 更多字段
}
```
## 文件路径
- `src/modules/peers/` - UI 组件
- `src/contexts/PeersProvider.tsx` - 数据提供者
- `src/interfaces/Peer.ts` - 类型定义
- `src/app/(dashboard)/peers/page.tsx` - 页面组件
- `src/app/(dashboard)/peer/[id]/page.tsx` - 详情页面
## 组件
- `PeersTable.tsx` - 主要对等节点列表表格
- `PeerNameCell.tsx` - 对等节点名称显示
- `PeerAddressCell.tsx` - IP 地址显示
- `PeerStatusCell.tsx` - 连接状态
- `PeerOSCell.tsx` - 操作系统图标
- `PeerGroupCell.tsx` - 分组成员资格
- `PeerActionCell.tsx` - 操作按钮
- `PeerMultiSelect.tsx` - 多对等节点选择器
## 使用方法
```tsx
import { usePeers } from "@/contexts/PeersProvider";
function MyComponent() {
const { peers, isLoading } = usePeers();
// ...
}
```
## 命令
- 页面:`/peers`
- 详情:`/peer/:id`
## 注意事项
- 对等节点列表可能很大 - 使用虚拟滚动
- 状态更新通过轮询实现SWR refreshInterval
- 操作系统图标位于 `src/assets/os-icons/`

View File

@@ -581,7 +581,10 @@ serviceUsersDescription: "Use service users to create API tokens and avoid losin
serviceUsersEmptyDescription: "It looks like you don't have any service users. Get started by creating a service user.",
blocked: "Blocked",
accessTokens: "Access Tokens",
accessTokensDescription: "Access tokens give access to NetBird API."
accessTokensDescription: "Access tokens give access to NetBird API.",
tokenName: "Name",
tokenNameHelp: "Set an easily identifiable name for your token",
tokenExpiresIn: "Expires in"
},
settings: {
title: "Settings",
@@ -948,7 +951,21 @@ serviceUsersDescription: "Use service users to create API tokens and avoid losin
resourceTab: "Resource",
optionalSettings: "Optional Settings",
accessControlPolicies: "Access Control Policies",
accessControlPoliciesHelp: "Define which source groups are allowed to access this resource. You can also restrict access to specific protocols and ports. Without policies access to this resource will not be possible."
accessControlPoliciesHelp: "Define which source groups are allowed to access this resource. You can also restrict access to specific protocols and ports. Without policies access to this resource will not be possible.",
routeType: "Route Type",
routeTypeHelp: "Select your route type to add either a network range or a list of domains.",
routeTypeNetworkRange: "Network Range",
routeTypeDomains: "Domains",
networkRange: "Network Range",
networkRangeHelp: "Add a private IPv4 or IPv6 address or range",
networkRangePlaceholder: "e.g., 172.16.0.1, 172.16.0.0/16, 2001:db8::1 or 2001:db8::/64",
domains: "Domains",
distributionGroups: "Distribution Groups",
networkIdentifier: "Network Identifier",
metric: "Metric",
metricHelp: "Set a metric value to prioritize routes. Lower values take precedence.",
metricPlaceholder: "Enter metric value (1-9999)",
additionalSettings: "Additional Settings"
},
postureChecks: {
title: "Posture Checks",
@@ -1062,7 +1079,16 @@ serviceUsersDescription: "Use service users to create API tokens and avoid losin
updated: "Setup key '{name}' successfully updated",
deleted: "Setup key '{name}' successfully deleted",
noSetupKeys: "No setup keys available",
searchPlaceholder: "Search setup keys..."
searchPlaceholder: "Search setup keys...",
createTitle: "Create New Setup Key",
createDescription: "Use this key to register new machines in your network",
nameHelp: "Set an easily identifiable name for your key",
usageLimitHelp: "For example, set to 30 if you want to enroll 30 peers",
usageLimitSuffix: "Peer(s)",
expiresIn: "Expires in",
expiresInHelp: "Days until the key expires.",
expiresInHelpEmpty: "Leave empty for no expiration.",
expiresInSuffix: "Day(s)"
},
activity: {
title: "Activity",

View File

@@ -581,7 +581,10 @@ serviceUsersDescription: "使用服务用户创建 API 令牌,避免丢失自
serviceUsersEmptyDescription: "看起来您还没有任何服务用户。开始使用,创建一个服务用户。",
blocked: "已阻止",
accessTokens: "访问令牌",
accessTokensDescription: "访问令牌提供对 NetBird API 的访问权限。"
accessTokensDescription: "访问令牌提供对 NetBird API 的访问权限。",
tokenName: "名称",
tokenNameHelp: "为令牌设置一个易于识别的名称",
tokenExpiresIn: "过期时间"
},
settings: {
title: "设置",
@@ -948,7 +951,21 @@ disable2FA: "禁用两步验证",
resourceTab: "资源",
optionalSettings: "可选设置",
accessControlPolicies: "访问控制策略",
accessControlPoliciesHelp: "定义允许访问此资源的源组。您还可以限制对特定协议和端口的访问。如果没有策略,将无法访问此资源。"
accessControlPoliciesHelp: "定义允许访问此资源的源组。您还可以限制对特定协议和端口的访问。如果没有策略,将无法访问此资源。",
routeType: "路由类型",
routeTypeHelp: "选择路由类型以添加网络范围或域名列表。",
routeTypeNetworkRange: "网络范围",
routeTypeDomains: "域名",
networkRange: "网络范围",
networkRangeHelp: "添加私有 IPv4 或 IPv6 地址或范围",
networkRangePlaceholder: "例如172.16.0.1, 172.16.0.0/16, 2001:db8::1 或 2001:db8::/64",
domains: "域名",
distributionGroups: "分发组",
networkIdentifier: "网络标识符",
metric: "度量值",
metricHelp: "设置度量值以优先选择路由。数值越低的优先级越高。",
metricPlaceholder: "输入度量值 (1-9999)",
additionalSettings: "其他设置"
},
postureChecks: {
title: "姿态检查",
@@ -1062,7 +1079,16 @@ disable2FA: "禁用两步验证",
updated: "安装密钥 '{name}' 更新成功",
deleted: "安装密钥 '{name}' 已删除",
noSetupKeys: "暂无安装密钥",
searchPlaceholder: "搜索安装密钥..."
searchPlaceholder: "搜索安装密钥...",
createTitle: "创建新的安装密钥",
createDescription: "使用此密钥在网络中注册新设备",
nameHelp: "为密钥设置一个易于识别的名称",
usageLimitHelp: "例如,设置为 30 表示允许注册 30 台设备",
usageLimitSuffix: "台设备",
expiresIn: "过期时间",
expiresInHelp: "密钥过期前的天数。",
expiresInHelpEmpty: "留空表示永不过期。",
expiresInSuffix: "天"
},
activity: {
title: "活动",

View File

@@ -134,7 +134,8 @@ export function AccessTokenModalContent({
onSuccess,
user,
}: Readonly<ModalProps>) {
const t = useTranslations("common");
const t = useTranslations("serviceUsers");
const tCommon = useTranslations("common");
const tokenRequest = useApiCall<AccessToken>(`/users/${user.id}/tokens`);
const { mutate } = useSWRConfig();
@@ -177,8 +178,8 @@ export function AccessTokenModalContent({
<div className={"px-8 py-6 flex flex-col gap-8"}>
<div>
<Label>Name</Label>
<HelpText>Set an easily identifiable name for your token</HelpText>
<Label>{t("tokenName")}</Label>
<HelpText>{t("tokenNameHelp")}</HelpText>
<Input
data-testid={"access-token-name"}
placeholder={"e.g., Infra token"}
@@ -189,7 +190,7 @@ export function AccessTokenModalContent({
<div className={"flex justify-between"}>
<div>
<Label>Expires in</Label>
<Label>{t("tokenExpiresIn")}</Label>
<HelpText>Should be between 1 and 365 days.</HelpText>
</div>
<Input
@@ -224,7 +225,7 @@ export function AccessTokenModalContent({
</div>
<div className={"flex gap-3 w-full justify-end"}>
<ModalClose asChild={true}>
<Button variant={"secondary"}>{t("cancel")}</Button>
<Button variant={"secondary"}>{tCommon("cancel")}</Button>
</ModalClose>
<Button

View File

@@ -171,7 +171,12 @@ export function EditGroupsModal({
<Button variant={"secondary"}>{tCommon("cancel")}</Button>
</ModalClose>
<Button variant={"primary"} onClick={handleSave} disabled={disabled} data-testid="save-groups">
<Button
variant={"primary"}
onClick={handleSave}
disabled={disabled}
data-testid="save-groups"
>
{t("saveGroups")}
</Button>
</div>

View File

@@ -160,7 +160,8 @@ export function RouteModalContent({
isFirstExitNode = false,
distributionGroups,
}: ModalProps) {
const t = useTranslations("common");
const t = useTranslations("networks");
const tCommon = useTranslations("common");
const { createRoute } = useRoutes();
const [tab, setTab] = useState(
exitNode && peer ? "access-control" : "network",
@@ -365,13 +366,13 @@ export function RouteModalContent({
const networkIdentifierError = useMemo(() => {
return (networkIdentifier?.length || 0) > 40
? "Network Identifier must be less than 40 characters"
? t("networkIdentifier") + " must be less than 40 characters"
: "";
}, [networkIdentifier]);
const metricError = useMemo(() => {
return parseInt(metric) < 1 || parseInt(metric) > 9999
? "Metric must be between 1 and 9999"
? t("metric") + " must be between 1 and 9999"
: "";
}, [metric]);
@@ -465,16 +466,15 @@ export function RouteModalContent({
"text-nb-gray-500 group-data-[state=active]/trigger:text-netbird transition-all"
}
/>
Additional Settings
{t("additionalSettings")}
</TabsTrigger>
</TabsList>
<TabsContent value={"network"} className={"pb-8"}>
<div className={"px-8 flex-col flex gap-4"}>
<div className={cn(exitNode && "hidden")}>
<Label>Route Type</Label>
<Label>{t("routeType")}</Label>
<HelpText>
Select your route type to add either a network range or a list
of domains.
{t("routeTypeHelp")}
</HelpText>
<div className={"flex justify-between items-center w-full"}>
<ButtonGroup className={"w-full"}>
@@ -484,7 +484,7 @@ export function RouteModalContent({
className={"w-full"}
>
<NetworkIcon size={16} />
Network Range
{t("routeTypeNetworkRange")}
</ButtonGroup.Button>
<ButtonGroup.Button
variant={routeType == "domains" ? "tertiary" : "secondary"}
@@ -493,7 +493,7 @@ export function RouteModalContent({
data-testid="route-type-domains"
>
<GlobeIcon size={16} />
Domains
{t("routeTypeDomains")}
</ButtonGroup.Button>
</ButtonGroup>
</div>
@@ -504,12 +504,12 @@ export function RouteModalContent({
routeType !== "ip-range" && "hidden",
)}
>
<Label>Network Range</Label>
<HelpText>Add a private IPv4 or IPv6 address or range</HelpText>
<Label>{t("networkRange")}</Label>
<HelpText>{t("networkRangeHelp")}</HelpText>
<Input
ref={networkRangeRef}
customPrefix={<NetworkIcon size={16} />}
placeholder={"e.g., 172.16.0.1, 172.16.0.0/16, 2001:db8::1 or 2001:db8::/64"}
placeholder={t("networkRangePlaceholder")}
value={networkRange}
data-testid={"network-range"}
className={"font-mono !text-[13px]"}
@@ -521,7 +521,7 @@ export function RouteModalContent({
<div
className={cn("mt-5 mb-3", routeType !== "domains" && "hidden")}
>
<Label>Domains</Label>
<Label>{t("domains")}</Label>
<HelpText>
Add domains that dynamically resolve to one or more IPv4
addresses. <br /> A maximum of 32 domains can be added.
@@ -670,7 +670,7 @@ export function RouteModalContent({
<TabsContent value={"access-control"} className={"pb-8"}>
<div className={"px-8 flex-col flex gap-6"}>
<div>
<Label>Distribution Groups</Label>
<Label>{t("distributionGroups")}</Label>
<HelpText>
{exitNode
? peer
@@ -701,7 +701,7 @@ export function RouteModalContent({
<TabsContent value={"general"} className={"px-8 pb-6"}>
<div className={"flex flex-col gap-6"}>
<div>
<Label>Network Identifier</Label>
<Label>{t("networkIdentifier")}</Label>
<HelpText>
Add a unique network identifier that is assigned to each device.
</HelpText>
@@ -774,7 +774,7 @@ export function RouteModalContent({
<div className={cn("flex justify-between")}>
<div>
<Label>Metric</Label>
<Label>{t("metric")}</Label>
<HelpText className={"max-w-[200px]"}>
A lower metric indicates higher priority routes.
</HelpText>
@@ -822,7 +822,7 @@ export function RouteModalContent({
<div className={"flex gap-3 w-full justify-end"}>
{(tab == "network" || (tab == "access-control" && exitNode)) && (
<ModalClose asChild={true}>
<Button variant={"secondary"}>{t("cancel")}</Button>
<Button variant={"secondary"}>{tCommon("cancel")}</Button>
</ModalClose>
)}

View File

@@ -58,7 +58,6 @@ export default function SetupKeyModal({
showOnlyRoutingPeerOS,
groups,
}: Readonly<Props>) {
const t = useTranslations("common");
const [successModal, setSuccessModal] = useState(false);
const [setupKey, setSetupKey] = useState<SetupKey>();
const [installModal, setInstallModal] = useState(false);
@@ -172,7 +171,8 @@ export function SetupKeyModalContent({
predefinedName = "",
groups,
}: Readonly<ModalProps>) {
const t = useTranslations("common");
const t = useTranslations("setupKeys");
const tCommon = useTranslations("common");
const setupKeyRequest = useApiCall<SetupKey>("/setup-keys", true);
const { mutate } = useSWRConfig();
@@ -230,8 +230,8 @@ export function SetupKeyModalContent({
<ModalContent maxWidthClass={"max-w-xl"}>
<ModalHeader
icon={<SetupKeysIcon className={"fill-netbird"} />}
title={"Create New Setup Key"}
description={"Use this key to register new machines in your network"}
title={t("createTitle")}
description={t("createDescription")}
color={"netbird"}
/>
@@ -240,10 +240,10 @@ export function SetupKeyModalContent({
<div className={"px-8 py-6 flex flex-col gap-8"}>
{/* Name Field */}
<div>
<Label>Name</Label>
<HelpText>Set an easily identifiable name for your key</HelpText>
<Label>{t("name")}</Label>
<HelpText>{t("nameHelp")}</HelpText>
<Input
placeholder={"e.g., AWS Servers"}
placeholder={t("namePlaceholder")}
value={name}
data-testid={"setup-key-name"}
onChange={(e) => setName(e.target.value)}
@@ -268,9 +268,9 @@ export function SetupKeyModalContent({
{/* Usage Limit */}
<div className={cn("flex justify-between", !reusable && "opacity-50")}>
<div>
<Label>Usage limit</Label>
<Label>{t("usageLimit")}</Label>
<HelpText className={"max-w-[200px]"}>
For example, set to 30 if you want to enroll 30 peers
{t("usageLimitHelp")}
</HelpText>
</div>
@@ -286,18 +286,18 @@ export function SetupKeyModalContent({
customPrefix={
<MonitorSmartphoneIcon size={16} className={"text-nb-gray-300"} />
}
customSuffix={"Peer(s)"}
customSuffix={t("usageLimitSuffix")}
/>
</div>
{/* Expires in Days */}
<div className={"flex justify-between"}>
<div>
<Label>Expires in</Label>
<Label>{t("expiresIn")}</Label>
<HelpText>
Days until the key expires.
{t("expiresInHelp")}
<br />
Leave empty for no expiration.
{t("expiresInHelpEmpty")}
</HelpText>
</div>
<Input
@@ -312,7 +312,7 @@ export function SetupKeyModalContent({
customPrefix={
<AlarmClock size={16} className={"text-nb-gray-300"} />
}
customSuffix={"Day(s)"}
customSuffix={t("expiresInSuffix")}
/>
</div>
@@ -383,7 +383,7 @@ export function SetupKeyModalContent({
</div>
<div className={"flex gap-3 w-full justify-end"}>
<ModalClose asChild={true}>
<Button variant={"secondary"}>{t("cancel")}</Button>
<Button variant={"secondary"}>{tCommon("cancel")}</Button>
</ModalClose>
<Button