diff --git a/CHANGELOG.md b/CHANGELOG.md index a1db7cff..7845eaf0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,10 @@ ## [Unreleased] +### Added +- **User scope UX (#227):** User Management now assigns **folders**, **direct devices**, and **RustDesk Pro strategies** per user; effective device count badge; folder ACL supports **allowed user groups**; optional **restricted default visibility** in Settings (`DEVICE_SCOPE_DEFAULT` / panel setting); docs [SCOPED_REMOTE_USER.md](docs/features/SCOPED_REMOTE_USER.md) and draft GitHub reply. + ### Changed -- _(none yet)_ +- **Role labels (UI only):** `operator` → “Remote Operator”, `pro` → “Pro License (client API only)”; dynamic role descriptions and scope hints in user form (all 26 locales). --- diff --git a/betterdesk-server/api/rustdesk_groups.go b/betterdesk-server/api/rustdesk_groups.go index c0663b8c..9553cc43 100644 --- a/betterdesk-server/api/rustdesk_groups.go +++ b/betterdesk-server/api/rustdesk_groups.go @@ -225,12 +225,27 @@ func (s *Server) rustDeskVisiblePeerSet(user *db.User, role string, peerByID map if s.panelStore == nil { return nil } + + restrictedDefault := s.panelStore.DeviceScopeDefaultRestricted() + + var peerGrants []string + if user != nil && user.ID > 0 { + if grants, err := s.panelStore.ListUserPeerGrants(user.ID); err == nil { + peerGrants = grants + } + } + panelGroups, err := s.panelStore.ListPanelDeviceGroups() if err != nil { return nil } userGroupGUIDs := s.consoleUserGroupGUIDs(user.ID) + assignments := map[string]int64{} + if a, err := s.panelStore.ListFolderAssignments(); err == nil { + assignments = a + } + hasRestricted := false for _, g := range panelGroups { if len(g.AllowedUsers) > 0 || len(g.AllowedGroupGUIDs) > 0 { @@ -239,6 +254,19 @@ func (s *Server) rustDeskVisiblePeerSet(user *db.User, role string, peerByID map } } if !hasRestricted { + folders, _ := s.panelStore.ListFolders() + for _, folder := range folders { + allowedUsers, allowedGroups, _ := s.panelStore.FolderGroupAccess(folder.ID) + if len(allowedUsers) > 0 || len(allowedGroups) > 0 { + hasRestricted = true + break + } + } + } + if !hasRestricted && len(peerGrants) == 0 { + if restrictedDefault { + return map[string]bool{} + } return nil } @@ -258,6 +286,34 @@ func (s *Server) rustDeskVisiblePeerSet(user *db.User, role string, peerByID map } } + folders, _ := s.panelStore.ListFolders() + for _, folder := range folders { + allowedUsers, allowedGroups, _ := s.panelStore.FolderGroupAccess(folder.ID) + if len(allowedUsers) == 0 && len(allowedGroups) == 0 { + continue + } + target := restricted + if panelAccessAllowed(user, role, userGroupGUIDs, allowedUsers, allowedGroups) { + target = allowed + } + for deviceID, folderID := range assignments { + if folderID != folder.ID { + continue + } + if _, ok := peerByID[deviceID]; !ok { + continue + } + target[deviceID] = true + } + } + for _, id := range peerGrants { + allowed[id] = true + } + + if restrictedDefault { + return allowed + } + visible := make(map[string]bool) for id := range peerByID { if !restricted[id] || allowed[id] { diff --git a/betterdesk-server/db/console_auth.go b/betterdesk-server/db/console_auth.go index b7029222..560a5b18 100644 --- a/betterdesk-server/db/console_auth.go +++ b/betterdesk-server/db/console_auth.go @@ -5,6 +5,7 @@ package db import ( "database/sql" "fmt" + "os" "strings" _ "modernc.org/sqlite" @@ -299,3 +300,41 @@ func (c *ConsoleAuthDB) FolderGroupAccess(folderID int64) ([]string, []string, e } return c.loadDeviceGroupAccess(id) } + +// ListUserPeerGrants returns peer IDs directly granted to a panel user. +func (c *ConsoleAuthDB) ListUserPeerGrants(userID int64) ([]string, error) { + if userID <= 0 || !c.hasTable("user_peer_grants") { + return nil, nil + } + rows, err := c.db.Query(` + SELECT peer_id FROM user_peer_grants WHERE user_id = ? ORDER BY peer_id ASC`, userID) + if err != nil { + return nil, err + } + defer rows.Close() + var out []string + for rows.Next() { + var id string + if err := rows.Scan(&id); err != nil { + return nil, err + } + id = strings.TrimSpace(id) + if id != "" { + out = append(out, id) + } + } + return out, rows.Err() +} + +// DeviceScopeDefaultRestricted reads panel settings for default-deny device visibility. +func (c *ConsoleAuthDB) DeviceScopeDefaultRestricted() bool { + if !c.hasTable("settings") { + return strings.EqualFold(strings.TrimSpace(os.Getenv("DEVICE_SCOPE_DEFAULT")), "restricted") + } + var value string + err := c.db.QueryRow(`SELECT value FROM settings WHERE key = 'device_scope_default' LIMIT 1`).Scan(&value) + if err == sql.ErrNoRows || strings.TrimSpace(value) == "" { + return strings.EqualFold(strings.TrimSpace(os.Getenv("DEVICE_SCOPE_DEFAULT")), "restricted") + } + return strings.EqualFold(strings.TrimSpace(value), "restricted") +} diff --git a/betterdesk-server/db/panel_sync.go b/betterdesk-server/db/panel_sync.go index b4d9f08a..49eccbe3 100644 --- a/betterdesk-server/db/panel_sync.go +++ b/betterdesk-server/db/panel_sync.go @@ -8,10 +8,12 @@ type PanelSyncStore interface { ListPanelDeviceGroups() ([]PanelDeviceGroup, error) ListDeviceGroupMemberPeerIDs(deviceGroupID int64) ([]string, error) ListUserGroupGUIDsForUser(userID int64) ([]string, error) + ListUserPeerGrants(userID int64) ([]string, error) ListFolders() ([]PanelFolder, error) ListFolderAssignments() (map[string]int64, error) ListPeerSysinfo() (map[string]ConsolePeerSysinfo, error) FolderGroupAccess(folderID int64) ([]string, []string, error) + DeviceScopeDefaultRestricted() bool } // ConsoleAuthDB implements PanelSyncStore for legacy SQLite auth.db deployments. diff --git a/betterdesk-server/db/panel_sync_postgres.go b/betterdesk-server/db/panel_sync_postgres.go index 7e7be391..80f9d0b6 100644 --- a/betterdesk-server/db/panel_sync_postgres.go +++ b/betterdesk-server/db/panel_sync_postgres.go @@ -2,6 +2,7 @@ package db import ( "fmt" + "os" "strings" "github.com/jackc/pgx/v5" @@ -255,3 +256,40 @@ func (pg *PostgresDB) FolderGroupAccess(folderID int64) ([]string, []string, err } return pg.loadDeviceGroupAccess(id) } + +// ListUserPeerGrants returns peer IDs directly granted to a panel user. +func (pg *PostgresDB) ListUserPeerGrants(userID int64) ([]string, error) { + if userID <= 0 || !pg.pgHasTable("user_peer_grants") { + return nil, nil + } + rows, err := pg.pool.Query(pg.ctx, ` + SELECT peer_id FROM user_peer_grants WHERE user_id = $1 ORDER BY peer_id ASC`, userID) + if err != nil { + return nil, err + } + defer rows.Close() + var out []string + for rows.Next() { + var id string + if err := rows.Scan(&id); err != nil { + return nil, err + } + id = strings.TrimSpace(id) + if id != "" { + out = append(out, id) + } + } + return out, rows.Err() +} + +// DeviceScopeDefaultRestricted reads panel settings for default-deny device visibility. +func (pg *PostgresDB) DeviceScopeDefaultRestricted() bool { + if pg.pgHasTable("settings") { + var value string + err := pg.pool.QueryRow(pg.ctx, `SELECT value FROM settings WHERE key = 'device_scope_default' LIMIT 1`).Scan(&value) + if err == nil && strings.TrimSpace(value) != "" { + return strings.EqualFold(strings.TrimSpace(value), "restricted") + } + } + return strings.EqualFold(strings.TrimSpace(os.Getenv("DEVICE_SCOPE_DEFAULT")), "restricted") +} diff --git a/docs/discussions/issue-227-reply.md b/docs/discussions/issue-227-reply.md new file mode 100644 index 00000000..ba4bc35e --- /dev/null +++ b/docs/discussions/issue-227-reply.md @@ -0,0 +1,36 @@ +# Reply for GitHub discussion #227 (copy/paste) + +--- + +Hi — thanks for raising this, it helped us spot a naming mix-up that trips people up often. + +**Short version:** In BetterDesk, the panel role called **Pro** is *not* what most people mean by a “Pro end user”. It’s a special **license/API-only** account (RustDesk desktop client, no web panel). If you want someone who can log into the console, change their password, use the Web Client, and only see *their* machines — that’s a **Remote Operator**, not `pro`. + +### What already works today + +For a typical “remote user with limited visibility”: + +1. Create the user as **Remote Operator** (was just labeled “Operator” in the UI). +2. **Password:** they can change it themselves under **Settings → Change password** (local accounts). +3. **Web Client:** Remote Operators can use **Remote Desktop** in the panel (`device.connect`). Viewers are read-only and cannot connect. +4. **Limit which devices they see:** + - Put devices in **folders** and set folder access (allowed users / user groups), **or** + - Use **user groups** + device group ACL (same idea as before). + +### What we’re shipping to make this easier + +On the next update (via **Settings → Updates**): + +- **User Management:** assign **folders**, **direct devices**, and an optional **RustDesk Pro strategy** per user — all in one place. +- Clearer role names in the UI: **Remote Operator** vs **Pro License (client API only)**. +- Optional **restricted default visibility** in Settings (for servers that want “see nothing until explicitly granted” instead of “see everything until ACL is set”). + +Docs for operators: [Scoped remote user recipe](https://github.com/UNITRONIX/BetterDesk/blob/dev/docs/features/SCOPED_REMOTE_USER.md) + +### When to use the `pro` role + +Keep **`pro`** for accounts that only need to **activate RustDesk Pro** through the desktop client API — not for normal staff who should use the web console. + +--- + +If you try the Remote Operator + folder/user-group setup and something still feels off, tell us your exact goal (how many users, LDAP or local, need Web Client or desktop only?) and we’ll refine from there. diff --git a/docs/features/SCOPED_REMOTE_USER.md b/docs/features/SCOPED_REMOTE_USER.md new file mode 100644 index 00000000..712f776e --- /dev/null +++ b/docs/features/SCOPED_REMOTE_USER.md @@ -0,0 +1,39 @@ +# Scoped remote user recipe + +Guide for operators who need **limited device visibility**, **Web Client access**, and **self-service password change** — without using the `pro` panel role. + +## Terminology + +| BetterDesk role | Use for | +|-----------------|---------| +| **Remote Operator** (`operator`) | End users who connect remotely, use Web Client, change own password | +| **Viewer** (`viewer`) | Read-only monitoring (no Web Client) | +| **Pro License** (`pro`) | RustDesk desktop API / license activation only — **no web panel** | + +RustDesk Pro **client features** are controlled separately via **strategy assignments**, not the `pro` role. + +## Recipe: scoped remote user + +1. **Create a user group** (Users → User groups) e.g. `Team-A`. +2. **Create folders** (Devices → Folders) and assign devices via drag-and-drop or folder picker. +3. **Restrict folder ACL**: edit folder → allowed users and/or allowed user groups. +4. **Create user** with role **Remote Operator**. +5. Assign **user groups**, **folders**, and/or **direct devices** in the user form. +6. Optional: assign a **RustDesk Pro strategy** on the same form for Pro client features. + +## Default visibility modes + +| Mode | Behavior | +|------|----------| +| **Open** (default) | Non-admins see all devices until folder/group ACL or direct grants exist | +| **Restricted** | Non-admins see only explicitly granted devices (Settings → Device visibility default) | + +## Password and Web Client + +- **Password**: Remote Operator / Viewer → Settings → Change password (local accounts). +- **Web Client**: requires **Remote Operator** (`device.connect` permission). + +## Related + +- [RBAC Phase 52](RBAC_PHASE52.md) +- GitHub discussion #227 diff --git a/web-nodejs/config/config.js b/web-nodejs/config/config.js index 9aab633f..d3c42a57 100644 --- a/web-nodejs/config/config.js +++ b/web-nodejs/config/config.js @@ -171,6 +171,9 @@ module.exports = { // bypass is ignored and TOTP is enforced normally on :21121. rustdeskApiDisableTotpAck: (process.env.RUSTDESK_API_DISABLE_TOTP_ACKNOWLEDGED || 'false').toLowerCase() === 'true', + // Device visibility for non-admin roles: open (legacy overlay ACL) or restricted (default-deny). + deviceScopeDefault: (process.env.DEVICE_SCOPE_DEFAULT || 'open').toLowerCase(), + // HTTPS / SSL httpsEnabled: (process.env.HTTPS_ENABLED || 'false').toLowerCase() === 'true', httpsPort: parseInt(process.env.HTTPS_PORT, 10) || 5443, diff --git a/web-nodejs/lang/ar.json b/web-nodejs/lang/ar.json index e9cf5d2b..beac907f 100644 --- a/web-nodejs/lang/ar.json +++ b/web-nodejs/lang/ar.json @@ -741,7 +741,15 @@ "public_endpoints_invalid_host": "اسم مضيف ID أو relay غير صالح", "public_endpoints_invalid_api_url": "URL API غير صالح — استخدم http:// أو https://", "public_endpoints_save_title": "حفظ نقاط نهاية العملاء العامة؟", - "public_endpoints_save_confirm": "تحديث قيم إعداد عميل RustDesk المستخدمة في لوحة التحكم؟" + "public_endpoints_save_confirm": "تحديث قيم إعداد عميل RustDesk المستخدمة في لوحة التحكم؟", + "device_scope_title": "Device visibility default", + "device_scope_desc": "Controls what non-admin users see when no folder, group, or direct device grants exist.", + "device_scope_open": "Open (legacy) — show all devices until ACL is configured", + "device_scope_restricted": "Restricted — show only explicitly granted devices", + "device_scope_save": "Save visibility default", + "device_scope_saved": "Device visibility default saved", + "device_scope_invalid": "Invalid visibility mode", + "device_scope_restricted_warning": "Restricted mode is active. Ensure users have folder, group, or direct device grants or they will see an empty device list." }, "audit": { "time": "Time", @@ -970,9 +978,9 @@ "password_leave_empty": "اتركه فارغًا للحفاظ على التيار", "role": "Role", "role_admin": "Administrator", - "role_operator": "Operator", + "role_operator": "Remote Operator", "role_viewer": "Viewer", - "role_hint": "Viewers can only view data, operators can manage devices, admins have full access", + "role_hint": "Server-wide role. Remote Operators connect remotely and can change their own password. Viewers are read-only (no Web Client). Pro License accounts activate RustDesk Pro via the desktop client API only — no web panel access. Organization roles are configured separately.", "created": "مخلوق", "last_login": "Last Login", "never": "Never", @@ -1007,10 +1015,10 @@ "security_tips_title": "أفضل الممارسات الأمنية", "tip_strong_passwords": "استخدم كلمات مرور قوية وفريدة لكل مستخدم", "tip_unique_accounts": "إنشاء حسابات فردية لكل شخص", - "tip_viewer_role": "استخدم دور العارض للمستخدمين الذين يحتاجون فقط إلى حق الوصول للقراءة", - "tip_operator_role": "استخدم دور عامل التشغيل للمستخدمين الذين يحتاجون إلى إدارة الأجهزة وليس المستخدمين", + "tip_viewer_role": "Use Viewer for read-only monitoring; Remote Operator for users who need to connect", + "tip_operator_role": "Use operator role for users who need to manage devices but not users", "tip_audit_logs": "قم بمراجعة سجلات التدقيق بانتظام بحثًا عن أي نشاط مشبوه", - "role_pro": "برو (API فقط)", + "role_pro": "Pro License (client API only)", "role_super_admin": "Super Admin", "role_server_admin": "مشرف الخادم", "role_global_admin": "المشرف العالمي", @@ -1053,7 +1061,27 @@ "provider_managed_hint": "يُدار هذا الحساب بواسطة موفر هوية خارجي (LDAP/AD أو SSO). تتحكم الجهة الموفرة في كلمة المرور والدور، ولا يمكن تغييرهما هنا.", "email": "Email", "email_placeholder": "operator@example.com", - "email_hint": "Used for help request notifications when the user is assigned to device folders or groups." + "email_hint": "Used for help request notifications when the user is assigned to device folders or groups.", + "scope_hint": "Limit which devices this user sees: assign user groups, folders, and/or direct devices. Without restrictions, operators and viewers see all devices (unless the server uses restricted default mode in Settings).", + "role_desc_viewer": "Read-only panel access: device list, audit, metrics. Cannot use Web Remote Desktop.", + "role_desc_operator": "Can connect via Web Client and RustDesk desktop, edit devices, and change own password in Settings.", + "role_desc_pro": "RustDesk desktop client API only — activates Pro license. Blocked from web panel login.", + "role_desc_admin": "Full access (legacy alias for Super Admin).", + "role_desc_super_admin": "Full server and panel access, including user and server configuration.", + "role_desc_server_admin": "Server infrastructure, keys, and read-only user visibility. No device connect.", + "role_desc_global_admin": "Manage all users, organizations, and devices. Cannot change server config.", + "user_folders": "Folder access", + "user_folders_hint": "Devices in selected folders are visible to this user when folder ACL is configured.", + "user_direct_devices": "Direct devices", + "user_direct_devices_hint": "Grant access to specific devices regardless of folder or group membership.", + "user_direct_devices_placeholder": "Enter device IDs separated by commas", + "effective_scope_count": "{count} devices visible", + "loading_folders": "Loading folders...", + "no_folders": "No folders available", + "pro_strategy_label": "RustDesk Pro strategy", + "pro_strategy_hint": "Optional access-control strategy for RustDesk Pro client features (separate from panel role).", + "pro_strategy_none": "None", + "column_scope": "Scope" }, "folders": { "title": "Folders", diff --git a/web-nodejs/lang/cs.json b/web-nodejs/lang/cs.json index 4b360c37..2c452914 100644 --- a/web-nodejs/lang/cs.json +++ b/web-nodejs/lang/cs.json @@ -734,7 +734,15 @@ "public_endpoints_invalid_host": "Neplatný hostname ID nebo relay serveru", "public_endpoints_invalid_api_url": "Neplatná API URL — použijte http:// nebo https://", "public_endpoints_save_title": "Uložit veřejné koncové body klientů?", - "public_endpoints_save_confirm": "Aktualizovat hodnoty konfigurace RustDesk klienta na dashboardu?" + "public_endpoints_save_confirm": "Aktualizovat hodnoty konfigurace RustDesk klienta na dashboardu?", + "device_scope_title": "Device visibility default", + "device_scope_desc": "Controls what non-admin users see when no folder, group, or direct device grants exist.", + "device_scope_open": "Open (legacy) — show all devices until ACL is configured", + "device_scope_restricted": "Restricted — show only explicitly granted devices", + "device_scope_save": "Save visibility default", + "device_scope_saved": "Device visibility default saved", + "device_scope_invalid": "Invalid visibility mode", + "device_scope_restricted_warning": "Restricted mode is active. Ensure users have folder, group, or direct device grants or they will see an empty device list." }, "audit": { "time": "Čas", @@ -963,9 +971,9 @@ "password_leave_empty": "Nechte prázdné pro zachování stávajícího", "role": "Role", "role_admin": "Administrátor", - "role_operator": "Operátor", + "role_operator": "Remote Operator", "role_viewer": "Prohlížející", - "role_hint": "Prohlížející mohou pouze zobrazovat data, operátoři mohou spravovat zařízení, administrátoři mají plný přístup", + "role_hint": "Server-wide role. Remote Operators connect remotely and can change their own password. Viewers are read-only (no Web Client). Pro License accounts activate RustDesk Pro via the desktop client API only — no web panel access. Organization roles are configured separately.", "created": "Vytvořeno", "last_login": "Poslední přihlášení", "never": "Nikdy", @@ -1000,10 +1008,10 @@ "security_tips_title": "Doporučené bezpečnostní postupy", "tip_strong_passwords": "Používejte silná a jedinečná hesla pro každého uživatele", "tip_unique_accounts": "Vytvořte samostatný účet pro každou osobu", - "tip_viewer_role": "Používejte roli prohlížejícího pro uživatele, kteří potřebují pouze přístup pro čtení", - "tip_operator_role": "Používejte roli operátora pro uživatele, kteří potřebují spravovat zařízení, ale ne uživatele", + "tip_viewer_role": "Use Viewer for read-only monitoring; Remote Operator for users who need to connect", + "tip_operator_role": "Use operator role for users who need to manage devices but not users", "tip_audit_logs": "Pravidelně kontrolujte auditní záznamy kvůli podezřelé aktivitě", - "role_pro": "Pro (pouze API)", + "role_pro": "Pro License (client API only)", "role_super_admin": "Super administrátor", "role_server_admin": "Administrátor serveru", "role_global_admin": "Globální administrátor", @@ -1046,7 +1054,27 @@ "provider_managed_hint": "To konto jest zarzadzane przez zewnetrznego dostawce tozsamosci (LDAP/AD lub SSO). Haslo i rola sa kontrolowane przez dostawce i nie mozna ich tu zmienic.", "email": "Email", "email_placeholder": "operator@example.com", - "email_hint": "Used for help request notifications when the user is assigned to device folders or groups." + "email_hint": "Used for help request notifications when the user is assigned to device folders or groups.", + "scope_hint": "Limit which devices this user sees: assign user groups, folders, and/or direct devices. Without restrictions, operators and viewers see all devices (unless the server uses restricted default mode in Settings).", + "role_desc_viewer": "Read-only panel access: device list, audit, metrics. Cannot use Web Remote Desktop.", + "role_desc_operator": "Can connect via Web Client and RustDesk desktop, edit devices, and change own password in Settings.", + "role_desc_pro": "RustDesk desktop client API only — activates Pro license. Blocked from web panel login.", + "role_desc_admin": "Full access (legacy alias for Super Admin).", + "role_desc_super_admin": "Full server and panel access, including user and server configuration.", + "role_desc_server_admin": "Server infrastructure, keys, and read-only user visibility. No device connect.", + "role_desc_global_admin": "Manage all users, organizations, and devices. Cannot change server config.", + "user_folders": "Folder access", + "user_folders_hint": "Devices in selected folders are visible to this user when folder ACL is configured.", + "user_direct_devices": "Direct devices", + "user_direct_devices_hint": "Grant access to specific devices regardless of folder or group membership.", + "user_direct_devices_placeholder": "Enter device IDs separated by commas", + "effective_scope_count": "{count} devices visible", + "loading_folders": "Loading folders...", + "no_folders": "No folders available", + "pro_strategy_label": "RustDesk Pro strategy", + "pro_strategy_hint": "Optional access-control strategy for RustDesk Pro client features (separate from panel role).", + "pro_strategy_none": "None", + "column_scope": "Scope" }, "folders": { "title": "Složky", diff --git a/web-nodejs/lang/da.json b/web-nodejs/lang/da.json index 667bac17..cc332c9d 100644 --- a/web-nodejs/lang/da.json +++ b/web-nodejs/lang/da.json @@ -740,7 +740,15 @@ "public_endpoints_invalid_host": "Ugyldigt ID- eller relay-værtsnavn", "public_endpoints_invalid_api_url": "Ugyldig API-URL — brug http:// eller https://", "public_endpoints_save_title": "Gem offentlige klient-endpoints?", - "public_endpoints_save_confirm": "Opdatere RustDesk-klientværdier på dashboardet?" + "public_endpoints_save_confirm": "Opdatere RustDesk-klientværdier på dashboardet?", + "device_scope_title": "Device visibility default", + "device_scope_desc": "Controls what non-admin users see when no folder, group, or direct device grants exist.", + "device_scope_open": "Open (legacy) — show all devices until ACL is configured", + "device_scope_restricted": "Restricted — show only explicitly granted devices", + "device_scope_save": "Save visibility default", + "device_scope_saved": "Device visibility default saved", + "device_scope_invalid": "Invalid visibility mode", + "device_scope_restricted_warning": "Restricted mode is active. Ensure users have folder, group, or direct device grants or they will see an empty device list." }, "audit": { "time": "Time", @@ -969,9 +977,9 @@ "password_leave_empty": "Lad være tom for at holde dig opdateret", "role": "Role", "role_admin": "Administrator", - "role_operator": "Operator", + "role_operator": "Remote Operator", "role_viewer": "Viewer", - "role_hint": "Viewers can only view data, operators can manage devices, admins have full access", + "role_hint": "Server-wide role. Remote Operators connect remotely and can change their own password. Viewers are read-only (no Web Client). Pro License accounts activate RustDesk Pro via the desktop client API only — no web panel access. Organization roles are configured separately.", "created": "Oprettet", "last_login": "Last Login", "never": "Never", @@ -1006,10 +1014,10 @@ "security_tips_title": "Bedste praksis for sikkerhed", "tip_strong_passwords": "Brug stærke, unikke adgangskoder til hver bruger", "tip_unique_accounts": "Opret individuelle konti for hver person", - "tip_viewer_role": "Brug fremviserrolle til brugere, der kun har brug for læseadgang", - "tip_operator_role": "Brug operatørrolle for brugere, der skal administrere enheder, men ikke brugere", + "tip_viewer_role": "Use Viewer for read-only monitoring; Remote Operator for users who need to connect", + "tip_operator_role": "Use operator role for users who need to manage devices but not users", "tip_audit_logs": "Gennemgå revisionslogfiler regelmæssigt for mistænkelig aktivitet", - "role_pro": "Pro (kun API)", + "role_pro": "Pro License (client API only)", "role_super_admin": "Super Admin", "role_server_admin": "Serveradministrator", "role_global_admin": "Overordnet administrator", @@ -1052,7 +1060,27 @@ "provider_managed_hint": "Denne konto administreres af en ekstern identitetsudbyder (LDAP/AD eller SSO). Adgangskoden og rollen styres af udbyderen.", "email": "Email", "email_placeholder": "operator@example.com", - "email_hint": "Used for help request notifications when the user is assigned to device folders or groups." + "email_hint": "Used for help request notifications when the user is assigned to device folders or groups.", + "scope_hint": "Limit which devices this user sees: assign user groups, folders, and/or direct devices. Without restrictions, operators and viewers see all devices (unless the server uses restricted default mode in Settings).", + "role_desc_viewer": "Read-only panel access: device list, audit, metrics. Cannot use Web Remote Desktop.", + "role_desc_operator": "Can connect via Web Client and RustDesk desktop, edit devices, and change own password in Settings.", + "role_desc_pro": "RustDesk desktop client API only — activates Pro license. Blocked from web panel login.", + "role_desc_admin": "Full access (legacy alias for Super Admin).", + "role_desc_super_admin": "Full server and panel access, including user and server configuration.", + "role_desc_server_admin": "Server infrastructure, keys, and read-only user visibility. No device connect.", + "role_desc_global_admin": "Manage all users, organizations, and devices. Cannot change server config.", + "user_folders": "Folder access", + "user_folders_hint": "Devices in selected folders are visible to this user when folder ACL is configured.", + "user_direct_devices": "Direct devices", + "user_direct_devices_hint": "Grant access to specific devices regardless of folder or group membership.", + "user_direct_devices_placeholder": "Enter device IDs separated by commas", + "effective_scope_count": "{count} devices visible", + "loading_folders": "Loading folders...", + "no_folders": "No folders available", + "pro_strategy_label": "RustDesk Pro strategy", + "pro_strategy_hint": "Optional access-control strategy for RustDesk Pro client features (separate from panel role).", + "pro_strategy_none": "None", + "column_scope": "Scope" }, "folders": { "title": "Folders", diff --git a/web-nodejs/lang/de.json b/web-nodejs/lang/de.json index c5d9213a..d97ac4a6 100644 --- a/web-nodejs/lang/de.json +++ b/web-nodejs/lang/de.json @@ -734,7 +734,15 @@ "public_endpoints_invalid_host": "Ungültiger ID- oder Relay-Hostname", "public_endpoints_invalid_api_url": "Ungültige API-URL — http:// oder https:// verwenden", "public_endpoints_save_title": "Öffentliche Client-Endpunkte speichern?", - "public_endpoints_save_confirm": "RustDesk-Clientwerte auf dem Dashboard aktualisieren?" + "public_endpoints_save_confirm": "RustDesk-Clientwerte auf dem Dashboard aktualisieren?", + "device_scope_title": "Device visibility default", + "device_scope_desc": "Controls what non-admin users see when no folder, group, or direct device grants exist.", + "device_scope_open": "Open (legacy) — show all devices until ACL is configured", + "device_scope_restricted": "Restricted — show only explicitly granted devices", + "device_scope_save": "Save visibility default", + "device_scope_saved": "Device visibility default saved", + "device_scope_invalid": "Invalid visibility mode", + "device_scope_restricted_warning": "Restricted mode is active. Ensure users have folder, group, or direct device grants or they will see an empty device list." }, "audit": { "time": "Zeit", @@ -963,10 +971,10 @@ "password_leave_empty": "Leer lassen, um aktuelles beizubehalten", "role": "Rolle", "role_admin": "Administrator", - "role_operator": "Operator", + "role_operator": "Remote Operator", "role_viewer": "Betrachter", - "role_pro": "Pro", - "role_hint": "Betrachter können nur Daten einsehen, Operatoren können Geräte verwalten, Administratoren haben vollen Zugriff", + "role_pro": "Pro License (client API only)", + "role_hint": "Server-wide role. Remote Operators connect remotely and can change their own password. Viewers are read-only (no Web Client). Pro License accounts activate RustDesk Pro via the desktop client API only — no web panel access. Organization roles are configured separately.", "created": "Erstellt", "last_login": "Letzte Anmeldung", "never": "Nie", @@ -1001,8 +1009,8 @@ "security_tips_title": "Sicherheitsempfehlungen", "tip_strong_passwords": "Verwenden Sie starke, einzigartige Passwörter für jeden Benutzer", "tip_unique_accounts": "Erstellen Sie individuelle Konten für jede Person", - "tip_viewer_role": "Verwenden Sie die Betrachter-Rolle für Benutzer, die nur Lesezugriff benötigen", - "tip_operator_role": "Verwenden Sie die Operator-Rolle für Benutzer, die Geräte verwalten, aber keine Benutzer verwalten müssen", + "tip_viewer_role": "Use Viewer for read-only monitoring; Remote Operator for users who need to connect", + "tip_operator_role": "Use operator role for users who need to manage devices but not users", "tip_audit_logs": "Überprüfen Sie regelmäßig die Audit-Protokolle auf verdächtige Aktivitäten", "role_super_admin": "Super Admin", "role_server_admin": "Serveradministrator", @@ -1046,7 +1054,27 @@ "provider_managed_hint": "Dieses Konto wird von einem externen Identitätsanbieter (LDAP/AD oder SSO) verwaltet. Passwort und Rolle werden vom Anbieter gesteuert und können hier nicht geändert werden.", "email": "E-Mail", "email_placeholder": "operator@example.com", - "email_hint": "Für Hilfeanfragen-Benachrichtigungen, wenn der Benutzer Geräteordnern oder -gruppen zugewiesen ist." + "email_hint": "Für Hilfeanfragen-Benachrichtigungen, wenn der Benutzer Geräteordnern oder -gruppen zugewiesen ist.", + "scope_hint": "Limit which devices this user sees: assign user groups, folders, and/or direct devices. Without restrictions, operators and viewers see all devices (unless the server uses restricted default mode in Settings).", + "role_desc_viewer": "Read-only panel access: device list, audit, metrics. Cannot use Web Remote Desktop.", + "role_desc_operator": "Can connect via Web Client and RustDesk desktop, edit devices, and change own password in Settings.", + "role_desc_pro": "RustDesk desktop client API only — activates Pro license. Blocked from web panel login.", + "role_desc_admin": "Full access (legacy alias for Super Admin).", + "role_desc_super_admin": "Full server and panel access, including user and server configuration.", + "role_desc_server_admin": "Server infrastructure, keys, and read-only user visibility. No device connect.", + "role_desc_global_admin": "Manage all users, organizations, and devices. Cannot change server config.", + "user_folders": "Folder access", + "user_folders_hint": "Devices in selected folders are visible to this user when folder ACL is configured.", + "user_direct_devices": "Direct devices", + "user_direct_devices_hint": "Grant access to specific devices regardless of folder or group membership.", + "user_direct_devices_placeholder": "Enter device IDs separated by commas", + "effective_scope_count": "{count} devices visible", + "loading_folders": "Loading folders...", + "no_folders": "No folders available", + "pro_strategy_label": "RustDesk Pro strategy", + "pro_strategy_hint": "Optional access-control strategy for RustDesk Pro client features (separate from panel role).", + "pro_strategy_none": "None", + "column_scope": "Scope" }, "folders": { "title": "Dashboard", diff --git a/web-nodejs/lang/en.json b/web-nodejs/lang/en.json index a339a4b3..c418c7fa 100644 --- a/web-nodejs/lang/en.json +++ b/web-nodejs/lang/en.json @@ -623,6 +623,14 @@ "req_lowercase": "At least one lowercase letter", "req_number": "At least one number", "server_info": "Server Information", + "device_scope_title": "Device visibility default", + "device_scope_desc": "Controls what non-admin users see when no folder, group, or direct device grants exist.", + "device_scope_open": "Open (legacy) — show all devices until ACL is configured", + "device_scope_restricted": "Restricted — show only explicitly granted devices", + "device_scope_save": "Save visibility default", + "device_scope_saved": "Device visibility default saved", + "device_scope_invalid": "Invalid visibility mode", + "device_scope_restricted_warning": "Restricted mode is active. Ensure users have folder, group, or direct device grants or they will see an empty device list.", "version": "Version", "database": "Database", "uptime": "Uptime", @@ -984,13 +992,33 @@ "password_leave_empty": "Leave empty to keep current", "role": "Role", "role_admin": "Administrator", - "role_operator": "Operator", + "role_operator": "Remote Operator", "role_viewer": "Viewer", - "role_pro": "Pro (API only)", + "role_pro": "Pro License (client API only)", "role_super_admin": "Super Admin", "role_server_admin": "Server Admin", "role_global_admin": "Global Admin", - "role_hint": "This is the server-wide role. Super Admin has full access. Server Admin manages server config and keys. Global Admin manages all users, orgs, and devices but cannot change server config. Administrator is equivalent to Super Admin. Operators manage devices. Viewers have read-only access. Pro activates Pro mode in RustDesk client (API only). Per-organization roles are configured separately under each user's Organizations.", + "role_hint": "Server-wide role. Remote Operators connect remotely and can change their own password. Viewers are read-only (no Web Client). Pro License accounts activate RustDesk Pro via the desktop client API only — no web panel access. Organization roles are configured separately.", + "scope_hint": "Limit which devices this user sees: assign user groups, folders, and/or direct devices. Without restrictions, operators and viewers see all devices (unless the server uses restricted default mode in Settings).", + "role_desc_viewer": "Read-only panel access: device list, audit, metrics. Cannot use Web Remote Desktop.", + "role_desc_operator": "Can connect via Web Client and RustDesk desktop, edit devices, and change own password in Settings.", + "role_desc_pro": "RustDesk desktop client API only — activates Pro license. Blocked from web panel login.", + "role_desc_admin": "Full access (legacy alias for Super Admin).", + "role_desc_super_admin": "Full server and panel access, including user and server configuration.", + "role_desc_server_admin": "Server infrastructure, keys, and read-only user visibility. No device connect.", + "role_desc_global_admin": "Manage all users, organizations, and devices. Cannot change server config.", + "user_folders": "Folder access", + "user_folders_hint": "Devices in selected folders are visible to this user when folder ACL is configured.", + "user_direct_devices": "Direct devices", + "user_direct_devices_hint": "Grant access to specific devices regardless of folder or group membership.", + "user_direct_devices_placeholder": "Enter device IDs separated by commas", + "effective_scope_count": "{count} devices visible", + "loading_folders": "Loading folders...", + "no_folders": "No folders available", + "pro_strategy_label": "RustDesk Pro strategy", + "pro_strategy_hint": "Optional access-control strategy for RustDesk Pro client features (separate from panel role).", + "pro_strategy_none": "None", + "column_scope": "Scope", "user_groups": "User groups", "user_groups_hint": "User groups can be used to grant access to selected device groups.", "loading_user_groups": "Loading user groups...", @@ -1043,8 +1071,8 @@ "security_tips_title": "Security Best Practices", "tip_strong_passwords": "Use strong, unique passwords for each user", "tip_unique_accounts": "Create individual accounts for each person", - "tip_viewer_role": "Use viewer role for users who only need read access", - "tip_operator_role": "Use operator role for users who need to manage devices but not users", + "tip_viewer_role": "Use Viewer for read-only monitoring; Remote Operator for users who need to connect", + "tip_operator_role": "Use Remote Operator for scoped remote users who need Web Client and password self-service", "tip_audit_logs": "Review audit logs regularly for suspicious activity", "organizations": "Organizations", "user_organizations": "Organizations for {username}", diff --git a/web-nodejs/lang/es.json b/web-nodejs/lang/es.json index 4606a73a..e14ff6d7 100644 --- a/web-nodejs/lang/es.json +++ b/web-nodejs/lang/es.json @@ -734,7 +734,15 @@ "public_endpoints_invalid_host": "Hostname ID o relay no válido", "public_endpoints_invalid_api_url": "URL API no válida — use http:// o https://", "public_endpoints_save_title": "¿Guardar endpoints públicos del cliente?", - "public_endpoints_save_confirm": "¿Actualizar valores de configuración RustDesk en el dashboard?" + "public_endpoints_save_confirm": "¿Actualizar valores de configuración RustDesk en el dashboard?", + "device_scope_title": "Device visibility default", + "device_scope_desc": "Controls what non-admin users see when no folder, group, or direct device grants exist.", + "device_scope_open": "Open (legacy) — show all devices until ACL is configured", + "device_scope_restricted": "Restricted — show only explicitly granted devices", + "device_scope_save": "Save visibility default", + "device_scope_saved": "Device visibility default saved", + "device_scope_invalid": "Invalid visibility mode", + "device_scope_restricted_warning": "Restricted mode is active. Ensure users have folder, group, or direct device grants or they will see an empty device list." }, "audit": { "time": "Hora", @@ -963,10 +971,10 @@ "password_leave_empty": "Dejar vacío para mantener la actual", "role": "Rol", "role_admin": "Administrador", - "role_operator": "Operador", + "role_operator": "Remote Operator", "role_viewer": "Observador", - "role_pro": "Profesional", - "role_hint": "Los observadores solo pueden ver datos, los operadores pueden gestionar dispositivos, los administradores tienen acceso completo", + "role_pro": "Pro License (client API only)", + "role_hint": "Server-wide role. Remote Operators connect remotely and can change their own password. Viewers are read-only (no Web Client). Pro License accounts activate RustDesk Pro via the desktop client API only — no web panel access. Organization roles are configured separately.", "created": "Creado", "last_login": "Último inicio de sesión", "never": "Nunca", @@ -1001,8 +1009,8 @@ "security_tips_title": "Buenas prácticas de seguridad", "tip_strong_passwords": "Use contraseñas fuertes y únicas para cada usuario", "tip_unique_accounts": "Cree cuentas individuales para cada persona", - "tip_viewer_role": "Use el rol de observador para usuarios que solo necesitan acceso de lectura", - "tip_operator_role": "Use el rol de operador para usuarios que necesitan gestionar dispositivos pero no usuarios", + "tip_viewer_role": "Use Viewer for read-only monitoring; Remote Operator for users who need to connect", + "tip_operator_role": "Use operator role for users who need to manage devices but not users", "tip_audit_logs": "Revise regularmente los registros de auditoría en busca de actividad sospechosa", "role_super_admin": "Super Admin", "role_server_admin": "Administrador del servidor", @@ -1046,7 +1054,27 @@ "provider_managed_hint": "Esta cuenta está gestionada por un proveedor de identidad externo (LDAP/AD o SSO). La contraseña y el rol son controlados por el proveedor y no se pueden cambiar aquí.", "email": "Correo", "email_placeholder": "operator@example.com", - "email_hint": "Se usa para notificaciones de solicitudes de ayuda cuando el usuario está asignado a carpetas o grupos de dispositivos." + "email_hint": "Se usa para notificaciones de solicitudes de ayuda cuando el usuario está asignado a carpetas o grupos de dispositivos.", + "scope_hint": "Limit which devices this user sees: assign user groups, folders, and/or direct devices. Without restrictions, operators and viewers see all devices (unless the server uses restricted default mode in Settings).", + "role_desc_viewer": "Read-only panel access: device list, audit, metrics. Cannot use Web Remote Desktop.", + "role_desc_operator": "Can connect via Web Client and RustDesk desktop, edit devices, and change own password in Settings.", + "role_desc_pro": "RustDesk desktop client API only — activates Pro license. Blocked from web panel login.", + "role_desc_admin": "Full access (legacy alias for Super Admin).", + "role_desc_super_admin": "Full server and panel access, including user and server configuration.", + "role_desc_server_admin": "Server infrastructure, keys, and read-only user visibility. No device connect.", + "role_desc_global_admin": "Manage all users, organizations, and devices. Cannot change server config.", + "user_folders": "Folder access", + "user_folders_hint": "Devices in selected folders are visible to this user when folder ACL is configured.", + "user_direct_devices": "Direct devices", + "user_direct_devices_hint": "Grant access to specific devices regardless of folder or group membership.", + "user_direct_devices_placeholder": "Enter device IDs separated by commas", + "effective_scope_count": "{count} devices visible", + "loading_folders": "Loading folders...", + "no_folders": "No folders available", + "pro_strategy_label": "RustDesk Pro strategy", + "pro_strategy_hint": "Optional access-control strategy for RustDesk Pro client features (separate from panel role).", + "pro_strategy_none": "None", + "column_scope": "Scope" }, "folders": { "title": "Panel", diff --git a/web-nodejs/lang/fi.json b/web-nodejs/lang/fi.json index 89e540d9..a0fc88e2 100644 --- a/web-nodejs/lang/fi.json +++ b/web-nodejs/lang/fi.json @@ -740,7 +740,15 @@ "public_endpoints_invalid_host": "Virheellinen ID- tai relay-isäntänimi", "public_endpoints_invalid_api_url": "Virheellinen API-URL — käytä http:// tai https://", "public_endpoints_save_title": "Tallenna julkiset asiakaspäätepisteet?", - "public_endpoints_save_confirm": "Päivitä RustDesk-asiakasarvot dashboardilla?" + "public_endpoints_save_confirm": "Päivitä RustDesk-asiakasarvot dashboardilla?", + "device_scope_title": "Device visibility default", + "device_scope_desc": "Controls what non-admin users see when no folder, group, or direct device grants exist.", + "device_scope_open": "Open (legacy) — show all devices until ACL is configured", + "device_scope_restricted": "Restricted — show only explicitly granted devices", + "device_scope_save": "Save visibility default", + "device_scope_saved": "Device visibility default saved", + "device_scope_invalid": "Invalid visibility mode", + "device_scope_restricted_warning": "Restricted mode is active. Ensure users have folder, group, or direct device grants or they will see an empty device list." }, "audit": { "time": "Time", @@ -969,9 +977,9 @@ "password_leave_empty": "Jätä tyhjäksi pitääksesi ajan tasalla", "role": "Role", "role_admin": "Administrator", - "role_operator": "Operator", + "role_operator": "Remote Operator", "role_viewer": "Viewer", - "role_hint": "Viewers can only view data, operators can manage devices, admins have full access", + "role_hint": "Server-wide role. Remote Operators connect remotely and can change their own password. Viewers are read-only (no Web Client). Pro License accounts activate RustDesk Pro via the desktop client API only — no web panel access. Organization roles are configured separately.", "created": "Luotu", "last_login": "Last Login", "never": "Never", @@ -1006,10 +1014,10 @@ "security_tips_title": "Turvallisuuden parhaat käytännöt", "tip_strong_passwords": "Käytä vahvoja, ainutlaatuisia salasanoja jokaiselle käyttäjälle", "tip_unique_accounts": "Luo henkilökohtainen tili jokaiselle henkilölle", - "tip_viewer_role": "Käytä katsojan roolia käyttäjille, jotka tarvitsevat vain lukuoikeuden", - "tip_operator_role": "Käytä operaattoriroolia käyttäjille, joiden on hallittava laitteita, mutta ei käyttäjiä", + "tip_viewer_role": "Use Viewer for read-only monitoring; Remote Operator for users who need to connect", + "tip_operator_role": "Use operator role for users who need to manage devices but not users", "tip_audit_logs": "Tarkista tarkastuslokit säännöllisesti epäilyttävän toiminnan varalta", - "role_pro": "Pro (vain API)", + "role_pro": "Pro License (client API only)", "role_super_admin": "Super Admin", "role_server_admin": "Palvelimen järjestelmänvalvoja", "role_global_admin": "Maailmanlaajuinen järjestelmänvalvoja", @@ -1052,7 +1060,27 @@ "provider_managed_hint": "Tätä tiliä hallitsee ulkoinen identiteettitarjoaja (LDAP/AD tai SSO). Salasana ja rooli ovat tarjoajan hallinnassa.", "email": "Email", "email_placeholder": "operator@example.com", - "email_hint": "Used for help request notifications when the user is assigned to device folders or groups." + "email_hint": "Used for help request notifications when the user is assigned to device folders or groups.", + "scope_hint": "Limit which devices this user sees: assign user groups, folders, and/or direct devices. Without restrictions, operators and viewers see all devices (unless the server uses restricted default mode in Settings).", + "role_desc_viewer": "Read-only panel access: device list, audit, metrics. Cannot use Web Remote Desktop.", + "role_desc_operator": "Can connect via Web Client and RustDesk desktop, edit devices, and change own password in Settings.", + "role_desc_pro": "RustDesk desktop client API only — activates Pro license. Blocked from web panel login.", + "role_desc_admin": "Full access (legacy alias for Super Admin).", + "role_desc_super_admin": "Full server and panel access, including user and server configuration.", + "role_desc_server_admin": "Server infrastructure, keys, and read-only user visibility. No device connect.", + "role_desc_global_admin": "Manage all users, organizations, and devices. Cannot change server config.", + "user_folders": "Folder access", + "user_folders_hint": "Devices in selected folders are visible to this user when folder ACL is configured.", + "user_direct_devices": "Direct devices", + "user_direct_devices_hint": "Grant access to specific devices regardless of folder or group membership.", + "user_direct_devices_placeholder": "Enter device IDs separated by commas", + "effective_scope_count": "{count} devices visible", + "loading_folders": "Loading folders...", + "no_folders": "No folders available", + "pro_strategy_label": "RustDesk Pro strategy", + "pro_strategy_hint": "Optional access-control strategy for RustDesk Pro client features (separate from panel role).", + "pro_strategy_none": "None", + "column_scope": "Scope" }, "folders": { "title": "Folders", diff --git a/web-nodejs/lang/fr.json b/web-nodejs/lang/fr.json index 6a3fab3f..ec746656 100644 --- a/web-nodejs/lang/fr.json +++ b/web-nodejs/lang/fr.json @@ -738,7 +738,15 @@ "public_endpoints_invalid_host": "Nom d’hôte ID ou relais invalide", "public_endpoints_invalid_api_url": "URL API invalide — utilisez http:// ou https://", "public_endpoints_save_title": "Enregistrer les points de terminaison publics ?", - "public_endpoints_save_confirm": "Mettre à jour la configuration client RustDesk du tableau de bord ?" + "public_endpoints_save_confirm": "Mettre à jour la configuration client RustDesk du tableau de bord ?", + "device_scope_title": "Device visibility default", + "device_scope_desc": "Controls what non-admin users see when no folder, group, or direct device grants exist.", + "device_scope_open": "Open (legacy) — show all devices until ACL is configured", + "device_scope_restricted": "Restricted — show only explicitly granted devices", + "device_scope_save": "Save visibility default", + "device_scope_saved": "Device visibility default saved", + "device_scope_invalid": "Invalid visibility mode", + "device_scope_restricted_warning": "Restricted mode is active. Ensure users have folder, group, or direct device grants or they will see an empty device list." }, "audit": { "time": "Heure", @@ -980,13 +988,13 @@ "password_leave_empty": "Laisser vide pour conserver l'actuel", "role": "Rôle", "role_admin": "Administrateur", - "role_operator": "Opérateur", + "role_operator": "Remote Operator", "role_viewer": "Observateur", - "role_pro": "Pro (API only)", + "role_pro": "Pro License (client API only)", "role_super_admin": "Super Admin", "role_server_admin": "Server Admin", "role_global_admin": "Global Admin", - "role_hint": "Les observateurs peuvent uniquement consulter les données, les opérateurs peuvent gérer les appareils, les administrateurs ont un accès complet", + "role_hint": "Server-wide role. Remote Operators connect remotely and can change their own password. Viewers are read-only (no Web Client). Pro License accounts activate RustDesk Pro via the desktop client API only — no web panel access. Organization roles are configured separately.", "user_groups": "Benutzergruppen", "user_groups_hint": "Benutzergruppen können Zugriff auf ausgewählte Gerätegruppen gewähren.", "loading_user_groups": "Benutzergruppen werden geladen...", @@ -1039,8 +1047,8 @@ "security_tips_title": "Bonnes pratiques de sécurité", "tip_strong_passwords": "Utilisez des mots de passe forts et uniques pour chaque utilisateur", "tip_unique_accounts": "Créez des comptes individuels pour chaque personne", - "tip_viewer_role": "Utilisez le rôle observateur pour les utilisateurs qui n'ont besoin que d'un accès en lecture", - "tip_operator_role": "Utilisez le rôle opérateur pour les utilisateurs qui doivent gérer les appareils mais pas les utilisateurs", + "tip_viewer_role": "Use Viewer for read-only monitoring; Remote Operator for users who need to connect", + "tip_operator_role": "Use operator role for users who need to manage devices but not users", "tip_audit_logs": "Vérifiez régulièrement les journaux d'audit pour détecter les activités suspectes", "organizations": "Organizations", "user_organizations": "Organizations for {username}", @@ -1063,7 +1071,27 @@ "all_orgs_assigned": "Der Benutzer ist bereits Mitglied jeder Organisation.", "email": "E-mail", "email_placeholder": "operator@example.com", - "email_hint": "Utilisé pour les notifications de demande d'aide lorsque l'utilisateur est assigné à des dossiers ou groupes d'appareils." + "email_hint": "Utilisé pour les notifications de demande d'aide lorsque l'utilisateur est assigné à des dossiers ou groupes d'appareils.", + "scope_hint": "Limit which devices this user sees: assign user groups, folders, and/or direct devices. Without restrictions, operators and viewers see all devices (unless the server uses restricted default mode in Settings).", + "role_desc_viewer": "Read-only panel access: device list, audit, metrics. Cannot use Web Remote Desktop.", + "role_desc_operator": "Can connect via Web Client and RustDesk desktop, edit devices, and change own password in Settings.", + "role_desc_pro": "RustDesk desktop client API only — activates Pro license. Blocked from web panel login.", + "role_desc_admin": "Full access (legacy alias for Super Admin).", + "role_desc_super_admin": "Full server and panel access, including user and server configuration.", + "role_desc_server_admin": "Server infrastructure, keys, and read-only user visibility. No device connect.", + "role_desc_global_admin": "Manage all users, organizations, and devices. Cannot change server config.", + "user_folders": "Folder access", + "user_folders_hint": "Devices in selected folders are visible to this user when folder ACL is configured.", + "user_direct_devices": "Direct devices", + "user_direct_devices_hint": "Grant access to specific devices regardless of folder or group membership.", + "user_direct_devices_placeholder": "Enter device IDs separated by commas", + "effective_scope_count": "{count} devices visible", + "loading_folders": "Loading folders...", + "no_folders": "No folders available", + "pro_strategy_label": "RustDesk Pro strategy", + "pro_strategy_hint": "Optional access-control strategy for RustDesk Pro client features (separate from panel role).", + "pro_strategy_none": "None", + "column_scope": "Scope" }, "folders": { "title": "Tableau de bord", diff --git a/web-nodejs/lang/hi.json b/web-nodejs/lang/hi.json index d3176690..f10d44d4 100644 --- a/web-nodejs/lang/hi.json +++ b/web-nodejs/lang/hi.json @@ -740,7 +740,15 @@ "public_endpoints_invalid_host": "अमान्य ID या relay होस्टनाम", "public_endpoints_invalid_api_url": "अमान्य API URL — http:// या https://", "public_endpoints_save_title": "सार्वजनिक क्लाइंट एंडपॉइंट सहेजें?", - "public_endpoints_save_confirm": "डैशबोर्ड RustDesk कॉन्फ़िग अपडेट करें?" + "public_endpoints_save_confirm": "डैशबोर्ड RustDesk कॉन्फ़िग अपडेट करें?", + "device_scope_title": "Device visibility default", + "device_scope_desc": "Controls what non-admin users see when no folder, group, or direct device grants exist.", + "device_scope_open": "Open (legacy) — show all devices until ACL is configured", + "device_scope_restricted": "Restricted — show only explicitly granted devices", + "device_scope_save": "Save visibility default", + "device_scope_saved": "Device visibility default saved", + "device_scope_invalid": "Invalid visibility mode", + "device_scope_restricted_warning": "Restricted mode is active. Ensure users have folder, group, or direct device grants or they will see an empty device list." }, "audit": { "time": "Time", @@ -969,9 +977,9 @@ "password_leave_empty": "चालू रखने के लिए खाली छोड़ें", "role": "Role", "role_admin": "Administrator", - "role_operator": "Operator", + "role_operator": "Remote Operator", "role_viewer": "Viewer", - "role_hint": "Viewers can only view data, operators can manage devices, admins have full access", + "role_hint": "Server-wide role. Remote Operators connect remotely and can change their own password. Viewers are read-only (no Web Client). Pro License accounts activate RustDesk Pro via the desktop client API only — no web panel access. Organization roles are configured separately.", "created": "बनाया था", "last_login": "Last Login", "never": "Never", @@ -1006,10 +1014,10 @@ "security_tips_title": "सुरक्षा सर्वोत्तम अभ्यास", "tip_strong_passwords": "प्रत्येक उपयोगकर्ता के लिए मजबूत, अद्वितीय पासवर्ड का उपयोग करें", "tip_unique_accounts": "प्रत्येक व्यक्ति के लिए अलग-अलग खाते बनाएं", - "tip_viewer_role": "उन उपयोगकर्ताओं के लिए दर्शक भूमिका का उपयोग करें जिन्हें केवल पढ़ने की पहुंच की आवश्यकता है", - "tip_operator_role": "उन उपयोगकर्ताओं के लिए ऑपरेटर भूमिका का उपयोग करें जिन्हें डिवाइस प्रबंधित करने की आवश्यकता है लेकिन उपयोगकर्ताओं की नहीं", + "tip_viewer_role": "Use Viewer for read-only monitoring; Remote Operator for users who need to connect", + "tip_operator_role": "Use operator role for users who need to manage devices but not users", "tip_audit_logs": "संदिग्ध गतिविधि के लिए नियमित रूप से ऑडिट लॉग की समीक्षा करें", - "role_pro": "प्रो (केवल API)", + "role_pro": "Pro License (client API only)", "role_super_admin": "Super Admin", "role_server_admin": "सर्वर व्यवस्थापक", "role_global_admin": "वैश्विक प्रशासन", @@ -1052,7 +1060,27 @@ "provider_managed_hint": "यह खाता बाहरी पहचान प्रदाता (LDAP/AD या SSO) द्वारा प्रबंधित है। पासवर्ड और भूमिका प्रदाता द्वारा नियंत्रित हैं और यहाँ नहीं बदले जा सकते।", "email": "Email", "email_placeholder": "operator@example.com", - "email_hint": "Used for help request notifications when the user is assigned to device folders or groups." + "email_hint": "Used for help request notifications when the user is assigned to device folders or groups.", + "scope_hint": "Limit which devices this user sees: assign user groups, folders, and/or direct devices. Without restrictions, operators and viewers see all devices (unless the server uses restricted default mode in Settings).", + "role_desc_viewer": "Read-only panel access: device list, audit, metrics. Cannot use Web Remote Desktop.", + "role_desc_operator": "Can connect via Web Client and RustDesk desktop, edit devices, and change own password in Settings.", + "role_desc_pro": "RustDesk desktop client API only — activates Pro license. Blocked from web panel login.", + "role_desc_admin": "Full access (legacy alias for Super Admin).", + "role_desc_super_admin": "Full server and panel access, including user and server configuration.", + "role_desc_server_admin": "Server infrastructure, keys, and read-only user visibility. No device connect.", + "role_desc_global_admin": "Manage all users, organizations, and devices. Cannot change server config.", + "user_folders": "Folder access", + "user_folders_hint": "Devices in selected folders are visible to this user when folder ACL is configured.", + "user_direct_devices": "Direct devices", + "user_direct_devices_hint": "Grant access to specific devices regardless of folder or group membership.", + "user_direct_devices_placeholder": "Enter device IDs separated by commas", + "effective_scope_count": "{count} devices visible", + "loading_folders": "Loading folders...", + "no_folders": "No folders available", + "pro_strategy_label": "RustDesk Pro strategy", + "pro_strategy_hint": "Optional access-control strategy for RustDesk Pro client features (separate from panel role).", + "pro_strategy_none": "None", + "column_scope": "Scope" }, "folders": { "title": "Folders", diff --git a/web-nodejs/lang/hu.json b/web-nodejs/lang/hu.json index 7f647909..f85d9118 100644 --- a/web-nodejs/lang/hu.json +++ b/web-nodejs/lang/hu.json @@ -740,7 +740,15 @@ "public_endpoints_invalid_host": "Érvénytelen ID vagy relay hostname", "public_endpoints_invalid_api_url": "Érvénytelen API URL — http:// vagy https://", "public_endpoints_save_title": "Nyilvános kliens végpontok mentése?", - "public_endpoints_save_confirm": "Frissíti a dashboard RustDesk beállításait?" + "public_endpoints_save_confirm": "Frissíti a dashboard RustDesk beállításait?", + "device_scope_title": "Device visibility default", + "device_scope_desc": "Controls what non-admin users see when no folder, group, or direct device grants exist.", + "device_scope_open": "Open (legacy) — show all devices until ACL is configured", + "device_scope_restricted": "Restricted — show only explicitly granted devices", + "device_scope_save": "Save visibility default", + "device_scope_saved": "Device visibility default saved", + "device_scope_invalid": "Invalid visibility mode", + "device_scope_restricted_warning": "Restricted mode is active. Ensure users have folder, group, or direct device grants or they will see an empty device list." }, "audit": { "time": "Time", @@ -969,9 +977,9 @@ "password_leave_empty": "Hagyja üresen, hogy az aktuális maradjon", "role": "Role", "role_admin": "Administrator", - "role_operator": "Operator", + "role_operator": "Remote Operator", "role_viewer": "Viewer", - "role_hint": "Viewers can only view data, operators can manage devices, admins have full access", + "role_hint": "Server-wide role. Remote Operators connect remotely and can change their own password. Viewers are read-only (no Web Client). Pro License accounts activate RustDesk Pro via the desktop client API only — no web panel access. Organization roles are configured separately.", "created": "Létrehozva", "last_login": "Last Login", "never": "Never", @@ -1006,10 +1014,10 @@ "security_tips_title": "Biztonsági bevált gyakorlatok", "tip_strong_passwords": "Használjon erős, egyedi jelszavakat minden felhasználóhoz", "tip_unique_accounts": "Hozzon létre egyéni fiókokat minden egyes személy számára", - "tip_viewer_role": "Nézői szerepkör használata olyan felhasználók számára, akiknek csak olvasási hozzáférésre van szükségük", - "tip_operator_role": "Operátori szerepkör használata olyan felhasználók számára, akiknek eszközöket kell kezelniük, de a felhasználókat nem", + "tip_viewer_role": "Use Viewer for read-only monitoring; Remote Operator for users who need to connect", + "tip_operator_role": "Use operator role for users who need to manage devices but not users", "tip_audit_logs": "Rendszeresen tekintse át az auditnaplókat a gyanús tevékenységekért", - "role_pro": "Pro (csak API)", + "role_pro": "Pro License (client API only)", "role_super_admin": "Super Admin", "role_server_admin": "Szerver Adminisztrátor", "role_global_admin": "Globális rendszergazda", @@ -1052,7 +1060,27 @@ "provider_managed_hint": "Ezt a fiókot egy külső identitásszolgáltató (LDAP/AD vagy SSO) kezeli. A jelszót és szerepkört a szolgáltató irányítja.", "email": "Email", "email_placeholder": "operator@example.com", - "email_hint": "Used for help request notifications when the user is assigned to device folders or groups." + "email_hint": "Used for help request notifications when the user is assigned to device folders or groups.", + "scope_hint": "Limit which devices this user sees: assign user groups, folders, and/or direct devices. Without restrictions, operators and viewers see all devices (unless the server uses restricted default mode in Settings).", + "role_desc_viewer": "Read-only panel access: device list, audit, metrics. Cannot use Web Remote Desktop.", + "role_desc_operator": "Can connect via Web Client and RustDesk desktop, edit devices, and change own password in Settings.", + "role_desc_pro": "RustDesk desktop client API only — activates Pro license. Blocked from web panel login.", + "role_desc_admin": "Full access (legacy alias for Super Admin).", + "role_desc_super_admin": "Full server and panel access, including user and server configuration.", + "role_desc_server_admin": "Server infrastructure, keys, and read-only user visibility. No device connect.", + "role_desc_global_admin": "Manage all users, organizations, and devices. Cannot change server config.", + "user_folders": "Folder access", + "user_folders_hint": "Devices in selected folders are visible to this user when folder ACL is configured.", + "user_direct_devices": "Direct devices", + "user_direct_devices_hint": "Grant access to specific devices regardless of folder or group membership.", + "user_direct_devices_placeholder": "Enter device IDs separated by commas", + "effective_scope_count": "{count} devices visible", + "loading_folders": "Loading folders...", + "no_folders": "No folders available", + "pro_strategy_label": "RustDesk Pro strategy", + "pro_strategy_hint": "Optional access-control strategy for RustDesk Pro client features (separate from panel role).", + "pro_strategy_none": "None", + "column_scope": "Scope" }, "folders": { "title": "Folders", diff --git a/web-nodejs/lang/id.json b/web-nodejs/lang/id.json index 12e83d85..453ddbb7 100644 --- a/web-nodejs/lang/id.json +++ b/web-nodejs/lang/id.json @@ -740,7 +740,15 @@ "public_endpoints_invalid_host": "Hostname ID atau relay tidak valid", "public_endpoints_invalid_api_url": "URL API tidak valid — gunakan http:// atau https://", "public_endpoints_save_title": "Simpan endpoint klien publik?", - "public_endpoints_save_confirm": "Perbarui konfigurasi klien RustDesk di dashboard?" + "public_endpoints_save_confirm": "Perbarui konfigurasi klien RustDesk di dashboard?", + "device_scope_title": "Device visibility default", + "device_scope_desc": "Controls what non-admin users see when no folder, group, or direct device grants exist.", + "device_scope_open": "Open (legacy) — show all devices until ACL is configured", + "device_scope_restricted": "Restricted — show only explicitly granted devices", + "device_scope_save": "Save visibility default", + "device_scope_saved": "Device visibility default saved", + "device_scope_invalid": "Invalid visibility mode", + "device_scope_restricted_warning": "Restricted mode is active. Ensure users have folder, group, or direct device grants or they will see an empty device list." }, "audit": { "time": "Time", @@ -969,9 +977,9 @@ "password_leave_empty": "Biarkan kosong agar tetap terkini", "role": "Role", "role_admin": "Administrator", - "role_operator": "Operator", + "role_operator": "Remote Operator", "role_viewer": "Viewer", - "role_hint": "Viewers can only view data, operators can manage devices, admins have full access", + "role_hint": "Server-wide role. Remote Operators connect remotely and can change their own password. Viewers are read-only (no Web Client). Pro License accounts activate RustDesk Pro via the desktop client API only — no web panel access. Organization roles are configured separately.", "created": "Dibuat", "last_login": "Last Login", "never": "Never", @@ -1006,10 +1014,10 @@ "security_tips_title": "Praktik Terbaik Keamanan", "tip_strong_passwords": "Gunakan kata sandi yang kuat dan unik untuk setiap pengguna", "tip_unique_accounts": "Buat akun individual untuk setiap orang", - "tip_viewer_role": "Gunakan peran pemirsa untuk pengguna yang hanya membutuhkan akses baca", - "tip_operator_role": "Gunakan peran operator untuk pengguna yang perlu mengelola perangkat tetapi bukan pengguna", + "tip_viewer_role": "Use Viewer for read-only monitoring; Remote Operator for users who need to connect", + "tip_operator_role": "Use operator role for users who need to manage devices but not users", "tip_audit_logs": "Tinjau log audit secara teratur untuk mengetahui adanya aktivitas mencurigakan", - "role_pro": "Pro (hanya API)", + "role_pro": "Pro License (client API only)", "role_super_admin": "Super Admin", "role_server_admin": "Admin Server", "role_global_admin": "Admin Sedunia", @@ -1052,7 +1060,27 @@ "provider_managed_hint": "Akun ini dikelola oleh penyedia identitas eksternal (LDAP/AD atau SSO). Kata sandi dan peran dikendalikan oleh penyedia dan tidak dapat diubah di sini.", "email": "Email", "email_placeholder": "operator@example.com", - "email_hint": "Used for help request notifications when the user is assigned to device folders or groups." + "email_hint": "Used for help request notifications when the user is assigned to device folders or groups.", + "scope_hint": "Limit which devices this user sees: assign user groups, folders, and/or direct devices. Without restrictions, operators and viewers see all devices (unless the server uses restricted default mode in Settings).", + "role_desc_viewer": "Read-only panel access: device list, audit, metrics. Cannot use Web Remote Desktop.", + "role_desc_operator": "Can connect via Web Client and RustDesk desktop, edit devices, and change own password in Settings.", + "role_desc_pro": "RustDesk desktop client API only — activates Pro license. Blocked from web panel login.", + "role_desc_admin": "Full access (legacy alias for Super Admin).", + "role_desc_super_admin": "Full server and panel access, including user and server configuration.", + "role_desc_server_admin": "Server infrastructure, keys, and read-only user visibility. No device connect.", + "role_desc_global_admin": "Manage all users, organizations, and devices. Cannot change server config.", + "user_folders": "Folder access", + "user_folders_hint": "Devices in selected folders are visible to this user when folder ACL is configured.", + "user_direct_devices": "Direct devices", + "user_direct_devices_hint": "Grant access to specific devices regardless of folder or group membership.", + "user_direct_devices_placeholder": "Enter device IDs separated by commas", + "effective_scope_count": "{count} devices visible", + "loading_folders": "Loading folders...", + "no_folders": "No folders available", + "pro_strategy_label": "RustDesk Pro strategy", + "pro_strategy_hint": "Optional access-control strategy for RustDesk Pro client features (separate from panel role).", + "pro_strategy_none": "None", + "column_scope": "Scope" }, "folders": { "title": "Folders", diff --git a/web-nodejs/lang/it.json b/web-nodejs/lang/it.json index c339cf5d..2eba728a 100644 --- a/web-nodejs/lang/it.json +++ b/web-nodejs/lang/it.json @@ -734,7 +734,15 @@ "public_endpoints_invalid_host": "Hostname ID o relay non valido", "public_endpoints_invalid_api_url": "URL API non valida — usare http:// o https://", "public_endpoints_save_title": "Salvare gli endpoint client pubblici?", - "public_endpoints_save_confirm": "Aggiornare la configurazione client RustDesk nel dashboard?" + "public_endpoints_save_confirm": "Aggiornare la configurazione client RustDesk nel dashboard?", + "device_scope_title": "Device visibility default", + "device_scope_desc": "Controls what non-admin users see when no folder, group, or direct device grants exist.", + "device_scope_open": "Open (legacy) — show all devices until ACL is configured", + "device_scope_restricted": "Restricted — show only explicitly granted devices", + "device_scope_save": "Save visibility default", + "device_scope_saved": "Device visibility default saved", + "device_scope_invalid": "Invalid visibility mode", + "device_scope_restricted_warning": "Restricted mode is active. Ensure users have folder, group, or direct device grants or they will see an empty device list." }, "audit": { "time": "Ora", @@ -963,9 +971,9 @@ "password_leave_empty": "Lascia vuoto per mantenere quella attuale", "role": "Ruolo", "role_admin": "Amministratore", - "role_operator": "Operatore", + "role_operator": "Remote Operator", "role_viewer": "Osservatore", - "role_hint": "Gli osservatori possono solo visualizzare i dati, gli operatori possono gestire i dispositivi, gli amministratori hanno accesso completo", + "role_hint": "Server-wide role. Remote Operators connect remotely and can change their own password. Viewers are read-only (no Web Client). Pro License accounts activate RustDesk Pro via the desktop client API only — no web panel access. Organization roles are configured separately.", "created": "Creato", "last_login": "Ultimo accesso", "never": "Mai", @@ -1000,10 +1008,10 @@ "security_tips_title": "Best practice di sicurezza", "tip_strong_passwords": "Usa password forti e uniche per ogni utente", "tip_unique_accounts": "Crea account individuali per ogni persona", - "tip_viewer_role": "Usa il ruolo osservatore per gli utenti che necessitano solo di accesso in lettura", - "tip_operator_role": "Usa il ruolo operatore per gli utenti che devono gestire i dispositivi ma non gli utenti", + "tip_viewer_role": "Use Viewer for read-only monitoring; Remote Operator for users who need to connect", + "tip_operator_role": "Use operator role for users who need to manage devices but not users", "tip_audit_logs": "Controlla regolarmente i registri di audit per attività sospette", - "role_pro": "Professionale", + "role_pro": "Pro License (client API only)", "role_super_admin": "Super Admin", "role_server_admin": "Amministratore server", "role_global_admin": "Amministratore globale", @@ -1046,7 +1054,27 @@ "provider_managed_hint": "Ce compte est gèrè par un provider d'identitè esterno (LDAP/AD ou SSO). Le password et le rôle sont contrôlès par le provider et ne peuvent pas ètre modifiès ici.", "email": "Email", "email_placeholder": "operator@example.com", - "email_hint": "Used for help request notifications when the user is assigned to device folders or groups." + "email_hint": "Used for help request notifications when the user is assigned to device folders or groups.", + "scope_hint": "Limit which devices this user sees: assign user groups, folders, and/or direct devices. Without restrictions, operators and viewers see all devices (unless the server uses restricted default mode in Settings).", + "role_desc_viewer": "Read-only panel access: device list, audit, metrics. Cannot use Web Remote Desktop.", + "role_desc_operator": "Can connect via Web Client and RustDesk desktop, edit devices, and change own password in Settings.", + "role_desc_pro": "RustDesk desktop client API only — activates Pro license. Blocked from web panel login.", + "role_desc_admin": "Full access (legacy alias for Super Admin).", + "role_desc_super_admin": "Full server and panel access, including user and server configuration.", + "role_desc_server_admin": "Server infrastructure, keys, and read-only user visibility. No device connect.", + "role_desc_global_admin": "Manage all users, organizations, and devices. Cannot change server config.", + "user_folders": "Folder access", + "user_folders_hint": "Devices in selected folders are visible to this user when folder ACL is configured.", + "user_direct_devices": "Direct devices", + "user_direct_devices_hint": "Grant access to specific devices regardless of folder or group membership.", + "user_direct_devices_placeholder": "Enter device IDs separated by commas", + "effective_scope_count": "{count} devices visible", + "loading_folders": "Loading folders...", + "no_folders": "No folders available", + "pro_strategy_label": "RustDesk Pro strategy", + "pro_strategy_hint": "Optional access-control strategy for RustDesk Pro client features (separate from panel role).", + "pro_strategy_none": "None", + "column_scope": "Scope" }, "folders": { "title": "Cartelle", diff --git a/web-nodejs/lang/ja.json b/web-nodejs/lang/ja.json index 83b4e88d..7643ea77 100644 --- a/web-nodejs/lang/ja.json +++ b/web-nodejs/lang/ja.json @@ -734,7 +734,15 @@ "public_endpoints_invalid_host": "ID またはリレーのホスト名が無効です", "public_endpoints_invalid_api_url": "API URL が無効 — http:// または https:// を使用", "public_endpoints_save_title": "公開クライアントエンドポイントを保存しますか?", - "public_endpoints_save_confirm": "ダッシュボードの RustDesk 設定値を更新しますか?" + "public_endpoints_save_confirm": "ダッシュボードの RustDesk 設定値を更新しますか?", + "device_scope_title": "Device visibility default", + "device_scope_desc": "Controls what non-admin users see when no folder, group, or direct device grants exist.", + "device_scope_open": "Open (legacy) — show all devices until ACL is configured", + "device_scope_restricted": "Restricted — show only explicitly granted devices", + "device_scope_save": "Save visibility default", + "device_scope_saved": "Device visibility default saved", + "device_scope_invalid": "Invalid visibility mode", + "device_scope_restricted_warning": "Restricted mode is active. Ensure users have folder, group, or direct device grants or they will see an empty device list." }, "audit": { "time": "Time", @@ -963,9 +971,9 @@ "password_leave_empty": "最新の状態を維持するには空のままにしておきます", "role": "Role", "role_admin": "Administrator", - "role_operator": "Operator", + "role_operator": "Remote Operator", "role_viewer": "Viewer", - "role_hint": "Viewers can only view data, operators can manage devices, admins have full access", + "role_hint": "Server-wide role. Remote Operators connect remotely and can change their own password. Viewers are read-only (no Web Client). Pro License accounts activate RustDesk Pro via the desktop client API only — no web panel access. Organization roles are configured separately.", "created": "作成されました", "last_login": "Last Login", "never": "Never", @@ -1000,10 +1008,10 @@ "security_tips_title": "セキュリティのベストプラクティス", "tip_strong_passwords": "ユーザーごとに強力で一意のパスワードを使用する", "tip_unique_accounts": "各人に個別のアカウントを作成する", - "tip_viewer_role": "読み取りアクセスのみが必要なユーザーには閲覧者ロールを使用します", - "tip_operator_role": "ユーザーではなくデバイスを管理する必要があるユーザーにはオペレーターの役割を使用します。", + "tip_viewer_role": "Use Viewer for read-only monitoring; Remote Operator for users who need to connect", + "tip_operator_role": "Use operator role for users who need to manage devices but not users", "tip_audit_logs": "監査ログを定期的に確認して、不審なアクティビティがないか確認する", - "role_pro": "プロ (API のみ)", + "role_pro": "Pro License (client API only)", "role_super_admin": "Super Admin", "role_server_admin": "サーバー管理者", "role_global_admin": "グローバル管理者", @@ -1046,7 +1054,27 @@ "provider_managed_hint": "このアカウントは外部 IDプロバイダー(LDAP/AD または SSO)によって管理されています。パスワードとロールはプロバイダーが制御しており、ここでは変更できません。", "email": "Email", "email_placeholder": "operator@example.com", - "email_hint": "Used for help request notifications when the user is assigned to device folders or groups." + "email_hint": "Used for help request notifications when the user is assigned to device folders or groups.", + "scope_hint": "Limit which devices this user sees: assign user groups, folders, and/or direct devices. Without restrictions, operators and viewers see all devices (unless the server uses restricted default mode in Settings).", + "role_desc_viewer": "Read-only panel access: device list, audit, metrics. Cannot use Web Remote Desktop.", + "role_desc_operator": "Can connect via Web Client and RustDesk desktop, edit devices, and change own password in Settings.", + "role_desc_pro": "RustDesk desktop client API only — activates Pro license. Blocked from web panel login.", + "role_desc_admin": "Full access (legacy alias for Super Admin).", + "role_desc_super_admin": "Full server and panel access, including user and server configuration.", + "role_desc_server_admin": "Server infrastructure, keys, and read-only user visibility. No device connect.", + "role_desc_global_admin": "Manage all users, organizations, and devices. Cannot change server config.", + "user_folders": "Folder access", + "user_folders_hint": "Devices in selected folders are visible to this user when folder ACL is configured.", + "user_direct_devices": "Direct devices", + "user_direct_devices_hint": "Grant access to specific devices regardless of folder or group membership.", + "user_direct_devices_placeholder": "Enter device IDs separated by commas", + "effective_scope_count": "{count} devices visible", + "loading_folders": "Loading folders...", + "no_folders": "No folders available", + "pro_strategy_label": "RustDesk Pro strategy", + "pro_strategy_hint": "Optional access-control strategy for RustDesk Pro client features (separate from panel role).", + "pro_strategy_none": "None", + "column_scope": "Scope" }, "folders": { "title": "Folders", diff --git a/web-nodejs/lang/ko.json b/web-nodejs/lang/ko.json index 54ed8b89..f111fb02 100644 --- a/web-nodejs/lang/ko.json +++ b/web-nodejs/lang/ko.json @@ -734,7 +734,15 @@ "public_endpoints_invalid_host": "ID 또는 relay 호스트명이 잘못됨", "public_endpoints_invalid_api_url": "API URL이 잘못됨 — http:// 또는 https:// 사용", "public_endpoints_save_title": "공개 클라이언트 엔드포인트를 저장할까요?", - "public_endpoints_save_confirm": "대시보드 RustDesk 설정 값을 업데이트할까요?" + "public_endpoints_save_confirm": "대시보드 RustDesk 설정 값을 업데이트할까요?", + "device_scope_title": "Device visibility default", + "device_scope_desc": "Controls what non-admin users see when no folder, group, or direct device grants exist.", + "device_scope_open": "Open (legacy) — show all devices until ACL is configured", + "device_scope_restricted": "Restricted — show only explicitly granted devices", + "device_scope_save": "Save visibility default", + "device_scope_saved": "Device visibility default saved", + "device_scope_invalid": "Invalid visibility mode", + "device_scope_restricted_warning": "Restricted mode is active. Ensure users have folder, group, or direct device grants or they will see an empty device list." }, "audit": { "time": "Time", @@ -963,9 +971,9 @@ "password_leave_empty": "현재 상태를 유지하려면 비워 두세요.", "role": "Role", "role_admin": "Administrator", - "role_operator": "Operator", + "role_operator": "Remote Operator", "role_viewer": "Viewer", - "role_hint": "Viewers can only view data, operators can manage devices, admins have full access", + "role_hint": "Server-wide role. Remote Operators connect remotely and can change their own password. Viewers are read-only (no Web Client). Pro License accounts activate RustDesk Pro via the desktop client API only — no web panel access. Organization roles are configured separately.", "created": "생성됨", "last_login": "Last Login", "never": "Never", @@ -1000,10 +1008,10 @@ "security_tips_title": "보안 모범 사례", "tip_strong_passwords": "각 사용자마다 강력하고 고유한 비밀번호를 사용하세요.", "tip_unique_accounts": "각 사람에 대한 개별 계정 만들기", - "tip_viewer_role": "읽기 액세스만 필요한 사용자에게는 뷰어 역할을 사용하세요.", - "tip_operator_role": "장치를 관리해야 하지만 사용자는 관리하지 않는 사용자에게 운영자 역할을 사용하세요.", + "tip_viewer_role": "Use Viewer for read-only monitoring; Remote Operator for users who need to connect", + "tip_operator_role": "Use operator role for users who need to manage devices but not users", "tip_audit_logs": "의심스러운 활동이 있는지 정기적으로 감사 로그를 검토하세요.", - "role_pro": "Pro(API만 해당)", + "role_pro": "Pro License (client API only)", "role_super_admin": "Super Admin", "role_server_admin": "서버 관리자", "role_global_admin": "글로벌 관리자", @@ -1046,7 +1054,27 @@ "provider_managed_hint": "이 계정은 외부 ID 공급자(LDAP/AD 또는 SSO)에 의해 관리됩니다. 비밀번호와 역할은 공급자가 제어하며 여기서 변경할 수 없습니다.", "email": "Email", "email_placeholder": "operator@example.com", - "email_hint": "Used for help request notifications when the user is assigned to device folders or groups." + "email_hint": "Used for help request notifications when the user is assigned to device folders or groups.", + "scope_hint": "Limit which devices this user sees: assign user groups, folders, and/or direct devices. Without restrictions, operators and viewers see all devices (unless the server uses restricted default mode in Settings).", + "role_desc_viewer": "Read-only panel access: device list, audit, metrics. Cannot use Web Remote Desktop.", + "role_desc_operator": "Can connect via Web Client and RustDesk desktop, edit devices, and change own password in Settings.", + "role_desc_pro": "RustDesk desktop client API only — activates Pro license. Blocked from web panel login.", + "role_desc_admin": "Full access (legacy alias for Super Admin).", + "role_desc_super_admin": "Full server and panel access, including user and server configuration.", + "role_desc_server_admin": "Server infrastructure, keys, and read-only user visibility. No device connect.", + "role_desc_global_admin": "Manage all users, organizations, and devices. Cannot change server config.", + "user_folders": "Folder access", + "user_folders_hint": "Devices in selected folders are visible to this user when folder ACL is configured.", + "user_direct_devices": "Direct devices", + "user_direct_devices_hint": "Grant access to specific devices regardless of folder or group membership.", + "user_direct_devices_placeholder": "Enter device IDs separated by commas", + "effective_scope_count": "{count} devices visible", + "loading_folders": "Loading folders...", + "no_folders": "No folders available", + "pro_strategy_label": "RustDesk Pro strategy", + "pro_strategy_hint": "Optional access-control strategy for RustDesk Pro client features (separate from panel role).", + "pro_strategy_none": "None", + "column_scope": "Scope" }, "folders": { "title": "Folders", diff --git a/web-nodejs/lang/nb.json b/web-nodejs/lang/nb.json index 15e401e1..7dd93a63 100644 --- a/web-nodejs/lang/nb.json +++ b/web-nodejs/lang/nb.json @@ -740,7 +740,15 @@ "public_endpoints_invalid_host": "Ugyldig ID- eller relay-vertsnavn", "public_endpoints_invalid_api_url": "Ugyldig API-URL — bruk http:// eller https://", "public_endpoints_save_title": "Lagre offentlige klient-endepunkter?", - "public_endpoints_save_confirm": "Oppdatere RustDesk-klientverdier på dashboardet?" + "public_endpoints_save_confirm": "Oppdatere RustDesk-klientverdier på dashboardet?", + "device_scope_title": "Device visibility default", + "device_scope_desc": "Controls what non-admin users see when no folder, group, or direct device grants exist.", + "device_scope_open": "Open (legacy) — show all devices until ACL is configured", + "device_scope_restricted": "Restricted — show only explicitly granted devices", + "device_scope_save": "Save visibility default", + "device_scope_saved": "Device visibility default saved", + "device_scope_invalid": "Invalid visibility mode", + "device_scope_restricted_warning": "Restricted mode is active. Ensure users have folder, group, or direct device grants or they will see an empty device list." }, "audit": { "time": "Time", @@ -969,9 +977,9 @@ "password_leave_empty": "La stå tomt for å holde deg oppdatert", "role": "Role", "role_admin": "Administrator", - "role_operator": "Operator", + "role_operator": "Remote Operator", "role_viewer": "Viewer", - "role_hint": "Viewers can only view data, operators can manage devices, admins have full access", + "role_hint": "Server-wide role. Remote Operators connect remotely and can change their own password. Viewers are read-only (no Web Client). Pro License accounts activate RustDesk Pro via the desktop client API only — no web panel access. Organization roles are configured separately.", "created": "Opprettet", "last_login": "Last Login", "never": "Never", @@ -1006,10 +1014,10 @@ "security_tips_title": "Beste praksis for sikkerhet", "tip_strong_passwords": "Bruk sterke, unike passord for hver bruker", "tip_unique_accounts": "Opprett individuelle kontoer for hver person", - "tip_viewer_role": "Bruk seerrolle for brukere som bare trenger lesetilgang", - "tip_operator_role": "Bruk operatørrolle for brukere som trenger å administrere enheter, men ikke brukere", + "tip_viewer_role": "Use Viewer for read-only monitoring; Remote Operator for users who need to connect", + "tip_operator_role": "Use operator role for users who need to manage devices but not users", "tip_audit_logs": "Gjennomgå revisjonslogger regelmessig for mistenkelig aktivitet", - "role_pro": "Pro (kun API)", + "role_pro": "Pro License (client API only)", "role_super_admin": "Super Admin", "role_server_admin": "Serveradministrator", "role_global_admin": "Overordnet administrator", @@ -1052,7 +1060,27 @@ "provider_managed_hint": "Denne kontoen administreres av en ekstern identitetsleverandør (LDAP/AD eller SSO).", "email": "Email", "email_placeholder": "operator@example.com", - "email_hint": "Used for help request notifications when the user is assigned to device folders or groups." + "email_hint": "Used for help request notifications when the user is assigned to device folders or groups.", + "scope_hint": "Limit which devices this user sees: assign user groups, folders, and/or direct devices. Without restrictions, operators and viewers see all devices (unless the server uses restricted default mode in Settings).", + "role_desc_viewer": "Read-only panel access: device list, audit, metrics. Cannot use Web Remote Desktop.", + "role_desc_operator": "Can connect via Web Client and RustDesk desktop, edit devices, and change own password in Settings.", + "role_desc_pro": "RustDesk desktop client API only — activates Pro license. Blocked from web panel login.", + "role_desc_admin": "Full access (legacy alias for Super Admin).", + "role_desc_super_admin": "Full server and panel access, including user and server configuration.", + "role_desc_server_admin": "Server infrastructure, keys, and read-only user visibility. No device connect.", + "role_desc_global_admin": "Manage all users, organizations, and devices. Cannot change server config.", + "user_folders": "Folder access", + "user_folders_hint": "Devices in selected folders are visible to this user when folder ACL is configured.", + "user_direct_devices": "Direct devices", + "user_direct_devices_hint": "Grant access to specific devices regardless of folder or group membership.", + "user_direct_devices_placeholder": "Enter device IDs separated by commas", + "effective_scope_count": "{count} devices visible", + "loading_folders": "Loading folders...", + "no_folders": "No folders available", + "pro_strategy_label": "RustDesk Pro strategy", + "pro_strategy_hint": "Optional access-control strategy for RustDesk Pro client features (separate from panel role).", + "pro_strategy_none": "None", + "column_scope": "Scope" }, "folders": { "title": "Folders", diff --git a/web-nodejs/lang/nl.json b/web-nodejs/lang/nl.json index 52e23b6f..552f71cf 100644 --- a/web-nodejs/lang/nl.json +++ b/web-nodejs/lang/nl.json @@ -734,7 +734,15 @@ "public_endpoints_invalid_host": "Ongeldige ID- of relay-hostnaam", "public_endpoints_invalid_api_url": "Ongeldige API-URL — gebruik http:// of https://", "public_endpoints_save_title": "Openbare client-eindpunten opslaan?", - "public_endpoints_save_confirm": "RustDesk-clientwaarden op het dashboard bijwerken?" + "public_endpoints_save_confirm": "RustDesk-clientwaarden op het dashboard bijwerken?", + "device_scope_title": "Device visibility default", + "device_scope_desc": "Controls what non-admin users see when no folder, group, or direct device grants exist.", + "device_scope_open": "Open (legacy) — show all devices until ACL is configured", + "device_scope_restricted": "Restricted — show only explicitly granted devices", + "device_scope_save": "Save visibility default", + "device_scope_saved": "Device visibility default saved", + "device_scope_invalid": "Invalid visibility mode", + "device_scope_restricted_warning": "Restricted mode is active. Ensure users have folder, group, or direct device grants or they will see an empty device list." }, "audit": { "time": "Tijd", @@ -963,9 +971,9 @@ "password_leave_empty": "Leeg laten om huidig te behouden", "role": "Rol", "role_admin": "Beheerder", - "role_operator": "Operator", + "role_operator": "Remote Operator", "role_viewer": "Waarnemer", - "role_hint": "Waarnemers kunnen alleen gegevens bekijken, operators kunnen apparaten beheren, beheerders hebben volledige toegang", + "role_hint": "Server-wide role. Remote Operators connect remotely and can change their own password. Viewers are read-only (no Web Client). Pro License accounts activate RustDesk Pro via the desktop client API only — no web panel access. Organization roles are configured separately.", "created": "Aangemaakt", "last_login": "Laatste inlog", "never": "Nooit", @@ -1000,10 +1008,10 @@ "security_tips_title": "Beveiligingstips", "tip_strong_passwords": "Gebruik sterke, unieke wachtwoorden voor elke gebruiker", "tip_unique_accounts": "Maak individuele accounts aan voor elke persoon", - "tip_viewer_role": "Gebruik de waarnemerrol voor gebruikers die alleen leestoegang nodig hebben", - "tip_operator_role": "Gebruik de operatorrol voor gebruikers die apparaten moeten beheren maar geen gebruikers", + "tip_viewer_role": "Use Viewer for read-only monitoring; Remote Operator for users who need to connect", + "tip_operator_role": "Use operator role for users who need to manage devices but not users", "tip_audit_logs": "Controleer regelmatig de auditlogs op verdachte activiteit", - "role_pro": "Professioneel", + "role_pro": "Pro License (client API only)", "role_super_admin": "Super Admin", "role_server_admin": "Serverbeheerder", "role_global_admin": "Globale beheerder", @@ -1046,7 +1054,27 @@ "provider_managed_hint": "Dieses Konto wird von einem externen Identitatsprovider (LDAP/AD oder SSO) verwaltet. Passwort und Rolle werden vom Provider gesteuert und konnen hier nicht geandert werden.", "email": "Email", "email_placeholder": "operator@example.com", - "email_hint": "Used for help request notifications when the user is assigned to device folders or groups." + "email_hint": "Used for help request notifications when the user is assigned to device folders or groups.", + "scope_hint": "Limit which devices this user sees: assign user groups, folders, and/or direct devices. Without restrictions, operators and viewers see all devices (unless the server uses restricted default mode in Settings).", + "role_desc_viewer": "Read-only panel access: device list, audit, metrics. Cannot use Web Remote Desktop.", + "role_desc_operator": "Can connect via Web Client and RustDesk desktop, edit devices, and change own password in Settings.", + "role_desc_pro": "RustDesk desktop client API only — activates Pro license. Blocked from web panel login.", + "role_desc_admin": "Full access (legacy alias for Super Admin).", + "role_desc_super_admin": "Full server and panel access, including user and server configuration.", + "role_desc_server_admin": "Server infrastructure, keys, and read-only user visibility. No device connect.", + "role_desc_global_admin": "Manage all users, organizations, and devices. Cannot change server config.", + "user_folders": "Folder access", + "user_folders_hint": "Devices in selected folders are visible to this user when folder ACL is configured.", + "user_direct_devices": "Direct devices", + "user_direct_devices_hint": "Grant access to specific devices regardless of folder or group membership.", + "user_direct_devices_placeholder": "Enter device IDs separated by commas", + "effective_scope_count": "{count} devices visible", + "loading_folders": "Loading folders...", + "no_folders": "No folders available", + "pro_strategy_label": "RustDesk Pro strategy", + "pro_strategy_hint": "Optional access-control strategy for RustDesk Pro client features (separate from panel role).", + "pro_strategy_none": "None", + "column_scope": "Scope" }, "folders": { "title": "Mappen", diff --git a/web-nodejs/lang/pl.json b/web-nodejs/lang/pl.json index 724c1ba4..c12d7401 100644 --- a/web-nodejs/lang/pl.json +++ b/web-nodejs/lang/pl.json @@ -1,4 +1,4 @@ -{ +{ "meta": { "lang": "pl", "name": "Polish", @@ -734,7 +734,15 @@ "public_endpoints_invalid_host": "Nieprawidłowy hostname ID lub relay", "public_endpoints_invalid_api_url": "Nieprawidłowy URL API — użyj http:// lub https://", "public_endpoints_save_title": "Zapisać publiczne endpointy klienta?", - "public_endpoints_save_confirm": "Zaktualizować konfigurację klienta RustDesk na pulpicie?" + "public_endpoints_save_confirm": "Zaktualizować konfigurację klienta RustDesk na pulpicie?", + "device_scope_title": "Domyślna widoczność urządzeń", + "device_scope_desc": "Określa, co widzą użytkownicy bez roli admin, gdy brak grantów folderów/grup/urządzeń.", + "device_scope_open": "Otwarty (legacy) — pokaż wszystkie urządzenia do skonfigurowania ACL", + "device_scope_restricted": "Restricted — pokaż tylko jawnie przyznane urządzenia", + "device_scope_save": "Zapisz domyślną widoczność", + "device_scope_saved": "Zapisano domyślną widoczność urządzeń", + "device_scope_invalid": "Nieprawidłowy tryb widoczności", + "device_scope_restricted_warning": "Tryb restricted jest aktywny. Upewnij się, że użytkownicy mają granty folderów, grup lub urządzeń — inaczej zobaczą pustą listę." }, "audit": { "time": "Czas", @@ -976,13 +984,13 @@ "password_leave_empty": "Zostaw puste, aby zachować obecne", "role": "Rola", "role_admin": "Administrator", - "role_operator": "Operator", + "role_operator": "Operator zdalny", "role_viewer": "Obserwator", - "role_pro": "Pro (tylko API)", + "role_pro": "Licencja Pro (tylko API klienta)", "role_super_admin": "Super Admin", "role_server_admin": "Admin serwera", "role_global_admin": "Admin globalny", - "role_hint": "To jest rola obowiązująca na poziomie całego serwera. Super Admin ma pełny dostęp. Admin serwera zarządza konfiguracją serwera i kluczami. Admin globalny zarządza użytkownikami, organizacjami i urządzeniami, ale nie może zmieniać konfiguracji serwera. Administrator jest równoważny Super Adminowi. Operatorzy zarządzają urządzeniami. Obserwatorzy mają dostęp tylko do odczytu. Pro aktywuje tryb Pro w kliencie RustDesk (tylko API). Role w poszczególnych organizacjach konfigurujesz osobno w sekcji Organizacje danego użytkownika.", + "role_hint": "Rola serwerowa. Operatorzy zdalni łączą się zdalnie i mogą zmieniać własne hasło. Viewer ma tylko odczyt (bez Web Client). Licencja Pro aktywuje RustDesk Pro przez API klienta — bez dostępu do panelu. Role organizacji konfigurujesz osobno.", "user_groups": "Grupy użytkowników", "user_groups_hint": "Grupy użytkowników mogą nadawać dostęp do wybranych grup urządzeń.", "loading_user_groups": "Ładowanie grup użytkowników...", @@ -1035,8 +1043,8 @@ "security_tips_title": "Najlepsze praktyki bezpieczeństwa", "tip_strong_passwords": "Używaj silnych, unikalnych haseł dla każdego użytkownika", "tip_unique_accounts": "Twórz indywidualne konta dla każdej osoby", - "tip_viewer_role": "Używaj roli obserwatora dla użytkowników, którzy potrzebują tylko odczytu", - "tip_operator_role": "Używaj roli operatora dla użytkowników, którzy muszą zarządzać urządzeniami, ale nie użytkownikami", + "tip_viewer_role": "Użyj Viewera do monitorowania tylko do odczytu; Operatora zdalnego do łączenia", + "tip_operator_role": "Użyj Operatora zdalnego dla użytkowników z Web Client i samodzielną zmianą hasła", "tip_audit_logs": "Regularnie przeglądaj dzienniki dla podejrzanej aktywności", "organizations": "Organizacje", "user_organizations": "Organizacje użytkownika {username}", @@ -1059,7 +1067,27 @@ "all_orgs_assigned": "Użytkownik jest już członkiem każdej organizacji.", "email": "E-mail", "email_placeholder": "operator@example.com", - "email_hint": "Używany do powiadomień o prośbach o pomoc, gdy użytkownik jest przypisany do folderów lub grup urządzeń." + "email_hint": "Używany do powiadomień o prośbach o pomoc, gdy użytkownik jest przypisany do folderów lub grup urządzeń.", + "scope_hint": "Ogranicz widoczność urządzeń: przypisz grupy użytkowników, foldery i/lub urządzenia bezpośrednio. Bez ograniczeń operatorzy i viewerzy widzą wszystkie urządzenia (chyba że włączono tryb restricted w Ustawieniach).", + "role_desc_viewer": "Tylko odczyt w panelu: lista urządzeń, audyt, metryki. Bez Web Remote Desktop.", + "role_desc_operator": "Może łączyć się przez Web Client i klienta RustDesk, edytować urządzenia i zmieniać własne hasło w Ustawieniach.", + "role_desc_pro": "Tylko API klienta RustDesk — aktywacja licencji Pro. Brak logowania do panelu.", + "role_desc_admin": "Pełny dostęp (legacy alias Super Admin).", + "role_desc_super_admin": "Pełny dostęp do serwera i panelu, w tym konfiguracja użytkowników i serwera.", + "role_desc_server_admin": "Infrastruktura serwera, klucze, podgląd użytkowników. Bez łączenia z urządzeniami.", + "role_desc_global_admin": "Zarządzanie użytkownikami, organizacjami i urządzeniami. Bez konfiguracji serwera.", + "user_folders": "Dostęp do folderów", + "user_folders_hint": "Urządzenia w wybranych folderach są widoczne, gdy skonfigurowano ACL folderu.", + "user_direct_devices": "Urządzenia bezpośrednie", + "user_direct_devices_hint": "Przyznaj dostęp do konkretnych urządzeń niezależnie od folderu lub grupy.", + "user_direct_devices_placeholder": "ID urządzeń oddzielone przecinkami", + "effective_scope_count": "{count} widocznych urządzeń", + "loading_folders": "Ładowanie folderów...", + "no_folders": "Brak folderów", + "pro_strategy_label": "Strategia RustDesk Pro", + "pro_strategy_hint": "Opcjonalna strategia kontroli dostępu dla funkcji Pro w kliencie (osobno od roli panelu).", + "pro_strategy_none": "Brak", + "column_scope": "Zakres" }, "folders": { "title": "Foldery", diff --git a/web-nodejs/lang/pt.json b/web-nodejs/lang/pt.json index bcfd369e..77b9f1a9 100644 --- a/web-nodejs/lang/pt.json +++ b/web-nodejs/lang/pt.json @@ -734,7 +734,15 @@ "public_endpoints_invalid_host": "Hostname ID ou relay inválido", "public_endpoints_invalid_api_url": "URL API inválida — use http:// ou https://", "public_endpoints_save_title": "Guardar endpoints públicos do cliente?", - "public_endpoints_save_confirm": "Atualizar configuração RustDesk no dashboard?" + "public_endpoints_save_confirm": "Atualizar configuração RustDesk no dashboard?", + "device_scope_title": "Device visibility default", + "device_scope_desc": "Controls what non-admin users see when no folder, group, or direct device grants exist.", + "device_scope_open": "Open (legacy) — show all devices until ACL is configured", + "device_scope_restricted": "Restricted — show only explicitly granted devices", + "device_scope_save": "Save visibility default", + "device_scope_saved": "Device visibility default saved", + "device_scope_invalid": "Invalid visibility mode", + "device_scope_restricted_warning": "Restricted mode is active. Ensure users have folder, group, or direct device grants or they will see an empty device list." }, "audit": { "time": "Hora", @@ -963,9 +971,9 @@ "password_leave_empty": "Deixe vazio para manter a atual", "role": "Papel", "role_admin": "Administrador", - "role_operator": "Operador", + "role_operator": "Remote Operator", "role_viewer": "Observador", - "role_hint": "Observadores podem apenas visualizar dados, operadores podem gerir dispositivos, administradores têm acesso total", + "role_hint": "Server-wide role. Remote Operators connect remotely and can change their own password. Viewers are read-only (no Web Client). Pro License accounts activate RustDesk Pro via the desktop client API only — no web panel access. Organization roles are configured separately.", "created": "Criado", "last_login": "Último início de sessão", "never": "Nunca", @@ -1000,10 +1008,10 @@ "security_tips_title": "Boas práticas de segurança", "tip_strong_passwords": "Use palavras-passe fortes e únicas para cada utilizador", "tip_unique_accounts": "Crie contas individuais para cada pessoa", - "tip_viewer_role": "Use o papel de observador para utilizadores que precisam apenas de acesso de leitura", - "tip_operator_role": "Use o papel de operador para utilizadores que precisam de gerir dispositivos mas não utilizadores", + "tip_viewer_role": "Use Viewer for read-only monitoring; Remote Operator for users who need to connect", + "tip_operator_role": "Use operator role for users who need to manage devices but not users", "tip_audit_logs": "Revise regularmente os registos de auditoria para atividade suspeita", - "role_pro": "Profissional", + "role_pro": "Pro License (client API only)", "role_super_admin": "Super Admin", "role_server_admin": "Administrador do servidor", "role_global_admin": "Administrador global", @@ -1046,7 +1054,27 @@ "provider_managed_hint": "Esta conta está gestionada por un fornecedor de identidade externo (LDAP/AD o SSO). La palavra-passe y el rol son controlados por el fornecedor y não é possíveln cambiar aquí.", "email": "Email", "email_placeholder": "operator@example.com", - "email_hint": "Used for help request notifications when the user is assigned to device folders or groups." + "email_hint": "Used for help request notifications when the user is assigned to device folders or groups.", + "scope_hint": "Limit which devices this user sees: assign user groups, folders, and/or direct devices. Without restrictions, operators and viewers see all devices (unless the server uses restricted default mode in Settings).", + "role_desc_viewer": "Read-only panel access: device list, audit, metrics. Cannot use Web Remote Desktop.", + "role_desc_operator": "Can connect via Web Client and RustDesk desktop, edit devices, and change own password in Settings.", + "role_desc_pro": "RustDesk desktop client API only — activates Pro license. Blocked from web panel login.", + "role_desc_admin": "Full access (legacy alias for Super Admin).", + "role_desc_super_admin": "Full server and panel access, including user and server configuration.", + "role_desc_server_admin": "Server infrastructure, keys, and read-only user visibility. No device connect.", + "role_desc_global_admin": "Manage all users, organizations, and devices. Cannot change server config.", + "user_folders": "Folder access", + "user_folders_hint": "Devices in selected folders are visible to this user when folder ACL is configured.", + "user_direct_devices": "Direct devices", + "user_direct_devices_hint": "Grant access to specific devices regardless of folder or group membership.", + "user_direct_devices_placeholder": "Enter device IDs separated by commas", + "effective_scope_count": "{count} devices visible", + "loading_folders": "Loading folders...", + "no_folders": "No folders available", + "pro_strategy_label": "RustDesk Pro strategy", + "pro_strategy_hint": "Optional access-control strategy for RustDesk Pro client features (separate from panel role).", + "pro_strategy_none": "None", + "column_scope": "Scope" }, "folders": { "title": "Pastas", diff --git a/web-nodejs/lang/ro.json b/web-nodejs/lang/ro.json index 0062ed99..31dbbbb3 100644 --- a/web-nodejs/lang/ro.json +++ b/web-nodejs/lang/ro.json @@ -740,7 +740,15 @@ "public_endpoints_invalid_host": "Hostname ID sau relay invalid", "public_endpoints_invalid_api_url": "URL API invalid — folosiți http:// sau https://", "public_endpoints_save_title": "Salvați endpoint-urile publice client?", - "public_endpoints_save_confirm": "Actualizați configurația client RustDesk pe dashboard?" + "public_endpoints_save_confirm": "Actualizați configurația client RustDesk pe dashboard?", + "device_scope_title": "Device visibility default", + "device_scope_desc": "Controls what non-admin users see when no folder, group, or direct device grants exist.", + "device_scope_open": "Open (legacy) — show all devices until ACL is configured", + "device_scope_restricted": "Restricted — show only explicitly granted devices", + "device_scope_save": "Save visibility default", + "device_scope_saved": "Device visibility default saved", + "device_scope_invalid": "Invalid visibility mode", + "device_scope_restricted_warning": "Restricted mode is active. Ensure users have folder, group, or direct device grants or they will see an empty device list." }, "audit": { "time": "Time", @@ -969,9 +977,9 @@ "password_leave_empty": "Lăsați gol pentru a menține curent", "role": "Role", "role_admin": "Administrator", - "role_operator": "Operator", + "role_operator": "Remote Operator", "role_viewer": "Viewer", - "role_hint": "Viewers can only view data, operators can manage devices, admins have full access", + "role_hint": "Server-wide role. Remote Operators connect remotely and can change their own password. Viewers are read-only (no Web Client). Pro License accounts activate RustDesk Pro via the desktop client API only — no web panel access. Organization roles are configured separately.", "created": "Creat", "last_login": "Last Login", "never": "Never", @@ -1006,10 +1014,10 @@ "security_tips_title": "Cele mai bune practici de securitate", "tip_strong_passwords": "Utilizați parole puternice, unice pentru fiecare utilizator", "tip_unique_accounts": "Creați conturi individuale pentru fiecare persoană", - "tip_viewer_role": "Utilizați rolul de vizualizator pentru utilizatorii care au nevoie doar de acces de citire", - "tip_operator_role": "Utilizați rolul de operator pentru utilizatorii care trebuie să gestioneze dispozitivele, dar nu utilizatorii", + "tip_viewer_role": "Use Viewer for read-only monitoring; Remote Operator for users who need to connect", + "tip_operator_role": "Use operator role for users who need to manage devices but not users", "tip_audit_logs": "Examinați în mod regulat jurnalele de audit pentru activități suspecte", - "role_pro": "Pro (doar API)", + "role_pro": "Pro License (client API only)", "role_super_admin": "Super Admin", "role_server_admin": "Administrator server", "role_global_admin": "Administrator global", @@ -1052,7 +1060,27 @@ "provider_managed_hint": "Acest cont este gestionat de un furnizor extern de identitate (LDAP/AD sau SSO). Parola și rolul sunt controlate de furnizor.", "email": "Email", "email_placeholder": "operator@example.com", - "email_hint": "Used for help request notifications when the user is assigned to device folders or groups." + "email_hint": "Used for help request notifications when the user is assigned to device folders or groups.", + "scope_hint": "Limit which devices this user sees: assign user groups, folders, and/or direct devices. Without restrictions, operators and viewers see all devices (unless the server uses restricted default mode in Settings).", + "role_desc_viewer": "Read-only panel access: device list, audit, metrics. Cannot use Web Remote Desktop.", + "role_desc_operator": "Can connect via Web Client and RustDesk desktop, edit devices, and change own password in Settings.", + "role_desc_pro": "RustDesk desktop client API only — activates Pro license. Blocked from web panel login.", + "role_desc_admin": "Full access (legacy alias for Super Admin).", + "role_desc_super_admin": "Full server and panel access, including user and server configuration.", + "role_desc_server_admin": "Server infrastructure, keys, and read-only user visibility. No device connect.", + "role_desc_global_admin": "Manage all users, organizations, and devices. Cannot change server config.", + "user_folders": "Folder access", + "user_folders_hint": "Devices in selected folders are visible to this user when folder ACL is configured.", + "user_direct_devices": "Direct devices", + "user_direct_devices_hint": "Grant access to specific devices regardless of folder or group membership.", + "user_direct_devices_placeholder": "Enter device IDs separated by commas", + "effective_scope_count": "{count} devices visible", + "loading_folders": "Loading folders...", + "no_folders": "No folders available", + "pro_strategy_label": "RustDesk Pro strategy", + "pro_strategy_hint": "Optional access-control strategy for RustDesk Pro client features (separate from panel role).", + "pro_strategy_none": "None", + "column_scope": "Scope" }, "folders": { "title": "Folders", diff --git a/web-nodejs/lang/sv.json b/web-nodejs/lang/sv.json index 2fb104d2..740b795a 100644 --- a/web-nodejs/lang/sv.json +++ b/web-nodejs/lang/sv.json @@ -740,7 +740,15 @@ "public_endpoints_invalid_host": "Ogiltigt ID- eller relay-värdnamn", "public_endpoints_invalid_api_url": "Ogiltig API-URL — använd http:// eller https://", "public_endpoints_save_title": "Spara offentliga klientändpunkter?", - "public_endpoints_save_confirm": "Uppdatera RustDesk-klientvärden på dashboarden?" + "public_endpoints_save_confirm": "Uppdatera RustDesk-klientvärden på dashboarden?", + "device_scope_title": "Device visibility default", + "device_scope_desc": "Controls what non-admin users see when no folder, group, or direct device grants exist.", + "device_scope_open": "Open (legacy) — show all devices until ACL is configured", + "device_scope_restricted": "Restricted — show only explicitly granted devices", + "device_scope_save": "Save visibility default", + "device_scope_saved": "Device visibility default saved", + "device_scope_invalid": "Invalid visibility mode", + "device_scope_restricted_warning": "Restricted mode is active. Ensure users have folder, group, or direct device grants or they will see an empty device list." }, "audit": { "time": "Time", @@ -969,9 +977,9 @@ "password_leave_empty": "Lämna tomt för att hålla dig uppdaterad", "role": "Role", "role_admin": "Administrator", - "role_operator": "Operator", + "role_operator": "Remote Operator", "role_viewer": "Viewer", - "role_hint": "Viewers can only view data, operators can manage devices, admins have full access", + "role_hint": "Server-wide role. Remote Operators connect remotely and can change their own password. Viewers are read-only (no Web Client). Pro License accounts activate RustDesk Pro via the desktop client API only — no web panel access. Organization roles are configured separately.", "created": "Skapad", "last_login": "Last Login", "never": "Never", @@ -1006,10 +1014,10 @@ "security_tips_title": "Säkerhet bästa praxis", "tip_strong_passwords": "Använd starka, unika lösenord för varje användare", "tip_unique_accounts": "Skapa individuella konton för varje person", - "tip_viewer_role": "Använd tittarroll för användare som bara behöver läsbehörighet", - "tip_operator_role": "Använd operatörsroll för användare som behöver hantera enheter men inte användare", + "tip_viewer_role": "Use Viewer for read-only monitoring; Remote Operator for users who need to connect", + "tip_operator_role": "Use operator role for users who need to manage devices but not users", "tip_audit_logs": "Granska granskningsloggar regelbundet för misstänkt aktivitet", - "role_pro": "Pro (endast API)", + "role_pro": "Pro License (client API only)", "role_super_admin": "Super Admin", "role_server_admin": "Serveradministratör", "role_global_admin": "Övergripande administratör", @@ -1052,7 +1060,27 @@ "provider_managed_hint": "Det här kontot hanteras av en extern identitetsleverantör (LDAP/AD eller SSO). Lösenordet och rollen styrs av leverantören.", "email": "Email", "email_placeholder": "operator@example.com", - "email_hint": "Used for help request notifications when the user is assigned to device folders or groups." + "email_hint": "Used for help request notifications when the user is assigned to device folders or groups.", + "scope_hint": "Limit which devices this user sees: assign user groups, folders, and/or direct devices. Without restrictions, operators and viewers see all devices (unless the server uses restricted default mode in Settings).", + "role_desc_viewer": "Read-only panel access: device list, audit, metrics. Cannot use Web Remote Desktop.", + "role_desc_operator": "Can connect via Web Client and RustDesk desktop, edit devices, and change own password in Settings.", + "role_desc_pro": "RustDesk desktop client API only — activates Pro license. Blocked from web panel login.", + "role_desc_admin": "Full access (legacy alias for Super Admin).", + "role_desc_super_admin": "Full server and panel access, including user and server configuration.", + "role_desc_server_admin": "Server infrastructure, keys, and read-only user visibility. No device connect.", + "role_desc_global_admin": "Manage all users, organizations, and devices. Cannot change server config.", + "user_folders": "Folder access", + "user_folders_hint": "Devices in selected folders are visible to this user when folder ACL is configured.", + "user_direct_devices": "Direct devices", + "user_direct_devices_hint": "Grant access to specific devices regardless of folder or group membership.", + "user_direct_devices_placeholder": "Enter device IDs separated by commas", + "effective_scope_count": "{count} devices visible", + "loading_folders": "Loading folders...", + "no_folders": "No folders available", + "pro_strategy_label": "RustDesk Pro strategy", + "pro_strategy_hint": "Optional access-control strategy for RustDesk Pro client features (separate from panel role).", + "pro_strategy_none": "None", + "column_scope": "Scope" }, "folders": { "title": "Folders", diff --git a/web-nodejs/lang/th.json b/web-nodejs/lang/th.json index fd714647..03047050 100644 --- a/web-nodejs/lang/th.json +++ b/web-nodejs/lang/th.json @@ -740,7 +740,15 @@ "public_endpoints_invalid_host": "ชื่อโฮสต์ ID หรือ relay ไม่ถูกต้อง", "public_endpoints_invalid_api_url": "URL API ไม่ถูกต้อง — ใช้ http:// หรือ https://", "public_endpoints_save_title": "บันทึกจุดปลายทางไคลเอนต์สาธารณะ?", - "public_endpoints_save_confirm": "อัปเดตค่าการตั้งค่า RustDesk บนแดชบอร์ด?" + "public_endpoints_save_confirm": "อัปเดตค่าการตั้งค่า RustDesk บนแดชบอร์ด?", + "device_scope_title": "Device visibility default", + "device_scope_desc": "Controls what non-admin users see when no folder, group, or direct device grants exist.", + "device_scope_open": "Open (legacy) — show all devices until ACL is configured", + "device_scope_restricted": "Restricted — show only explicitly granted devices", + "device_scope_save": "Save visibility default", + "device_scope_saved": "Device visibility default saved", + "device_scope_invalid": "Invalid visibility mode", + "device_scope_restricted_warning": "Restricted mode is active. Ensure users have folder, group, or direct device grants or they will see an empty device list." }, "audit": { "time": "Time", @@ -969,9 +977,9 @@ "password_leave_empty": "เว้นว่างไว้เพื่อให้เป็นปัจจุบัน", "role": "Role", "role_admin": "Administrator", - "role_operator": "Operator", + "role_operator": "Remote Operator", "role_viewer": "Viewer", - "role_hint": "Viewers can only view data, operators can manage devices, admins have full access", + "role_hint": "Server-wide role. Remote Operators connect remotely and can change their own password. Viewers are read-only (no Web Client). Pro License accounts activate RustDesk Pro via the desktop client API only — no web panel access. Organization roles are configured separately.", "created": "สร้างแล้ว", "last_login": "Last Login", "never": "Never", @@ -1006,10 +1014,10 @@ "security_tips_title": "แนวทางปฏิบัติที่ดีที่สุดด้านความปลอดภัย", "tip_strong_passwords": "ใช้รหัสผ่านที่รัดกุมและไม่ซ้ำกันสำหรับผู้ใช้แต่ละคน", "tip_unique_accounts": "สร้างบัญชีส่วนบุคคลสำหรับแต่ละบุคคล", - "tip_viewer_role": "ใช้บทบาทผู้ดูสำหรับผู้ใช้ที่ต้องการสิทธิ์การอ่านเท่านั้น", - "tip_operator_role": "ใช้บทบาทผู้ดำเนินการสำหรับผู้ใช้ที่ต้องการจัดการอุปกรณ์ แต่ไม่ใช่ผู้ใช้", + "tip_viewer_role": "Use Viewer for read-only monitoring; Remote Operator for users who need to connect", + "tip_operator_role": "Use operator role for users who need to manage devices but not users", "tip_audit_logs": "ตรวจสอบบันทึกการตรวจสอบเป็นประจำเพื่อหากิจกรรมที่น่าสงสัย", - "role_pro": "รุ่นโปร (API เท่านั้น)", + "role_pro": "Pro License (client API only)", "role_super_admin": "Super Admin", "role_server_admin": "ผู้ดูแลระบบเซิร์ฟเวอร์", "role_global_admin": "ผู้ดูแลระบบทั่วโลก", @@ -1052,7 +1060,27 @@ "provider_managed_hint": "บัญชีนี้ถูกจัดการโดย identity provider ภายนอก (LDAP/AD หรือ SSO) รหัสผ่านและบทบาทถูกควบคุมโดย provider และไม่สามารถเปลี่ยนแปลงได้ที่นี่", "email": "Email", "email_placeholder": "operator@example.com", - "email_hint": "Used for help request notifications when the user is assigned to device folders or groups." + "email_hint": "Used for help request notifications when the user is assigned to device folders or groups.", + "scope_hint": "Limit which devices this user sees: assign user groups, folders, and/or direct devices. Without restrictions, operators and viewers see all devices (unless the server uses restricted default mode in Settings).", + "role_desc_viewer": "Read-only panel access: device list, audit, metrics. Cannot use Web Remote Desktop.", + "role_desc_operator": "Can connect via Web Client and RustDesk desktop, edit devices, and change own password in Settings.", + "role_desc_pro": "RustDesk desktop client API only — activates Pro license. Blocked from web panel login.", + "role_desc_admin": "Full access (legacy alias for Super Admin).", + "role_desc_super_admin": "Full server and panel access, including user and server configuration.", + "role_desc_server_admin": "Server infrastructure, keys, and read-only user visibility. No device connect.", + "role_desc_global_admin": "Manage all users, organizations, and devices. Cannot change server config.", + "user_folders": "Folder access", + "user_folders_hint": "Devices in selected folders are visible to this user when folder ACL is configured.", + "user_direct_devices": "Direct devices", + "user_direct_devices_hint": "Grant access to specific devices regardless of folder or group membership.", + "user_direct_devices_placeholder": "Enter device IDs separated by commas", + "effective_scope_count": "{count} devices visible", + "loading_folders": "Loading folders...", + "no_folders": "No folders available", + "pro_strategy_label": "RustDesk Pro strategy", + "pro_strategy_hint": "Optional access-control strategy for RustDesk Pro client features (separate from panel role).", + "pro_strategy_none": "None", + "column_scope": "Scope" }, "folders": { "title": "Folders", diff --git a/web-nodejs/lang/tr.json b/web-nodejs/lang/tr.json index 7e1897df..524b25c5 100644 --- a/web-nodejs/lang/tr.json +++ b/web-nodejs/lang/tr.json @@ -740,7 +740,15 @@ "public_endpoints_invalid_host": "Geçersiz ID veya relay hostname", "public_endpoints_invalid_api_url": "Geçersiz API URL — http:// veya https:// kullanın", "public_endpoints_save_title": "Genel istemci uç noktaları kaydedilsin mi?", - "public_endpoints_save_confirm": "Dashboard RustDesk yapılandırma değerleri güncellensin mi?" + "public_endpoints_save_confirm": "Dashboard RustDesk yapılandırma değerleri güncellensin mi?", + "device_scope_title": "Device visibility default", + "device_scope_desc": "Controls what non-admin users see when no folder, group, or direct device grants exist.", + "device_scope_open": "Open (legacy) — show all devices until ACL is configured", + "device_scope_restricted": "Restricted — show only explicitly granted devices", + "device_scope_save": "Save visibility default", + "device_scope_saved": "Device visibility default saved", + "device_scope_invalid": "Invalid visibility mode", + "device_scope_restricted_warning": "Restricted mode is active. Ensure users have folder, group, or direct device grants or they will see an empty device list." }, "audit": { "time": "Time", @@ -969,9 +977,9 @@ "password_leave_empty": "Güncel tutmak için boş bırakın", "role": "Role", "role_admin": "Administrator", - "role_operator": "Operator", + "role_operator": "Remote Operator", "role_viewer": "Viewer", - "role_hint": "Viewers can only view data, operators can manage devices, admins have full access", + "role_hint": "Server-wide role. Remote Operators connect remotely and can change their own password. Viewers are read-only (no Web Client). Pro License accounts activate RustDesk Pro via the desktop client API only — no web panel access. Organization roles are configured separately.", "created": "Oluşturuldu", "last_login": "Last Login", "never": "Never", @@ -1006,10 +1014,10 @@ "security_tips_title": "En İyi Güvenlik Uygulamaları", "tip_strong_passwords": "Her kullanıcı için güçlü, benzersiz şifreler kullanın", "tip_unique_accounts": "Her kişi için ayrı hesaplar oluşturun", - "tip_viewer_role": "Yalnızca okuma erişimine ihtiyaç duyan kullanıcılar için görüntüleyici rolünü kullanın", - "tip_operator_role": "Cihazları yönetmesi gereken ancak kullanıcıları yönetmesi gerekmeyen kullanıcılar için operatör rolünü kullanın", + "tip_viewer_role": "Use Viewer for read-only monitoring; Remote Operator for users who need to connect", + "tip_operator_role": "Use operator role for users who need to manage devices but not users", "tip_audit_logs": "Şüpheli etkinlik açısından denetim günlüklerini düzenli olarak inceleyin", - "role_pro": "Pro (yalnızca API)", + "role_pro": "Pro License (client API only)", "role_super_admin": "Super Admin", "role_server_admin": "Sunucu Yöneticisi", "role_global_admin": "Genel Yönetici", @@ -1052,7 +1060,27 @@ "provider_managed_hint": "Bu hesap harici bir kimlik sağlayıcı (LDAP/AD veya SSO) tarafından yönetiliyor. Şifre ve rol sağlayıcı tarafından kontrol ediliyor ve burada değiştirilemez.", "email": "Email", "email_placeholder": "operator@example.com", - "email_hint": "Used for help request notifications when the user is assigned to device folders or groups." + "email_hint": "Used for help request notifications when the user is assigned to device folders or groups.", + "scope_hint": "Limit which devices this user sees: assign user groups, folders, and/or direct devices. Without restrictions, operators and viewers see all devices (unless the server uses restricted default mode in Settings).", + "role_desc_viewer": "Read-only panel access: device list, audit, metrics. Cannot use Web Remote Desktop.", + "role_desc_operator": "Can connect via Web Client and RustDesk desktop, edit devices, and change own password in Settings.", + "role_desc_pro": "RustDesk desktop client API only — activates Pro license. Blocked from web panel login.", + "role_desc_admin": "Full access (legacy alias for Super Admin).", + "role_desc_super_admin": "Full server and panel access, including user and server configuration.", + "role_desc_server_admin": "Server infrastructure, keys, and read-only user visibility. No device connect.", + "role_desc_global_admin": "Manage all users, organizations, and devices. Cannot change server config.", + "user_folders": "Folder access", + "user_folders_hint": "Devices in selected folders are visible to this user when folder ACL is configured.", + "user_direct_devices": "Direct devices", + "user_direct_devices_hint": "Grant access to specific devices regardless of folder or group membership.", + "user_direct_devices_placeholder": "Enter device IDs separated by commas", + "effective_scope_count": "{count} devices visible", + "loading_folders": "Loading folders...", + "no_folders": "No folders available", + "pro_strategy_label": "RustDesk Pro strategy", + "pro_strategy_hint": "Optional access-control strategy for RustDesk Pro client features (separate from panel role).", + "pro_strategy_none": "None", + "column_scope": "Scope" }, "folders": { "title": "Folders", diff --git a/web-nodejs/lang/uk.json b/web-nodejs/lang/uk.json index d713b912..5aa0b5f1 100644 --- a/web-nodejs/lang/uk.json +++ b/web-nodejs/lang/uk.json @@ -740,7 +740,15 @@ "public_endpoints_invalid_host": "Недійсне ім’я хоста ID або relay", "public_endpoints_invalid_api_url": "Недійсний URL API — використовуйте http:// або https://", "public_endpoints_save_title": "Зберегти публічні кінцеві точки клієнта?", - "public_endpoints_save_confirm": "Оновити конфігурацію RustDesk на панелі?" + "public_endpoints_save_confirm": "Оновити конфігурацію RustDesk на панелі?", + "device_scope_title": "Device visibility default", + "device_scope_desc": "Controls what non-admin users see when no folder, group, or direct device grants exist.", + "device_scope_open": "Open (legacy) — show all devices until ACL is configured", + "device_scope_restricted": "Restricted — show only explicitly granted devices", + "device_scope_save": "Save visibility default", + "device_scope_saved": "Device visibility default saved", + "device_scope_invalid": "Invalid visibility mode", + "device_scope_restricted_warning": "Restricted mode is active. Ensure users have folder, group, or direct device grants or they will see an empty device list." }, "audit": { "time": "Time", @@ -969,9 +977,9 @@ "password_leave_empty": "Залиште пустим, щоб зберегти актуальність", "role": "Role", "role_admin": "Administrator", - "role_operator": "Operator", + "role_operator": "Remote Operator", "role_viewer": "Viewer", - "role_hint": "Viewers can only view data, operators can manage devices, admins have full access", + "role_hint": "Server-wide role. Remote Operators connect remotely and can change their own password. Viewers are read-only (no Web Client). Pro License accounts activate RustDesk Pro via the desktop client API only — no web panel access. Organization roles are configured separately.", "created": "Створено", "last_login": "Last Login", "never": "Never", @@ -1006,10 +1014,10 @@ "security_tips_title": "Найкращі методи безпеки", "tip_strong_passwords": "Використовуйте надійні унікальні паролі для кожного користувача", "tip_unique_accounts": "Створіть індивідуальні облікові записи для кожної людини", - "tip_viewer_role": "Використовуйте роль переглядача для користувачів, яким потрібен лише доступ для читання", - "tip_operator_role": "Використовуйте роль оператора для користувачів, яким потрібно керувати пристроями, але не користувачами", + "tip_viewer_role": "Use Viewer for read-only monitoring; Remote Operator for users who need to connect", + "tip_operator_role": "Use operator role for users who need to manage devices but not users", "tip_audit_logs": "Регулярно переглядайте журнали аудиту на наявність підозрілої активності", - "role_pro": "Pro (лише API)", + "role_pro": "Pro License (client API only)", "role_super_admin": "Super Admin", "role_server_admin": "Адміністратор сервера", "role_global_admin": "Глобальний адмін", @@ -1052,7 +1060,27 @@ "provider_managed_hint": "Цей обліковий запис керується зовнішнім провайдером ідентичності (LDAP/AD або SSO). Пароль і роль контролюються провайдером.", "email": "Email", "email_placeholder": "operator@example.com", - "email_hint": "Used for help request notifications when the user is assigned to device folders or groups." + "email_hint": "Used for help request notifications when the user is assigned to device folders or groups.", + "scope_hint": "Limit which devices this user sees: assign user groups, folders, and/or direct devices. Without restrictions, operators and viewers see all devices (unless the server uses restricted default mode in Settings).", + "role_desc_viewer": "Read-only panel access: device list, audit, metrics. Cannot use Web Remote Desktop.", + "role_desc_operator": "Can connect via Web Client and RustDesk desktop, edit devices, and change own password in Settings.", + "role_desc_pro": "RustDesk desktop client API only — activates Pro license. Blocked from web panel login.", + "role_desc_admin": "Full access (legacy alias for Super Admin).", + "role_desc_super_admin": "Full server and panel access, including user and server configuration.", + "role_desc_server_admin": "Server infrastructure, keys, and read-only user visibility. No device connect.", + "role_desc_global_admin": "Manage all users, organizations, and devices. Cannot change server config.", + "user_folders": "Folder access", + "user_folders_hint": "Devices in selected folders are visible to this user when folder ACL is configured.", + "user_direct_devices": "Direct devices", + "user_direct_devices_hint": "Grant access to specific devices regardless of folder or group membership.", + "user_direct_devices_placeholder": "Enter device IDs separated by commas", + "effective_scope_count": "{count} devices visible", + "loading_folders": "Loading folders...", + "no_folders": "No folders available", + "pro_strategy_label": "RustDesk Pro strategy", + "pro_strategy_hint": "Optional access-control strategy for RustDesk Pro client features (separate from panel role).", + "pro_strategy_none": "None", + "column_scope": "Scope" }, "folders": { "title": "Folders", diff --git a/web-nodejs/lang/vi.json b/web-nodejs/lang/vi.json index 9c18a202..f28c9358 100644 --- a/web-nodejs/lang/vi.json +++ b/web-nodejs/lang/vi.json @@ -740,7 +740,15 @@ "public_endpoints_invalid_host": "Hostname ID hoặc relay không hợp lệ", "public_endpoints_invalid_api_url": "URL API không hợp lệ — dùng http:// hoặc https://", "public_endpoints_save_title": "Lưu điểm cuối client công khai?", - "public_endpoints_save_confirm": "Cập nhật cấu hình client RustDesk trên dashboard?" + "public_endpoints_save_confirm": "Cập nhật cấu hình client RustDesk trên dashboard?", + "device_scope_title": "Device visibility default", + "device_scope_desc": "Controls what non-admin users see when no folder, group, or direct device grants exist.", + "device_scope_open": "Open (legacy) — show all devices until ACL is configured", + "device_scope_restricted": "Restricted — show only explicitly granted devices", + "device_scope_save": "Save visibility default", + "device_scope_saved": "Device visibility default saved", + "device_scope_invalid": "Invalid visibility mode", + "device_scope_restricted_warning": "Restricted mode is active. Ensure users have folder, group, or direct device grants or they will see an empty device list." }, "audit": { "time": "Time", @@ -969,9 +977,9 @@ "password_leave_empty": "Để trống để giữ hiện tại", "role": "Role", "role_admin": "Administrator", - "role_operator": "Operator", + "role_operator": "Remote Operator", "role_viewer": "Viewer", - "role_hint": "Viewers can only view data, operators can manage devices, admins have full access", + "role_hint": "Server-wide role. Remote Operators connect remotely and can change their own password. Viewers are read-only (no Web Client). Pro License accounts activate RustDesk Pro via the desktop client API only — no web panel access. Organization roles are configured separately.", "created": "Đã tạo", "last_login": "Last Login", "never": "Never", @@ -1006,10 +1014,10 @@ "security_tips_title": "Thực tiễn tốt nhất về bảo mật", "tip_strong_passwords": "Sử dụng mật khẩu mạnh, duy nhất cho mỗi người dùng", "tip_unique_accounts": "Tạo tài khoản cá nhân cho mỗi người", - "tip_viewer_role": "Sử dụng vai trò người xem cho người dùng chỉ c��n quyền truy cập đọc", - "tip_operator_role": "Sử dụng vai trò nhà điều hành cho người dùng cần quản lý thiết bị chứ không phải người dùng", + "tip_viewer_role": "Use Viewer for read-only monitoring; Remote Operator for users who need to connect", + "tip_operator_role": "Use operator role for users who need to manage devices but not users", "tip_audit_logs": "Xem lại nhật ký kiểm tra thường xuyên để phát hiện hoạt động đáng ngờ", - "role_pro": "Chuyên nghiệp (chỉ API)", + "role_pro": "Pro License (client API only)", "role_super_admin": "Super Admin", "role_server_admin": "Quản trị viên máy chủ", "role_global_admin": "Quản trị viên toàn cầu", @@ -1052,7 +1060,27 @@ "provider_managed_hint": "Tài khoản này được quản lý bởi nhà cung cấp danh tính bên ngoài (LDAP/AD hoặc SSO). Mật khẩu và vai trò được kiểm soát bởi nhà cung cấp và không thể thay đổi ở đây.", "email": "Email", "email_placeholder": "operator@example.com", - "email_hint": "Used for help request notifications when the user is assigned to device folders or groups." + "email_hint": "Used for help request notifications when the user is assigned to device folders or groups.", + "scope_hint": "Limit which devices this user sees: assign user groups, folders, and/or direct devices. Without restrictions, operators and viewers see all devices (unless the server uses restricted default mode in Settings).", + "role_desc_viewer": "Read-only panel access: device list, audit, metrics. Cannot use Web Remote Desktop.", + "role_desc_operator": "Can connect via Web Client and RustDesk desktop, edit devices, and change own password in Settings.", + "role_desc_pro": "RustDesk desktop client API only — activates Pro license. Blocked from web panel login.", + "role_desc_admin": "Full access (legacy alias for Super Admin).", + "role_desc_super_admin": "Full server and panel access, including user and server configuration.", + "role_desc_server_admin": "Server infrastructure, keys, and read-only user visibility. No device connect.", + "role_desc_global_admin": "Manage all users, organizations, and devices. Cannot change server config.", + "user_folders": "Folder access", + "user_folders_hint": "Devices in selected folders are visible to this user when folder ACL is configured.", + "user_direct_devices": "Direct devices", + "user_direct_devices_hint": "Grant access to specific devices regardless of folder or group membership.", + "user_direct_devices_placeholder": "Enter device IDs separated by commas", + "effective_scope_count": "{count} devices visible", + "loading_folders": "Loading folders...", + "no_folders": "No folders available", + "pro_strategy_label": "RustDesk Pro strategy", + "pro_strategy_hint": "Optional access-control strategy for RustDesk Pro client features (separate from panel role).", + "pro_strategy_none": "None", + "column_scope": "Scope" }, "folders": { "title": "Folders", diff --git a/web-nodejs/lang/zh-TW.json b/web-nodejs/lang/zh-TW.json index 68d449d2..bbc2c573 100644 --- a/web-nodejs/lang/zh-TW.json +++ b/web-nodejs/lang/zh-TW.json @@ -738,7 +738,15 @@ "public_endpoints_invalid_host": "ID 或中繼伺服器 hostname 無效", "public_endpoints_invalid_api_url": "API URL 無效 — 請使用 http:// 或 https://", "public_endpoints_save_title": "儲存公共用戶端端點?", - "public_endpoints_save_confirm": "更新儀表板上的 RustDesk 用戶端設定值?" + "public_endpoints_save_confirm": "更新儀表板上的 RustDesk 用戶端設定值?", + "device_scope_title": "Device visibility default", + "device_scope_desc": "Controls what non-admin users see when no folder, group, or direct device grants exist.", + "device_scope_open": "Open (legacy) — show all devices until ACL is configured", + "device_scope_restricted": "Restricted — show only explicitly granted devices", + "device_scope_save": "Save visibility default", + "device_scope_saved": "Device visibility default saved", + "device_scope_invalid": "Invalid visibility mode", + "device_scope_restricted_warning": "Restricted mode is active. Ensure users have folder, group, or direct device grants or they will see an empty device list." }, "audit": { "time": "時間", @@ -980,13 +988,13 @@ "password_leave_empty": "留空保持不變", "role": "角色", "role_admin": "管理員", - "role_operator": "操作員", + "role_operator": "Remote Operator", "role_viewer": "查看者", - "role_pro": "Pro(僅限API)", + "role_pro": "Pro License (client API only)", "role_super_admin": "超級管理員", "role_server_admin": "服務器管理員", "role_global_admin": "全局管理員", - "role_hint": "超級管理員擁有所有權限。服務器管理員管理服務器配置和密鑰。全局管理員管理所有用戶、組織和設備,但不能更改服務器配置。管理員等同於超級管理員。操作員管理設備。查看者只有只讀權限。Pro 僅通過 RustDesk 客戶端登錄。", + "role_hint": "Server-wide role. Remote Operators connect remotely and can change their own password. Viewers are read-only (no Web Client). Pro License accounts activate RustDesk Pro via the desktop client API only — no web panel access. Organization roles are configured separately.", "user_groups": "用戶組", "user_groups_hint": "用戶組可用於授予對所選設備分組的訪問權限。", "loading_user_groups": "正在加載用戶組...", @@ -1039,8 +1047,8 @@ "security_tips_title": "安全最佳實踐", "tip_strong_passwords": "爲每個用戶使用強且唯一的密碼", "tip_unique_accounts": "爲每個人創建獨立的賬戶", - "tip_viewer_role": "對只需要查看權限的用戶使用查看者角色", - "tip_operator_role": "對需要管理設備但不需要管理用戶的人員使用操作員角色", + "tip_viewer_role": "Use Viewer for read-only monitoring; Remote Operator for users who need to connect", + "tip_operator_role": "Use operator role for users who need to manage devices but not users", "tip_audit_logs": "定期檢查審計日誌以發現可疑活動", "organizations": "組織", "user_organizations": "{username} 的組織", @@ -1063,7 +1071,27 @@ "all_orgs_assigned": "該用戶已經是所有組織的成員。", "email": "Email", "email_placeholder": "operator@example.com", - "email_hint": "Used for help request notifications when the user is assigned to device folders or groups." + "email_hint": "Used for help request notifications when the user is assigned to device folders or groups.", + "scope_hint": "Limit which devices this user sees: assign user groups, folders, and/or direct devices. Without restrictions, operators and viewers see all devices (unless the server uses restricted default mode in Settings).", + "role_desc_viewer": "Read-only panel access: device list, audit, metrics. Cannot use Web Remote Desktop.", + "role_desc_operator": "Can connect via Web Client and RustDesk desktop, edit devices, and change own password in Settings.", + "role_desc_pro": "RustDesk desktop client API only — activates Pro license. Blocked from web panel login.", + "role_desc_admin": "Full access (legacy alias for Super Admin).", + "role_desc_super_admin": "Full server and panel access, including user and server configuration.", + "role_desc_server_admin": "Server infrastructure, keys, and read-only user visibility. No device connect.", + "role_desc_global_admin": "Manage all users, organizations, and devices. Cannot change server config.", + "user_folders": "Folder access", + "user_folders_hint": "Devices in selected folders are visible to this user when folder ACL is configured.", + "user_direct_devices": "Direct devices", + "user_direct_devices_hint": "Grant access to specific devices regardless of folder or group membership.", + "user_direct_devices_placeholder": "Enter device IDs separated by commas", + "effective_scope_count": "{count} devices visible", + "loading_folders": "Loading folders...", + "no_folders": "No folders available", + "pro_strategy_label": "RustDesk Pro strategy", + "pro_strategy_hint": "Optional access-control strategy for RustDesk Pro client features (separate from panel role).", + "pro_strategy_none": "None", + "column_scope": "Scope" }, "folders": { "title": "文件夾", diff --git a/web-nodejs/lang/zh.json b/web-nodejs/lang/zh.json index 8125031d..1980d828 100644 --- a/web-nodejs/lang/zh.json +++ b/web-nodejs/lang/zh.json @@ -734,7 +734,15 @@ "public_endpoints_invalid_host": "ID 或中继服务器 hostname 无效", "public_endpoints_invalid_api_url": "API URL 无效 — 请使用 http:// 或 https://", "public_endpoints_save_title": "保存公共客户端端点?", - "public_endpoints_save_confirm": "更新仪表板上的 RustDesk 客户端配置值?" + "public_endpoints_save_confirm": "更新仪表板上的 RustDesk 客户端配置值?", + "device_scope_title": "Device visibility default", + "device_scope_desc": "Controls what non-admin users see when no folder, group, or direct device grants exist.", + "device_scope_open": "Open (legacy) — show all devices until ACL is configured", + "device_scope_restricted": "Restricted — show only explicitly granted devices", + "device_scope_save": "Save visibility default", + "device_scope_saved": "Device visibility default saved", + "device_scope_invalid": "Invalid visibility mode", + "device_scope_restricted_warning": "Restricted mode is active. Ensure users have folder, group, or direct device grants or they will see an empty device list." }, "audit": { "time": "时间", @@ -963,13 +971,13 @@ "password_leave_empty": "留空保持不变", "role": "角色", "role_admin": "管理员", - "role_operator": "操作员", + "role_operator": "Remote Operator", "role_viewer": "查看者", - "role_pro": "Pro(仅限API)", + "role_pro": "Pro License (client API only)", "role_super_admin": "超级管理员", "role_server_admin": "服务器管理员", "role_global_admin": "全局管理员", - "role_hint": "超级管理员拥有所有权限。服务器管理员管理服务器配置和密钥。全局管理员管理所有用户、组织和设备,但不能更改服务器配置。管理员等同于超级管理员。操作员管理设备。查看者只有只读权限。Pro 仅通过 RustDesk 客户端登录。", + "role_hint": "Server-wide role. Remote Operators connect remotely and can change their own password. Viewers are read-only (no Web Client). Pro License accounts activate RustDesk Pro via the desktop client API only — no web panel access. Organization roles are configured separately.", "created": "创建时间", "last_login": "最后登录", "never": "从未", @@ -1004,8 +1012,8 @@ "security_tips_title": "安全最佳实践", "tip_strong_passwords": "为每个用户使用强且唯一的密码", "tip_unique_accounts": "为每个人创建独立的账户", - "tip_viewer_role": "对只需要查看权限的用户使用查看者角色", - "tip_operator_role": "对需要管理设备但不需要管理用户的人员使用操作员角色", + "tip_viewer_role": "Use Viewer for read-only monitoring; Remote Operator for users who need to connect", + "tip_operator_role": "Use operator role for users who need to manage devices but not users", "tip_audit_logs": "定期检查审计日志以发现可疑活动", "organizations": "组织", "user_organizations": "{username} 的组织", @@ -1046,7 +1054,27 @@ "provider_managed_hint": "此账户由外部身份提供商(LDAP/AD 或 SSO)管理。密码和角色由提供商控制,无法在此处更改。", "email": "Email", "email_placeholder": "operator@example.com", - "email_hint": "Used for help request notifications when the user is assigned to device folders or groups." + "email_hint": "Used for help request notifications when the user is assigned to device folders or groups.", + "scope_hint": "Limit which devices this user sees: assign user groups, folders, and/or direct devices. Without restrictions, operators and viewers see all devices (unless the server uses restricted default mode in Settings).", + "role_desc_viewer": "Read-only panel access: device list, audit, metrics. Cannot use Web Remote Desktop.", + "role_desc_operator": "Can connect via Web Client and RustDesk desktop, edit devices, and change own password in Settings.", + "role_desc_pro": "RustDesk desktop client API only — activates Pro license. Blocked from web panel login.", + "role_desc_admin": "Full access (legacy alias for Super Admin).", + "role_desc_super_admin": "Full server and panel access, including user and server configuration.", + "role_desc_server_admin": "Server infrastructure, keys, and read-only user visibility. No device connect.", + "role_desc_global_admin": "Manage all users, organizations, and devices. Cannot change server config.", + "user_folders": "Folder access", + "user_folders_hint": "Devices in selected folders are visible to this user when folder ACL is configured.", + "user_direct_devices": "Direct devices", + "user_direct_devices_hint": "Grant access to specific devices regardless of folder or group membership.", + "user_direct_devices_placeholder": "Enter device IDs separated by commas", + "effective_scope_count": "{count} devices visible", + "loading_folders": "Loading folders...", + "no_folders": "No folders available", + "pro_strategy_label": "RustDesk Pro strategy", + "pro_strategy_hint": "Optional access-control strategy for RustDesk Pro client features (separate from panel role).", + "pro_strategy_none": "None", + "column_scope": "Scope" }, "folders": { "title": "文件夹", diff --git a/web-nodejs/public/js/devices.js b/web-nodejs/public/js/devices.js index 63d0abdd..1d159a49 100644 --- a/web-nodejs/public/js/devices.js +++ b/web-nodejs/public/js/devices.js @@ -2940,10 +2940,21 @@ requestAnimationFrame(updateTableHScroll); } + function selectedFolderUserGroupGuids() { + return Array.from(document.querySelectorAll('#folder-user-groups-list input:checked')).map(input => input.value); + } + + function renderFolderUserGroupOptions(selectedGuids) { + const container = document.getElementById('folder-user-groups-list'); + if (!container) return; + container.innerHTML = renderUserGroupAccessOptions(selectedGuids || []); + } + /** * Show add folder modal */ - function showAddFolderModal() { + async function showAddFolderModal() { + await ensureUserGroupsLoaded(); const template = document.getElementById('folder-form-template'); if (!template) return; @@ -2960,6 +2971,7 @@ ], onOpen: () => { initColorPicker(); + renderFolderUserGroupOptions([]); document.getElementById('folder-name')?.focus(); } }); @@ -2969,6 +2981,7 @@ * Edit folder */ async function editFolder(folderId) { + await ensureUserGroupsLoaded(); const folder = findFolderById(folderId); if (!folder) return; @@ -2991,6 +3004,7 @@ document.getElementById('folder-name').value = folder.name; document.getElementById('folder-color').value = folder.color; document.getElementById('folder-allowed-users').value = (folder.allowed_users || []).join(', '); + renderFolderUserGroupOptions(folder.allowed_groups || []); // Set active color document.querySelectorAll('.color-option').forEach(btn => { @@ -3020,6 +3034,7 @@ const name = document.getElementById('folder-name')?.value.trim(); const color = document.getElementById('folder-color')?.value; const allowedUsers = document.getElementById('folder-allowed-users')?.value || ''; + const allowedGroups = selectedFolderUserGroupGuids(); if (!name) { Notifications.error(_('folders.name_required')); @@ -3030,13 +3045,13 @@ if (folderId) { await Utils.api(`/api/folders/${folderId}`, { method: 'PATCH', - body: { name, color, allowed_users: allowedUsers } + body: { name, color, allowed_users: allowedUsers, allowed_groups: allowedGroups } }); Notifications.success(_('folders.updated')); } else { await Utils.api('/api/folders', { method: 'POST', - body: { name, color, allowed_users: allowedUsers } + body: { name, color, allowed_users: allowedUsers, allowed_groups: allowedGroups } }); Notifications.success(_('folders.created')); } diff --git a/web-nodejs/public/js/settings.js b/web-nodejs/public/js/settings.js index 5e33a7ce..e891e314 100644 --- a/web-nodejs/public/js/settings.js +++ b/web-nodejs/public/js/settings.js @@ -41,6 +41,7 @@ loadAuditLog(); loadServerInfo(); initMeshSettingsSection(); + initDeviceScopeSection(); initConnectionModeSection(); initPublicEndpointsSection(); initAuthSubnav(); @@ -366,6 +367,56 @@ return parts.join(' '); } + // ==================== Device scope default ==================== + + function initDeviceScopeSection() { + const form = document.getElementById('device-scope-form'); + if (!form) return; + + const warningEl = document.getElementById('device-scope-warning'); + + function updateWarning() { + const restricted = document.getElementById('device-scope-restricted')?.checked; + if (warningEl) warningEl.hidden = !restricted; + } + + async function loadDeviceScopeSetting() { + try { + const data = await Utils.api('/api/settings/device-scope'); + const mode = data.mode === 'restricted' ? 'restricted' : 'open'; + const openEl = document.getElementById('device-scope-open'); + const restrictedEl = document.getElementById('device-scope-restricted'); + if (openEl) openEl.checked = mode === 'open'; + if (restrictedEl) restrictedEl.checked = mode === 'restricted'; + updateWarning(); + } catch (err) { + console.error('Failed to load device scope setting:', err); + } + } + + form.querySelectorAll('input[name="device_scope_mode"]').forEach(input => { + input.addEventListener('change', updateWarning); + }); + + form.addEventListener('submit', async (e) => { + e.preventDefault(); + const selected = form.querySelector('input[name="device_scope_mode"]:checked'); + const mode = selected ? selected.value : 'open'; + try { + await Utils.api('/api/settings/device-scope', { + method: 'POST', + body: { mode } + }); + Notifications.success(_('settings.device_scope_saved')); + updateWarning(); + } catch (err) { + Notifications.error(err.message || _('errors.server_error')); + } + }); + + loadDeviceScopeSetting(); + } + // ==================== MeshCentral compatibility ==================== function initMeshSettingsSection() { diff --git a/web-nodejs/public/js/users.js b/web-nodejs/public/js/users.js index b75f0eea..8223ea8c 100644 --- a/web-nodejs/public/js/users.js +++ b/web-nodejs/public/js/users.js @@ -12,6 +12,10 @@ let users = []; let userGroups = []; let userGroupsLoaded = false; + let folders = []; + let foldersLoaded = false; + let strategies = []; + let strategiesLoaded = false; let editingUserId = null; // Cache: userId -> [{ id, org_id, name, org_name, role }] const userOrgsCache = new Map(); @@ -25,6 +29,8 @@ userGroupsManager = document.getElementById('user-groups-manager-list'); loadUserGroups(); + loadFolders(); + loadStrategies(); loadUsers(); initEventListeners(); focusUserGroupsFromHash(); @@ -119,6 +125,104 @@ return Array.from(document.querySelectorAll('#user-groups-list input:checked')).map(input => input.value); } + async function loadFolders() { + try { + const response = await Utils.api('/api/folders'); + folders = response.folders || []; + foldersLoaded = true; + } catch (error) { + folders = []; + foldersLoaded = true; + console.error('Failed to load folders:', error); + } + } + + async function loadStrategies() { + try { + const response = await Utils.api('/api/strategies'); + strategies = Array.isArray(response) ? response : (response.data || []); + strategiesLoaded = true; + } catch (error) { + strategies = []; + strategiesLoaded = true; + } + } + + async function ensureFoldersLoaded() { + if (!foldersLoaded) await loadFolders(); + } + + async function ensureStrategiesLoaded() { + if (!strategiesLoaded) await loadStrategies(); + } + + function renderFolderCheckboxes(selectedIds) { + const selected = new Set((selectedIds || []).map(id => Number(id)).filter(Number.isFinite)); + const container = document.getElementById('user-folders-list'); + if (!container) return; + if (!folders.length) { + container.innerHTML = `
${_('users.no_folders') || 'No folders'}
`; + return; + } + container.innerHTML = folders.map(folder => ` + `).join(''); + } + + function selectedFolderIds() { + return Array.from(document.querySelectorAll('#user-folders-list input:checked')) + .map(input => Number.parseInt(input.value, 10)) + .filter(Number.isFinite); + } + + function renderStrategyOptions(selectedGuid) { + const select = document.getElementById('user-strategy'); + if (!select) return; + const current = String(selectedGuid || ''); + select.innerHTML = `` + + strategies.map(st => ` + `).join(''); + } + + const ROLE_DESC_KEYS = { + viewer: 'users.role_desc_viewer', + operator: 'users.role_desc_operator', + pro: 'users.role_desc_pro', + admin: 'users.role_desc_admin', + super_admin: 'users.role_desc_super_admin', + server_admin: 'users.role_desc_server_admin', + global_admin: 'users.role_desc_global_admin' + }; + + function updateRoleDescription() { + const role = document.getElementById('user-role')?.value || 'viewer'; + const descEl = document.getElementById('user-role-desc'); + if (!descEl) return; + const key = ROLE_DESC_KEYS[role]; + descEl.textContent = key ? (_(key) || '') : ''; + } + + async function loadEffectiveScopeCounts() { + if (!tableBody) return; + const cells = tableBody.querySelectorAll('.user-scope-cell'); + await Promise.all(Array.from(cells).map(async cell => { + const userId = cell.dataset.userId; + if (!userId) return; + try { + const resp = await Utils.api(`/api/users/${userId}/effective-scope`); + const count = resp.data?.count ?? 0; + cell.textContent = _('users.effective_scope_count', { count }) || `${count} devices`; + } catch (_) { + cell.textContent = '—'; + } + })); + } + function renderUserGroupsManager() { if (!userGroupsManager) return; if (!userGroupsLoaded) { @@ -342,6 +446,9 @@ ${Utils.escapeHtml(_(roleLabelKey))} + + + ${Utils.escapeHtml(providerLabel)} @@ -385,6 +492,7 @@ if (id && username) showOrganizationsModal(id, username); }); }); + loadEffectiveScopeCounts(); } /** @@ -411,7 +519,7 @@ * Show add user modal */ async function showAddUserModal() { - await ensureUserGroupsLoaded(); + await Promise.all([ensureUserGroupsLoaded(), ensureFoldersLoaded(), ensureStrategiesLoaded()]); editingUserId = null; const template = document.getElementById('user-form-template'); @@ -429,6 +537,10 @@ onOpen: () => { initFormListeners(); renderUserGroupCheckboxes([]); + renderFolderCheckboxes([]); + renderStrategyOptions(''); + updateRoleDescription(); + document.getElementById('user-direct-devices').value = ''; document.getElementById('user-username')?.focus(); } }); @@ -438,7 +550,7 @@ * Show edit user modal */ async function showEditUserModal(userId) { - await ensureUserGroupsLoaded(); + await Promise.all([ensureUserGroupsLoaded(), ensureFoldersLoaded(), ensureStrategiesLoaded()]); const user = users.find(u => Number(u.id) === Number(userId)); if (!user) return; @@ -491,6 +603,13 @@ } } renderUserGroupCheckboxes(user.user_groups || []); + renderFolderCheckboxes(user.folder_ids || []); + renderStrategyOptions(user.strategy_guid || ''); + updateRoleDescription(); + const directDevices = document.getElementById('user-direct-devices'); + if (directDevices) { + directDevices.value = Array.isArray(user.peer_grants) ? user.peer_grants.join(', ') : ''; + } } }); } @@ -516,6 +635,8 @@ document.getElementById('user-password')?.addEventListener('input', function() { updatePasswordStrength(this.value); }); + + document.getElementById('user-role')?.addEventListener('change', updateRoleDescription); } /** @@ -572,6 +693,10 @@ const role = document.getElementById('user-role')?.value; const email = document.getElementById('user-email')?.value.trim(); const groupGuids = selectedUserGroupGuids(); + const folderIds = selectedFolderIds(); + const strategyGuid = document.getElementById('user-strategy')?.value || ''; + const peerIdsRaw = document.getElementById('user-direct-devices')?.value || ''; + const peerIds = peerIdsRaw.split(/[,;\s]+/).map(v => v.trim()).filter(Boolean); // Validate if (!editingUserId) { @@ -595,9 +720,8 @@ try { if (editingUserId) { // Update existing user - const data = { role, email }; + const data = { role, email, groupGuids, folderIds, peerIds, strategyGuid }; if (password) data.password = password; - data.groupGuids = groupGuids; await Utils.api(`/api/users/${editingUserId}`, { method: 'PATCH', @@ -608,7 +732,7 @@ // Create new user await Utils.api('/api/users', { method: 'POST', - body: { username, password, role, email, groupGuids } + body: { username, password, role, email, groupGuids, folderIds, peerIds, strategyGuid } }); Notifications.success(_('users.user_created')); } diff --git a/web-nodejs/routes/folders.routes.js b/web-nodejs/routes/folders.routes.js index bbe88858..03916c23 100644 --- a/web-nodejs/routes/folders.routes.js +++ b/web-nodejs/routes/folders.routes.js @@ -48,6 +48,26 @@ async function setFolderAllowedUsers(folder, allowedUsers) { return db.setDeviceGroupUserAccess(group.guid, deviceGroupService.normalizeUsernames(allowedUsers)); } +async function getFolderAllowedGroups(folderId) { + try { + const group = await db.getDeviceGroupByGuid(folderGroupGuid(folderId)); + return Array.isArray(group && group.allowed_groups) ? group.allowed_groups : []; + } catch (_) { + return []; + } +} + +async function setFolderAllowedUserGroups(folder, allowedGroups) { + const group = await ensureFolderDeviceGroup(folder); + return db.setDeviceGroupUserGroupAccess(group.guid, deviceGroupService.normalizeGroupGuids(allowedGroups)); +} + +async function enrichFolderAccess(folder) { + folder.allowed_users = await getFolderAllowedUsers(folder.id); + folder.allowed_groups = await getFolderAllowedGroups(folder.id); + return folder; +} + /** * GET /api/folders - Get all folders */ @@ -66,7 +86,7 @@ router.get('/api/folders', requireAuth, requirePermission('device.view'), async } for (const f of folders) { f.device_count = countMap[Number(f.id)] || 0; - f.allowed_users = await getFolderAllowedUsers(f.id); + await enrichFolderAccess(f); } } catch (err) { console.error('Failed to compute folder device counts:', err.message); @@ -93,7 +113,7 @@ router.get('/api/folders', requireAuth, requirePermission('device.view'), async */ router.post('/api/folders', requireAuth, requirePermission('device.edit'), async (req, res) => { try { - const { name, color, icon, allowed_users } = req.body; + const { name, color, icon, allowed_users, allowed_groups } = req.body; if (!name || name.trim().length === 0) { return res.status(400).json({ @@ -119,7 +139,10 @@ router.post('/api/folders', requireAuth, requirePermission('device.edit'), async if (Object.prototype.hasOwnProperty.call(req.body || {}, 'allowed_users')) { await setFolderAllowedUsers(folder, allowed_users); } - folder.allowed_users = await getFolderAllowedUsers(folder.id); + if (Object.prototype.hasOwnProperty.call(req.body || {}, 'allowed_groups')) { + await setFolderAllowedUserGroups(folder, allowed_groups); + } + await enrichFolderAccess(folder); // Log action await db.logAction(req.session.userId, 'folder_created', `Created folder: ${name}`, req.ip); @@ -145,7 +168,7 @@ router.post('/api/folders', requireAuth, requirePermission('device.edit'), async router.patch('/api/folders/:id', requireAuth, requirePermission('device.edit'), async (req, res) => { try { const folderId = parseInt(req.params.id, 10); - const { name, color, icon, allowed_users } = req.body; + const { name, color, icon, allowed_users, allowed_groups } = req.body; const folder = await db.getFolderById(folderId); if (!folder) { @@ -184,6 +207,9 @@ router.patch('/api/folders/:id', requireAuth, requirePermission('device.edit'), }; if (Object.prototype.hasOwnProperty.call(req.body || {}, 'allowed_users')) { await setFolderAllowedUsers(updatedFolder, allowed_users); + } + if (Object.prototype.hasOwnProperty.call(req.body || {}, 'allowed_groups')) { + await setFolderAllowedUserGroups(updatedFolder, allowed_groups); } else { await ensureFolderDeviceGroup(updatedFolder); } diff --git a/web-nodejs/routes/settings.routes.js b/web-nodejs/routes/settings.routes.js index 7727f9bb..eca584a3 100644 --- a/web-nodejs/routes/settings.routes.js +++ b/web-nodejs/routes/settings.routes.js @@ -30,6 +30,7 @@ const clientConfigHost = require('../services/clientConfigHost'); const { getSmtpSettings, putSmtpSettings, testSmtpSettings } = require('../lib/smtpSettingsHandlers'); const { apiClient } = require('../services/betterdeskApi'); const { requireAuth, requirePermission, roleHasPermission } = require('../middleware/auth'); +const deviceGroupService = require('../services/deviceGroupService'); const os = require('os'); const multer = require('multer'); @@ -137,9 +138,43 @@ router.get('/api/settings/server-info', requireAuth, (req, res) => { } }); +/** + * GET /api/settings/device-scope - Default device visibility mode for non-admin users. + */ +router.get('/api/settings/device-scope', requireAuth, requirePermission('server.config'), async (req, res) => { + try { + const stored = await db.getSetting('device_scope_default'); + const mode = stored && String(stored).toLowerCase() === 'restricted' ? 'restricted' : 'open'; + res.json({ success: true, data: { mode } }); + } catch (err) { + console.error('Get device scope setting error:', err); + res.status(500).json({ success: false, error: req.t('errors.server_error') }); + } +}); + +/** + * POST /api/settings/device-scope - Set default device visibility mode. + */ +router.post('/api/settings/device-scope', requireAuth, requirePermission('server.config'), async (req, res) => { + try { + const mode = String((req.body && req.body.mode) || 'open').toLowerCase(); + if (mode !== 'open' && mode !== 'restricted') { + return res.status(400).json({ success: false, error: req.t('settings.device_scope_invalid') }); + } + await db.setSetting('device_scope_default', mode); + deviceGroupService.invalidateDeviceScopeDefaultCache(); + await db.logAction(req.session.userId, 'device_scope_default_updated', `Device scope default: ${mode}`, req.ip); + res.json({ success: true, data: { mode } }); + } catch (err) { + console.error('Set device scope setting error:', err); + res.status(500).json({ success: false, error: req.t('errors.server_error') }); + } +}); + /** * GET /api/settings/audit - Get audit log */ + */ router.get('/api/settings/audit', requireAuth, async (req, res) => { try { const limit = parseInt(req.query.limit, 10) || 100; diff --git a/web-nodejs/routes/users.routes.js b/web-nodejs/routes/users.routes.js index 195cb7d9..604a8048 100644 --- a/web-nodejs/routes/users.routes.js +++ b/web-nodejs/routes/users.routes.js @@ -10,6 +10,9 @@ const db = require('../services/database'); const { apiClient } = require('../services/betterdeskApi'); const { assertSafeApiId } = require('../lib/goApiPath'); const userSync = require('../services/userSync'); +const userScopeService = require('../services/userScopeService'); +const deviceGroupService = require('../services/deviceGroupService'); +const serverBackend = require('../services/serverBackend'); const { requireAuth, requirePermission, roleHasPermission, isSuperAdminRole } = require('../middleware/auth'); const { passwordChangeLimiter } = require('../middleware/rateLimiter'); @@ -110,6 +113,59 @@ function normalizeUserGroupPayload(body) { return { name, note, team_id: teamId }; } +async function serializeUserForList(u) { + const folderIds = await userScopeService.getUserFolderIds(db, u.username); + const peerGrants = await userScopeService.getUserPeerGrantIds(db, u.id); + let strategyGuid = ''; + if (typeof db.getUserStrategyGuid === 'function') { + try { + strategyGuid = await db.getUserStrategyGuid(u.id); + } catch (_) {} + } + return { + id: u.id, + username: u.username, + role: u.role, + email: u.email || '', + auth_provider: u.auth_provider || 'local', + created_at: u.created_at, + last_login: u.last_login, + user_groups: await getUserGroupGuids(u.id), + folder_ids: folderIds, + peer_grants: peerGrants, + strategy_guid: strategyGuid + }; +} + +async function applyUserScopeFromBody(userId, username, body) { + if (Object.prototype.hasOwnProperty.call(body || {}, 'folderIds')) { + await userScopeService.syncUserFolderAccess(db, username, body.folderIds); + } + if (Object.prototype.hasOwnProperty.call(body || {}, 'peerIds')) { + await userScopeService.syncUserPeerGrants(db, userId, body.peerIds); + } + if (Object.prototype.hasOwnProperty.call(body || {}, 'strategyGuid') && typeof db.setUserStrategyAssignment === 'function') { + const strategyGuid = await db.setUserStrategyAssignment(userId, body.strategyGuid || ''); + if (await serverBackend.isBetterDesk()) { + try { + const userKey = typeof db.resolveUserAssignmentKey === 'function' + ? await db.resolveUserAssignmentKey(username) + : username; + await apiClient({ + method: 'POST', + url: '/strategies/assign', + data: { + strategy: strategyGuid || undefined, + users: userKey ? [userKey] : [] + } + }); + } catch (err) { + console.warn('[users] Strategy assign Go sync failed:', err.message); + } + } + } +} + async function getUserGroupGuids(userId) { if (typeof db.getUserGroupsForUser !== 'function') return []; const groups = await db.getUserGroupsForUser(userId); @@ -175,16 +231,7 @@ router.get('/api/users', requireAuth, requirePermission('user.view'), async (req const users = await db.getAllUsers(); // Remove sensitive data - const safeUsers = await Promise.all(users.map(async u => ({ - id: u.id, - username: u.username, - role: u.role, - email: u.email || '', - auth_provider: u.auth_provider || 'local', - created_at: u.created_at, - last_login: u.last_login, - user_groups: await getUserGroupGuids(u.id) - }))); + const safeUsers = await Promise.all(users.map(u => serializeUserForList(u))); res.json({ success: true, @@ -356,6 +403,12 @@ router.post('/api/users', requireAuth, requirePermission('user.create'), passwor // (Issue #125). Best-effort — does not fail panel-side creation. runBestEffortUserSync(() => userSync.mirrorCreate(username, password, userRole)); + if (Object.prototype.hasOwnProperty.call(req.body || {}, 'folderIds') || + Object.prototype.hasOwnProperty.call(req.body || {}, 'peerIds') || + Object.prototype.hasOwnProperty.call(req.body || {}, 'strategyGuid')) { + await applyUserScopeFromBody(result.id, username, req.body); + } + // Log action await db.logAction(req.session.userId, 'user_created', `Created user: ${username} (${userRole})`, req.ip); @@ -366,7 +419,9 @@ router.post('/api/users', requireAuth, requirePermission('user.create'), passwor username, role: userRole, email: savedEmail, - user_groups: groupGuids + user_groups: groupGuids, + folder_ids: await userScopeService.getUserFolderIds(db, username), + peer_grants: await userScopeService.getUserPeerGrantIds(db, result.id) } }); } catch (err) { @@ -453,6 +508,7 @@ router.patch('/api/users/:id', requireAuth, requirePermission('user.edit'), asyn } await updateUserGroupMembershipsFromBody(userId, req.body); + await applyUserScopeFromBody(userId, user.username, req.body); if (email !== undefined) { const normalizedEmail = normalizeUserEmail(email); @@ -472,6 +528,33 @@ router.patch('/api/users/:id', requireAuth, requirePermission('user.edit'), asyn } }); +/** + * GET /api/users/:id/effective-scope - Count devices visible to a user (admin). + */ +router.get('/api/users/:id/effective-scope', requireAuth, requirePermission('user.view'), async (req, res) => { + try { + const userId = parseInt(req.params.id, 10); + if (isNaN(userId) || userId <= 0) { + return res.status(400).json({ success: false, error: 'Invalid user ID' }); + } + const user = await db.getUserById(userId); + if (!user) { + return res.status(404).json({ success: false, error: req.t('users.not_found') }); + } + let devices = []; + try { + devices = await serverBackend.getAllDevices({}); + } catch (_) { + devices = []; + } + const result = await userScopeService.countEffectiveScope(db, user, devices); + res.json({ success: true, data: result }); + } catch (err) { + console.error('Effective scope error:', err); + res.status(500).json({ success: false, error: req.t('errors.server_error') }); + } +}); + /** * DELETE /api/users/:id - Delete user (admin only) */ diff --git a/web-nodejs/scripts/patch-role-scope-i18n.js b/web-nodejs/scripts/patch-role-scope-i18n.js new file mode 100644 index 00000000..f111240a --- /dev/null +++ b/web-nodejs/scripts/patch-role-scope-i18n.js @@ -0,0 +1,108 @@ +#!/usr/bin/env node +/** + * One-shot i18n patch for role/scope UX (#227). Run: node web-nodejs/scripts/patch-role-scope-i18n.js + */ +const fs = require('fs'); +const path = require('path'); + +const langDir = path.join(__dirname, '..', 'lang'); +const locales = fs.readdirSync(langDir).filter(f => f.endsWith('.json')); + +const patches = { + pl: { + users: { + role_operator: 'Operator zdalny', + role_pro: 'Licencja Pro (tylko API klienta)', + role_hint: 'Rola serwerowa. Operatorzy zdalni łączą się zdalnie i mogą zmieniać własne hasło. Viewer ma tylko odczyt (bez Web Client). Licencja Pro aktywuje RustDesk Pro przez API klienta — bez dostępu do panelu. Role organizacji konfigurujesz osobno.', + scope_hint: 'Ogranicz widoczność urządzeń: przypisz grupy użytkowników, foldery i/lub urządzenia bezpośrednio. Bez ograniczeń operatorzy i viewerzy widzą wszystkie urządzenia (chyba że włączono tryb restricted w Ustawieniach).', + role_desc_viewer: 'Tylko odczyt w panelu: lista urządzeń, audyt, metryki. Bez Web Remote Desktop.', + role_desc_operator: 'Może łączyć się przez Web Client i klienta RustDesk, edytować urządzenia i zmieniać własne hasło w Ustawieniach.', + role_desc_pro: 'Tylko API klienta RustDesk — aktywacja licencji Pro. Brak logowania do panelu.', + role_desc_admin: 'Pełny dostęp (legacy alias Super Admin).', + role_desc_super_admin: 'Pełny dostęp do serwera i panelu, w tym konfiguracja użytkowników i serwera.', + role_desc_server_admin: 'Infrastruktura serwera, klucze, podgląd użytkowników. Bez łączenia z urządzeniami.', + role_desc_global_admin: 'Zarządzanie użytkownikami, organizacjami i urządzeniami. Bez konfiguracji serwera.', + user_folders: 'Dostęp do folderów', + user_folders_hint: 'Urządzenia w wybranych folderach są widoczne, gdy skonfigurowano ACL folderu.', + user_direct_devices: 'Urządzenia bezpośrednie', + user_direct_devices_hint: 'Przyznaj dostęp do konkretnych urządzeń niezależnie od folderu lub grupy.', + user_direct_devices_placeholder: 'ID urządzeń oddzielone przecinkami', + effective_scope_count: '{count} widocznych urządzeń', + loading_folders: 'Ładowanie folderów...', + no_folders: 'Brak folderów', + pro_strategy_label: 'Strategia RustDesk Pro', + pro_strategy_hint: 'Opcjonalna strategia kontroli dostępu dla funkcji Pro w kliencie (osobno od roli panelu).', + pro_strategy_none: 'Brak', + column_scope: 'Zakres', + tip_operator_role: 'Użyj Operatora zdalnego dla użytkowników z Web Client i samodzielną zmianą hasła', + tip_viewer_role: 'Użyj Viewera do monitorowania tylko do odczytu; Operatora zdalnego do łączenia' + }, + settings: { + device_scope_title: 'Domyślna widoczność urządzeń', + device_scope_desc: 'Określa, co widzą użytkownicy bez roli admin, gdy brak grantów folderów/grup/urządzeń.', + device_scope_open: 'Otwarty (legacy) — pokaż wszystkie urządzenia do skonfigurowania ACL', + device_scope_restricted: 'Restricted — pokaż tylko jawnie przyznane urządzenia', + device_scope_save: 'Zapisz domyślną widoczność', + device_scope_saved: 'Zapisano domyślną widoczność urządzeń', + device_scope_invalid: 'Nieprawidłowy tryb widoczności', + device_scope_restricted_warning: 'Tryb restricted jest aktywny. Upewnij się, że użytkownicy mają granty folderów, grup lub urządzeń — inaczej zobaczą pustą listę.' + } + } +}; + +const enFallback = JSON.parse(fs.readFileSync(path.join(langDir, 'en.json'), 'utf8')); + +function deepSet(obj, keyPath, value) { + const parts = keyPath.split('.'); + let cur = obj; + for (let i = 0; i < parts.length - 1; i++) { + if (!cur[parts[i]] || typeof cur[parts[i]] !== 'object') cur[parts[i]] = {}; + cur = cur[parts[i]]; + } + cur[parts[parts.length - 1]] = value; +} + +for (const file of locales) { + const locale = file.replace('.json', ''); + const filePath = path.join(langDir, file); + const raw = fs.readFileSync(filePath, 'utf8').replace(/^\uFEFF/, ''); + const data = JSON.parse(raw); + + const userKeys = Object.keys(enFallback.users || {}).filter(k => + ['role_operator', 'role_pro', 'role_hint', 'scope_hint', 'role_desc_', 'user_folders', 'user_direct', 'effective_scope', 'loading_folders', 'no_folders', 'pro_strategy', 'column_scope', 'tip_operator', 'tip_viewer'].some(p => k.startsWith(p.replace('_', '')) || k.includes(p.split('_')[0])) + ); + + const keysToCopy = [ + 'role_operator', 'role_pro', 'role_hint', 'scope_hint', + 'role_desc_viewer', 'role_desc_operator', 'role_desc_pro', 'role_desc_admin', + 'role_desc_super_admin', 'role_desc_server_admin', 'role_desc_global_admin', + 'user_folders', 'user_folders_hint', 'user_direct_devices', 'user_direct_devices_hint', + 'user_direct_devices_placeholder', 'effective_scope_count', 'loading_folders', 'no_folders', + 'pro_strategy_label', 'pro_strategy_hint', 'pro_strategy_none', 'column_scope', + 'tip_operator_role', 'tip_viewer_role' + ]; + + const settingKeys = [ + 'device_scope_title', 'device_scope_desc', 'device_scope_open', 'device_scope_restricted', + 'device_scope_save', 'device_scope_saved', 'device_scope_invalid', 'device_scope_restricted_warning' + ]; + + for (const key of keysToCopy) { + if (patches[locale]?.users?.[key]) { + data.users[key] = patches[locale].users[key]; + } else if (enFallback.users[key]) { + data.users[key] = enFallback.users[key]; + } + } + + for (const key of settingKeys) { + if (patches[locale]?.settings?.[key]) { + data.settings[key] = patches[locale].settings[key]; + } else if (enFallback.settings[key]) { + data.settings[key] = enFallback.settings[key]; + } + } + + fs.writeFileSync(filePath, JSON.stringify(data, null, 2) + '\n', 'utf8'); + console.log('Patched', file); +} diff --git a/web-nodejs/services/dbAdapter.js b/web-nodejs/services/dbAdapter.js index b39f39c2..e5a1572b 100644 --- a/web-nodejs/services/dbAdapter.js +++ b/web-nodejs/services/dbAdapter.js @@ -523,6 +523,14 @@ function createSqliteAdapter(config) { UNIQUE(target_type, target_key) ); CREATE INDEX IF NOT EXISTS idx_strategy_assignments_strategy ON strategy_assignments (strategy_guid); + CREATE TABLE IF NOT EXISTS user_peer_grants ( + user_id INTEGER NOT NULL, + peer_id TEXT NOT NULL, + granted_at TEXT DEFAULT (datetime('now')), + PRIMARY KEY (user_id, peer_id), + FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE + ); + CREATE INDEX IF NOT EXISTS idx_user_peer_grants_user ON user_peer_grants (user_id); CREATE TABLE IF NOT EXISTS notification_reads ( user_id INTEGER NOT NULL, notification_id TEXT NOT NULL, @@ -3260,6 +3268,61 @@ function createSqliteAdapter(config) { }; }, + async getUserStrategyGuid(userId) { + const userGuid = await this.ensureUserGuid(userId); + if (!userGuid) return ''; + const row = openAuth().prepare(` + SELECT strategy_guid FROM strategy_assignments + WHERE target_type = 'user' AND target_key = ? + LIMIT 1 + `).get(userGuid); + return row?.strategy_guid || ''; + }, + + async setUserStrategyAssignment(userId, strategyGuid) { + const userGuid = await this.ensureUserGuid(userId); + if (!userGuid) throw new Error('user not found'); + strategyGuid = String(strategyGuid || '').trim(); + if (!strategyGuid) { + openAuth().prepare(` + DELETE FROM strategy_assignments WHERE target_type = 'user' AND target_key = ? + `).run(userGuid); + return ''; + } + const st = await this.getStrategyByGuid(strategyGuid); + if (!st) throw new Error('strategy not found'); + openAuth().prepare(` + INSERT INTO strategy_assignments (target_type, target_key, strategy_guid, updated_at) + VALUES ('user', ?, ?, datetime('now')) + ON CONFLICT(target_type, target_key) DO UPDATE SET + strategy_guid = excluded.strategy_guid, + updated_at = excluded.updated_at + `).run(userGuid, strategyGuid); + return strategyGuid; + }, + + async getUserPeerGrants(userId) { + const rows = openAuth().prepare(` + SELECT peer_id FROM user_peer_grants WHERE user_id = ? ORDER BY peer_id ASC + `).all(userId); + return rows.map(r => r.peer_id); + }, + + async setUserPeerGrants(userId, peerIds = []) { + const auth = openAuth(); + const normalized = Array.from(new Set( + (peerIds || []).map(id => String(id || '').trim()).filter(Boolean) + )).slice(0, 500); + auth.prepare('DELETE FROM user_peer_grants WHERE user_id = ?').run(userId); + const insert = auth.prepare(` + INSERT INTO user_peer_grants (user_id, peer_id) VALUES (?, ?) + `); + auth.transaction((ids) => { + for (const peerId of ids) insert.run(userId, peerId); + })(normalized); + return normalized; + }, + async setStrategyEnabled(guid, enabled) { const row = await this.updateStrategy(guid, { enabled: !!enabled }); if (!row) throw new Error('strategy not found'); @@ -4237,6 +4300,15 @@ function createPostgresAdapter() { ) `); await q('CREATE INDEX IF NOT EXISTS idx_strategy_assignments_strategy ON strategy_assignments (strategy_guid)'); + await q(` + CREATE TABLE IF NOT EXISTS user_peer_grants ( + user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE, + peer_id TEXT NOT NULL, + granted_at TIMESTAMPTZ DEFAULT NOW(), + PRIMARY KEY (user_id, peer_id) + ) + `); + await q('CREATE INDEX IF NOT EXISTS idx_user_peer_grants_user ON user_peer_grants (user_id)'); // Seed default groups if empty const ugCheck = await one('SELECT COUNT(*)::INTEGER AS c FROM user_groups'); @@ -6479,6 +6551,68 @@ function createPostgresAdapter() { }; }, + async getUserStrategyGuid(userId) { + const userGuid = await this.ensureUserGuid(userId); + if (!userGuid) return ''; + const row = await one(` + SELECT strategy_guid FROM strategy_assignments + WHERE target_type = 'user' AND target_key = $1 + LIMIT 1 + `, [userGuid]); + return row?.strategy_guid || ''; + }, + + async setUserStrategyAssignment(userId, strategyGuid) { + const userGuid = await this.ensureUserGuid(userId); + if (!userGuid) throw new Error('user not found'); + strategyGuid = String(strategyGuid || '').trim(); + if (!strategyGuid) { + await q(`DELETE FROM strategy_assignments WHERE target_type = 'user' AND target_key = $1`, [userGuid]); + return ''; + } + const st = await this.getStrategyByGuid(strategyGuid); + if (!st) throw new Error('strategy not found'); + await q(` + INSERT INTO strategy_assignments (target_type, target_key, strategy_guid, updated_at) + VALUES ('user', $1, $2, NOW()) + ON CONFLICT (target_type, target_key) DO UPDATE SET + strategy_guid = EXCLUDED.strategy_guid, + updated_at = EXCLUDED.updated_at + `, [userGuid, strategyGuid]); + return strategyGuid; + }, + + async getUserPeerGrants(userId) { + const rows = await all(` + SELECT peer_id FROM user_peer_grants WHERE user_id = $1 ORDER BY peer_id ASC + `, [userId]); + return rows.map(r => r.peer_id); + }, + + async setUserPeerGrants(userId, peerIds = []) { + const normalized = Array.from(new Set( + (peerIds || []).map(id => String(id || '').trim()).filter(Boolean) + )).slice(0, 500); + const client = await getPool().connect(); + try { + await client.query('BEGIN'); + await client.query('DELETE FROM user_peer_grants WHERE user_id = $1', [userId]); + for (const peerId of normalized) { + await client.query( + 'INSERT INTO user_peer_grants (user_id, peer_id) VALUES ($1, $2)', + [userId, peerId] + ); + } + await client.query('COMMIT'); + } catch (err) { + await client.query('ROLLBACK'); + throw err; + } finally { + client.release(); + } + return normalized; + }, + async setStrategyEnabled(guid, enabled) { const row = await this.updateStrategy(guid, { enabled: !!enabled }); if (!row) throw new Error('strategy not found'); diff --git a/web-nodejs/services/deviceGroupService.js b/web-nodejs/services/deviceGroupService.js index cf5b1d29..948929fa 100644 --- a/web-nodejs/services/deviceGroupService.js +++ b/web-nodejs/services/deviceGroupService.js @@ -1,6 +1,32 @@ 'use strict'; const { isSuperAdminRole } = require('../middleware/auth'); +const config = require('../config/config'); + +let scopeDefaultCache = { value: null, at: 0 }; + +async function isDeviceScopeRestrictedDefault(db) { + const envRestricted = String(config.deviceScopeDefault || 'open').toLowerCase() === 'restricted'; + if (!db || typeof db.getSetting !== 'function') return envRestricted; + const now = Date.now(); + if (scopeDefaultCache.value !== null && now - scopeDefaultCache.at < 30000) { + return scopeDefaultCache.value; + } + try { + const stored = await db.getSetting('device_scope_default'); + const restricted = stored + ? String(stored).toLowerCase() === 'restricted' + : envRestricted; + scopeDefaultCache = { value: restricted, at: now }; + return restricted; + } catch (_) { + return envRestricted; + } +} + +function invalidateDeviceScopeDefaultCache() { + scopeDefaultCache = { value: null, at: 0 }; +} function normalizeTags(value) { if (!value) return []; @@ -177,13 +203,26 @@ async function getDeviceScopeForUser(db, user, devices = []) { if (typeof db.getAllDeviceGroups !== 'function') return null; + const restrictedDefault = await isDeviceScopeRestrictedDefault(db); const accessUser = await getUserAccessContext(db, user); const groups = await db.getAllDeviceGroups(); const restrictedGroups = (groups || []).filter(group => normalizeUsernames(group.allowed_users).length > 0 || normalizeGroupGuids(group.allowed_groups || group.allowed_user_groups).length > 0 ); - if (restrictedGroups.length === 0) return null; + + let peerGrants = []; + if (typeof db.getUserPeerGrants === 'function') { + try { + peerGrants = await db.getUserPeerGrants(user.id); + } catch (_) { + peerGrants = []; + } + } + + if (restrictedGroups.length === 0 && peerGrants.length === 0) { + return restrictedDefault ? new Set() : null; + } const allowedIds = new Set(); const restrictedIds = new Set(); @@ -192,6 +231,11 @@ async function getDeviceScopeForUser(db, user, devices = []) { const target = groupAllowedForUser(group, accessUser) ? allowedIds : restrictedIds; for (const id of ids) target.add(id); } + for (const id of peerGrants) allowedIds.add(String(id)); + + if (restrictedDefault) { + return allowedIds; + } const visible = new Set(); for (const device of devices || []) { @@ -313,4 +357,5 @@ module.exports = { resolveOperatorUsernamesForDevice, resolveOperatorEmailsForDevice, resolveFolderNameForDevice, + invalidateDeviceScopeDefaultCache, }; diff --git a/web-nodejs/services/userScopeService.js b/web-nodejs/services/userScopeService.js new file mode 100644 index 00000000..ebed5106 --- /dev/null +++ b/web-nodejs/services/userScopeService.js @@ -0,0 +1,108 @@ +/** + * User device scope — folder ACL sync, peer grants, effective visibility. + */ + +const config = require('../config/config'); +const deviceGroupService = require('./deviceGroupService'); + +function folderGroupGuid(folderId) { + return `folder_${folderId}`; +} + +function normalizeFolderIds(value) { + const raw = Array.isArray(value) ? value : []; + return Array.from(new Set(raw.map(v => Number.parseInt(v, 10)).filter(Number.isFinite))).slice(0, 200); +} + +function normalizePeerIds(value) { + const raw = Array.isArray(value) ? value : String(value || '').split(','); + return Array.from(new Set(raw.map(v => String(v || '').trim()).filter(Boolean))).slice(0, 500); +} + +async function ensureFolderMirrorGroup(db, folder) { + const guid = folderGroupGuid(folder.id); + const payload = { + guid, + name: folder.name, + note: 'BetterDesk folder access scope', + source_type: 'manual', + tag_filter: '' + }; + let group = await db.getDeviceGroupByGuid(guid); + if (group) { + await db.updateDeviceGroup(guid, payload); + group = await db.getDeviceGroupByGuid(guid); + } else if (typeof db.createDeviceGroup === 'function') { + group = await db.createDeviceGroup(payload); + } + return group; +} + +async function getUserFolderIds(db, username) { + if (!username || typeof db.getAllFolders !== 'function') return []; + const folders = await db.getAllFolders(); + const result = []; + for (const folder of folders || []) { + const group = await db.getDeviceGroupByGuid(folderGroupGuid(folder.id)); + if (!group) continue; + const users = deviceGroupService.normalizeUsernames(group.allowed_users); + if (users.includes(username)) result.push(Number(folder.id)); + } + return result; +} + +async function syncUserFolderAccess(db, username, folderIds) { + if (!username || typeof db.getAllFolders !== 'function') return []; + const selected = new Set(normalizeFolderIds(folderIds)); + const folders = await db.getAllFolders(); + for (const folder of folders || []) { + const group = await ensureFolderMirrorGroup(db, folder); + if (!group) continue; + const currentUsers = deviceGroupService.normalizeUsernames(group.allowed_users); + const wantAccess = selected.has(Number(folder.id)); + const hasAccess = currentUsers.includes(username); + if (wantAccess && !hasAccess) { + await db.setDeviceGroupUserAccess(group.guid, [...currentUsers, username]); + } else if (!wantAccess && hasAccess) { + await db.setDeviceGroupUserAccess( + group.guid, + currentUsers.filter(name => name !== username) + ); + } + } + return Array.from(selected); +} + +async function getUserPeerGrantIds(db, userId) { + if (!userId || typeof db.getUserPeerGrants !== 'function') return []; + return db.getUserPeerGrants(userId); +} + +async function syncUserPeerGrants(db, userId, peerIds) { + if (!userId || typeof db.setUserPeerGrants !== 'function') return []; + const normalized = normalizePeerIds(peerIds); + await db.setUserPeerGrants(userId, normalized); + return normalized; +} + +async function countEffectiveScope(db, user, devices) { + const scope = await deviceGroupService.getDeviceScopeForUser(db, user, devices); + if (scope === null) return { count: (devices || []).length, restricted: false }; + return { count: scope.size, restricted: true }; +} + +function isDeviceScopeRestrictedDefault() { + return String(config.deviceScopeDefault || 'open').toLowerCase() === 'restricted'; +} + +module.exports = { + folderGroupGuid, + normalizeFolderIds, + normalizePeerIds, + getUserFolderIds, + syncUserFolderAccess, + getUserPeerGrantIds, + syncUserPeerGrants, + countEffectiveScope, + isDeviceScopeRestrictedDefault +}; diff --git a/web-nodejs/tests/deviceGroupService.scope.test.js b/web-nodejs/tests/deviceGroupService.scope.test.js new file mode 100644 index 00000000..ee72fd7c --- /dev/null +++ b/web-nodejs/tests/deviceGroupService.scope.test.js @@ -0,0 +1,39 @@ +const deviceGroupService = require('../services/deviceGroupService'); + +describe('deviceGroupService scope (#227)', () => { + const devices = [ + { id: '100', folder_id: 1 }, + { id: '200', folder_id: 2 }, + { id: '300', folder_id: null } + ]; + + test('open default returns null when no ACL or grants', async () => { + const db = { + getAllDeviceGroups: jest.fn().mockResolvedValue([]), + getUserPeerGrants: jest.fn().mockResolvedValue([]) + }; + const user = { id: 5, username: 'op1', role: 'operator' }; + const scope = await deviceGroupService.getDeviceScopeForUser(db, user, devices); + expect(scope).toBeNull(); + }); + + test('direct peer grants are always visible in open overlay mode', async () => { + const db = { + getAllDeviceGroups: jest.fn().mockResolvedValue([ + { + guid: 'folder_1', + folder_id: 1, + allowed_users: ['other'], + allowed_groups: [], + source_type: 'manual' + } + ]), + getUserGroupsForUser: jest.fn().mockResolvedValue([]), + getUserPeerGrants: jest.fn().mockResolvedValue(['300']) + }; + const user = { id: 5, username: 'op1', role: 'operator', user_groups: [] }; + const scope = await deviceGroupService.getDeviceScopeForUser(db, user, devices); + expect(scope).not.toBeNull(); + expect(scope.has('300')).toBe(true); + }); +}); diff --git a/web-nodejs/views/devices.ejs b/web-nodejs/views/devices.ejs index 4310eb3e..ad7db70c 100644 --- a/web-nodejs/views/devices.ejs +++ b/web-nodejs/views/devices.ejs @@ -221,6 +221,11 @@ placeholder="operator1, operator2">

