feat: one-click join + live pod presence
Join UX: click a member's name to join directly, or type a name and "Join" (adds to roster + joins) — removes the redundant dropdown step. "Add" still adds to the roster without joining. Presence: new GET /api/presence (LiveKit RoomServiceClient) reports who's connected per pod. The pod list polls it and shows a "N in room" badge plus a green dot on members currently connected, so people can see active pods and jump in. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,39 @@
|
||||
import { RoomServiceClient } from 'livekit-server-sdk';
|
||||
import { env } from '../env.js';
|
||||
|
||||
function isConfigured(): boolean {
|
||||
return !!env.LIVEKIT_URL && !env.LIVEKIT_URL.includes('REPLACE_ME');
|
||||
}
|
||||
|
||||
let client: RoomServiceClient | null = null;
|
||||
function svc(): RoomServiceClient {
|
||||
if (!client) {
|
||||
const httpUrl = env.LIVEKIT_URL.replace(/^wss:/, 'https:').replace(/^ws:/, 'http:');
|
||||
client = new RoomServiceClient(httpUrl, env.LIVEKIT_API_KEY, env.LIVEKIT_API_SECRET);
|
||||
}
|
||||
return client;
|
||||
}
|
||||
|
||||
/**
|
||||
* Who is currently connected in each pod's LiveKit room, as display names,
|
||||
* keyed by pod id (= room name). Empty rooms are omitted. Returns {} if
|
||||
* LiveKit isn't configured or the API call fails (presence is best-effort).
|
||||
*/
|
||||
export async function getPresence(): Promise<Record<string, string[]>> {
|
||||
if (!isConfigured()) return {};
|
||||
const out: Record<string, string[]> = {};
|
||||
const rooms = await svc().listRooms();
|
||||
await Promise.all(
|
||||
rooms
|
||||
.filter((r) => r.numParticipants > 0)
|
||||
.map(async (r) => {
|
||||
try {
|
||||
const ps = await svc().listParticipants(r.name);
|
||||
out[r.name] = ps.map((p) => p.name || p.identity);
|
||||
} catch {
|
||||
out[r.name] = [];
|
||||
}
|
||||
}),
|
||||
);
|
||||
return out;
|
||||
}
|
||||
@@ -17,6 +17,7 @@ import {
|
||||
removeMember,
|
||||
seedDefaultPods,
|
||||
} from './pods/store.js';
|
||||
import { getPresence } from './livekit/rooms.js';
|
||||
import type { InterventionOutcome } from '@podman/shared';
|
||||
|
||||
const app = express();
|
||||
@@ -72,6 +73,15 @@ app.get('/api/memory/stats', async (_req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
// Live presence: who is currently connected in each pod's LiveKit room.
|
||||
app.get('/api/presence', async (_req, res) => {
|
||||
try {
|
||||
res.json(await getPresence());
|
||||
} catch (e) {
|
||||
res.status(500).json({ error: (e as Error).message });
|
||||
}
|
||||
});
|
||||
|
||||
// --- Pods CRUD (Mongo-backed) ---
|
||||
app.get('/api/pods', async (_req, res) => {
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user