diff --git a/CHANGELOG.md b/CHANGELOG.md index f74afe09..dc1ae6a6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,13 @@ ## [Unreleased] +### Fixed +- **Device ID change (#213):** RustDesk 1.4.x client rename works again — the server accepts `RegisterPk` ID-change requests with an empty PK (stock client wire format) instead of returning `NOT_SUPPORT` / “Not yet supported by the server”. +- **Panel ID change:** Permanent delete now clears `id_change_history` so released IDs can be reused; panel/API ID changes cascade to enrollment tokens, org assignments, and linked peers; renamed-ID registration allows the same device (matching IP or PK/UUID) to stay connected after a panel-side rename. +- **Panel sync:** Go `peer_id_changed` events now cascade panel DB rows in real time; periodic Go→panel sync applies client-side renames; hard delete purges stale panel `peer` rows; BD-API `/register` uses the same identity-aware rename guard as the signal server. + ### Changed -- _(none yet)_ +- **Panel UX:** Device list reloads on live ID-change events; online-device warning before change-ID from both list and detail views. +- **RustDesk Pro parity:** Panel strategies manager on Devices page; org address book structured editor; `/api/device-group/accessible` returns accessible payload (matches Go); Go API supports `POST /api/device-group`, strategy update/delete; device detail panel receives live status and ID-change events. --- diff --git a/betterdesk-server/api/client_group_handlers.go b/betterdesk-server/api/client_group_handlers.go index 81a9e796..875fbca3 100644 --- a/betterdesk-server/api/client_group_handlers.go +++ b/betterdesk-server/api/client_group_handlers.go @@ -130,9 +130,10 @@ func (s *Server) handleStrategiesGet(w http.ResponseWriter, r *http.Request) { writeJSON(w, http.StatusOK, map[string]any{"data": data, "total": len(data)}) } -// handleStrategiesPost creates a strategy. Auth enforced by route wrapper. +// handleStrategiesPost creates or updates a strategy. Auth enforced by route wrapper. func (s *Server) handleStrategiesPost(w http.ResponseWriter, r *http.Request) { var body struct { + GUID string `json:"guid"` Name string `json:"name"` UserGroupGUID string `json:"user_group_guid"` DeviceGroupGUID string `json:"device_group_guid"` @@ -156,6 +157,38 @@ func (s *Server) handleStrategiesPost(w http.ResponseWriter, r *http.Request) { if len(body.Permissions) > 0 && string(body.Permissions) != "null" { perms = string(body.Permissions) } + + guid := truncStr(body.GUID, maxIDLen) + if guid != "" { + existing, err := s.db.GetStrategy(guid) + if err != nil { + writeInternalError(w, err, "GetStrategy") + return + } + if existing == nil { + writeJSON(w, http.StatusNotFound, map[string]string{"error": "Strategy not found"}) + return + } + st := &db.Strategy{ + Name: body.Name, + UserGroupGUID: truncStr(body.UserGroupGUID, maxIDLen), + DeviceGroupGUID: truncStr(body.DeviceGroupGUID, maxIDLen), + Enabled: enabled, + Permissions: perms, + } + if err := s.db.UpdateStrategy(guid, st); err != nil { + writeInternalError(w, err, "UpdateStrategy") + return + } + updated, err := s.db.GetStrategy(guid) + if err != nil { + writeInternalError(w, err, "GetStrategy") + return + } + writeJSON(w, http.StatusOK, updated) + return + } + st := &db.Strategy{ Name: body.Name, UserGroupGUID: truncStr(body.UserGroupGUID, maxIDLen), @@ -169,3 +202,26 @@ func (s *Server) handleStrategiesPost(w http.ResponseWriter, r *http.Request) { } writeJSON(w, http.StatusOK, st) } + +// handleStrategiesDelete removes a strategy by GUID. +func (s *Server) handleStrategiesDelete(w http.ResponseWriter, r *http.Request) { + guid := truncStr(r.PathValue("guid"), maxIDLen) + if guid == "" { + writeJSON(w, http.StatusBadRequest, map[string]string{"error": "guid is required"}) + return + } + existing, err := s.db.GetStrategy(guid) + if err != nil { + writeInternalError(w, err, "GetStrategy") + return + } + if existing == nil { + writeJSON(w, http.StatusNotFound, map[string]string{"error": "Strategy not found"}) + return + } + if err := s.db.DeleteStrategy(guid); err != nil { + writeInternalError(w, err, "DeleteStrategy") + return + } + writeJSON(w, http.StatusOK, map[string]string{"status": "deleted"}) +} diff --git a/betterdesk-server/api/server.go b/betterdesk-server/api/server.go index a7041c39..6df47d0a 100644 --- a/betterdesk-server/api/server.go +++ b/betterdesk-server/api/server.go @@ -361,6 +361,7 @@ func (s *Server) Start(ctx context.Context) error { mux.HandleFunc("POST /api/group/get", s.handleClientGroupList) mux.HandleFunc("GET /api/device-group", s.handleClientGroupList) mux.HandleFunc("GET /api/device-group/accessible", s.handleClientGroupList) + mux.HandleFunc("POST /api/device-group", s.requirePanelAdmin(s.handleDeviceGroupsPost)) mux.HandleFunc("GET /api/peers/list", s.handleClientGroupPeers) mux.HandleFunc("POST /api/peers/list", s.handleClientGroupPeers) @@ -378,6 +379,7 @@ func (s *Server) Start(ctx context.Context) error { mux.HandleFunc("POST /api/user-groups", s.requirePanelAdmin(s.handleUserGroupsPost)) mux.HandleFunc("GET /api/strategies", s.handleStrategiesGet) mux.HandleFunc("POST /api/strategies", s.requirePanelAdmin(s.handleStrategiesPost)) + mux.HandleFunc("DELETE /api/strategies/{guid}", s.requirePanelAdmin(s.handleStrategiesDelete)) mux.HandleFunc("POST /api/audit/conn", s.handleAuditConnPost) mux.HandleFunc("GET /api/audit/conn", s.requirePermission(auth.PermAuditView, s.handleAuditConnGet)) mux.HandleFunc("POST /api/audit/file", s.handleAuditFilePost) @@ -1294,7 +1296,7 @@ func (s *Server) handleChangePeerID(w http.ResponseWriter, r *http.Request) { return } - if err := s.db.ChangePeerID(oldID, body.NewID); err != nil { + if err := s.db.ChangePeerID(oldID, body.NewID, "panel"); err != nil { if errors.Is(err, db.ErrPeerIDExists) { writeJSON(w, http.StatusConflict, map[string]string{"error": "Device ID already exists"}) return @@ -1324,6 +1326,17 @@ func (s *Server) handleChangePeerID(w http.ResponseWriter, r *http.Request) { s.auditLog.Log(audit.ActionPeerIDChanged, s.remoteIP(r), oldID, map[string]string{"new_id": body.NewID}) } + if s.eventBus != nil { + s.eventBus.Publish(eventsModule.Event{ + Type: eventsModule.EventPeerIDChanged, + Data: map[string]string{ + "old_id": oldID, + "new_id": body.NewID, + "source": "panel", + }, + }) + } + writeJSON(w, http.StatusOK, map[string]string{ "status": "changed", "old_id": oldID, diff --git a/betterdesk-server/db/database.go b/betterdesk-server/db/database.go index 86cae862..f3fffdd2 100644 --- a/betterdesk-server/db/database.go +++ b/betterdesk-server/db/database.go @@ -467,11 +467,15 @@ type Database interface { RestorePeer(id string) error // ID change - ChangePeerID(oldID, newID string) error + ChangePeerID(oldID, newID, reason string) error GetIDChangeHistory(id string) ([]*IDChangeHistory, error) // IsRenamedPeerID returns true if the given ID was previously used and // changed to a different one (appears as old_id in id_change_history). IsRenamedPeerID(id string) (bool, error) + // GetLatestRenameTarget returns the most recent new_id for a renamed old_id. + GetLatestRenameTarget(oldID string) (string, error) + // ReleasePeerID clears id_change_history rows involving id so the ID can be reused. + ReleasePeerID(id string) error // CDAP: linked device queries GetLinkedPeers(id string) ([]*Peer, error) diff --git a/betterdesk-server/db/postgres.go b/betterdesk-server/db/postgres.go index 5c3d47ae..77cc9ac1 100644 --- a/betterdesk-server/db/postgres.go +++ b/betterdesk-server/db/postgres.go @@ -751,10 +751,23 @@ func (pg *PostgresDB) DeletePeer(id string) error { return err } -// HardDeletePeer permanently removes a peer from the database. +// HardDeletePeer permanently removes a peer from the database and releases +// any id_change_history reservations for that ID (#213). func (pg *PostgresDB) HardDeletePeer(id string) error { - _, err := pg.pool.Exec(pg.ctx, `DELETE FROM peers WHERE id = $1`, id) - return err + tx, err := pg.pool.Begin(pg.ctx) + if err != nil { + return err + } + defer tx.Rollback(pg.ctx) + + if _, err := tx.Exec(pg.ctx, `DELETE FROM peers WHERE id = $1`, id); err != nil { + return err + } + if _, err := tx.Exec(pg.ctx, + `DELETE FROM id_change_history WHERE old_id = $1 OR new_id = $1`, id); err != nil { + return err + } + return tx.Commit(pg.ctx) } // ListPeers returns all peers, optionally including soft-deleted ones. @@ -952,9 +965,28 @@ func (pg *PostgresDB) UpdatePeerFields(id string, fields map[string]string) erro // ── ID Change ───────────────────────────────────────────────────────── +func (pg *PostgresDB) cascadePeerIDInTx(ctx context.Context, tx pgx.Tx, oldID, newID string) error { + stmts := []struct { + query string + args []any + }{ + {`UPDATE device_tokens SET peer_id = $1 WHERE peer_id = $2`, []any{newID, oldID}}, + {`UPDATE org_devices SET device_id = $1 WHERE device_id = $2`, []any{newID, oldID}}, + {`UPDATE peers SET linked_peer_id = $1 WHERE linked_peer_id = $2`, []any{newID, oldID}}, + {`UPDATE device_folder_assignments SET device_id = $1 WHERE device_id = $2`, []any{newID, oldID}}, + {`UPDATE device_group_members SET peer_id = $1 WHERE peer_id = $2`, []any{newID, oldID}}, + } + for _, st := range stmts { + if _, err := tx.Exec(ctx, st.query, st.args...); err != nil { + return fmt.Errorf("db: cascadePeerID %s→%s: %w", oldID, newID, err) + } + } + return nil +} + // ChangePeerID changes a peer's ID and records it in history. // Uses a PostgreSQL transaction with row-level locking. -func (pg *PostgresDB) ChangePeerID(oldID, newID string) error { +func (pg *PostgresDB) ChangePeerID(oldID, newID, reason string) error { tx, err := pg.pool.Begin(pg.ctx) if err != nil { return err @@ -996,9 +1028,13 @@ func (pg *PostgresDB) ChangePeerID(oldID, newID string) error { return fmt.Errorf("db: ChangePeerID delete: %w", err) } + if err := pg.cascadePeerIDInTx(pg.ctx, tx, oldID, newID); err != nil { + return err + } + if _, err := tx.Exec(pg.ctx, - `INSERT INTO id_change_history (old_id, new_id) VALUES ($1, $2)`, - oldID, newID); err != nil { + `INSERT INTO id_change_history (old_id, new_id, reason) VALUES ($1, $2, $3)`, + oldID, newID, reason); err != nil { return fmt.Errorf("db: ChangePeerID history: %w", err) } @@ -1038,6 +1074,28 @@ func (pg *PostgresDB) IsRenamedPeerID(id string) (bool, error) { return count > 0, nil } +// GetLatestRenameTarget returns the most recent new_id for old_id. +func (pg *PostgresDB) GetLatestRenameTarget(oldID string) (string, error) { + var newID string + err := pg.pool.QueryRow(pg.ctx, + `SELECT new_id FROM id_change_history WHERE old_id = $1 ORDER BY id DESC LIMIT 1`, + oldID).Scan(&newID) + if err == pgx.ErrNoRows { + return "", nil + } + if err != nil { + return "", err + } + return newID, nil +} + +// ReleasePeerID clears id_change_history rows involving id. +func (pg *PostgresDB) ReleasePeerID(id string) error { + _, err := pg.pool.Exec(pg.ctx, + `DELETE FROM id_change_history WHERE old_id = $1 OR new_id = $1`, id) + return err +} + // GetLinkedPeers returns all non-deleted peers that have linked_peer_id matching the given ID. func (pg *PostgresDB) GetLinkedPeers(id string) ([]*Peer, error) { rows, err := pg.pool.Query(pg.ctx, ` diff --git a/betterdesk-server/db/sqlite.go b/betterdesk-server/db/sqlite.go index 67006b40..d1189e07 100644 --- a/betterdesk-server/db/sqlite.go +++ b/betterdesk-server/db/sqlite.go @@ -746,13 +746,26 @@ func (s *SQLiteDB) DeletePeer(id string) error { return err } -// HardDeletePeer permanently removes a peer from the database. +// HardDeletePeer permanently removes a peer from the database and releases +// any id_change_history reservations for that ID (#213). func (s *SQLiteDB) HardDeletePeer(id string) error { s.mu.Lock() defer s.mu.Unlock() - _, err := s.db.Exec(`DELETE FROM peers WHERE id = ?`, id) - return err + tx, err := s.db.Begin() + if err != nil { + return err + } + defer tx.Rollback() + + if _, err := tx.Exec(`DELETE FROM peers WHERE id = ?`, id); err != nil { + return err + } + if _, err := tx.Exec( + `DELETE FROM id_change_history WHERE old_id = ? OR new_id = ?`, id, id); err != nil { + return err + } + return tx.Commit() } // ListPeers returns all peers, optionally including soft-deleted ones. @@ -1024,8 +1037,25 @@ func (s *SQLiteDB) UpdatePeerFields(id string, fields map[string]string) error { return err } +func (s *SQLiteDB) cascadePeerIDInTx(tx *sql.Tx, oldID, newID string) error { + stmts := []struct { + query string + args []any + }{ + {`UPDATE device_tokens SET peer_id = ? WHERE peer_id = ?`, []any{newID, oldID}}, + {`UPDATE org_devices SET device_id = ? WHERE device_id = ?`, []any{newID, oldID}}, + {`UPDATE peers SET linked_peer_id = ? WHERE linked_peer_id = ?`, []any{newID, oldID}}, + } + for _, st := range stmts { + if _, err := tx.Exec(st.query, st.args...); err != nil { + return fmt.Errorf("db: cascadePeerID %s→%s: %w", oldID, newID, err) + } + } + return nil +} + // ChangePeerID changes a peer's ID and records it in history. -func (s *SQLiteDB) ChangePeerID(oldID, newID string) error { +func (s *SQLiteDB) ChangePeerID(oldID, newID, reason string) error { s.mu.Lock() defer s.mu.Unlock() @@ -1074,10 +1104,14 @@ func (s *SQLiteDB) ChangePeerID(oldID, newID string) error { return fmt.Errorf("db: ChangePeerID delete: %w", err) } + if err := s.cascadePeerIDInTx(tx, oldID, newID); err != nil { + return err + } + // Record in history if _, err := tx.Exec( - `INSERT INTO id_change_history (old_id, new_id) VALUES (?, ?)`, - oldID, newID); err != nil { + `INSERT INTO id_change_history (old_id, new_id, reason) VALUES (?, ?, ?)`, + oldID, newID, reason); err != nil { return fmt.Errorf("db: ChangePeerID history: %w", err) } @@ -1126,6 +1160,34 @@ func (s *SQLiteDB) IsRenamedPeerID(id string) (bool, error) { return count > 0, nil } +// GetLatestRenameTarget returns the most recent new_id for old_id. +func (s *SQLiteDB) GetLatestRenameTarget(oldID string) (string, error) { + s.mu.RLock() + defer s.mu.RUnlock() + + var newID string + err := s.db.QueryRow( + `SELECT new_id FROM id_change_history WHERE old_id = ? ORDER BY rowid DESC LIMIT 1`, + oldID).Scan(&newID) + if err == sql.ErrNoRows { + return "", nil + } + if err != nil { + return "", err + } + return newID, nil +} + +// ReleasePeerID clears id_change_history rows involving id. +func (s *SQLiteDB) ReleasePeerID(id string) error { + s.mu.Lock() + defer s.mu.Unlock() + + _, err := s.db.Exec( + `DELETE FROM id_change_history WHERE old_id = ? OR new_id = ?`, id, id) + return err +} + // GetLinkedPeers returns all non-deleted peers that have linked_peer_id matching the given ID. func (s *SQLiteDB) GetLinkedPeers(id string) ([]*Peer, error) { s.mu.RLock() diff --git a/betterdesk-server/db/sqlite_test.go b/betterdesk-server/db/sqlite_test.go index dd25cfbd..14eb3606 100644 --- a/betterdesk-server/db/sqlite_test.go +++ b/betterdesk-server/db/sqlite_test.go @@ -406,7 +406,7 @@ func TestChangePeerID(t *testing.T) { db.UpsertPeer(&Peer{ID: "OLD1", Hostname: "mypc", Status: "ONLINE"}) - if err := db.ChangePeerID("OLD1", "NEW1"); err != nil { + if err := db.ChangePeerID("OLD1", "NEW1", ""); err != nil { t.Fatal(err) } @@ -441,7 +441,7 @@ func TestChangePeerIDDuplicate(t *testing.T) { db.UpsertPeer(&Peer{ID: "A1", Status: "ONLINE"}) db.UpsertPeer(&Peer{ID: "B1", Status: "ONLINE"}) - err := db.ChangePeerID("A1", "B1") + err := db.ChangePeerID("A1", "B1", "") if err == nil { t.Error("expected error when changing to existing ID") } @@ -468,12 +468,12 @@ func TestChangePeerIDSoftDeletedTarget(t *testing.T) { t.Fatalf("MACPRO state = %s, want %s", state, PeerIDSoftDeleted) } - err = db.ChangePeerID("NEWCLIENT", "MACPRO") + err = db.ChangePeerID("NEWCLIENT", "MACPRO", "") if !errors.Is(err, ErrPeerIDSoftDeleted) { t.Fatalf("ChangePeerID to soft-deleted target error = %v, want ErrPeerIDSoftDeleted", err) } - if err := db.ChangePeerID("NEWCLIENT", "MACPRO1"); err != nil { + if err := db.ChangePeerID("NEWCLIENT", "MACPRO1", ""); err != nil { t.Fatalf("ChangePeerID to free target: %v", err) } peer, err := db.GetPeer("MACPRO1") @@ -495,7 +495,7 @@ func TestChangePeerIDSoftDeletedSourceDoesNotMoveReservation(t *testing.T) { t.Fatal(err) } - err := db.ChangePeerID("MACPRO", "MACPRO1") + err := db.ChangePeerID("MACPRO", "MACPRO1", "") if !errors.Is(err, ErrPeerNotFound) { t.Fatalf("ChangePeerID from soft-deleted source error = %v, want ErrPeerNotFound", err) } @@ -602,6 +602,63 @@ func TestHardDelete(t *testing.T) { } } +func TestHardDeleteReleasesIDHistory(t *testing.T) { + db := newTestDB(t) + + if err := db.UpsertPeer(&Peer{ID: "OLDREL", Status: "ONLINE"}); err != nil { + t.Fatal(err) + } + if err := db.ChangePeerID("OLDREL", "NEWREL", "client"); err != nil { + t.Fatal(err) + } + renamed, err := db.IsRenamedPeerID("OLDREL") + if err != nil || !renamed { + t.Fatalf("OLDREL should be reserved before hard delete: %v %v", renamed, err) + } + + if err := db.HardDeletePeer("NEWREL"); err != nil { + t.Fatal(err) + } + renamed, err = db.IsRenamedPeerID("OLDREL") + if err != nil { + t.Fatal(err) + } + if renamed { + t.Fatal("OLDREL should be released after permanent delete of successor") + } +} + +func TestChangePeerIDCascadesDeviceTokens(t *testing.T) { + db := newTestDB(t) + + if err := db.UpsertPeer(&Peer{ID: "OLDCAS", Status: "ONLINE"}); err != nil { + t.Fatal(err) + } + token := &DeviceToken{ + Token: "cascadetoken12345", + TokenHash: "hash-cascade-token", + Name: "Cascade test", + PeerID: "OLDCAS", + Status: TokenStatusPending, + MaxUses: 1, + } + if err := db.CreateDeviceToken(token); err != nil { + t.Fatal(err) + } + + if err := db.ChangePeerID("OLDCAS", "NEWCAS", "panel"); err != nil { + t.Fatal(err) + } + + got, err := db.GetDeviceTokenByPeerID("NEWCAS") + if err != nil || got == nil { + t.Fatalf("device token should follow ID change: %v %+v", err, got) + } + if stale, _ := db.GetDeviceTokenByPeerID("OLDCAS"); stale != nil { + t.Fatalf("token should not remain on old ID: %+v", stale) + } +} + // TestMigrateUpgradesLegacySchema simulates a database created by an older // version (without totp_secret, totp_enabled in users table). Migrate() // should add the missing columns so CreateUser and GetUser work correctly. diff --git a/betterdesk-server/signal/handler.go b/betterdesk-server/signal/handler.go index b580c7ab..7b7b0d36 100644 --- a/betterdesk-server/signal/handler.go +++ b/betterdesk-server/signal/handler.go @@ -272,9 +272,8 @@ func (s *Server) handleRegisterPeer(msg *pb.RegisterPeer, raddr *net.UDPAddr) { } // Check if this ID was previously changed to a different one (#97) — - // do not allow a device to come back under its old ID. - if renamed, _ := s.db.IsRenamedPeerID(id); renamed { - log.Printf("[signal] Rejected registration for renamed peer ID: %s from %s", id, raddr.IP) + // allow the same device to keep using the old ID until it adopts the new one (#213). + if s.rejectRenamedPeerRegistration(id, clientHost, nil, nil) { return } @@ -412,9 +411,8 @@ func (s *Server) processRegisterPk(msg *pb.RegisterPk, addrStr string) *pb.Rende return registerPkResponse(pb.RegisterPkResponse_NOT_SUPPORT) } - // Check if this ID was previously changed to a different one (#97) - if renamed, _ := s.db.IsRenamedPeerID(id); renamed { - log.Printf("[signal] Rejected PK registration for renamed peer ID: %s", id) + // Check if this ID was previously changed to a different one (#97). + if s.rejectRenamedPeerRegistration(id, clientHost, msg.Uuid, msg.Pk) { return registerPkResponse(pb.RegisterPkResponse_NOT_SUPPORT) } @@ -487,15 +485,26 @@ func (s *Server) processIDChange(msg *pb.RegisterPk) *pb.RendezvousMessage { if oldPeer == nil { return registerPkResponse(pb.RegisterPkResponse_NOT_SUPPORT) } - if oldPeer.UUID != "" && oldPeer.UUID != fmt.Sprintf("%x", msg.Uuid) { - log.Printf("[signal] Rejected ID change %s → %s: UUID mismatch", oldID, newID) - return registerPkResponse(pb.RegisterPkResponse_UUID_MISMATCH) - } - if len(oldPeer.PK) > 0 && !bytes.Equal(oldPeer.PK, msg.Pk) { + + // RustDesk 1.4.x change-ID sends RegisterPk without pk (see change_id_shared). + // When pk is omitted, use the stored key; when present, enforce a match (#213). + effectivePK := msg.Pk + if len(effectivePK) == 0 { + effectivePK = oldPeer.PK + } else if len(oldPeer.PK) > 0 && !bytes.Equal(oldPeer.PK, effectivePK) { log.Printf("[signal] Rejected ID change %s → %s: PK mismatch", oldID, newID) return registerPkResponse(pb.RegisterPkResponse_NOT_SUPPORT) } + // UUID: stock clients send machine_uid bytes on ID change (not 16-byte get_uuid()). + // Enforce match only for wire-format 16-byte UUIDs. + if len(msg.Uuid) == 16 && oldPeer.UUID != "" { + if !peerUUIDEqual(peerUUIDFromDB(oldPeer.UUID), msg.Uuid) { + log.Printf("[signal] Rejected ID change %s → %s: UUID mismatch", oldID, newID) + return registerPkResponse(pb.RegisterPkResponse_UUID_MISMATCH) + } + } + // Validate new ID doesn't exist existing := s.peers.Get(newID) if existing != nil { @@ -512,7 +521,7 @@ func (s *Server) processIDChange(msg *pb.RegisterPk) *pb.RendezvousMessage { } // Perform the change - if err := s.db.ChangePeerID(oldID, newID); err != nil { + if err := s.db.ChangePeerID(oldID, newID, "client"); err != nil { if errors.Is(err, db.ErrPeerIDExists) || errors.Is(err, db.ErrPeerIDSoftDeleted) { return registerPkResponse(pb.RegisterPkResponse_ID_EXISTS) } @@ -527,12 +536,24 @@ func (s *Server) processIDChange(msg *pb.RegisterPk) *pb.RendezvousMessage { oldEntry := s.peers.Remove(oldID) if oldEntry != nil { oldEntry.ID = newID - oldEntry.PK = msg.Pk - oldEntry.UUID = msg.Uuid + oldEntry.PK = effectivePK + if len(msg.Uuid) > 0 { + oldEntry.UUID = normalizePeerUUIDBytes(msg.Uuid) + } s.peers.Put(oldEntry) } log.Printf("[signal] ID changed: %s → %s", oldID, newID) + if s.eventBus != nil { + s.eventBus.Publish(events.Event{ + Type: events.EventPeerIDChanged, + Data: map[string]string{ + "old_id": oldID, + "new_id": newID, + "source": "client", + }, + }) + } return registerPkResponse(pb.RegisterPkResponse_OK) } diff --git a/betterdesk-server/signal/handler_test.go b/betterdesk-server/signal/handler_test.go index a5584b54..baa7a9d4 100644 --- a/betterdesk-server/signal/handler_test.go +++ b/betterdesk-server/signal/handler_test.go @@ -338,6 +338,120 @@ func TestProcessIDChangeRejectsPKMismatch(t *testing.T) { } } +func TestProcessIDChangeSuccessEmptyPK(t *testing.T) { + srv, database := newTestSignalServer(t, config.EnrollmentModeOpen) + + storedPK := bytes.Repeat([]byte{0x42}, 32) + if err := database.UpsertPeer(&db.Peer{ + ID: "MACPRO1", + Status: "ONLINE", + PK: storedPK, + UUID: "aabbccddeeff00112233445566778899", + }); err != nil { + t.Fatal(err) + } + + msg := &pb.RegisterPk{ + Id: "MACPRO", + OldId: "MACPRO1", + Uuid: []byte("machine-uid-bytes"), + } + resp := srv.processIDChange(msg) + if got := registerPkResult(resp); got != pb.RegisterPkResponse_OK { + t.Fatalf("ID change result = %v, want %v", got, pb.RegisterPkResponse_OK) + } + + peer, err := database.GetPeer("MACPRO") + if err != nil || peer == nil { + t.Fatalf("GetPeer MACPRO: %v %+v", err, peer) + } + if len(peer.PK) != 32 || !bytes.Equal(peer.PK, storedPK) { + t.Fatalf("PK not preserved: len=%d", len(peer.PK)) + } + if old, _ := database.GetPeer("MACPRO1"); old != nil { + t.Fatalf("MACPRO1 should be removed after ID change: %+v", old) + } +} + +func TestProcessIDChangeRejectsWrongPKWhenPKSent(t *testing.T) { + srv, database := newTestSignalServer(t, config.EnrollmentModeOpen) + + if err := database.UpsertPeer(&db.Peer{ + ID: "OLD213", + Status: "ONLINE", + PK: bytes.Repeat([]byte{0x99}, 32), + }); err != nil { + t.Fatal(err) + } + + msg := newRegisterPk("NEW213") + msg.OldId = "OLD213" + msg.Pk = bytes.Repeat([]byte{0x42}, 32) + resp := srv.processIDChange(msg) + if got := registerPkResult(resp); got != pb.RegisterPkResponse_NOT_SUPPORT { + t.Fatalf("ID change result = %v, want %v", got, pb.RegisterPkResponse_NOT_SUPPORT) + } +} + +func TestRejectRenamedPeerRegistrationAllowsSameIP(t *testing.T) { + srv, database := newTestSignalServer(t, config.EnrollmentModeOpen) + + if err := database.UpsertPeer(&db.Peer{ + ID: "MACPRO1", + Status: "ONLINE", + IP: "203.0.113.50", + PK: bytes.Repeat([]byte{0x42}, 32), + }); err != nil { + t.Fatal(err) + } + if err := database.ChangePeerID("MACPRO1", "MACPRO2", "panel"); err != nil { + t.Fatal(err) + } + + if srv.rejectRenamedPeerRegistration("MACPRO1", "203.0.113.50", nil, nil) { + t.Fatal("same IP should be allowed on renamed old ID") + } + if !srv.rejectRenamedPeerRegistration("MACPRO1", "198.51.100.99", nil, nil) { + t.Fatal("different IP should be rejected on renamed old ID") + } +} + +func TestTCPIDChangeEmptyPKRustDesk147(t *testing.T) { + srv, database := newTestSignalServer(t, config.EnrollmentModeOpen) + + storedPK := bytes.Repeat([]byte{0x42}, 32) + if err := database.UpsertPeer(&db.Peer{ + ID: "MACPRO1", + Status: "ONLINE", + PK: storedPK, + IP: "203.0.113.10", + }); err != nil { + t.Fatal(err) + } + + fakeAddr, err := net.ResolveTCPAddr("tcp", "203.0.113.10:50123") + if err != nil { + t.Fatal(err) + } + + resp := srv.handleRegisterPkTCP(&pb.RegisterPk{ + OldId: "MACPRO1", + Id: "MACPRO", + Uuid: []byte("machine-uid-bytes"), + }, fakeAddr) + if got := registerPkResult(resp); got != pb.RegisterPkResponse_OK { + t.Fatalf("TCP ID change result = %v, want OK", got) + } + + peer, err := database.GetPeer("MACPRO") + if err != nil || peer == nil { + t.Fatalf("GetPeer MACPRO: %v %+v", err, peer) + } + if !bytes.Equal(peer.PK, storedPK) { + t.Fatal("stored PK should be preserved when client omits pk") + } +} + func TestProcessRegisterPkManagedAllowsTokenBoundPeer(t *testing.T) { srv, database := newTestSignalServer(t, config.EnrollmentModeManaged) diff --git a/betterdesk-server/signal/peer_id_registration.go b/betterdesk-server/signal/peer_id_registration.go new file mode 100644 index 00000000..1ab0f5be --- /dev/null +++ b/betterdesk-server/signal/peer_id_registration.go @@ -0,0 +1,68 @@ +package signal + +import ( + "bytes" + "log" + "net" + "strings" +) + +// rejectRenamedPeerRegistration returns true when a peer must be blocked from +// registering under id because it was renamed and the caller is not the owner. +// +// RustDesk 1.4.x clients may keep using the old ID after a panel-initiated +// rename; allow the same device (matching PK/UUID or IP) to continue until it +// adopts the new ID (#213). +func (s *Server) rejectRenamedPeerRegistration(id, clientHost string, uuid, pk []byte) bool { + renamed, err := s.db.IsRenamedPeerID(id) + if err != nil { + log.Printf("[signal] IsRenamedPeerID(%s): %v", id, err) + return true + } + if !renamed { + return false + } + + newID, err := s.db.GetLatestRenameTarget(id) + if err != nil { + log.Printf("[signal] GetLatestRenameTarget(%s): %v", id, err) + return true + } + if newID == "" { + return true + } + + successor, err := s.db.GetPeer(newID) + if err != nil { + log.Printf("[signal] GetPeer(%s) for rename successor: %v", newID, err) + return true + } + if successor == nil { + // Successor removed — old ID is free to reuse. + return false + } + + if len(pk) > 0 && len(successor.PK) > 0 && bytes.Equal(successor.PK, pk) { + return false + } + if len(uuid) > 0 && successor.UUID != "" && peerUUIDEqual(peerUUIDFromDB(successor.UUID), uuid) { + return false + } + if len(pk) == 0 && len(uuid) == 0 && clientHost != "" && peerIPMatches(clientHost, successor.IP) { + return false + } + + log.Printf("[signal] Rejected registration for renamed peer ID: %s (successor %s)", id, newID) + return true +} + +func peerIPMatches(clientHost, storedIP string) bool { + if clientHost == "" || storedIP == "" { + return false + } + storedHost := storedIP + if h, _, err := net.SplitHostPort(storedIP); err == nil && h != "" { + storedHost = h + } + return clientHost == storedHost || strings.HasPrefix(storedIP, clientHost+":") +} diff --git a/betterdesk-server/signal/ws.go b/betterdesk-server/signal/ws.go index 55e0c815..3a45acab 100644 --- a/betterdesk-server/signal/ws.go +++ b/betterdesk-server/signal/ws.go @@ -371,8 +371,7 @@ func (s *Server) handleRegisterPeerWS(msg *pb.RegisterPeer, remoteAddr string) * return nil } - if renamed, _ := s.db.IsRenamedPeerID(id); renamed { - log.Printf("[signal] Rejected WS registration for renamed peer ID: %s from %s", id, remoteAddr) + if s.rejectRenamedPeerRegistration(id, clientHost, nil, nil) { return nil } diff --git a/web-nodejs/lang/ar.json b/web-nodejs/lang/ar.json index 4fe4f145..87141660 100644 --- a/web-nodejs/lang/ar.json +++ b/web-nodejs/lang/ar.json @@ -1,4 +1,4 @@ -{ +{ "_meta": { "name": "Arabic", "native_name": "العربية", @@ -245,6 +245,7 @@ "change_id_title": "تغيير معرف الجهاز", "new_id": "New ID", "change_id_hint": "6-16 حرفًا وأحرفًا وأرقامًا وشرطات وشرطات سفلية فقط", + "change_id_online_warn": "الجهاز متصل. يُفضّل تغيير المعرّف في عميل RustDesk. يحافظ التغيير من اللوحة على هوية الجهاز، لكن يجب على العميل اعتماد المعرّف الجديد.", "change_id_success": "تم تغيير معرف الجهاز بنجاح", "id_length_error": "يجب أن يتراوح طول المعرف من 6 إلى 16 حرفًا", "id_format_error": "يمكن أن يحتوي المعرف فقط على أحرف وأرقام وشرطات وشرطات سفلية", @@ -341,6 +342,41 @@ "group_invalid": "معرف المجموعة غير صالح", "group_not_found": "لم يتم العثور على المجموعة", "group_saved": "تم حفظ مجموعة الأجهزة", + "strategies_title": "Access control strategies", + "strategies_hint": "Link user groups to device groups and push RustDesk Pro security settings to matching clients.", + "create_strategy": "Add strategy", + "edit_strategy": "Edit strategy", + "delete_strategy": "Delete strategy", + "delete_strategy_confirm": "Delete strategy {name}?", + "strategy_name": "Strategy name", + "strategy_name_required": "Strategy name is required", + "strategy_user_group": "User group", + "strategy_device_group": "Device group", + "strategy_any_user_group": "Any user group", + "strategy_any_device_group": "Any device group", + "strategy_enabled": "Enabled", + "strategy_disabled": "Disabled", + "strategy_enabled_label": "Strategy enabled", + "strategy_permissions": "Client security settings", + "strategy_permissions_hint": "Uses RustDesk advanced-setting keys (Y/N). Leave Default to keep client or strategy inheritance.", + "strategy_permissions_count": "{count} settings", + "strategy_perm_default": "Default", + "strategy_perm_allow": "Allow (Y)", + "strategy_perm_deny": "Deny (N)", + "strategy_perm_file_transfer": "File transfer", + "strategy_perm_disable_clipboard": "Disable clipboard", + "strategy_perm_enable_clipboard": "Enable clipboard", + "strategy_perm_audio": "Audio", + "strategy_perm_tunnel": "TCP tunnel", + "strategy_perm_camera": "Camera", + "strategy_perm_restart": "Remote restart", + "strategy_perm_block_input": "Block user input", + "strategy_created": "Strategy created", + "strategy_updated": "Strategy updated", + "strategy_deleted": "Strategy deleted", + "strategy_not_found": "Strategy not found", + "no_strategies": "No access control strategies yet", + "loading_strategies": "Loading strategies...", "groups_saved": "تم حفظ مجموعات الأجهزة", "group_membership_added": "تمت الإضافة إلى المجموعة", "group_membership_removed": "تمت الإزالة من المجموعة", @@ -2607,7 +2643,19 @@ "address_book_saved": "تم حفظ دفتر العناوين", "address_book_save_failed": "فشل حفظ دفتر العناوين", "address_book_imported": "تم استيراد أجهزة المنظمة", - "address_book_import_none": "لا توجد أجهزة منظمة جديدة للاستيراد" + "address_book_import_none": "لا توجد أجهزة منظمة جديدة للاستيراد", + "address_book_contacts": "Shared contacts", + "address_book_add_peer": "Add contact", + "address_book_peer_id": "Device ID", + "address_book_alias": "Alias", + "address_book_peer_tags": "Contact tags", + "address_book_peer_tags_placeholder": "office, support", + "address_book_tags_label": "Global tags", + "address_book_tags_placeholder": "Office, Support", + "address_book_tags_hint": "Comma-separated tags available in the RustDesk client address book.", + "address_book_no_peers": "No contacts yet — add a device ID or import organization devices.", + "address_book_show_json": "Advanced JSON", + "address_book_hide_json": "Structured editor" }, "chat": { "e2e_enabled": "مشفرة من طرف إلى طرف", diff --git a/web-nodejs/lang/cs.json b/web-nodejs/lang/cs.json index c4d71ab4..e053cfed 100644 --- a/web-nodejs/lang/cs.json +++ b/web-nodejs/lang/cs.json @@ -1,4 +1,4 @@ -{ +{ "meta": { "lang": "cs", "name": "Czech", @@ -238,6 +238,7 @@ "change_id_title": "Změnit ID zařízení", "new_id": "Nové ID", "change_id_hint": "6-16 znaků, pouze písmena, číslice, pomlčky a podtržítka", + "change_id_online_warn": "Zařízení je online. ID raději měňte v klientu RustDesk. Změna v panelu zachová identitu, ale klient musí přijmout nové ID.", "change_id_success": "ID zařízení úspěšně změněno", "id_length_error": "ID musí mít 6-16 znaků", "id_format_error": "ID může obsahovat pouze písmena, číslice, pomlčky a podtržítka", @@ -334,6 +335,41 @@ "group_invalid": "Neplatný identifikátor skupiny", "group_not_found": "Skupina nebyla nalezena", "group_saved": "Skupina zařízení uložena", + "strategies_title": "Access control strategies", + "strategies_hint": "Link user groups to device groups and push RustDesk Pro security settings to matching clients.", + "create_strategy": "Add strategy", + "edit_strategy": "Edit strategy", + "delete_strategy": "Delete strategy", + "delete_strategy_confirm": "Delete strategy {name}?", + "strategy_name": "Strategy name", + "strategy_name_required": "Strategy name is required", + "strategy_user_group": "User group", + "strategy_device_group": "Device group", + "strategy_any_user_group": "Any user group", + "strategy_any_device_group": "Any device group", + "strategy_enabled": "Enabled", + "strategy_disabled": "Disabled", + "strategy_enabled_label": "Strategy enabled", + "strategy_permissions": "Client security settings", + "strategy_permissions_hint": "Uses RustDesk advanced-setting keys (Y/N). Leave Default to keep client or strategy inheritance.", + "strategy_permissions_count": "{count} settings", + "strategy_perm_default": "Default", + "strategy_perm_allow": "Allow (Y)", + "strategy_perm_deny": "Deny (N)", + "strategy_perm_file_transfer": "File transfer", + "strategy_perm_disable_clipboard": "Disable clipboard", + "strategy_perm_enable_clipboard": "Enable clipboard", + "strategy_perm_audio": "Audio", + "strategy_perm_tunnel": "TCP tunnel", + "strategy_perm_camera": "Camera", + "strategy_perm_restart": "Remote restart", + "strategy_perm_block_input": "Block user input", + "strategy_created": "Strategy created", + "strategy_updated": "Strategy updated", + "strategy_deleted": "Strategy deleted", + "strategy_not_found": "Strategy not found", + "no_strategies": "No access control strategies yet", + "loading_strategies": "Loading strategies...", "groups_saved": "Skupiny zařízení uloženy", "group_membership_added": "Přidáno do skupiny", "group_membership_removed": "Odebráno ze skupiny", @@ -2600,7 +2636,19 @@ "address_book_saved": "Adresář uložen", "address_book_save_failed": "Adresář se nepodařilo uložit", "address_book_imported": "Zařízení organizace importována", - "address_book_import_none": "Žádná nová zařízení organizace k importu" + "address_book_import_none": "Žádná nová zařízení organizace k importu", + "address_book_contacts": "Shared contacts", + "address_book_add_peer": "Add contact", + "address_book_peer_id": "Device ID", + "address_book_alias": "Alias", + "address_book_peer_tags": "Contact tags", + "address_book_peer_tags_placeholder": "office, support", + "address_book_tags_label": "Global tags", + "address_book_tags_placeholder": "Office, Support", + "address_book_tags_hint": "Comma-separated tags available in the RustDesk client address book.", + "address_book_no_peers": "No contacts yet — add a device ID or import organization devices.", + "address_book_show_json": "Advanced JSON", + "address_book_hide_json": "Structured editor" }, "chat": { "e2e_enabled": "Koncové šifrování aktivní", diff --git a/web-nodejs/lang/da.json b/web-nodejs/lang/da.json index 15693dda..394a2262 100644 --- a/web-nodejs/lang/da.json +++ b/web-nodejs/lang/da.json @@ -1,4 +1,4 @@ -{ +{ "_meta": { "name": "Danish", "native_name": "Dansk", @@ -244,6 +244,7 @@ "change_id_title": "Skift enheds-id", "new_id": "New ID", "change_id_hint": "Kun 6-16 tegn, bogstaver, tal, bindestreger og understregninger", + "change_id_online_warn": "Enheden er online. Skift ID helst i RustDesk-klienten. Ændring i panelet bevarer identiteten, men klienten skal overtage det nye ID.", "change_id_success": "Enheds-id blev ændret", "id_length_error": "ID skal være på 6-16 tegn", "id_format_error": "ID kan kun indeholde bogstaver, tal, bindestreger og understregninger", @@ -340,6 +341,41 @@ "group_invalid": "Ugyldig gruppe-id", "group_not_found": "Gruppen blev ikke fundet", "group_saved": "Enhedsgruppen er gemt", + "strategies_title": "Access control strategies", + "strategies_hint": "Link user groups to device groups and push RustDesk Pro security settings to matching clients.", + "create_strategy": "Add strategy", + "edit_strategy": "Edit strategy", + "delete_strategy": "Delete strategy", + "delete_strategy_confirm": "Delete strategy {name}?", + "strategy_name": "Strategy name", + "strategy_name_required": "Strategy name is required", + "strategy_user_group": "User group", + "strategy_device_group": "Device group", + "strategy_any_user_group": "Any user group", + "strategy_any_device_group": "Any device group", + "strategy_enabled": "Enabled", + "strategy_disabled": "Disabled", + "strategy_enabled_label": "Strategy enabled", + "strategy_permissions": "Client security settings", + "strategy_permissions_hint": "Uses RustDesk advanced-setting keys (Y/N). Leave Default to keep client or strategy inheritance.", + "strategy_permissions_count": "{count} settings", + "strategy_perm_default": "Default", + "strategy_perm_allow": "Allow (Y)", + "strategy_perm_deny": "Deny (N)", + "strategy_perm_file_transfer": "File transfer", + "strategy_perm_disable_clipboard": "Disable clipboard", + "strategy_perm_enable_clipboard": "Enable clipboard", + "strategy_perm_audio": "Audio", + "strategy_perm_tunnel": "TCP tunnel", + "strategy_perm_camera": "Camera", + "strategy_perm_restart": "Remote restart", + "strategy_perm_block_input": "Block user input", + "strategy_created": "Strategy created", + "strategy_updated": "Strategy updated", + "strategy_deleted": "Strategy deleted", + "strategy_not_found": "Strategy not found", + "no_strategies": "No access control strategies yet", + "loading_strategies": "Loading strategies...", "groups_saved": "Enhedsgrupper blev gemt", "group_membership_added": "Tilføjet til gruppe", "group_membership_removed": "Fjernet fra gruppe", @@ -2606,7 +2642,19 @@ "address_book_saved": "Adressebog gemt", "address_book_save_failed": "Kunne ikke gemme adressebog", "address_book_imported": "Organisationsenheder importeret", - "address_book_import_none": "Ingen nye organisationsenheder at importere" + "address_book_import_none": "Ingen nye organisationsenheder at importere", + "address_book_contacts": "Shared contacts", + "address_book_add_peer": "Add contact", + "address_book_peer_id": "Device ID", + "address_book_alias": "Alias", + "address_book_peer_tags": "Contact tags", + "address_book_peer_tags_placeholder": "office, support", + "address_book_tags_label": "Global tags", + "address_book_tags_placeholder": "Office, Support", + "address_book_tags_hint": "Comma-separated tags available in the RustDesk client address book.", + "address_book_no_peers": "No contacts yet — add a device ID or import organization devices.", + "address_book_show_json": "Advanced JSON", + "address_book_hide_json": "Structured editor" }, "chat": { "e2e_enabled": "End-to-end krypteret", diff --git a/web-nodejs/lang/de.json b/web-nodejs/lang/de.json index a51c4436..c5eea0d4 100644 --- a/web-nodejs/lang/de.json +++ b/web-nodejs/lang/de.json @@ -1,4 +1,4 @@ -{ +{ "meta": { "lang": "de", "name": "German", @@ -238,6 +238,7 @@ "change_id_title": "Geräte-ID ändern", "new_id": "Neue ID", "change_id_hint": "6-16 Zeichen, nur Buchstaben, Zahlen, Bindestriche und Unterstriche", + "change_id_online_warn": "Das Gerät ist online. Ändern Sie die ID nach Möglichkeit im RustDesk-Client. Eine Änderung im Panel behält die Geräteidentität bei, der Client muss jedoch die neue ID übernehmen.", "change_id_success": "Geräte-ID erfolgreich geändert", "id_length_error": "ID muss 6-16 Zeichen lang sein", "id_format_error": "ID darf nur Buchstaben, Zahlen, Bindestriche und Unterstriche enthalten", @@ -334,6 +335,41 @@ "group_invalid": "Ungültige Gruppenkennung", "group_not_found": "Gruppe nicht gefunden", "group_saved": "Gerätegruppe gespeichert", + "strategies_title": "Access control strategies", + "strategies_hint": "Link user groups to device groups and push RustDesk Pro security settings to matching clients.", + "create_strategy": "Add strategy", + "edit_strategy": "Edit strategy", + "delete_strategy": "Delete strategy", + "delete_strategy_confirm": "Delete strategy {name}?", + "strategy_name": "Strategy name", + "strategy_name_required": "Strategy name is required", + "strategy_user_group": "User group", + "strategy_device_group": "Device group", + "strategy_any_user_group": "Any user group", + "strategy_any_device_group": "Any device group", + "strategy_enabled": "Enabled", + "strategy_disabled": "Disabled", + "strategy_enabled_label": "Strategy enabled", + "strategy_permissions": "Client security settings", + "strategy_permissions_hint": "Uses RustDesk advanced-setting keys (Y/N). Leave Default to keep client or strategy inheritance.", + "strategy_permissions_count": "{count} settings", + "strategy_perm_default": "Default", + "strategy_perm_allow": "Allow (Y)", + "strategy_perm_deny": "Deny (N)", + "strategy_perm_file_transfer": "File transfer", + "strategy_perm_disable_clipboard": "Disable clipboard", + "strategy_perm_enable_clipboard": "Enable clipboard", + "strategy_perm_audio": "Audio", + "strategy_perm_tunnel": "TCP tunnel", + "strategy_perm_camera": "Camera", + "strategy_perm_restart": "Remote restart", + "strategy_perm_block_input": "Block user input", + "strategy_created": "Strategy created", + "strategy_updated": "Strategy updated", + "strategy_deleted": "Strategy deleted", + "strategy_not_found": "Strategy not found", + "no_strategies": "No access control strategies yet", + "loading_strategies": "Loading strategies...", "groups_saved": "Gerätegruppen gespeichert", "group_membership_added": "Zur Gruppe hinzugefügt", "group_membership_removed": "Aus Gruppe entfernt", @@ -2600,7 +2636,19 @@ "address_book_saved": "Adressbuch gespeichert", "address_book_save_failed": "Adressbuch konnte nicht gespeichert werden", "address_book_imported": "Organisationsgeräte importiert", - "address_book_import_none": "Keine neuen Organisationsgeräte zum Importieren" + "address_book_import_none": "Keine neuen Organisationsgeräte zum Importieren", + "address_book_contacts": "Shared contacts", + "address_book_add_peer": "Add contact", + "address_book_peer_id": "Device ID", + "address_book_alias": "Alias", + "address_book_peer_tags": "Contact tags", + "address_book_peer_tags_placeholder": "office, support", + "address_book_tags_label": "Global tags", + "address_book_tags_placeholder": "Office, Support", + "address_book_tags_hint": "Comma-separated tags available in the RustDesk client address book.", + "address_book_no_peers": "No contacts yet — add a device ID or import organization devices.", + "address_book_show_json": "Advanced JSON", + "address_book_hide_json": "Structured editor" }, "chat": { "e2e_enabled": "Ende-zu-Ende verschlüsselt", diff --git a/web-nodejs/lang/en.json b/web-nodejs/lang/en.json index 0d085c71..719eb3f1 100644 --- a/web-nodejs/lang/en.json +++ b/web-nodejs/lang/en.json @@ -248,6 +248,7 @@ "change_id_title": "Change Device ID", "new_id": "New ID", "change_id_hint": "6-16 characters, letters, numbers, dashes and underscores only", + "change_id_online_warn": "This device is online. Prefer changing the ID in the RustDesk client when possible. A panel-side change keeps the same device identity but the client must adopt the new ID to stay connected.", "change_id_success": "Device ID changed successfully", "id_length_error": "ID must be 6-16 characters long", "id_format_error": "ID can only contain letters, numbers, dashes and underscores", @@ -311,6 +312,41 @@ "group_invalid": "Invalid group identifier", "group_not_found": "Group not found", "group_saved": "Device group saved", + "strategies_title": "Access control strategies", + "strategies_hint": "Link user groups to device groups and push RustDesk Pro security settings to matching clients.", + "create_strategy": "Add strategy", + "edit_strategy": "Edit strategy", + "delete_strategy": "Delete strategy", + "delete_strategy_confirm": "Delete strategy {name}?", + "strategy_name": "Strategy name", + "strategy_name_required": "Strategy name is required", + "strategy_user_group": "User group", + "strategy_device_group": "Device group", + "strategy_any_user_group": "Any user group", + "strategy_any_device_group": "Any device group", + "strategy_enabled": "Enabled", + "strategy_disabled": "Disabled", + "strategy_enabled_label": "Strategy enabled", + "strategy_permissions": "Client security settings", + "strategy_permissions_hint": "Uses RustDesk advanced-setting keys (Y/N). Leave Default to keep client or strategy inheritance.", + "strategy_permissions_count": "{count} settings", + "strategy_perm_default": "Default", + "strategy_perm_allow": "Allow (Y)", + "strategy_perm_deny": "Deny (N)", + "strategy_perm_file_transfer": "File transfer", + "strategy_perm_disable_clipboard": "Disable clipboard", + "strategy_perm_enable_clipboard": "Enable clipboard", + "strategy_perm_audio": "Audio", + "strategy_perm_tunnel": "TCP tunnel", + "strategy_perm_camera": "Camera", + "strategy_perm_restart": "Remote restart", + "strategy_perm_block_input": "Block user input", + "strategy_created": "Strategy created", + "strategy_updated": "Strategy updated", + "strategy_deleted": "Strategy deleted", + "strategy_not_found": "Strategy not found", + "no_strategies": "No access control strategies yet", + "loading_strategies": "Loading strategies...", "groups_saved": "Device groups saved", "group_membership_added": "Added to group", "group_membership_removed": "Removed from group", @@ -2730,7 +2766,19 @@ "address_book_saved": "Address book saved", "address_book_save_failed": "Failed to save address book", "address_book_imported": "Organization devices imported", - "address_book_import_none": "No new organization devices to import" + "address_book_import_none": "No new organization devices to import", + "address_book_contacts": "Shared contacts", + "address_book_add_peer": "Add contact", + "address_book_peer_id": "Device ID", + "address_book_alias": "Alias", + "address_book_peer_tags": "Contact tags", + "address_book_peer_tags_placeholder": "office, support", + "address_book_tags_label": "Global tags", + "address_book_tags_placeholder": "Office, Support", + "address_book_tags_hint": "Comma-separated tags available in the RustDesk client address book.", + "address_book_no_peers": "No contacts yet — add a device ID or import organization devices.", + "address_book_show_json": "Advanced JSON", + "address_book_hide_json": "Structured editor" }, "chat": { "e2e_enabled": "End-to-end encrypted", diff --git a/web-nodejs/lang/es.json b/web-nodejs/lang/es.json index 159a0905..ae286bd6 100644 --- a/web-nodejs/lang/es.json +++ b/web-nodejs/lang/es.json @@ -1,4 +1,4 @@ -{ +{ "meta": { "lang": "es", "name": "Spanish", @@ -238,6 +238,7 @@ "change_id_title": "Cambiar ID del dispositivo", "new_id": "Nuevo ID", "change_id_hint": "6-16 caracteres, solo letras, números, guiones y guiones bajos", + "change_id_online_warn": "El dispositivo está en línea. Prefiera cambiar el ID en el cliente RustDesk. Un cambio en el panel conserva la identidad del dispositivo, pero el cliente debe adoptar el nuevo ID.", "change_id_success": "ID del dispositivo cambiado correctamente", "id_length_error": "El ID debe tener entre 6 y 16 caracteres", "id_format_error": "El ID solo puede contener letras, números, guiones y guiones bajos", @@ -334,6 +335,41 @@ "group_invalid": "Identificador de grupo no válido", "group_not_found": "Grupo no encontrado", "group_saved": "Grupo de dispositivos guardado", + "strategies_title": "Access control strategies", + "strategies_hint": "Link user groups to device groups and push RustDesk Pro security settings to matching clients.", + "create_strategy": "Add strategy", + "edit_strategy": "Edit strategy", + "delete_strategy": "Delete strategy", + "delete_strategy_confirm": "Delete strategy {name}?", + "strategy_name": "Strategy name", + "strategy_name_required": "Strategy name is required", + "strategy_user_group": "User group", + "strategy_device_group": "Device group", + "strategy_any_user_group": "Any user group", + "strategy_any_device_group": "Any device group", + "strategy_enabled": "Enabled", + "strategy_disabled": "Disabled", + "strategy_enabled_label": "Strategy enabled", + "strategy_permissions": "Client security settings", + "strategy_permissions_hint": "Uses RustDesk advanced-setting keys (Y/N). Leave Default to keep client or strategy inheritance.", + "strategy_permissions_count": "{count} settings", + "strategy_perm_default": "Default", + "strategy_perm_allow": "Allow (Y)", + "strategy_perm_deny": "Deny (N)", + "strategy_perm_file_transfer": "File transfer", + "strategy_perm_disable_clipboard": "Disable clipboard", + "strategy_perm_enable_clipboard": "Enable clipboard", + "strategy_perm_audio": "Audio", + "strategy_perm_tunnel": "TCP tunnel", + "strategy_perm_camera": "Camera", + "strategy_perm_restart": "Remote restart", + "strategy_perm_block_input": "Block user input", + "strategy_created": "Strategy created", + "strategy_updated": "Strategy updated", + "strategy_deleted": "Strategy deleted", + "strategy_not_found": "Strategy not found", + "no_strategies": "No access control strategies yet", + "loading_strategies": "Loading strategies...", "groups_saved": "Grupos de dispositivos guardados", "group_membership_added": "Añadido al grupo", "group_membership_removed": "Eliminado del grupo", @@ -2600,7 +2636,19 @@ "address_book_saved": "Libreta de direcciones guardada", "address_book_save_failed": "No se pudo guardar la libreta de direcciones", "address_book_imported": "Dispositivos de la organización importados", - "address_book_import_none": "No hay nuevos dispositivos de la organización para importar" + "address_book_import_none": "No hay nuevos dispositivos de la organización para importar", + "address_book_contacts": "Shared contacts", + "address_book_add_peer": "Add contact", + "address_book_peer_id": "Device ID", + "address_book_alias": "Alias", + "address_book_peer_tags": "Contact tags", + "address_book_peer_tags_placeholder": "office, support", + "address_book_tags_label": "Global tags", + "address_book_tags_placeholder": "Office, Support", + "address_book_tags_hint": "Comma-separated tags available in the RustDesk client address book.", + "address_book_no_peers": "No contacts yet — add a device ID or import organization devices.", + "address_book_show_json": "Advanced JSON", + "address_book_hide_json": "Structured editor" }, "chat": { "e2e_enabled": "Cifrado de extremo a extremo", diff --git a/web-nodejs/lang/fi.json b/web-nodejs/lang/fi.json index 9d039197..c78dff29 100644 --- a/web-nodejs/lang/fi.json +++ b/web-nodejs/lang/fi.json @@ -1,4 +1,4 @@ -{ +{ "_meta": { "name": "Finnish", "native_name": "Suomi", @@ -244,6 +244,7 @@ "change_id_title": "Vaihda laitetunnus", "new_id": "New ID", "change_id_hint": "Vain 6-16 merkkiä, kirjaimia, numeroita, väliviivoja ja alaviivoja", + "change_id_online_warn": "Laite on online-tilassa. Muuta ID mieluiten RustDesk-asiakkaassa. Paneelimuutos säilyttää identiteetin, mutta asiakkaan on otettava uusi ID käyttöön.", "change_id_success": "Laitetunnus vaihdettiin onnistuneesti", "id_length_error": "ID:n tulee olla 6-16 merkkiä pitkä", "id_format_error": "Tunnus voi sisältää vain kirjaimia, numeroita, väliviivoja ja alaviivoja", @@ -340,6 +341,41 @@ "group_invalid": "Virheellinen ryhmän tunniste", "group_not_found": "Ryhmää ei löydy", "group_saved": "Laiteryhmä tallennettu", + "strategies_title": "Access control strategies", + "strategies_hint": "Link user groups to device groups and push RustDesk Pro security settings to matching clients.", + "create_strategy": "Add strategy", + "edit_strategy": "Edit strategy", + "delete_strategy": "Delete strategy", + "delete_strategy_confirm": "Delete strategy {name}?", + "strategy_name": "Strategy name", + "strategy_name_required": "Strategy name is required", + "strategy_user_group": "User group", + "strategy_device_group": "Device group", + "strategy_any_user_group": "Any user group", + "strategy_any_device_group": "Any device group", + "strategy_enabled": "Enabled", + "strategy_disabled": "Disabled", + "strategy_enabled_label": "Strategy enabled", + "strategy_permissions": "Client security settings", + "strategy_permissions_hint": "Uses RustDesk advanced-setting keys (Y/N). Leave Default to keep client or strategy inheritance.", + "strategy_permissions_count": "{count} settings", + "strategy_perm_default": "Default", + "strategy_perm_allow": "Allow (Y)", + "strategy_perm_deny": "Deny (N)", + "strategy_perm_file_transfer": "File transfer", + "strategy_perm_disable_clipboard": "Disable clipboard", + "strategy_perm_enable_clipboard": "Enable clipboard", + "strategy_perm_audio": "Audio", + "strategy_perm_tunnel": "TCP tunnel", + "strategy_perm_camera": "Camera", + "strategy_perm_restart": "Remote restart", + "strategy_perm_block_input": "Block user input", + "strategy_created": "Strategy created", + "strategy_updated": "Strategy updated", + "strategy_deleted": "Strategy deleted", + "strategy_not_found": "Strategy not found", + "no_strategies": "No access control strategies yet", + "loading_strategies": "Loading strategies...", "groups_saved": "Laiteryhmät tallennettu", "group_membership_added": "Lisätty ryhmään", "group_membership_removed": "Poistettu ryhmästä", @@ -2606,7 +2642,19 @@ "address_book_saved": "Osoitekirja tallennettu", "address_book_save_failed": "Osoitekirjan tallennus epäonnistui", "address_book_imported": "Organisaation laitteet tuotu", - "address_book_import_none": "Ei uusia organisaation laitteita tuotavaksi" + "address_book_import_none": "Ei uusia organisaation laitteita tuotavaksi", + "address_book_contacts": "Shared contacts", + "address_book_add_peer": "Add contact", + "address_book_peer_id": "Device ID", + "address_book_alias": "Alias", + "address_book_peer_tags": "Contact tags", + "address_book_peer_tags_placeholder": "office, support", + "address_book_tags_label": "Global tags", + "address_book_tags_placeholder": "Office, Support", + "address_book_tags_hint": "Comma-separated tags available in the RustDesk client address book.", + "address_book_no_peers": "No contacts yet — add a device ID or import organization devices.", + "address_book_show_json": "Advanced JSON", + "address_book_hide_json": "Structured editor" }, "chat": { "e2e_enabled": "Päästä päähän salattu", diff --git a/web-nodejs/lang/fr.json b/web-nodejs/lang/fr.json index 88c189f5..ceb1f24b 100644 --- a/web-nodejs/lang/fr.json +++ b/web-nodejs/lang/fr.json @@ -1,4 +1,4 @@ -{ +{ "meta": { "language": "Français", "code": "fr", @@ -244,6 +244,7 @@ "change_id_title": "Changer l'ID de l'appareil", "new_id": "Nouvel ID", "change_id_hint": "6-16 caractères, lettres, chiffres, tirets et underscores uniquement", + "change_id_online_warn": "L'appareil est en ligne. Privilégiez le changement d'ID dans le client RustDesk. Un changement via le panneau conserve l'identité, mais le client doit adopter le nouvel ID.", "change_id_success": "ID de l'appareil modifié avec succès", "id_length_error": "L'ID doit contenir entre 6 et 16 caractères", "id_format_error": "L'ID ne peut contenir que des lettres, des chiffres, des tirets et des underscores", @@ -307,6 +308,41 @@ "group_invalid": "Ungültige Gruppenkennung", "group_not_found": "Gruppe nicht gefunden", "group_saved": "Gerätegruppe gespeichert", + "strategies_title": "Access control strategies", + "strategies_hint": "Link user groups to device groups and push RustDesk Pro security settings to matching clients.", + "create_strategy": "Add strategy", + "edit_strategy": "Edit strategy", + "delete_strategy": "Delete strategy", + "delete_strategy_confirm": "Delete strategy {name}?", + "strategy_name": "Strategy name", + "strategy_name_required": "Strategy name is required", + "strategy_user_group": "User group", + "strategy_device_group": "Device group", + "strategy_any_user_group": "Any user group", + "strategy_any_device_group": "Any device group", + "strategy_enabled": "Enabled", + "strategy_disabled": "Disabled", + "strategy_enabled_label": "Strategy enabled", + "strategy_permissions": "Client security settings", + "strategy_permissions_hint": "Uses RustDesk advanced-setting keys (Y/N). Leave Default to keep client or strategy inheritance.", + "strategy_permissions_count": "{count} settings", + "strategy_perm_default": "Default", + "strategy_perm_allow": "Allow (Y)", + "strategy_perm_deny": "Deny (N)", + "strategy_perm_file_transfer": "File transfer", + "strategy_perm_disable_clipboard": "Disable clipboard", + "strategy_perm_enable_clipboard": "Enable clipboard", + "strategy_perm_audio": "Audio", + "strategy_perm_tunnel": "TCP tunnel", + "strategy_perm_camera": "Camera", + "strategy_perm_restart": "Remote restart", + "strategy_perm_block_input": "Block user input", + "strategy_created": "Strategy created", + "strategy_updated": "Strategy updated", + "strategy_deleted": "Strategy deleted", + "strategy_not_found": "Strategy not found", + "no_strategies": "No access control strategies yet", + "loading_strategies": "Loading strategies...", "groups_saved": "Gerätegruppen gespeichert", "group_membership_added": "Zur Gruppe hinzugefügt", "group_membership_removed": "Aus Gruppe entfernt", @@ -2688,7 +2724,19 @@ "address_book_saved": "Carnet d'adresses enregistré", "address_book_save_failed": "Impossible d'enregistrer le carnet d'adresses", "address_book_imported": "Appareils de l'organisation importés", - "address_book_import_none": "Aucun nouvel appareil de l'organisation à importer" + "address_book_import_none": "Aucun nouvel appareil de l'organisation à importer", + "address_book_contacts": "Shared contacts", + "address_book_add_peer": "Add contact", + "address_book_peer_id": "Device ID", + "address_book_alias": "Alias", + "address_book_peer_tags": "Contact tags", + "address_book_peer_tags_placeholder": "office, support", + "address_book_tags_label": "Global tags", + "address_book_tags_placeholder": "Office, Support", + "address_book_tags_hint": "Comma-separated tags available in the RustDesk client address book.", + "address_book_no_peers": "No contacts yet — add a device ID or import organization devices.", + "address_book_show_json": "Advanced JSON", + "address_book_hide_json": "Structured editor" }, "chat": { "e2e_enabled": "Chiffré de bout en bout", diff --git a/web-nodejs/lang/hi.json b/web-nodejs/lang/hi.json index b1d1b8fb..415ab63d 100644 --- a/web-nodejs/lang/hi.json +++ b/web-nodejs/lang/hi.json @@ -1,4 +1,4 @@ -{ +{ "_meta": { "name": "Hindi", "native_name": "हिन्दी", @@ -244,6 +244,7 @@ "change_id_title": "डिवाइस आईडी बदलें", "new_id": "New ID", "change_id_hint": "केवल 6-16 अक्षर, अक्षर, संख्याएँ, डैश और अंडरस्कोर", + "change_id_online_warn": "डिवाइस ऑनलाइन है। RustDesk क्लाइंट में ID बदलना बेहतर है। पैनल पर बदलाव पहचान बनाए रखता है, लेकिन क्लाइंट को नई ID अपनानी होगी।", "change_id_success": "डिवाइस आईडी सफलतापूर्वक बदल दी गई", "id_length_error": "आईडी 6-16 अक्षर लंबी होनी चाहिए", "id_format_error": "आईडी में केवल अक्षर, संख्याएं, डैश और अंडरस्कोर हो सकते हैं", @@ -340,6 +341,41 @@ "group_invalid": "अमान्य समूह पहचानकर्ता", "group_not_found": "समूह नहीं मिला", "group_saved": "डिवाइस समूह सहेजा गया", + "strategies_title": "Access control strategies", + "strategies_hint": "Link user groups to device groups and push RustDesk Pro security settings to matching clients.", + "create_strategy": "Add strategy", + "edit_strategy": "Edit strategy", + "delete_strategy": "Delete strategy", + "delete_strategy_confirm": "Delete strategy {name}?", + "strategy_name": "Strategy name", + "strategy_name_required": "Strategy name is required", + "strategy_user_group": "User group", + "strategy_device_group": "Device group", + "strategy_any_user_group": "Any user group", + "strategy_any_device_group": "Any device group", + "strategy_enabled": "Enabled", + "strategy_disabled": "Disabled", + "strategy_enabled_label": "Strategy enabled", + "strategy_permissions": "Client security settings", + "strategy_permissions_hint": "Uses RustDesk advanced-setting keys (Y/N). Leave Default to keep client or strategy inheritance.", + "strategy_permissions_count": "{count} settings", + "strategy_perm_default": "Default", + "strategy_perm_allow": "Allow (Y)", + "strategy_perm_deny": "Deny (N)", + "strategy_perm_file_transfer": "File transfer", + "strategy_perm_disable_clipboard": "Disable clipboard", + "strategy_perm_enable_clipboard": "Enable clipboard", + "strategy_perm_audio": "Audio", + "strategy_perm_tunnel": "TCP tunnel", + "strategy_perm_camera": "Camera", + "strategy_perm_restart": "Remote restart", + "strategy_perm_block_input": "Block user input", + "strategy_created": "Strategy created", + "strategy_updated": "Strategy updated", + "strategy_deleted": "Strategy deleted", + "strategy_not_found": "Strategy not found", + "no_strategies": "No access control strategies yet", + "loading_strategies": "Loading strategies...", "groups_saved": "डिवाइस समूह सहेजे गए", "group_membership_added": "समूह में जोड़ा गया", "group_membership_removed": "समूह से हटाया गया", @@ -2606,7 +2642,19 @@ "address_book_saved": "पता पुस्तिका सहेजी गई", "address_book_save_failed": "पता पुस्तिका सहेजने में विफल", "address_book_imported": "संगठन डिवाइस आयात किए गए", - "address_book_import_none": "आयात करने के लिए कोई नया संगठन डिवाइस नहीं" + "address_book_import_none": "आयात करने के लिए कोई नया संगठन डिवाइस नहीं", + "address_book_contacts": "Shared contacts", + "address_book_add_peer": "Add contact", + "address_book_peer_id": "Device ID", + "address_book_alias": "Alias", + "address_book_peer_tags": "Contact tags", + "address_book_peer_tags_placeholder": "office, support", + "address_book_tags_label": "Global tags", + "address_book_tags_placeholder": "Office, Support", + "address_book_tags_hint": "Comma-separated tags available in the RustDesk client address book.", + "address_book_no_peers": "No contacts yet — add a device ID or import organization devices.", + "address_book_show_json": "Advanced JSON", + "address_book_hide_json": "Structured editor" }, "chat": { "e2e_enabled": "एंड-टू-एंड एन्क्रिप्टेड", diff --git a/web-nodejs/lang/hu.json b/web-nodejs/lang/hu.json index d649da5a..497a7082 100644 --- a/web-nodejs/lang/hu.json +++ b/web-nodejs/lang/hu.json @@ -1,4 +1,4 @@ -{ +{ "_meta": { "name": "Hungarian", "native_name": "Magyar", @@ -244,6 +244,7 @@ "change_id_title": "Eszközazonosító módosítása", "new_id": "New ID", "change_id_hint": "Csak 6-16 karakter, betűk, számok, kötőjelek és aláhúzásjelek", + "change_id_online_warn": "Az eszköz online. Az ID-t inkább a RustDesk kliensben változtassa. A panelen történő módosítás megtartja az identitást, de a kliensnek át kell vennie az új ID-t.", "change_id_success": "Az eszközazonosító sikeresen módosítva", "id_length_error": "Az azonosító 6-16 karakter hosszú lehet", "id_format_error": "Az azonosító csak betűket, számokat, kötőjeleket és aláhúzásjeleket tartalmazhat", @@ -340,6 +341,41 @@ "group_invalid": "Érvénytelen csoportazonosító", "group_not_found": "Csoport nem található", "group_saved": "Eszközcsoport mentve", + "strategies_title": "Access control strategies", + "strategies_hint": "Link user groups to device groups and push RustDesk Pro security settings to matching clients.", + "create_strategy": "Add strategy", + "edit_strategy": "Edit strategy", + "delete_strategy": "Delete strategy", + "delete_strategy_confirm": "Delete strategy {name}?", + "strategy_name": "Strategy name", + "strategy_name_required": "Strategy name is required", + "strategy_user_group": "User group", + "strategy_device_group": "Device group", + "strategy_any_user_group": "Any user group", + "strategy_any_device_group": "Any device group", + "strategy_enabled": "Enabled", + "strategy_disabled": "Disabled", + "strategy_enabled_label": "Strategy enabled", + "strategy_permissions": "Client security settings", + "strategy_permissions_hint": "Uses RustDesk advanced-setting keys (Y/N). Leave Default to keep client or strategy inheritance.", + "strategy_permissions_count": "{count} settings", + "strategy_perm_default": "Default", + "strategy_perm_allow": "Allow (Y)", + "strategy_perm_deny": "Deny (N)", + "strategy_perm_file_transfer": "File transfer", + "strategy_perm_disable_clipboard": "Disable clipboard", + "strategy_perm_enable_clipboard": "Enable clipboard", + "strategy_perm_audio": "Audio", + "strategy_perm_tunnel": "TCP tunnel", + "strategy_perm_camera": "Camera", + "strategy_perm_restart": "Remote restart", + "strategy_perm_block_input": "Block user input", + "strategy_created": "Strategy created", + "strategy_updated": "Strategy updated", + "strategy_deleted": "Strategy deleted", + "strategy_not_found": "Strategy not found", + "no_strategies": "No access control strategies yet", + "loading_strategies": "Loading strategies...", "groups_saved": "Eszközcsoportok mentve", "group_membership_added": "Hozzáadva a csoporthoz", "group_membership_removed": "Eltávolítva a csoportból", @@ -2606,7 +2642,19 @@ "address_book_saved": "Címjegyzék mentve", "address_book_save_failed": "Nem sikerült menteni a címjegyzéket", "address_book_imported": "Szervezeti eszközök importálva", - "address_book_import_none": "Nincs importálható új szervezeti eszköz" + "address_book_import_none": "Nincs importálható új szervezeti eszköz", + "address_book_contacts": "Shared contacts", + "address_book_add_peer": "Add contact", + "address_book_peer_id": "Device ID", + "address_book_alias": "Alias", + "address_book_peer_tags": "Contact tags", + "address_book_peer_tags_placeholder": "office, support", + "address_book_tags_label": "Global tags", + "address_book_tags_placeholder": "Office, Support", + "address_book_tags_hint": "Comma-separated tags available in the RustDesk client address book.", + "address_book_no_peers": "No contacts yet — add a device ID or import organization devices.", + "address_book_show_json": "Advanced JSON", + "address_book_hide_json": "Structured editor" }, "chat": { "e2e_enabled": "Végponttól végpontig titkosítva", diff --git a/web-nodejs/lang/id.json b/web-nodejs/lang/id.json index 7aeb9f58..6809f1d1 100644 --- a/web-nodejs/lang/id.json +++ b/web-nodejs/lang/id.json @@ -1,4 +1,4 @@ -{ +{ "_meta": { "name": "Indonesian", "native_name": "Bahasa Indonesia", @@ -244,6 +244,7 @@ "change_id_title": "Ubah ID Perangkat", "new_id": "New ID", "change_id_hint": "6-16 karakter, huruf, angka, tanda hubung, dan garis bawah saja", + "change_id_online_warn": "Perangkat sedang online. Ubah ID di klien RustDesk jika memungkinkan. Perubahan panel mempertahankan identitas, tetapi klien harus mengadopsi ID baru.", "change_id_success": "ID Perangkat berhasil diubah", "id_length_error": "Panjang ID harus 6-16 karakter", "id_format_error": "ID hanya boleh berisi huruf, angka, tanda hubung, dan garis bawah", @@ -340,6 +341,41 @@ "group_invalid": "Pengenal grup tidak valid", "group_not_found": "Grup tidak ditemukan", "group_saved": "Grup perangkat disimpan", + "strategies_title": "Access control strategies", + "strategies_hint": "Link user groups to device groups and push RustDesk Pro security settings to matching clients.", + "create_strategy": "Add strategy", + "edit_strategy": "Edit strategy", + "delete_strategy": "Delete strategy", + "delete_strategy_confirm": "Delete strategy {name}?", + "strategy_name": "Strategy name", + "strategy_name_required": "Strategy name is required", + "strategy_user_group": "User group", + "strategy_device_group": "Device group", + "strategy_any_user_group": "Any user group", + "strategy_any_device_group": "Any device group", + "strategy_enabled": "Enabled", + "strategy_disabled": "Disabled", + "strategy_enabled_label": "Strategy enabled", + "strategy_permissions": "Client security settings", + "strategy_permissions_hint": "Uses RustDesk advanced-setting keys (Y/N). Leave Default to keep client or strategy inheritance.", + "strategy_permissions_count": "{count} settings", + "strategy_perm_default": "Default", + "strategy_perm_allow": "Allow (Y)", + "strategy_perm_deny": "Deny (N)", + "strategy_perm_file_transfer": "File transfer", + "strategy_perm_disable_clipboard": "Disable clipboard", + "strategy_perm_enable_clipboard": "Enable clipboard", + "strategy_perm_audio": "Audio", + "strategy_perm_tunnel": "TCP tunnel", + "strategy_perm_camera": "Camera", + "strategy_perm_restart": "Remote restart", + "strategy_perm_block_input": "Block user input", + "strategy_created": "Strategy created", + "strategy_updated": "Strategy updated", + "strategy_deleted": "Strategy deleted", + "strategy_not_found": "Strategy not found", + "no_strategies": "No access control strategies yet", + "loading_strategies": "Loading strategies...", "groups_saved": "Grup perangkat disimpan", "group_membership_added": "Ditambahkan ke grup", "group_membership_removed": "Dihapus dari grup", @@ -2606,7 +2642,19 @@ "address_book_saved": "Buku alamat disimpan", "address_book_save_failed": "Gagal menyimpan buku alamat", "address_book_imported": "Perangkat organisasi diimpor", - "address_book_import_none": "Tidak ada perangkat organisasi baru untuk diimpor" + "address_book_import_none": "Tidak ada perangkat organisasi baru untuk diimpor", + "address_book_contacts": "Shared contacts", + "address_book_add_peer": "Add contact", + "address_book_peer_id": "Device ID", + "address_book_alias": "Alias", + "address_book_peer_tags": "Contact tags", + "address_book_peer_tags_placeholder": "office, support", + "address_book_tags_label": "Global tags", + "address_book_tags_placeholder": "Office, Support", + "address_book_tags_hint": "Comma-separated tags available in the RustDesk client address book.", + "address_book_no_peers": "No contacts yet — add a device ID or import organization devices.", + "address_book_show_json": "Advanced JSON", + "address_book_hide_json": "Structured editor" }, "chat": { "e2e_enabled": "Dienkripsi ujung ke ujung", diff --git a/web-nodejs/lang/it.json b/web-nodejs/lang/it.json index 8a5a7f5a..fc5f5453 100644 --- a/web-nodejs/lang/it.json +++ b/web-nodejs/lang/it.json @@ -1,4 +1,4 @@ -{ +{ "meta": { "lang": "it", "name": "Italian", @@ -238,6 +238,7 @@ "change_id_title": "Cambia ID dispositivo", "new_id": "Nuovo ID", "change_id_hint": "6-16 caratteri, solo lettere, numeri, trattini e underscore", + "change_id_online_warn": "Il dispositivo è online. Preferire la modifica ID nel client RustDesk. La modifica dal pannello mantiene l'identità, ma il client deve adottare il nuovo ID.", "change_id_success": "ID dispositivo cambiato con successo", "id_length_error": "L'ID deve essere lungo 6-16 caratteri", "id_format_error": "L'ID può contenere solo lettere, numeri, trattini e underscore", @@ -334,6 +335,41 @@ "group_invalid": "ID gruppo non valido", "group_not_found": "Gruppo non trovato", "group_saved": "Gruppo dispositivi salvato", + "strategies_title": "Access control strategies", + "strategies_hint": "Link user groups to device groups and push RustDesk Pro security settings to matching clients.", + "create_strategy": "Add strategy", + "edit_strategy": "Edit strategy", + "delete_strategy": "Delete strategy", + "delete_strategy_confirm": "Delete strategy {name}?", + "strategy_name": "Strategy name", + "strategy_name_required": "Strategy name is required", + "strategy_user_group": "User group", + "strategy_device_group": "Device group", + "strategy_any_user_group": "Any user group", + "strategy_any_device_group": "Any device group", + "strategy_enabled": "Enabled", + "strategy_disabled": "Disabled", + "strategy_enabled_label": "Strategy enabled", + "strategy_permissions": "Client security settings", + "strategy_permissions_hint": "Uses RustDesk advanced-setting keys (Y/N). Leave Default to keep client or strategy inheritance.", + "strategy_permissions_count": "{count} settings", + "strategy_perm_default": "Default", + "strategy_perm_allow": "Allow (Y)", + "strategy_perm_deny": "Deny (N)", + "strategy_perm_file_transfer": "File transfer", + "strategy_perm_disable_clipboard": "Disable clipboard", + "strategy_perm_enable_clipboard": "Enable clipboard", + "strategy_perm_audio": "Audio", + "strategy_perm_tunnel": "TCP tunnel", + "strategy_perm_camera": "Camera", + "strategy_perm_restart": "Remote restart", + "strategy_perm_block_input": "Block user input", + "strategy_created": "Strategy created", + "strategy_updated": "Strategy updated", + "strategy_deleted": "Strategy deleted", + "strategy_not_found": "Strategy not found", + "no_strategies": "No access control strategies yet", + "loading_strategies": "Loading strategies...", "groups_saved": "Gruppi dispositivi salvati", "group_membership_added": "Aggiunto al gruppo", "group_membership_removed": "Rimosso dal gruppo", @@ -2600,7 +2636,19 @@ "address_book_saved": "Rubrica salvata", "address_book_save_failed": "Impossibile salvare la rubrica", "address_book_imported": "Dispositivi dell'organizzazione importati", - "address_book_import_none": "Nessun nuovo dispositivo dell'organizzazione da importare" + "address_book_import_none": "Nessun nuovo dispositivo dell'organizzazione da importare", + "address_book_contacts": "Shared contacts", + "address_book_add_peer": "Add contact", + "address_book_peer_id": "Device ID", + "address_book_alias": "Alias", + "address_book_peer_tags": "Contact tags", + "address_book_peer_tags_placeholder": "office, support", + "address_book_tags_label": "Global tags", + "address_book_tags_placeholder": "Office, Support", + "address_book_tags_hint": "Comma-separated tags available in the RustDesk client address book.", + "address_book_no_peers": "No contacts yet — add a device ID or import organization devices.", + "address_book_show_json": "Advanced JSON", + "address_book_hide_json": "Structured editor" }, "chat": { "e2e_enabled": "Crittografia end-to-end", diff --git a/web-nodejs/lang/ja.json b/web-nodejs/lang/ja.json index 3fcc1e31..d90cf508 100644 --- a/web-nodejs/lang/ja.json +++ b/web-nodejs/lang/ja.json @@ -1,4 +1,4 @@ -{ +{ "meta": { "lang": "ja", "name": "Japanese", @@ -238,6 +238,7 @@ "change_id_title": "デバイスIDの変更", "new_id": "New ID", "change_id_hint": "6 ~ 16 文字、文字、数字、ダッシュ、アンダースコアのみ", + "change_id_online_warn": "デバイスはオンラインです。可能であれば RustDesk クライアントで ID を変更してください。パネルでの変更は同一性を維持しますが、クライアントが新しい ID を採用する必要があります。", "change_id_success": "デバイスIDが正常に変更されました", "id_length_error": "ID の長さは 6 ~ 16 文字���ある必要があります", "id_format_error": "ID には文字、数字、ダッシュ、アンダースコアのみを含めることができます", @@ -334,6 +335,41 @@ "group_invalid": "無効なグループ識別子", "group_not_found": "グループが見つかりません", "group_saved": "デバイスグループが保存されました", + "strategies_title": "Access control strategies", + "strategies_hint": "Link user groups to device groups and push RustDesk Pro security settings to matching clients.", + "create_strategy": "Add strategy", + "edit_strategy": "Edit strategy", + "delete_strategy": "Delete strategy", + "delete_strategy_confirm": "Delete strategy {name}?", + "strategy_name": "Strategy name", + "strategy_name_required": "Strategy name is required", + "strategy_user_group": "User group", + "strategy_device_group": "Device group", + "strategy_any_user_group": "Any user group", + "strategy_any_device_group": "Any device group", + "strategy_enabled": "Enabled", + "strategy_disabled": "Disabled", + "strategy_enabled_label": "Strategy enabled", + "strategy_permissions": "Client security settings", + "strategy_permissions_hint": "Uses RustDesk advanced-setting keys (Y/N). Leave Default to keep client or strategy inheritance.", + "strategy_permissions_count": "{count} settings", + "strategy_perm_default": "Default", + "strategy_perm_allow": "Allow (Y)", + "strategy_perm_deny": "Deny (N)", + "strategy_perm_file_transfer": "File transfer", + "strategy_perm_disable_clipboard": "Disable clipboard", + "strategy_perm_enable_clipboard": "Enable clipboard", + "strategy_perm_audio": "Audio", + "strategy_perm_tunnel": "TCP tunnel", + "strategy_perm_camera": "Camera", + "strategy_perm_restart": "Remote restart", + "strategy_perm_block_input": "Block user input", + "strategy_created": "Strategy created", + "strategy_updated": "Strategy updated", + "strategy_deleted": "Strategy deleted", + "strategy_not_found": "Strategy not found", + "no_strategies": "No access control strategies yet", + "loading_strategies": "Loading strategies...", "groups_saved": "デバイスグループが保存されました", "group_membership_added": "グループに追加しました", "group_membership_removed": "グループから削除しました", @@ -2600,7 +2636,19 @@ "address_book_saved": "アドレス帳を保存しました", "address_book_save_failed": "アドレス帳の保存に失敗しました", "address_book_imported": "組織のデバイスをインポートしました", - "address_book_import_none": "インポートする新しい組織デバイスはありません" + "address_book_import_none": "インポートする新しい組織デバイスはありません", + "address_book_contacts": "Shared contacts", + "address_book_add_peer": "Add contact", + "address_book_peer_id": "Device ID", + "address_book_alias": "Alias", + "address_book_peer_tags": "Contact tags", + "address_book_peer_tags_placeholder": "office, support", + "address_book_tags_label": "Global tags", + "address_book_tags_placeholder": "Office, Support", + "address_book_tags_hint": "Comma-separated tags available in the RustDesk client address book.", + "address_book_no_peers": "No contacts yet — add a device ID or import organization devices.", + "address_book_show_json": "Advanced JSON", + "address_book_hide_json": "Structured editor" }, "chat": { "e2e_enabled": "エンドツーエンド暗号化", diff --git a/web-nodejs/lang/ko.json b/web-nodejs/lang/ko.json index 834a10a4..346d4d04 100644 --- a/web-nodejs/lang/ko.json +++ b/web-nodejs/lang/ko.json @@ -1,4 +1,4 @@ -{ +{ "meta": { "lang": "ko", "name": "Korean", @@ -238,6 +238,7 @@ "change_id_title": "장치 ID 변경", "new_id": "New ID", "change_id_hint": "6~16자, 문자, 숫자, 대시, 밑줄만 가능", + "change_id_online_warn": "기기가 온라인 상태입니다. 가능하면 RustDesk 클라이언트에서 ID를 변경하세요. 패널 변경은 동일성을 유지하지만 클라이언트가 새 ID를 채택해야 합니다.", "change_id_success": "기기 ID가 성공적으로 변경되었습니다.", "id_length_error": "ID는 6~16자여야 합니다.", "id_format_error": "ID에는 문자, 숫자, 대시, 밑줄만 포함할 수 있습니다.", @@ -334,6 +335,41 @@ "group_invalid": "잘못된 그룹 식별자", "group_not_found": "그룹을 찾을 수 없습니다", "group_saved": "장치 그룹이 저장되었습니다.", + "strategies_title": "Access control strategies", + "strategies_hint": "Link user groups to device groups and push RustDesk Pro security settings to matching clients.", + "create_strategy": "Add strategy", + "edit_strategy": "Edit strategy", + "delete_strategy": "Delete strategy", + "delete_strategy_confirm": "Delete strategy {name}?", + "strategy_name": "Strategy name", + "strategy_name_required": "Strategy name is required", + "strategy_user_group": "User group", + "strategy_device_group": "Device group", + "strategy_any_user_group": "Any user group", + "strategy_any_device_group": "Any device group", + "strategy_enabled": "Enabled", + "strategy_disabled": "Disabled", + "strategy_enabled_label": "Strategy enabled", + "strategy_permissions": "Client security settings", + "strategy_permissions_hint": "Uses RustDesk advanced-setting keys (Y/N). Leave Default to keep client or strategy inheritance.", + "strategy_permissions_count": "{count} settings", + "strategy_perm_default": "Default", + "strategy_perm_allow": "Allow (Y)", + "strategy_perm_deny": "Deny (N)", + "strategy_perm_file_transfer": "File transfer", + "strategy_perm_disable_clipboard": "Disable clipboard", + "strategy_perm_enable_clipboard": "Enable clipboard", + "strategy_perm_audio": "Audio", + "strategy_perm_tunnel": "TCP tunnel", + "strategy_perm_camera": "Camera", + "strategy_perm_restart": "Remote restart", + "strategy_perm_block_input": "Block user input", + "strategy_created": "Strategy created", + "strategy_updated": "Strategy updated", + "strategy_deleted": "Strategy deleted", + "strategy_not_found": "Strategy not found", + "no_strategies": "No access control strategies yet", + "loading_strategies": "Loading strategies...", "groups_saved": "장치 그룹이 저장되었습니다.", "group_membership_added": "그룹에 추가됨", "group_membership_removed": "그룹에서 제거됨", @@ -2600,7 +2636,19 @@ "address_book_saved": "주소록이 저장되었습니다", "address_book_save_failed": "주소록 저장에 실패했습니다", "address_book_imported": "조직 장치를 가져왔습니다", - "address_book_import_none": "가져올 새 조직 장치가 없습니다" + "address_book_import_none": "가져올 새 조직 장치가 없습니다", + "address_book_contacts": "Shared contacts", + "address_book_add_peer": "Add contact", + "address_book_peer_id": "Device ID", + "address_book_alias": "Alias", + "address_book_peer_tags": "Contact tags", + "address_book_peer_tags_placeholder": "office, support", + "address_book_tags_label": "Global tags", + "address_book_tags_placeholder": "Office, Support", + "address_book_tags_hint": "Comma-separated tags available in the RustDesk client address book.", + "address_book_no_peers": "No contacts yet — add a device ID or import organization devices.", + "address_book_show_json": "Advanced JSON", + "address_book_hide_json": "Structured editor" }, "chat": { "e2e_enabled": "종단 간 암호화", diff --git a/web-nodejs/lang/nb.json b/web-nodejs/lang/nb.json index 8ece5af0..2d4e3219 100644 --- a/web-nodejs/lang/nb.json +++ b/web-nodejs/lang/nb.json @@ -1,4 +1,4 @@ -{ +{ "_meta": { "name": "Norwegian", "native_name": "Norsk", @@ -244,6 +244,7 @@ "change_id_title": "Endre enhets-ID", "new_id": "New ID", "change_id_hint": "Kun 6-16 tegn, bokstaver, tall, bindestreker og understreker", + "change_id_online_warn": "Enheten er online. Endre ID helst i RustDesk-klienten. Endring i panelet beholder identiteten, men klienten må ta i bruk ny ID.", "change_id_success": "Enhets-ID ble endret", "id_length_error": "ID må bestå av 6–16 tegn", "id_format_error": "ID kan bare inneholde bokstaver, tall, bindestreker og understreker", @@ -340,6 +341,41 @@ "group_invalid": "Ugyldig gruppeidentifikator", "group_not_found": "Finner ikke gruppen", "group_saved": "Enhetsgruppen er lagret", + "strategies_title": "Access control strategies", + "strategies_hint": "Link user groups to device groups and push RustDesk Pro security settings to matching clients.", + "create_strategy": "Add strategy", + "edit_strategy": "Edit strategy", + "delete_strategy": "Delete strategy", + "delete_strategy_confirm": "Delete strategy {name}?", + "strategy_name": "Strategy name", + "strategy_name_required": "Strategy name is required", + "strategy_user_group": "User group", + "strategy_device_group": "Device group", + "strategy_any_user_group": "Any user group", + "strategy_any_device_group": "Any device group", + "strategy_enabled": "Enabled", + "strategy_disabled": "Disabled", + "strategy_enabled_label": "Strategy enabled", + "strategy_permissions": "Client security settings", + "strategy_permissions_hint": "Uses RustDesk advanced-setting keys (Y/N). Leave Default to keep client or strategy inheritance.", + "strategy_permissions_count": "{count} settings", + "strategy_perm_default": "Default", + "strategy_perm_allow": "Allow (Y)", + "strategy_perm_deny": "Deny (N)", + "strategy_perm_file_transfer": "File transfer", + "strategy_perm_disable_clipboard": "Disable clipboard", + "strategy_perm_enable_clipboard": "Enable clipboard", + "strategy_perm_audio": "Audio", + "strategy_perm_tunnel": "TCP tunnel", + "strategy_perm_camera": "Camera", + "strategy_perm_restart": "Remote restart", + "strategy_perm_block_input": "Block user input", + "strategy_created": "Strategy created", + "strategy_updated": "Strategy updated", + "strategy_deleted": "Strategy deleted", + "strategy_not_found": "Strategy not found", + "no_strategies": "No access control strategies yet", + "loading_strategies": "Loading strategies...", "groups_saved": "Enhetsgrupper lagret", "group_membership_added": "Lagt til i gruppe", "group_membership_removed": "Fjernet fra gruppe", @@ -2606,7 +2642,19 @@ "address_book_saved": "Adressebok lagret", "address_book_save_failed": "Kunne ikke lagre adresseboken", "address_book_imported": "Organisasjonsenheter importert", - "address_book_import_none": "Ingen nye organisasjonsenheter å importere" + "address_book_import_none": "Ingen nye organisasjonsenheter å importere", + "address_book_contacts": "Shared contacts", + "address_book_add_peer": "Add contact", + "address_book_peer_id": "Device ID", + "address_book_alias": "Alias", + "address_book_peer_tags": "Contact tags", + "address_book_peer_tags_placeholder": "office, support", + "address_book_tags_label": "Global tags", + "address_book_tags_placeholder": "Office, Support", + "address_book_tags_hint": "Comma-separated tags available in the RustDesk client address book.", + "address_book_no_peers": "No contacts yet — add a device ID or import organization devices.", + "address_book_show_json": "Advanced JSON", + "address_book_hide_json": "Structured editor" }, "chat": { "e2e_enabled": "Ende-til-ende kryptert", diff --git a/web-nodejs/lang/nl.json b/web-nodejs/lang/nl.json index f10fd5da..7dfafad6 100644 --- a/web-nodejs/lang/nl.json +++ b/web-nodejs/lang/nl.json @@ -1,4 +1,4 @@ -{ +{ "meta": { "lang": "nl", "name": "Dutch", @@ -238,6 +238,7 @@ "change_id_title": "Apparaat-ID wijzigen", "new_id": "Nieuw ID", "change_id_hint": "6-16 tekens, alleen letters, cijfers, koppeltekens en underscores", + "change_id_online_warn": "Apparaat is online. Wijzig de ID bij voorkeur in de RustDesk-client. Een wijziging in het paneel behoudt de identiteit, maar de client moet de nieuwe ID overnemen.", "change_id_success": "Apparaat-ID succesvol gewijzigd", "id_length_error": "ID moet 6-16 tekens lang zijn", "id_format_error": "ID mag alleen letters, cijfers, koppeltekens en underscores bevatten", @@ -334,6 +335,41 @@ "group_invalid": "Ongeldige groeps-ID", "group_not_found": "Groep niet gevonden", "group_saved": "Apparaatgroep opgeslagen", + "strategies_title": "Access control strategies", + "strategies_hint": "Link user groups to device groups and push RustDesk Pro security settings to matching clients.", + "create_strategy": "Add strategy", + "edit_strategy": "Edit strategy", + "delete_strategy": "Delete strategy", + "delete_strategy_confirm": "Delete strategy {name}?", + "strategy_name": "Strategy name", + "strategy_name_required": "Strategy name is required", + "strategy_user_group": "User group", + "strategy_device_group": "Device group", + "strategy_any_user_group": "Any user group", + "strategy_any_device_group": "Any device group", + "strategy_enabled": "Enabled", + "strategy_disabled": "Disabled", + "strategy_enabled_label": "Strategy enabled", + "strategy_permissions": "Client security settings", + "strategy_permissions_hint": "Uses RustDesk advanced-setting keys (Y/N). Leave Default to keep client or strategy inheritance.", + "strategy_permissions_count": "{count} settings", + "strategy_perm_default": "Default", + "strategy_perm_allow": "Allow (Y)", + "strategy_perm_deny": "Deny (N)", + "strategy_perm_file_transfer": "File transfer", + "strategy_perm_disable_clipboard": "Disable clipboard", + "strategy_perm_enable_clipboard": "Enable clipboard", + "strategy_perm_audio": "Audio", + "strategy_perm_tunnel": "TCP tunnel", + "strategy_perm_camera": "Camera", + "strategy_perm_restart": "Remote restart", + "strategy_perm_block_input": "Block user input", + "strategy_created": "Strategy created", + "strategy_updated": "Strategy updated", + "strategy_deleted": "Strategy deleted", + "strategy_not_found": "Strategy not found", + "no_strategies": "No access control strategies yet", + "loading_strategies": "Loading strategies...", "groups_saved": "Apparaatgroepen opgeslagen", "group_membership_added": "Toegevoegd aan groep", "group_membership_removed": "Verwijderd uit groep", @@ -2600,7 +2636,19 @@ "address_book_saved": "Adresboek opgeslagen", "address_book_save_failed": "Adresboek opslaan mislukt", "address_book_imported": "Organisatieapparaten geïmporteerd", - "address_book_import_none": "Geen nieuwe organisatieapparaten om te importeren" + "address_book_import_none": "Geen nieuwe organisatieapparaten om te importeren", + "address_book_contacts": "Shared contacts", + "address_book_add_peer": "Add contact", + "address_book_peer_id": "Device ID", + "address_book_alias": "Alias", + "address_book_peer_tags": "Contact tags", + "address_book_peer_tags_placeholder": "office, support", + "address_book_tags_label": "Global tags", + "address_book_tags_placeholder": "Office, Support", + "address_book_tags_hint": "Comma-separated tags available in the RustDesk client address book.", + "address_book_no_peers": "No contacts yet — add a device ID or import organization devices.", + "address_book_show_json": "Advanced JSON", + "address_book_hide_json": "Structured editor" }, "chat": { "e2e_enabled": "End-to-end versleuteld", diff --git a/web-nodejs/lang/pl.json b/web-nodejs/lang/pl.json index 21c899ce..ba71bee3 100644 --- a/web-nodejs/lang/pl.json +++ b/web-nodejs/lang/pl.json @@ -1,4 +1,4 @@ -{ +{ "meta": { "lang": "pl", "name": "Polish", @@ -238,6 +238,7 @@ "change_id_title": "Zmień ID urządzenia", "new_id": "Nowe ID", "change_id_hint": "6-16 znaków, tylko litery, cyfry, myślniki i podkreślenia", + "change_id_online_warn": "Urządzenie jest online. Zmianę ID lepiej wykonać w kliencie RustDesk. Zmiana w panelu zachowuje tożsamość urządzenia, ale klient musi przyjąć nowe ID, aby pozostać połączony.", "change_id_success": "ID urządzenia zmienione", "id_length_error": "ID musi mieć 6-16 znaków", "id_format_error": "ID może zawierać tylko litery, cyfry, myślniki i podkreślenia", @@ -301,6 +302,41 @@ "group_invalid": "Nieprawidłowy identyfikator grupy", "group_not_found": "Nie znaleziono grupy", "group_saved": "Grupa urządzeń zapisana", + "strategies_title": "Strategie kontroli dostępu", + "strategies_hint": "Powiąż grupy użytkowników z grupami urządzeń i wymuszaj ustawienia bezpieczeństwa RustDesk Pro na pasujących klientach.", + "create_strategy": "Dodaj strategię", + "edit_strategy": "Edytuj strategię", + "delete_strategy": "Usuń strategię", + "delete_strategy_confirm": "Usunąć strategię {name}?", + "strategy_name": "Nazwa strategii", + "strategy_name_required": "Nazwa strategii jest wymagana", + "strategy_user_group": "Grupa użytkowników", + "strategy_device_group": "Grupa urządzeń", + "strategy_any_user_group": "Dowolna grupa użytkowników", + "strategy_any_device_group": "Dowolna grupa urządzeń", + "strategy_enabled": "Włączona", + "strategy_disabled": "Wyłączona", + "strategy_enabled_label": "Strategia włączona", + "strategy_permissions": "Ustawienia bezpieczeństwa klienta", + "strategy_permissions_hint": "Używa kluczy zaawansowanych ustawień RustDesk (Y/N). Domyślne pozostawia dziedziczenie po kliencie lub strategii.", + "strategy_permissions_count": "{count} ustawień", + "strategy_perm_default": "Domyślne", + "strategy_perm_allow": "Zezwól (Y)", + "strategy_perm_deny": "Odmów (N)", + "strategy_perm_file_transfer": "Transfer plików", + "strategy_perm_disable_clipboard": "Wyłącz schowek", + "strategy_perm_enable_clipboard": "Włącz schowek", + "strategy_perm_audio": "Audio", + "strategy_perm_tunnel": "Tunel TCP", + "strategy_perm_camera": "Kamera", + "strategy_perm_restart": "Zdalny restart", + "strategy_perm_block_input": "Blokada wejścia użytkownika", + "strategy_created": "Strategia utworzona", + "strategy_updated": "Strategia zaktualizowana", + "strategy_deleted": "Strategia usunięta", + "strategy_not_found": "Nie znaleziono strategii", + "no_strategies": "Brak strategii kontroli dostępu", + "loading_strategies": "Ładowanie strategii...", "groups_saved": "Grupy urządzenia zapisane", "group_membership_added": "Dodano do grupy", "group_membership_removed": "Usunięto z grupy", @@ -2684,7 +2720,19 @@ "address_book_saved": "Książka adresowa zapisana", "address_book_save_failed": "Nie udało się zapisać książki adresowej", "address_book_imported": "Urządzenia organizacji zaimportowane", - "address_book_import_none": "Brak nowych urządzeń organizacji do importu" + "address_book_import_none": "Brak nowych urządzeń organizacji do importu", + "address_book_contacts": "Współdzielone kontakty", + "address_book_add_peer": "Dodaj kontakt", + "address_book_peer_id": "ID urządzenia", + "address_book_alias": "Alias", + "address_book_peer_tags": "Tagi kontaktu", + "address_book_peer_tags_placeholder": "biuro, wsparcie", + "address_book_tags_label": "Tagi globalne", + "address_book_tags_placeholder": "Biuro, Wsparcie", + "address_book_tags_hint": "Tagi oddzielone przecinkami dostępne w książce adresowej klienta RustDesk.", + "address_book_no_peers": "Brak kontaktów — dodaj ID urządzenia lub zaimportuj urządzenia organizacji.", + "address_book_show_json": "Zaawansowany JSON", + "address_book_hide_json": "Edytor strukturalny" }, "chat": { "e2e_enabled": "Szyfrowanie end-to-end", diff --git a/web-nodejs/lang/pt.json b/web-nodejs/lang/pt.json index 7b6aad74..8e25c8c1 100644 --- a/web-nodejs/lang/pt.json +++ b/web-nodejs/lang/pt.json @@ -1,4 +1,4 @@ -{ +{ "meta": { "lang": "pt", "name": "Portuguese", @@ -238,6 +238,7 @@ "change_id_title": "Alterar ID do dispositivo", "new_id": "Novo ID", "change_id_hint": "6-16 caracteres, apenas letras, números, hífens e underscores", + "change_id_online_warn": "O dispositivo está online. Prefira alterar o ID no cliente RustDesk. A alteração no painel mantém a identidade, mas o cliente deve adotar o novo ID.", "change_id_success": "ID do dispositivo alterado com sucesso", "id_length_error": "O ID deve ter 6-16 caracteres", "id_format_error": "O ID só pode conter letras, números, hífens e underscores", @@ -334,6 +335,41 @@ "group_invalid": "Identificador de grupo inválido", "group_not_found": "Grupo não encontrado", "group_saved": "Grupo de dispositivos salvo", + "strategies_title": "Access control strategies", + "strategies_hint": "Link user groups to device groups and push RustDesk Pro security settings to matching clients.", + "create_strategy": "Add strategy", + "edit_strategy": "Edit strategy", + "delete_strategy": "Delete strategy", + "delete_strategy_confirm": "Delete strategy {name}?", + "strategy_name": "Strategy name", + "strategy_name_required": "Strategy name is required", + "strategy_user_group": "User group", + "strategy_device_group": "Device group", + "strategy_any_user_group": "Any user group", + "strategy_any_device_group": "Any device group", + "strategy_enabled": "Enabled", + "strategy_disabled": "Disabled", + "strategy_enabled_label": "Strategy enabled", + "strategy_permissions": "Client security settings", + "strategy_permissions_hint": "Uses RustDesk advanced-setting keys (Y/N). Leave Default to keep client or strategy inheritance.", + "strategy_permissions_count": "{count} settings", + "strategy_perm_default": "Default", + "strategy_perm_allow": "Allow (Y)", + "strategy_perm_deny": "Deny (N)", + "strategy_perm_file_transfer": "File transfer", + "strategy_perm_disable_clipboard": "Disable clipboard", + "strategy_perm_enable_clipboard": "Enable clipboard", + "strategy_perm_audio": "Audio", + "strategy_perm_tunnel": "TCP tunnel", + "strategy_perm_camera": "Camera", + "strategy_perm_restart": "Remote restart", + "strategy_perm_block_input": "Block user input", + "strategy_created": "Strategy created", + "strategy_updated": "Strategy updated", + "strategy_deleted": "Strategy deleted", + "strategy_not_found": "Strategy not found", + "no_strategies": "No access control strategies yet", + "loading_strategies": "Loading strategies...", "groups_saved": "Grupos de dispositivos salvos", "group_membership_added": "Adicionado ao grupo", "group_membership_removed": "Removido do grupo", @@ -2600,7 +2636,19 @@ "address_book_saved": "Livro de endereços guardado", "address_book_save_failed": "Falha ao guardar o livro de endereços", "address_book_imported": "Dispositivos da organização importados", - "address_book_import_none": "Nenhum novo dispositivo da organização para importar" + "address_book_import_none": "Nenhum novo dispositivo da organização para importar", + "address_book_contacts": "Shared contacts", + "address_book_add_peer": "Add contact", + "address_book_peer_id": "Device ID", + "address_book_alias": "Alias", + "address_book_peer_tags": "Contact tags", + "address_book_peer_tags_placeholder": "office, support", + "address_book_tags_label": "Global tags", + "address_book_tags_placeholder": "Office, Support", + "address_book_tags_hint": "Comma-separated tags available in the RustDesk client address book.", + "address_book_no_peers": "No contacts yet — add a device ID or import organization devices.", + "address_book_show_json": "Advanced JSON", + "address_book_hide_json": "Structured editor" }, "chat": { "e2e_enabled": "Criptografia ponta a ponta", diff --git a/web-nodejs/lang/ro.json b/web-nodejs/lang/ro.json index eab7bc65..5eaaaf5f 100644 --- a/web-nodejs/lang/ro.json +++ b/web-nodejs/lang/ro.json @@ -1,4 +1,4 @@ -{ +{ "_meta": { "name": "Romanian", "native_name": "Română", @@ -244,6 +244,7 @@ "change_id_title": "Schimbați ID-ul dispozitivului", "new_id": "New ID", "change_id_hint": "Numai 6-16 caractere, litere, cifre, liniuțe și caractere de subliniere", + "change_id_online_warn": "Dispozitivul este online. Schimbați ID-ul preferabil în clientul RustDesk. Modificarea din panou păstrează identitatea, dar clientul trebuie să adopte noul ID.", "change_id_success": "ID-ul dispozitivului a fost schimbat cu succes", "id_length_error": "ID-ul trebuie să aibă 6-16 caractere", "id_format_error": "ID-ul poate conține doar litere, cifre, liniuțe și liniuțe de subliniere", @@ -340,6 +341,41 @@ "group_invalid": "Identificator de grup nevalid", "group_not_found": "Grupul nu a fost găsit", "group_saved": "Grupul de dispozitive a fost salvat", + "strategies_title": "Access control strategies", + "strategies_hint": "Link user groups to device groups and push RustDesk Pro security settings to matching clients.", + "create_strategy": "Add strategy", + "edit_strategy": "Edit strategy", + "delete_strategy": "Delete strategy", + "delete_strategy_confirm": "Delete strategy {name}?", + "strategy_name": "Strategy name", + "strategy_name_required": "Strategy name is required", + "strategy_user_group": "User group", + "strategy_device_group": "Device group", + "strategy_any_user_group": "Any user group", + "strategy_any_device_group": "Any device group", + "strategy_enabled": "Enabled", + "strategy_disabled": "Disabled", + "strategy_enabled_label": "Strategy enabled", + "strategy_permissions": "Client security settings", + "strategy_permissions_hint": "Uses RustDesk advanced-setting keys (Y/N). Leave Default to keep client or strategy inheritance.", + "strategy_permissions_count": "{count} settings", + "strategy_perm_default": "Default", + "strategy_perm_allow": "Allow (Y)", + "strategy_perm_deny": "Deny (N)", + "strategy_perm_file_transfer": "File transfer", + "strategy_perm_disable_clipboard": "Disable clipboard", + "strategy_perm_enable_clipboard": "Enable clipboard", + "strategy_perm_audio": "Audio", + "strategy_perm_tunnel": "TCP tunnel", + "strategy_perm_camera": "Camera", + "strategy_perm_restart": "Remote restart", + "strategy_perm_block_input": "Block user input", + "strategy_created": "Strategy created", + "strategy_updated": "Strategy updated", + "strategy_deleted": "Strategy deleted", + "strategy_not_found": "Strategy not found", + "no_strategies": "No access control strategies yet", + "loading_strategies": "Loading strategies...", "groups_saved": "Grupuri de dispozitive salvate", "group_membership_added": "Adăugat la grup", "group_membership_removed": "Eliminat din grup", @@ -2606,7 +2642,19 @@ "address_book_saved": "Agenda de adrese a fost salvată", "address_book_save_failed": "Nu s-a putut salva agenda de adrese", "address_book_imported": "Dispozitivele organizației au fost importate", - "address_book_import_none": "Nu există dispozitive noi ale organizației de importat" + "address_book_import_none": "Nu există dispozitive noi ale organizației de importat", + "address_book_contacts": "Shared contacts", + "address_book_add_peer": "Add contact", + "address_book_peer_id": "Device ID", + "address_book_alias": "Alias", + "address_book_peer_tags": "Contact tags", + "address_book_peer_tags_placeholder": "office, support", + "address_book_tags_label": "Global tags", + "address_book_tags_placeholder": "Office, Support", + "address_book_tags_hint": "Comma-separated tags available in the RustDesk client address book.", + "address_book_no_peers": "No contacts yet — add a device ID or import organization devices.", + "address_book_show_json": "Advanced JSON", + "address_book_hide_json": "Structured editor" }, "chat": { "e2e_enabled": "Criptat de la capăt la capăt", diff --git a/web-nodejs/lang/sv.json b/web-nodejs/lang/sv.json index 06f0cb26..07a1f720 100644 --- a/web-nodejs/lang/sv.json +++ b/web-nodejs/lang/sv.json @@ -1,4 +1,4 @@ -{ +{ "_meta": { "name": "Swedish", "native_name": "Svenska", @@ -244,6 +244,7 @@ "change_id_title": "Ändra enhets-ID", "new_id": "New ID", "change_id_hint": "Endast 6-16 tecken, bokstäver, siffror, bindestreck och understreck", + "change_id_online_warn": "Enheten är online. Byt ID helst i RustDesk-klienten. Ändring i panelen behåller identiteten, men klienten måste anta det nya ID:t.", "change_id_success": "Enhets-ID har ändrats", "id_length_error": "ID måste vara 6-16 tecken långt", "id_format_error": "ID kan bara innehålla bokstäver, siffror, bindestreck och understreck", @@ -340,6 +341,41 @@ "group_invalid": "Ogiltig gruppidentifierare", "group_not_found": "Gruppen hittades inte", "group_saved": "Enhetsgruppen har sparats", + "strategies_title": "Access control strategies", + "strategies_hint": "Link user groups to device groups and push RustDesk Pro security settings to matching clients.", + "create_strategy": "Add strategy", + "edit_strategy": "Edit strategy", + "delete_strategy": "Delete strategy", + "delete_strategy_confirm": "Delete strategy {name}?", + "strategy_name": "Strategy name", + "strategy_name_required": "Strategy name is required", + "strategy_user_group": "User group", + "strategy_device_group": "Device group", + "strategy_any_user_group": "Any user group", + "strategy_any_device_group": "Any device group", + "strategy_enabled": "Enabled", + "strategy_disabled": "Disabled", + "strategy_enabled_label": "Strategy enabled", + "strategy_permissions": "Client security settings", + "strategy_permissions_hint": "Uses RustDesk advanced-setting keys (Y/N). Leave Default to keep client or strategy inheritance.", + "strategy_permissions_count": "{count} settings", + "strategy_perm_default": "Default", + "strategy_perm_allow": "Allow (Y)", + "strategy_perm_deny": "Deny (N)", + "strategy_perm_file_transfer": "File transfer", + "strategy_perm_disable_clipboard": "Disable clipboard", + "strategy_perm_enable_clipboard": "Enable clipboard", + "strategy_perm_audio": "Audio", + "strategy_perm_tunnel": "TCP tunnel", + "strategy_perm_camera": "Camera", + "strategy_perm_restart": "Remote restart", + "strategy_perm_block_input": "Block user input", + "strategy_created": "Strategy created", + "strategy_updated": "Strategy updated", + "strategy_deleted": "Strategy deleted", + "strategy_not_found": "Strategy not found", + "no_strategies": "No access control strategies yet", + "loading_strategies": "Loading strategies...", "groups_saved": "Enhetsgrupper har sparats", "group_membership_added": "Tillagd i grupp", "group_membership_removed": "Borttagen från grupp", @@ -2606,7 +2642,19 @@ "address_book_saved": "Adressbok sparad", "address_book_save_failed": "Kunde inte spara adressboken", "address_book_imported": "Organisationsenheter importerade", - "address_book_import_none": "Inga nya organisationsenheter att importera" + "address_book_import_none": "Inga nya organisationsenheter att importera", + "address_book_contacts": "Shared contacts", + "address_book_add_peer": "Add contact", + "address_book_peer_id": "Device ID", + "address_book_alias": "Alias", + "address_book_peer_tags": "Contact tags", + "address_book_peer_tags_placeholder": "office, support", + "address_book_tags_label": "Global tags", + "address_book_tags_placeholder": "Office, Support", + "address_book_tags_hint": "Comma-separated tags available in the RustDesk client address book.", + "address_book_no_peers": "No contacts yet — add a device ID or import organization devices.", + "address_book_show_json": "Advanced JSON", + "address_book_hide_json": "Structured editor" }, "chat": { "e2e_enabled": "End-to-end krypterad", diff --git a/web-nodejs/lang/th.json b/web-nodejs/lang/th.json index 3fa80bb2..83304747 100644 --- a/web-nodejs/lang/th.json +++ b/web-nodejs/lang/th.json @@ -1,4 +1,4 @@ -{ +{ "_meta": { "name": "Thai", "native_name": "ไทย", @@ -244,6 +244,7 @@ "change_id_title": "เปลี่ยนรหัสอุปกรณ์", "new_id": "New ID", "change_id_hint": "อักขระ 6-16 ตัว ตัวอักษร ตัวเลข ขีดกลาง และขีดล่างเท่านั้น", + "change_id_online_warn": "อุปกรณ์ออนไลน์ ควรเปลี่ยน ID ในไคลเอนต์ RustDesk การเปลี่ยนในแผงควบคุมยังคงตัวตนเดิม แต่ไคลเอนต์ต้องใช้ ID ใหม่", "change_id_success": "เปลี่ยนรหัสอุปกรณ์เรียบร้อยแล้ว", "id_length_error": "รหัสต้องมีความยาว 6-16 ตัวอักษร", "id_format_error": "รหัสมีได้เฉพาะตัวอักษร ตัวเลข ขีดกลาง และขีดล่างเท่านั้น", @@ -340,6 +341,41 @@ "group_invalid": "ตัวระบุกลุ่มไม่ถูกต้อง", "group_not_found": "ไม่พบกลุ่ม", "group_saved": "บันทึกกลุ่มอุปกรณ์แล้ว", + "strategies_title": "Access control strategies", + "strategies_hint": "Link user groups to device groups and push RustDesk Pro security settings to matching clients.", + "create_strategy": "Add strategy", + "edit_strategy": "Edit strategy", + "delete_strategy": "Delete strategy", + "delete_strategy_confirm": "Delete strategy {name}?", + "strategy_name": "Strategy name", + "strategy_name_required": "Strategy name is required", + "strategy_user_group": "User group", + "strategy_device_group": "Device group", + "strategy_any_user_group": "Any user group", + "strategy_any_device_group": "Any device group", + "strategy_enabled": "Enabled", + "strategy_disabled": "Disabled", + "strategy_enabled_label": "Strategy enabled", + "strategy_permissions": "Client security settings", + "strategy_permissions_hint": "Uses RustDesk advanced-setting keys (Y/N). Leave Default to keep client or strategy inheritance.", + "strategy_permissions_count": "{count} settings", + "strategy_perm_default": "Default", + "strategy_perm_allow": "Allow (Y)", + "strategy_perm_deny": "Deny (N)", + "strategy_perm_file_transfer": "File transfer", + "strategy_perm_disable_clipboard": "Disable clipboard", + "strategy_perm_enable_clipboard": "Enable clipboard", + "strategy_perm_audio": "Audio", + "strategy_perm_tunnel": "TCP tunnel", + "strategy_perm_camera": "Camera", + "strategy_perm_restart": "Remote restart", + "strategy_perm_block_input": "Block user input", + "strategy_created": "Strategy created", + "strategy_updated": "Strategy updated", + "strategy_deleted": "Strategy deleted", + "strategy_not_found": "Strategy not found", + "no_strategies": "No access control strategies yet", + "loading_strategies": "Loading strategies...", "groups_saved": "บันทึกกลุ่มอุปกรณ์แล้ว", "group_membership_added": "เพิ่มในกลุ่มแล้ว", "group_membership_removed": "นำออกจากกลุ่มแล้ว", @@ -2606,7 +2642,19 @@ "address_book_saved": "บันทึกสมุดที่อยู่แล้ว", "address_book_save_failed": "ไม่สามารถบันทึกสมุดที่อยู่ได้", "address_book_imported": "นำเข้าอุปกรณ์ขององค์กรแล้ว", - "address_book_import_none": "ไม่มีอุปกรณ์ใหม่ขององค์กรให้นำเข้า" + "address_book_import_none": "ไม่มีอุปกรณ์ใหม่ขององค์กรให้นำเข้า", + "address_book_contacts": "Shared contacts", + "address_book_add_peer": "Add contact", + "address_book_peer_id": "Device ID", + "address_book_alias": "Alias", + "address_book_peer_tags": "Contact tags", + "address_book_peer_tags_placeholder": "office, support", + "address_book_tags_label": "Global tags", + "address_book_tags_placeholder": "Office, Support", + "address_book_tags_hint": "Comma-separated tags available in the RustDesk client address book.", + "address_book_no_peers": "No contacts yet — add a device ID or import organization devices.", + "address_book_show_json": "Advanced JSON", + "address_book_hide_json": "Structured editor" }, "chat": { "e2e_enabled": "เข้ารหัสตั้งแต่ต้นทางถึงปลายทาง", diff --git a/web-nodejs/lang/tr.json b/web-nodejs/lang/tr.json index 0f2adf8c..5adc147a 100644 --- a/web-nodejs/lang/tr.json +++ b/web-nodejs/lang/tr.json @@ -1,4 +1,4 @@ -{ +{ "_meta": { "name": "Turkish", "native_name": "Türkçe", @@ -244,6 +244,7 @@ "change_id_title": "Cihaz Kimliğini Değiştir", "new_id": "New ID", "change_id_hint": "Yalnızca 6-16 karakter, harf, rakam, tire ve alt çizgi", + "change_id_online_warn": "Cihaz çevrimiçi. Mümkünse ID'yi RustDesk istemcisinde değiştirin. Panel değişikliği kimliği korur, ancak istemci yeni ID'yi benimsemelidir.", "change_id_success": "Cihaz kimliği başarıyla değiştirildi", "id_length_error": "Kimlik 6-16 karakter uzunluğunda olmalıdır", "id_format_error": "Kimlik yalnızca harf, rakam, tire ve alt çizgi içerebilir", @@ -340,6 +341,41 @@ "group_invalid": "Geçersiz grup tanımlayıcı", "group_not_found": "Grup bulunamadı", "group_saved": "Cihaz grubu kaydedildi", + "strategies_title": "Access control strategies", + "strategies_hint": "Link user groups to device groups and push RustDesk Pro security settings to matching clients.", + "create_strategy": "Add strategy", + "edit_strategy": "Edit strategy", + "delete_strategy": "Delete strategy", + "delete_strategy_confirm": "Delete strategy {name}?", + "strategy_name": "Strategy name", + "strategy_name_required": "Strategy name is required", + "strategy_user_group": "User group", + "strategy_device_group": "Device group", + "strategy_any_user_group": "Any user group", + "strategy_any_device_group": "Any device group", + "strategy_enabled": "Enabled", + "strategy_disabled": "Disabled", + "strategy_enabled_label": "Strategy enabled", + "strategy_permissions": "Client security settings", + "strategy_permissions_hint": "Uses RustDesk advanced-setting keys (Y/N). Leave Default to keep client or strategy inheritance.", + "strategy_permissions_count": "{count} settings", + "strategy_perm_default": "Default", + "strategy_perm_allow": "Allow (Y)", + "strategy_perm_deny": "Deny (N)", + "strategy_perm_file_transfer": "File transfer", + "strategy_perm_disable_clipboard": "Disable clipboard", + "strategy_perm_enable_clipboard": "Enable clipboard", + "strategy_perm_audio": "Audio", + "strategy_perm_tunnel": "TCP tunnel", + "strategy_perm_camera": "Camera", + "strategy_perm_restart": "Remote restart", + "strategy_perm_block_input": "Block user input", + "strategy_created": "Strategy created", + "strategy_updated": "Strategy updated", + "strategy_deleted": "Strategy deleted", + "strategy_not_found": "Strategy not found", + "no_strategies": "No access control strategies yet", + "loading_strategies": "Loading strategies...", "groups_saved": "Cihaz grupları kaydedildi", "group_membership_added": "Gruba eklendi", "group_membership_removed": "Gruptan kaldırıldı", @@ -2606,7 +2642,19 @@ "address_book_saved": "Adres defteri kaydedildi", "address_book_save_failed": "Adres defteri kaydedilemedi", "address_book_imported": "Kuruluş cihazları içe aktarıldı", - "address_book_import_none": "İçe aktarılacak yeni kuruluş cihazı yok" + "address_book_import_none": "İçe aktarılacak yeni kuruluş cihazı yok", + "address_book_contacts": "Shared contacts", + "address_book_add_peer": "Add contact", + "address_book_peer_id": "Device ID", + "address_book_alias": "Alias", + "address_book_peer_tags": "Contact tags", + "address_book_peer_tags_placeholder": "office, support", + "address_book_tags_label": "Global tags", + "address_book_tags_placeholder": "Office, Support", + "address_book_tags_hint": "Comma-separated tags available in the RustDesk client address book.", + "address_book_no_peers": "No contacts yet — add a device ID or import organization devices.", + "address_book_show_json": "Advanced JSON", + "address_book_hide_json": "Structured editor" }, "chat": { "e2e_enabled": "Uçtan uca şifrelenmiş", diff --git a/web-nodejs/lang/uk.json b/web-nodejs/lang/uk.json index d262f1a9..3d89aeea 100644 --- a/web-nodejs/lang/uk.json +++ b/web-nodejs/lang/uk.json @@ -1,4 +1,4 @@ -{ +{ "_meta": { "name": "Ukrainian", "native_name": "Українська", @@ -244,6 +244,7 @@ "change_id_title": "Змінити ідентифікатор пристрою", "new_id": "New ID", "change_id_hint": "6-16 символів, лише літери, цифри, тире та підкреслення", + "change_id_online_warn": "Пристрій онлайн. Краще змінювати ID у клієнті RustDesk. Зміна в панелі зберігає ідентичність, але клієнт має прийняти новий ID.", "change_id_success": "Ідентифікатор пристрою успішно змінено", "id_length_error": "ID має містити 6-16 символів", "id_format_error": "ID може містити лише літери, цифри, тире та підкреслення", @@ -340,6 +341,41 @@ "group_invalid": "Недійсний ідентифікатор групи", "group_not_found": "Групу не знайдено", "group_saved": "Групу пристроїв збережено", + "strategies_title": "Access control strategies", + "strategies_hint": "Link user groups to device groups and push RustDesk Pro security settings to matching clients.", + "create_strategy": "Add strategy", + "edit_strategy": "Edit strategy", + "delete_strategy": "Delete strategy", + "delete_strategy_confirm": "Delete strategy {name}?", + "strategy_name": "Strategy name", + "strategy_name_required": "Strategy name is required", + "strategy_user_group": "User group", + "strategy_device_group": "Device group", + "strategy_any_user_group": "Any user group", + "strategy_any_device_group": "Any device group", + "strategy_enabled": "Enabled", + "strategy_disabled": "Disabled", + "strategy_enabled_label": "Strategy enabled", + "strategy_permissions": "Client security settings", + "strategy_permissions_hint": "Uses RustDesk advanced-setting keys (Y/N). Leave Default to keep client or strategy inheritance.", + "strategy_permissions_count": "{count} settings", + "strategy_perm_default": "Default", + "strategy_perm_allow": "Allow (Y)", + "strategy_perm_deny": "Deny (N)", + "strategy_perm_file_transfer": "File transfer", + "strategy_perm_disable_clipboard": "Disable clipboard", + "strategy_perm_enable_clipboard": "Enable clipboard", + "strategy_perm_audio": "Audio", + "strategy_perm_tunnel": "TCP tunnel", + "strategy_perm_camera": "Camera", + "strategy_perm_restart": "Remote restart", + "strategy_perm_block_input": "Block user input", + "strategy_created": "Strategy created", + "strategy_updated": "Strategy updated", + "strategy_deleted": "Strategy deleted", + "strategy_not_found": "Strategy not found", + "no_strategies": "No access control strategies yet", + "loading_strategies": "Loading strategies...", "groups_saved": "Групи пристроїв збережено", "group_membership_added": "Додано до групи", "group_membership_removed": "Вилучено з групи", @@ -2606,7 +2642,19 @@ "address_book_saved": "Адресну книгу збережено", "address_book_save_failed": "Не вдалося зберегти адресну книгу", "address_book_imported": "Пристрої організації імпортовано", - "address_book_import_none": "Немає нових пристроїв організації для імпорту" + "address_book_import_none": "Немає нових пристроїв організації для імпорту", + "address_book_contacts": "Shared contacts", + "address_book_add_peer": "Add contact", + "address_book_peer_id": "Device ID", + "address_book_alias": "Alias", + "address_book_peer_tags": "Contact tags", + "address_book_peer_tags_placeholder": "office, support", + "address_book_tags_label": "Global tags", + "address_book_tags_placeholder": "Office, Support", + "address_book_tags_hint": "Comma-separated tags available in the RustDesk client address book.", + "address_book_no_peers": "No contacts yet — add a device ID or import organization devices.", + "address_book_show_json": "Advanced JSON", + "address_book_hide_json": "Structured editor" }, "chat": { "e2e_enabled": "Наскрізне шифрування", diff --git a/web-nodejs/lang/vi.json b/web-nodejs/lang/vi.json index 3d6c9a6f..c8e63284 100644 --- a/web-nodejs/lang/vi.json +++ b/web-nodejs/lang/vi.json @@ -1,4 +1,4 @@ -{ +{ "_meta": { "name": "Vietnamese", "native_name": "Tiếng Việt", @@ -244,6 +244,7 @@ "change_id_title": "Thay đổi ID thiết bị", "new_id": "New ID", "change_id_hint": "Chỉ 6-16 ký tự, chữ cái, số, dấu gạch ngang và dấu gạch dưới", + "change_id_online_warn": "Thiết bị đang trực tuyến. Nên đổi ID trong client RustDesk. Thay đổi trên panel giữ nguyên danh tính, nhưng client phải áp dụng ID mới.", "change_id_success": "ID thiết bị đã được thay đổi thành công", "id_length_error": "ID phải dài 6-16 ký tự", "id_format_error": "ID chỉ có thể chứa chữ cái, số, dấu gạch ngang và dấu gạch dưới", @@ -340,6 +341,41 @@ "group_invalid": "Mã định danh nhóm không hợp lệ", "group_not_found": "Không tìm thấy nhóm", "group_saved": "Đã lưu nhóm thiết bị", + "strategies_title": "Access control strategies", + "strategies_hint": "Link user groups to device groups and push RustDesk Pro security settings to matching clients.", + "create_strategy": "Add strategy", + "edit_strategy": "Edit strategy", + "delete_strategy": "Delete strategy", + "delete_strategy_confirm": "Delete strategy {name}?", + "strategy_name": "Strategy name", + "strategy_name_required": "Strategy name is required", + "strategy_user_group": "User group", + "strategy_device_group": "Device group", + "strategy_any_user_group": "Any user group", + "strategy_any_device_group": "Any device group", + "strategy_enabled": "Enabled", + "strategy_disabled": "Disabled", + "strategy_enabled_label": "Strategy enabled", + "strategy_permissions": "Client security settings", + "strategy_permissions_hint": "Uses RustDesk advanced-setting keys (Y/N). Leave Default to keep client or strategy inheritance.", + "strategy_permissions_count": "{count} settings", + "strategy_perm_default": "Default", + "strategy_perm_allow": "Allow (Y)", + "strategy_perm_deny": "Deny (N)", + "strategy_perm_file_transfer": "File transfer", + "strategy_perm_disable_clipboard": "Disable clipboard", + "strategy_perm_enable_clipboard": "Enable clipboard", + "strategy_perm_audio": "Audio", + "strategy_perm_tunnel": "TCP tunnel", + "strategy_perm_camera": "Camera", + "strategy_perm_restart": "Remote restart", + "strategy_perm_block_input": "Block user input", + "strategy_created": "Strategy created", + "strategy_updated": "Strategy updated", + "strategy_deleted": "Strategy deleted", + "strategy_not_found": "Strategy not found", + "no_strategies": "No access control strategies yet", + "loading_strategies": "Loading strategies...", "groups_saved": "Đã lưu nhóm thiết bị", "group_membership_added": "Đã thêm vào nhóm", "group_membership_removed": "Đã xóa khỏi nhóm", @@ -2606,7 +2642,19 @@ "address_book_saved": "Đã lưu sổ địa chỉ", "address_book_save_failed": "Không thể lưu sổ địa chỉ", "address_book_imported": "Đã nhập thiết bị của tổ chức", - "address_book_import_none": "Không có thiết bị tổ chức mới để nhập" + "address_book_import_none": "Không có thiết bị tổ chức mới để nhập", + "address_book_contacts": "Shared contacts", + "address_book_add_peer": "Add contact", + "address_book_peer_id": "Device ID", + "address_book_alias": "Alias", + "address_book_peer_tags": "Contact tags", + "address_book_peer_tags_placeholder": "office, support", + "address_book_tags_label": "Global tags", + "address_book_tags_placeholder": "Office, Support", + "address_book_tags_hint": "Comma-separated tags available in the RustDesk client address book.", + "address_book_no_peers": "No contacts yet — add a device ID or import organization devices.", + "address_book_show_json": "Advanced JSON", + "address_book_hide_json": "Structured editor" }, "chat": { "e2e_enabled": "Mã hóa đầu cuối", diff --git a/web-nodejs/lang/zh-TW.json b/web-nodejs/lang/zh-TW.json index 4d3f77bf..b84a1a56 100644 --- a/web-nodejs/lang/zh-TW.json +++ b/web-nodejs/lang/zh-TW.json @@ -1,4 +1,4 @@ -{ +{ "meta": { "language": "繁體中文", "code": "zh-TW", @@ -244,6 +244,7 @@ "change_id_title": "更改設備 ID", "new_id": "新 ID", "change_id_hint": "6-16 個字符,僅限字母、數字、短橫線和下劃線", + "change_id_online_warn": "裝置在線。請優先在 RustDesk 用戶端中變更 ID。在面板中變更會保留裝置身分,但用戶端必須採用新 ID 才能保持連線。", "change_id_success": "設備 ID 更改成功", "id_length_error": "ID 必須爲 6-16 個字符", "id_format_error": "ID 只能包含字母、數字、短橫線和下劃線", @@ -307,6 +308,41 @@ "group_invalid": "分組標識符無效", "group_not_found": "未找到分組", "group_saved": "設備分組已保存", + "strategies_title": "Access control strategies", + "strategies_hint": "Link user groups to device groups and push RustDesk Pro security settings to matching clients.", + "create_strategy": "Add strategy", + "edit_strategy": "Edit strategy", + "delete_strategy": "Delete strategy", + "delete_strategy_confirm": "Delete strategy {name}?", + "strategy_name": "Strategy name", + "strategy_name_required": "Strategy name is required", + "strategy_user_group": "User group", + "strategy_device_group": "Device group", + "strategy_any_user_group": "Any user group", + "strategy_any_device_group": "Any device group", + "strategy_enabled": "Enabled", + "strategy_disabled": "Disabled", + "strategy_enabled_label": "Strategy enabled", + "strategy_permissions": "Client security settings", + "strategy_permissions_hint": "Uses RustDesk advanced-setting keys (Y/N). Leave Default to keep client or strategy inheritance.", + "strategy_permissions_count": "{count} settings", + "strategy_perm_default": "Default", + "strategy_perm_allow": "Allow (Y)", + "strategy_perm_deny": "Deny (N)", + "strategy_perm_file_transfer": "File transfer", + "strategy_perm_disable_clipboard": "Disable clipboard", + "strategy_perm_enable_clipboard": "Enable clipboard", + "strategy_perm_audio": "Audio", + "strategy_perm_tunnel": "TCP tunnel", + "strategy_perm_camera": "Camera", + "strategy_perm_restart": "Remote restart", + "strategy_perm_block_input": "Block user input", + "strategy_created": "Strategy created", + "strategy_updated": "Strategy updated", + "strategy_deleted": "Strategy deleted", + "strategy_not_found": "Strategy not found", + "no_strategies": "No access control strategies yet", + "loading_strategies": "Loading strategies...", "groups_saved": "設備分組已保存", "group_membership_added": "已添加到分組", "group_membership_removed": "已從分組移除", @@ -2688,7 +2724,19 @@ "address_book_saved": "通訊錄已儲存", "address_book_save_failed": "無法儲存通訊錄", "address_book_imported": "已匯入組織裝置", - "address_book_import_none": "沒有新的組織裝置可匯入" + "address_book_import_none": "沒有新的組織裝置可匯入", + "address_book_contacts": "Shared contacts", + "address_book_add_peer": "Add contact", + "address_book_peer_id": "Device ID", + "address_book_alias": "Alias", + "address_book_peer_tags": "Contact tags", + "address_book_peer_tags_placeholder": "office, support", + "address_book_tags_label": "Global tags", + "address_book_tags_placeholder": "Office, Support", + "address_book_tags_hint": "Comma-separated tags available in the RustDesk client address book.", + "address_book_no_peers": "No contacts yet — add a device ID or import organization devices.", + "address_book_show_json": "Advanced JSON", + "address_book_hide_json": "Structured editor" }, "chat": { "e2e_enabled": "端到端加密", diff --git a/web-nodejs/lang/zh.json b/web-nodejs/lang/zh.json index 2775653d..7f090a5b 100644 --- a/web-nodejs/lang/zh.json +++ b/web-nodejs/lang/zh.json @@ -1,4 +1,4 @@ -{ +{ "meta": { "lang": "zh", "name": "中文", @@ -238,6 +238,7 @@ "change_id_title": "更改设备 ID", "new_id": "新 ID", "change_id_hint": "6-16 个字符,仅限字母、数字、短横线和下划线", + "change_id_online_warn": "设备在线。请优先在 RustDesk 客户端中更改 ID。在面板中更改会保留设备身份,但客户端必须采用新 ID 才能保持连接。", "change_id_success": "设备 ID 更改成功", "id_length_error": "ID 必须为 6-16 个字符", "id_format_error": "ID 只能包含字母、数字、短横线和下划线", @@ -334,6 +335,41 @@ "group_invalid": "分组标识符无效", "group_not_found": "未找到分组", "group_saved": "设备分组已保存", + "strategies_title": "Access control strategies", + "strategies_hint": "Link user groups to device groups and push RustDesk Pro security settings to matching clients.", + "create_strategy": "Add strategy", + "edit_strategy": "Edit strategy", + "delete_strategy": "Delete strategy", + "delete_strategy_confirm": "Delete strategy {name}?", + "strategy_name": "Strategy name", + "strategy_name_required": "Strategy name is required", + "strategy_user_group": "User group", + "strategy_device_group": "Device group", + "strategy_any_user_group": "Any user group", + "strategy_any_device_group": "Any device group", + "strategy_enabled": "Enabled", + "strategy_disabled": "Disabled", + "strategy_enabled_label": "Strategy enabled", + "strategy_permissions": "Client security settings", + "strategy_permissions_hint": "Uses RustDesk advanced-setting keys (Y/N). Leave Default to keep client or strategy inheritance.", + "strategy_permissions_count": "{count} settings", + "strategy_perm_default": "Default", + "strategy_perm_allow": "Allow (Y)", + "strategy_perm_deny": "Deny (N)", + "strategy_perm_file_transfer": "File transfer", + "strategy_perm_disable_clipboard": "Disable clipboard", + "strategy_perm_enable_clipboard": "Enable clipboard", + "strategy_perm_audio": "Audio", + "strategy_perm_tunnel": "TCP tunnel", + "strategy_perm_camera": "Camera", + "strategy_perm_restart": "Remote restart", + "strategy_perm_block_input": "Block user input", + "strategy_created": "Strategy created", + "strategy_updated": "Strategy updated", + "strategy_deleted": "Strategy deleted", + "strategy_not_found": "Strategy not found", + "no_strategies": "No access control strategies yet", + "loading_strategies": "Loading strategies...", "groups_saved": "设备分组已保存", "group_membership_added": "已添加到分组", "group_membership_removed": "已从分组移除", @@ -2671,7 +2707,19 @@ "address_book_saved": "通讯录已保存", "address_book_save_failed": "无法保存通讯录", "address_book_imported": "已导入组织设备", - "address_book_import_none": "没有新的组织设备可导入" + "address_book_import_none": "没有新的组织设备可导入", + "address_book_contacts": "Shared contacts", + "address_book_add_peer": "Add contact", + "address_book_peer_id": "Device ID", + "address_book_alias": "Alias", + "address_book_peer_tags": "Contact tags", + "address_book_peer_tags_placeholder": "office, support", + "address_book_tags_label": "Global tags", + "address_book_tags_placeholder": "Office, Support", + "address_book_tags_hint": "Comma-separated tags available in the RustDesk client address book.", + "address_book_no_peers": "No contacts yet — add a device ID or import organization devices.", + "address_book_show_json": "Advanced JSON", + "address_book_hide_json": "Structured editor" }, "chat": { "e2e_enabled": "端到端加密", diff --git a/web-nodejs/public/css/devices.css b/web-nodejs/public/css/devices.css index 090b44bc..2e36f94b 100644 --- a/web-nodejs/public/css/devices.css +++ b/web-nodejs/public/css/devices.css @@ -1986,6 +1986,122 @@ td.column-hidden { color: var(--text-muted, #8b949e); margin-top: 2px; } + +/* Access control strategies (RustDesk Pro) */ +.devices-strategies-card { + background: var(--bg-secondary); + border: 1px solid var(--border-color); + border-radius: var(--radius-lg); + padding: var(--space-md) var(--space-lg); + margin: var(--space-md) 0; +} + +.devices-strategies-header { + display: flex; + align-items: flex-start; + justify-content: space-between; + gap: var(--space-md); + margin-bottom: var(--space-md); +} + +.devices-strategies-header h3 { + margin: 0 0 var(--space-xs) 0; + font-size: var(--font-size-lg); +} + +.devices-strategies-header p { + margin: 0; + color: var(--text-secondary); + font-size: var(--font-size-sm); +} + +.devices-strategies-list { + display: grid; + gap: var(--space-sm); +} + +.strategy-manager-item { + display: grid; + grid-template-columns: minmax(0, 1fr) auto auto; + align-items: center; + gap: var(--space-md); + padding: var(--space-sm) var(--space-md); + border: 1px solid var(--border-color); + border-radius: var(--radius-md); + background: var(--bg-primary); +} + +.strategy-manager-item.strategy-disabled { + opacity: 0.72; +} + +.strategy-manager-main { + display: flex; + align-items: center; + gap: var(--space-sm); + min-width: 0; +} + +.strategy-manager-main > .material-icons { + color: var(--accent-purple, #8b5cf6); + font-size: 20px; +} + +.strategy-manager-text { + display: flex; + flex-direction: column; + gap: 2px; + min-width: 0; +} + +.strategy-manager-text span { + color: var(--text-secondary); + font-size: var(--font-size-sm); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.strategy-perm-count { + font-size: var(--font-size-xs) !important; +} + +.strategy-status-badge { + font-size: var(--font-size-xs); + padding: 2px 8px; + border-radius: var(--radius-full); + border: 1px solid var(--border-color); +} + +.strategy-status-badge.enabled { + color: var(--accent-green, #22c55e); + border-color: rgba(34, 197, 94, 0.35); +} + +.strategy-status-badge.disabled { + color: var(--text-secondary); +} + +.strategy-manager-actions { + display: flex; + gap: var(--space-xs); +} + +.strategy-perm-grid { + display: grid; + gap: var(--space-sm); +} + +.strategy-perm-option { + display: grid; + grid-template-columns: minmax(0, 1fr) 160px; + align-items: center; + gap: var(--space-sm); +} + +.strategy-perm-select { + padding: 6px 8px; +} .mesh-groups-list { margin-top: 8px; } .mesh-group-row { padding: 4px 0; } .mesh-recordings-table { margin-top: 8px; } diff --git a/web-nodejs/public/css/organizations.css b/web-nodejs/public/css/organizations.css index b2040cc4..0d2d7bc6 100644 --- a/web-nodejs/public/css/organizations.css +++ b/web-nodejs/public/css/organizations.css @@ -275,6 +275,42 @@ resize: vertical; } +.org-address-book-structured { + margin-top: var(--space-md); +} + +.org-ab-section-header { + margin-bottom: var(--space-sm); +} + +.org-ab-table-wrap { + margin-bottom: var(--space-md); +} + +.org-address-book-table .form-input { + width: 100%; + min-width: 0; +} + +.org-ab-actions { + width: 48px; + text-align: right; +} + +.org-ab-empty { + text-align: center; + color: var(--text-secondary); + padding: var(--space-md); +} + +.org-address-book-advanced { + margin-top: var(--space-md); +} + +.org-address-book-json-wrap.hidden { + display: none; +} + /* Empty state */ .empty-state { text-align: center; diff --git a/web-nodejs/public/js/deviceDetail.js b/web-nodejs/public/js/deviceDetail.js index a6ad1702..a1a5014d 100644 --- a/web-nodejs/public/js/deviceDetail.js +++ b/web-nodejs/public/js/deviceDetail.js @@ -22,10 +22,49 @@ const DeviceDetail = (function () { if (typeof detail.note === 'string') { device.note = detail.note; } + if (typeof detail.online === 'boolean' || detail.status || detail.live_status) { + device.online = detail.online ?? device.online; + device.status = detail.status || device.status; + device.live_status = detail.live_status || detail.status || device.live_status; + } _render(); }); + document.addEventListener('devices:live-status', function (event) { + const detail = event.detail || {}; + if (!device || !detail.id || device.id !== detail.id) return; + const normalized = String(detail.status || '').toLowerCase(); + device.live_status = normalized; + device.online = normalized === 'online'; + device.status = normalized; + const statusBar = overlayEl?.querySelector('.device-panel-status-bar'); + if (statusBar) { + const statusInfo = _resolveDeviceStatus(device); + const badge = statusBar.querySelector('.device-panel-status-badge'); + if (badge) { + badge.className = 'device-panel-status-badge ' + statusInfo.className; + if (statusInfo.title) badge.title = statusInfo.title; + else badge.removeAttribute('title'); + badge.innerHTML = '' + statusInfo.label; + } + } + }); + + document.addEventListener('devices:id-changed', function (event) { + const detail = event.detail || {}; + if (!device || !detail.old_id || device.id !== detail.old_id) return; + const newId = detail.new_id; + if (!newId) return; + device.id = newId; + _stopRefresh(); + _startRefresh(newId); + const titleEl = overlayEl?.querySelector('.device-panel-device-id'); + if (titleEl) titleEl.textContent = newId; + const copyBtn = overlayEl?.querySelector('.device-panel-copy-btn[data-copy]'); + if (copyBtn) copyBtn.dataset.copy = newId; + }); + // ────────────────────────────────────────────────────────────────────── // State // ────────────────────────────────────────────────────────────────────── @@ -1279,6 +1318,16 @@ const DeviceDetail = (function () { const deviceId = device && device.id; if (!deviceId) return; + if (device.online) { + const proceed = await Modal.confirm({ + title: _('devices.change_id_title'), + message: _('devices.change_id_online_warn'), + confirmLabel: _('devices.change_id'), + danger: false + }); + if (!proceed) return; + } + const newId = await Modal.prompt({ title: _('devices.change_id_title'), label: _('devices.new_id'), diff --git a/web-nodejs/public/js/devices.js b/web-nodejs/public/js/devices.js index 4906dcca..802c9521 100644 --- a/web-nodejs/public/js/devices.js +++ b/web-nodejs/public/js/devices.js @@ -54,6 +54,8 @@ let filteredDevices = []; let folders = []; let deviceGroups = []; + let accessStrategies = []; + let strategiesLoaded = false; let availableUserGroups = []; let userGroupsLoaded = false; let availableTags = []; @@ -90,6 +92,7 @@ loadFolders(); loadUserGroups(); loadDeviceGroups(); + loadStrategies(); loadTags(); loadDevices(); @@ -102,6 +105,7 @@ initSync(); initFolders(); initDeviceGroups(); + initStrategies(); initDragDrop(); attachFolderDropEvents(); // For static folder chips attachGroupDropEvents(); // For static group chips @@ -127,6 +131,7 @@ loadFolders(); loadUserGroups(); loadDeviceGroups(); + loadStrategies(); loadTags(); loadDevices(); }); @@ -166,6 +171,14 @@ const data = JSON.parse(event.data); if (data.type === 'device_status') { updateDeviceStatusInPlace(data.device_id, data.status); + document.dispatchEvent(new CustomEvent('devices:live-status', { + detail: { id: data.device_id, status: data.status } + })); + } else if (data.type === 'device_id_changed') { + document.dispatchEvent(new CustomEvent('devices:id-changed', { + detail: { old_id: data.old_id, new_id: data.new_id, source: data.source || '' } + })); + loadDevices(); } } catch (_) {} }; @@ -1655,6 +1668,17 @@ * Change device ID */ async function changeDeviceId(deviceId) { + const device = devices.find(d => d.id === deviceId); + if (device?.online) { + const proceed = await Modal.confirm({ + title: _('devices.change_id_title'), + message: _('devices.change_id_online_warn'), + confirmLabel: _('devices.change_id'), + danger: false + }); + if (!proceed) return; + } + const newId = await Modal.prompt({ title: _('devices.change_id_title'), label: _('devices.new_id'), @@ -2322,6 +2346,214 @@ document.getElementById('add-device-group-btn')?.addEventListener('click', () => showDeviceGroupModal()); document.querySelector('.group-chip[data-group="all"]')?.addEventListener('click', () => selectDeviceGroup('all')); } + + const RUSTDESK_STRATEGY_PERM_KEYS = [ + { key: 'enable-file-transfer', labelKey: 'devices.strategy_perm_file_transfer' }, + { key: 'disable-clipboard', labelKey: 'devices.strategy_perm_disable_clipboard' }, + { key: 'enable-clipboard', labelKey: 'devices.strategy_perm_enable_clipboard' }, + { key: 'enable-audio', labelKey: 'devices.strategy_perm_audio' }, + { key: 'enable-tunnel', labelKey: 'devices.strategy_perm_tunnel' }, + { key: 'enable-camera', labelKey: 'devices.strategy_perm_camera' }, + { key: 'enable-remote-restart', labelKey: 'devices.strategy_perm_restart' }, + { key: 'enable-block-input', labelKey: 'devices.strategy_perm_block_input' }, + ]; + + async function loadStrategies() { + try { + const response = await Utils.api('/api/panel/strategies'); + accessStrategies = response.strategies || []; + strategiesLoaded = true; + renderStrategies(); + } catch (error) { + console.error('Failed to load strategies:', error); + strategiesLoaded = true; + accessStrategies = []; + renderStrategies(); + } + } + + function renderStrategies() { + const container = document.getElementById('devices-strategies-list'); + if (!container) return; + if (!strategiesLoaded) { + container.innerHTML = `
${escHtml(t('address_book_intro'))}
-${escHtml(t('address_book_json_hint'))}
+| ${escHtml(t('address_book_peer_id'))} | +${escHtml(t('address_book_alias'))} | +${escHtml(t('address_book_peer_tags'))} | ++ |
|---|
${escHtml(t('address_book_tags_hint'))}
+${_('devices.strategies_hint')}
+