feat: dedicated Instance Settings modal with admin controls

Move instance-level administration out of Space Settings into its own
modal. Adds admin UI for instance name, registration toggle, and
discovery toggle. Streaming limits panel relocated from SpaceSettings.

- Add InstanceAdminSettings type and GET/PATCH /api/settings/instance
- Add registration_open DB column (nullable, env var fallback)
- Auth registration and instance info now check DB override
- New InstanceSettings modal with General and Streaming tabs
- Admin shield button in UserAreaPanel (visible to admins only)
- Remove Streaming tab from SpaceSettings
This commit is contained in:
Jannis Braun
2026-03-09 03:52:01 +01:00
parent 096251e5b0
commit 450df432b2
15 changed files with 575 additions and 239 deletions
@@ -12,6 +12,7 @@ import { CreateChannelModal } from '../modals/CreateChannel';
import { InviteModal } from '../modals/InviteModal';
import { UserSettingsModal } from '../modals/UserSettings';
import { SpaceSettingsModal } from '../modals/SpaceSettings';
import { InstanceSettingsModal } from '../modals/InstanceSettings';
import { ChannelSettingsModal } from '../modals/ChannelSettingsModal';
import { NewDmModal } from '../modals/NewDmModal';
import { AddDmMemberModal } from '../modals/AddDmMemberModal';
@@ -273,6 +274,7 @@ export function AppLayout() {
<InviteModal />
<UserSettingsModal />
<SpaceSettingsModal />
<InstanceSettingsModal />
<ChannelSettingsModal />
<NewDmModal />
<AddDmMemberModal />
@@ -122,9 +122,11 @@ export function ChannelSidebar() {
user={user}
isMuted={isMuted}
isDeafened={isDeafened}
isAdmin={!!user.isAdmin}
onMicToggle={handleMicToggle}
onDeafenToggle={handleDeafenToggle}
onSettingsClick={() => openModal('userSettings')}
onAdminClick={() => openModal('instanceSettings')}
/>
</div>
</div>
@@ -442,16 +444,20 @@ function UserAreaPanel({
user,
isMuted,
isDeafened,
isAdmin,
onMicToggle,
onDeafenToggle,
onSettingsClick,
onAdminClick,
}: {
user: any;
isMuted: boolean;
isDeafened: boolean;
isAdmin: boolean;
onMicToggle: () => void;
onDeafenToggle: () => void;
onSettingsClick: () => void;
onAdminClick: () => void;
}) {
const [openPanel, setOpenPanel] = useState<'input' | 'output' | null>(null);
const [inputDevices, setInputDevices] = useState<MediaDeviceInfo[]>([]);
@@ -812,6 +818,19 @@ function UserAreaPanel({
</svg>
</button>
{/* Admin — Instance Settings */}
{isAdmin && (
<button
onClick={onAdminClick}
className="w-8 h-8 flex items-center justify-center text-txt-tertiary hover:text-accent-amber hover:bg-interactive-hover rounded-[4px] transition-colors"
title="Instance Settings"
>
<svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor">
<path d="M12 1L3 5v6c0 5.55 3.84 10.74 9 12 5.16-1.26 9-6.45 9-12V5l-9-4zm0 10.99h7c-.53 4.12-3.28 7.79-7 8.94V12H5V6.3l7-3.11v8.8z" />
</svg>
</button>
)}
{/* Settings */}
<button
onClick={onSettingsClick}