feat: v2.7.6 — edit users in one form, Settings → Account, display names, copy over plain HTTP

- Users page: one Edit user form (display name, email, role, active, a new or
  generated password, workspaces given or taken back, sign out everywhere),
  validated as a whole before anything is written (PATCH /api/users/:id,
  GET /api/users/:id); users.display_name/email added migration-safe.
- Settings → Account for every signed-in user: profile, own password (the
  current one required, this login kept), sign out other devices
  (/api/me/account, /api/me/password, /api/me/sign-out-others; login cookie
  only, never a pane token).
- Display names wherever a person is named (sidebar, workspace owner groups,
  Users page and audit log); owner_display_name / reader_display additive.
- Users page: admins add and remove Claude/Codex pool accounts.
- Folder browser: an admin with their own Linux account opens in their home;
  the "Server folder" tick opens the host's (never remembered).
- AI Proxy: "Config 9router" beside Add profile when 9router is installed,
  opened on the dashboard's own host (GET /api/ai-proxies/9router).
- Terminal: a tmux selection reaches the clipboard over plain HTTP too
  (writeClipboard's textarea fallback, focus handed back).
- Chat: a file the session touched outside the workspace previews
  (GET /api/sessions/:id/file).
- install-system: `!requiretty` only when visudo accepts it (sudo-rs on
  Ubuntu 25.10+ rejects the setting).
- openapi.yaml regenerated; docs (API, SERVER-MODE, LANES) updated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-13 17:10:29 +07:00
parent 9c573f1e3c
commit 0c2f49876d
74 changed files with 5347 additions and 392 deletions
+2
View File
@@ -58,6 +58,8 @@
"browse.recent": "Gần đây",
"browse.selectFile": "Chọn tệp",
"browse.showHidden": "Hiện mục ẩn",
"browse.hostFolder": "Thư mục máy chủ",
"browse.hostFolderHint": "Duyệt thư mục của tài khoản máy chủ ({{path}}). Workspace ở đó chạy bằng tài khoản máy chủ dùng chung cho mọi admin — không phải tài khoản riêng của bạn.",
"browse.up": "Lên một cấp (Backspace)",
"browse.workspaces": "Workspace",
"browse.title": "Duyệt chọn thư mục",
+33
View File
@@ -626,6 +626,7 @@
"title": "AI Proxy",
"description": "Ví dụ 9Router, OpenRouter hoặc gateway tự dựng. Lưu ở đây rồi chọn cho từng session ở ô nhập prompt của workspace.",
"add": "Thêm profile",
"config9router": "Config 9router",
"edit": "Sửa",
"remove": "Xoá",
"empty": "Chưa lưu profile proxy nào. Native là chạy thẳng vào API của hãng.",
@@ -820,6 +821,10 @@
"data": "Dữ liệu"
},
"section": {
"account": {
"title": "Tài khoản",
"description": "Hồ sơ, mật khẩu và các phiên đăng nhập của bạn trên server này."
},
"appearance": {
"title": "Giao diện",
"description": "Ngôn ngữ, chế độ màu và theme, và linh vật ở góc màn hình."
@@ -1044,5 +1049,33 @@
"chooseFile": "Chọn file",
"fileTooBig": "{{name}} dài {{size}} ký tự — hướng dẫn tối đa {{max}}",
"appliesTo": "Áp dụng cho"
},
"account": {
"loadFailed": "Không tải được tài khoản: {{message}}",
"memberSince": "Tham gia từ",
"workspaces": "Workspace",
"sessions": "Session",
"logins": "Phiên đăng nhập",
"lastLogin": "Đăng nhập gần nhất",
"roleNote": "Vai trò và quyền truy cập của bạn do admin đặt ở trang Người dùng.",
"profile": "Hồ sơ",
"profileDescription": "Tên của bạn hiển thị với người khác trên server này.",
"save": "Lưu",
"profileSaved": "Đã lưu hồ sơ",
"password": "Mật khẩu",
"passwordDescription": "Đổi mật khẩu giữ trình duyệt này đăng nhập và đăng xuất bạn ở mọi nơi khác.",
"currentPassword": "Mật khẩu hiện tại",
"newPassword": "Mật khẩu mới",
"confirmPassword": "Nhập lại mật khẩu mới",
"mismatch": "Hai mật khẩu mới không khớp.",
"changePassword": "Đổi mật khẩu",
"passwordChanged": "Đã đổi mật khẩu",
"sessionsTitle": "Phiên đăng nhập",
"sessionsDescription": "Bạn đang đăng nhập trên {{n}} trình duyệt, tính cả trình duyệt này.",
"signOutOthers": "Đăng xuất các thiết bị khác",
"signOutOthersConfirm": "Đăng xuất mọi thiết bị khác?",
"signOutOthersBody": "Mọi trình duyệt đang đăng nhập tài khoản của bạn, trừ trình duyệt này, sẽ bị đăng xuất.",
"signedOut_one": "Đã đăng xuất {{count}} thiết bị khác",
"signedOut_other": "Đã đăng xuất {{count}} thiết bị khác"
}
}
+119 -3
View File
@@ -56,7 +56,8 @@
"disabled": "Đã khoá {{name}}",
"enabled": "Đã mở khoá {{name}}",
"password": "Đã đặt lại mật khẩu cho {{name}}",
"workspace": "Đã giao workspace cho {{name}}"
"workspace": "Đã giao workspace cho {{name}}",
"updated": "Đã cập nhật {{name}}"
},
"errors": {
"EREAUTH": "Mật khẩu của bạn không đúng.",
@@ -64,12 +65,127 @@
"ESELF": "Bạn không thể tự đổi vai trò hay tự khoá chính mình.",
"EEXISTS": "Tên đăng nhập này đã có người dùng.",
"EBADUSER": "Tên đăng nhập: chữ, số, '.', '_' hoặc '-' (tối đa 64 ký tự).",
"EBADPASSWORD": "Mật khẩu không được để trống."
"EBADPASSWORD": "Mật khẩu không được để trống.",
"EBADEMAIL": "Địa chỉ email không hợp lệ.",
"EBADFIELD": "Có một giá trị không hợp lệ.",
"ENOLANE": "Workspace đó không còn tồn tại.",
"ENOTOWNED": "Workspace đó không còn thuộc người này.",
"EOWNERFOLDER": "Thư mục của workspace đó thuộc tài khoản của người dùng khác.",
"EBADROLE": "Vai trò không hợp lệ.",
"ELOCALMODE": "Chỉ quản lý tài khoản được ở chế độ server."
},
"readOnly": "Analyst chỉ xem được tài khoản, không thay đổi được.",
"auditTitle": "Nhật ký audit",
"auditDescription": "Các thay đổi tài khoản thực hiện ở đây, và mỗi lần admin hoặc analyst mở phiên của một developer.",
"auditEmpty": "Chưa ghi nhận gì.",
"activeCount": "{{n}} đang hoạt động",
"cost30d": "Chi phí · 30 ngày"
"cost30d": "Chi phí · 30 ngày",
"displayName": "Tên hiển thị",
"displayNameHint": "Hiện cạnh tên đăng nhập. Không bắt buộc.",
"email": "Email",
"edit": {
"button": "Sửa người dùng",
"title": "Sửa {{name}}",
"created": "Ngày tạo",
"lastLogin": "Đăng nhập gần nhất",
"never": "Chưa bao giờ",
"logins": "Phiên đăng nhập",
"profile": "Hồ sơ",
"access": "Quyền truy cập",
"status": "Trạng thái",
"active": "Đang hoạt động",
"statusHint": "Người dùng bị vô hiệu hoá không thể đăng nhập; workspace và lịch sử vẫn được giữ.",
"selfRole": "Bạn không thể đổi vai trò của chính mình.",
"selfDisable": "Bạn không thể tự vô hiệu hoá mình.",
"password": "Mật khẩu",
"passwordHint": "Để trống để giữ mật khẩu hiện tại. Mật khẩu mới sẽ đăng xuất họ ở mọi nơi.",
"show": "Hiện mật khẩu",
"hide": "Ẩn mật khẩu",
"copy": "Sao chép mật khẩu",
"generate": "Tạo ngẫu nhiên",
"loading": "Đang tải…",
"noOwned": "Chưa sở hữu workspace nào.",
"undo": "Hoàn tác",
"removeWorkspace": "Bỏ {{name}}",
"removeWorkspaceHint": "Workspace sẽ không còn chủ sở hữu",
"addWorkspace": "Thêm workspace",
"chooseWorkspace": "Chọn một workspace…",
"sessions": "Phiên đăng nhập",
"signOut": "Đăng xuất ở mọi nơi",
"signOutHint": "Kết thúc {{n}} phiên đăng nhập đang hoạt động của người này; họ đăng nhập lại bằng mật khẩu.",
"noChanges": "Chưa có thay đổi."
},
"pool": {
"title": "Pool tài khoản",
"description": "Các tài khoản Claude và Codex mà phiên mới của mọi user chạy trên đó — tự chọn tài khoản ít tải nhất, server ghi login vào home của user, không ai phải đăng nhập riêng.",
"refresh": "Đọc lại quota",
"add": "Thêm tài khoản",
"empty": "Chưa có tài khoản nào. Khi chưa thêm, phiên của mỗi người dùng cần chính người đó đăng nhập.",
"account": "Tài khoản",
"tool": "Công cụ",
"livePanes": "Đang chạy",
"quota": "Quota",
"spend": "5 giờ",
"spend7d": "{{cost}} trong 7 ngày",
"added": "thêm lúc {{at}}",
"expired": "hết hạn",
"full": "đầy",
"kind": {
"token": "Claude · setup-token",
"login": "Claude · login",
"apikey": "Codex · API key",
"chatgpt": "Codex · ChatGPT"
},
"name": "Tên",
"nameHint": "Nhãn tuỳ ý để bạn nhận ra: chữ, số, '.', '_' hoặc '-'",
"remove": "Xoá {{name}}",
"removeHint": "Phiên mới sẽ không dùng tài khoản này nữa. Các phiên đang chạy trên nó vẫn chạy tiếp.",
"removeButton": "Xoá",
"done": {
"added": "Đã thêm {{name}} vào pool",
"removed": "Đã xoá {{name}} khỏi pool"
},
"errors": {
"EREAUTH": "Mật khẩu của bạn không đúng.",
"ETOOMANY": "Nhập sai mật khẩu quá nhiều lần. Thử lại sau 15 phút.",
"EBADNAME": "Tên: 1–64 ký tự chữ, số, '.', '_' hoặc '-'.",
"EBADTOKEN": "Token không hợp lệ cho công cụ này.",
"EEXISTS": "Đã có tài khoản trùng tên trong pool.",
"ENOACCOUNT": "Tài khoản đó không còn trong pool.",
"ELOCALMODE": "Ở chế độ local, pool được thay đổi từ shell của máy (agenthub accounts).",
"EACTIVE": "Claude Code của server giữ login này trong macOS Keychain. Hãy chuyển nó sang profile khác trước.",
"ENOTFOUND": "Login đã lưu đó không còn nữa.",
"ESTALE": "Login này đã được làm mới ở nơi khác. Đăng nhập lại và lưu lại.",
"EUPSTREAM": "Không kết nối được Anthropic. Thử lại."
},
"source": "Nguồn",
"sources": {
"profile": "Login đã lưu (profile Switch account)",
"credentials": "Dán .credentials.json / Keychain",
"token": "claude setup-token"
},
"sourceHint": {
"profile": "Login được chuyển vào pool: profile rời khỏi trình đổi tài khoản, và server ghi nó vào home của từng user khi phiên của họ bắt đầu.",
"credentials": "Lấy từ ~/.claude/.credentials.json, hoặc trên Mac: `security find-generic-password -s \"Claude Code-credentials\" -w`. Đừng dùng tiếp login đó ở nơi đã copy — hai bản sẽ làm nhau bị đăng xuất.",
"token": "Token 1 năm từ `claude setup-token`, đưa vào mọi phiên qua biến môi trường."
},
"profile": "Profile",
"profileHint": "Lưu ở Settings → Claude config → Switch account. Profile mà Claude Code của server đang dùng cũng chuyển được: server vẫn chạy trên nó và tự làm mới cho mọi người.",
"chooseProfile": "Chọn login đã lưu…",
"noProfiles": "Chưa có login nào — lưu ở Settings → Claude config → Switch account",
"loadingProfiles": "Đang tải…",
"activeHere": "server đang dùng — vẫn chạy tiếp",
"nameFromProfile": "Để trống để dùng tên profile",
"secret": {
"credentials": "Credentials JSON",
"token": "Token",
"codex": "API key hoặc auth.json"
},
"secretHint": {
"credentials": "Toàn bộ file: {\"claudeAiOauth\": {…}}",
"token": "Chuỗi sk-ant-… mà `claude setup-token` in ra.",
"codex": "OpenAI API key (sk-…), hoặc toàn bộ ~/.codex/auth.json sau khi `codex login` bằng tài khoản ChatGPT."
},
"refreshFailed": "làm mới lỗi"
}
}