feat: collaboration rooms with switchable presenter (#1328)

* feat: add collaboration rooms with switchable presenter

Rooms are a group of members watching one stage - the live SSH/RDP/VNC
session the current presenter shares. Any member can take over the
stage; the host can invite, force-stop and end the meeting. Stages
reuse session_shares (new room share type), so gating, recording,
expiry and the global sharing toggle all apply unchanged.

* feat: add stage control handoff to collaboration rooms

The presenter or host can grant any member write access to the live
stage and take it back; members can raise a hand to ask. SSH flips the
participant's permission on the live gate; RDP/VNC re-mint the viewer's
join token. Control clears on every stage switch.

* feat: guest links, role invites and invite awareness for collab rooms

- Anonymous guest link per room (host toggles/rotates), followed by
  polling the public resolve endpoint; SSH guests join over the terminal
  WS with roomGuestToken, guac guests get read-only join tokens
- Invite by role (expands to current members, snapshot semantics)
- Toast when a room you were invited to appears
- Stale stages are cleared lazily when the presenter is gone
- Telnet presenting, expired-tab fallback, documented single-instance
  and guac-kick limits
- Tests for the collab routes, room hub, share access and control flip

* fix: keep remote desktop collaboration read-only
This commit is contained in:
ZacharyZcR
2026-08-25 00:56:04 +08:00
committed by GitHub
parent d35458f78b
commit 81d79cc89b
50 changed files with 56698 additions and 81 deletions
+54
View File
@@ -662,6 +662,7 @@
"aiNote": "Nothing is sent anywhere until you add a provider and send a message."
},
"nav": {
"collab": "Meetings",
"home": "Home",
"terminal": "Terminal",
"localTerminal": "Local Terminal",
@@ -1806,6 +1807,59 @@
"join": "Join",
"sharedSessionLabel": "{{hostName}} (shared)"
},
"collab": {
"createRoom": "New room",
"roomName": "Room name",
"persistentRoom": "Persistent room",
"persistentRoomHint": "Persistent rooms stay listed after the meeting ends and can be reused.",
"noRooms": "No rooms yet. Create one to start a meeting.",
"openRoom": "Open",
"endRoom": "End meeting",
"leaveRoom": "Leave",
"deleteMember": "Remove",
"invite": "Invite",
"inviteTitle": "Invite members",
"members": "Members",
"online": "online",
"hostBadge": "Host",
"presenterBadge": "Presenting",
"present": "Present",
"presentTitle": "Take the stage",
"presentHost": "Host",
"presentProtocol": "Protocol",
"stopPresenting": "Stop presenting",
"takeOver": "Take over",
"emptyStage": "Nobody is presenting. Take the stage to share a session.",
"stageLoading": "Connecting to the stage...",
"stageEnded": "The presentation ended",
"roomEnded": "This meeting has ended",
"presenterLabel": "{{name}} is presenting",
"guestLink": "Guest link",
"guestLinkOn": "Guest link is on. Anyone with the link can watch the stage.",
"guestLinkOff": "Guest link is off.",
"copyLink": "Copy link",
"linkCopied": "Link copied",
"roles": "Roles",
"users": "Users",
"invitedTo": "You were invited to \"{{name}}\"",
"guest": {
"title": "Meeting",
"waiting": "Waiting for the presenter...",
"linkInvalid": "This guest link is invalid or the meeting has ended"
},
"reopenFromPanel": "This meeting tab expired. Reopen the room from the Meetings panel.",
"requestControl": "Request control",
"releaseControl": "Release control",
"grantControl": "Give control",
"revokeControl": "Take back control",
"controlRequestedBy": "{{name}} asked for control",
"grant": "Grant",
"controlBadge": "In control",
"youArePresenting": "You are presenting in another tab. Stop presenting to hand the stage over.",
"created": "Room created",
"invited": "Members invited",
"roomTab": "Meeting"
},
"sessionSharing": {
"guestView": {
"loading": "Connecting to shared session...",