${_('devices.group_allowed_users_hint')}

+
+ +
+

${_('devices.group_allowed_user_groups_hint')}

+
` diff --git a/web-nodejs/views/settings.ejs b/web-nodejs/views/settings.ejs index 8263496c..1e68486d 100644 --- a/web-nodejs/views/settings.ejs +++ b/web-nodejs/views/settings.ejs @@ -142,6 +142,39 @@ + ${canServerConfig ? ` +
+
+

+ devices + ${_('settings.device_scope_title')} +

+

${_('settings.device_scope_desc')}

+
+
+
+
+ +
+
+ +
+ + +
+
+
+ ` : ''} + ${canServerConfig ? `
diff --git a/web-nodejs/views/users.ejs b/web-nodejs/views/users.ejs index 4f8dec42..aaa8cbfc 100644 --- a/web-nodejs/views/users.ejs +++ b/web-nodejs/views/users.ejs @@ -28,6 +28,7 @@
  • check_circle ${_('users.tip_strong_passwords')}
  • check_circle ${_('users.tip_unique_accounts')}
  • +
  • check_circle ${_('users.tip_operator_role')}
  • check_circle ${_('users.tip_viewer_role')}
  • check_circle ${_('users.tip_audit_logs')}
@@ -59,6 +60,7 @@ ${_('users.username')} ${_('users.email')} ${_('users.role')} + ${_('users.column_scope')} ${_('users.provider')} ${_('users.column_organizations')} ${_('users.created')} @@ -68,7 +70,7 @@ - + @@ -126,6 +128,7 @@ +

${_('users.role_hint')}
@@ -136,6 +139,31 @@
${_('users.user_groups_hint')} + +
+ +
+
${_('users.loading_folders')}
+
+ ${_('users.user_folders_hint')} +
+ +
+ + + ${_('users.user_direct_devices_hint')} +
+ +
+ + + ${_('users.pro_strategy_hint')} +
+ +

${_('users.scope_hint')}

`