feat: instance-level streaming limits with admin settings panel

Add a server-side instance_settings table (single-row, CHECK(id=1))
that stores admin-configurable streaming bounds: bitrate min/max/step,
allowed resolutions, and allowed framerates.

Backend:
- New instance_settings schema + migrations (is_admin on users, default
  settings row, first-registered-user promoted to admin)
- GET/PATCH /api/settings/streaming endpoints with admin-only writes
  and full input validation including cross-field checks

Frontend:
- settingsStore fetches limits on WebSocket ready, exposes isAdmin flag
- ScreenShareSettingsPopover reads bounds from store instead of
  hardcoded constants, auto-clamps stale localStorage values
- buildScreenShareOptions() clamps bitrate to server limits at build
  time as enforcement backstop
- ServerSettings modal gains a "Streaming" tab (admin-only) with
  bitrate range sliders, resolution/framerate toggles, and save/reset
This commit is contained in:
Jannis Braun
2026-02-26 03:33:29 +01:00
parent 2184ded2c1
commit 773a03b1aa
22 changed files with 541 additions and 9 deletions
+47
View File
@@ -0,0 +1,47 @@
import { create } from 'zustand';
import type { InstanceStreamingLimits } from '@opencord/shared';
import { api } from '../api/client';
interface SettingsState {
streamingLimits: InstanceStreamingLimits | null;
isAdmin: boolean;
fetchStreamingLimits: () => Promise<void>;
updateStreamingLimits: (limits: Partial<InstanceStreamingLimits>) => Promise<void>;
setIsAdmin: (isAdmin: boolean) => void;
}
const DEFAULT_LIMITS: InstanceStreamingLimits = {
maxBitrateKbps: 20000,
minBitrateKbps: 500,
bitrateStepKbps: 500,
allowedResolutions: [540, 720, 1080],
allowedFramerates: [30, 45, 60],
maxResolution: 1080,
maxFramerate: 60,
};
export function getStreamingLimits(): InstanceStreamingLimits {
return useSettingsStore.getState().streamingLimits ?? DEFAULT_LIMITS;
}
export const useSettingsStore = create<SettingsState>((set) => ({
streamingLimits: null,
isAdmin: false,
fetchStreamingLimits: async () => {
try {
const limits = await api.settings.getStreaming();
set({ streamingLimits: limits });
} catch (err) {
console.warn('[Settings] Failed to fetch streaming limits, using defaults:', err);
set({ streamingLimits: DEFAULT_LIMITS });
}
},
updateStreamingLimits: async (limits: Partial<InstanceStreamingLimits>) => {
const updated = await api.settings.updateStreaming(limits);
set({ streamingLimits: updated });
},
setIsAdmin: (isAdmin: boolean) => set({ isAdmin }),
}));