439 lines
17 KiB
HTML
439 lines
17 KiB
HTML
@if (isAdmin()) {
|
|
<div class="h-full flex flex-col bg-card">
|
|
<!-- Header -->
|
|
<div class="p-4 border-b border-border flex items-center gap-2">
|
|
<ng-icon
|
|
name="lucideShield"
|
|
class="w-5 h-5 text-primary"
|
|
/>
|
|
<h2 class="font-semibold text-foreground">Admin Panel</h2>
|
|
</div>
|
|
|
|
<!-- Tabs -->
|
|
<div class="flex border-b border-border">
|
|
<button
|
|
type="button"
|
|
(click)="activeTab.set('settings')"
|
|
class="flex-1 px-4 py-2 text-sm font-medium transition-colors"
|
|
[class.text-primary]="activeTab() === 'settings'"
|
|
[class.border-b-2]="activeTab() === 'settings'"
|
|
[class.border-primary]="activeTab() === 'settings'"
|
|
[class.text-muted-foreground]="activeTab() !== 'settings'"
|
|
>
|
|
<ng-icon
|
|
name="lucideSettings"
|
|
class="w-4 h-4 inline mr-1"
|
|
/>
|
|
Settings
|
|
</button>
|
|
<button
|
|
type="button"
|
|
(click)="activeTab.set('members')"
|
|
class="flex-1 px-4 py-2 text-sm font-medium transition-colors"
|
|
[class.text-primary]="activeTab() === 'members'"
|
|
[class.border-b-2]="activeTab() === 'members'"
|
|
[class.border-primary]="activeTab() === 'members'"
|
|
[class.text-muted-foreground]="activeTab() !== 'members'"
|
|
>
|
|
<ng-icon
|
|
name="lucideUsers"
|
|
class="w-4 h-4 inline mr-1"
|
|
/>
|
|
Members
|
|
</button>
|
|
<button
|
|
type="button"
|
|
(click)="activeTab.set('bans')"
|
|
class="flex-1 px-4 py-2 text-sm font-medium transition-colors"
|
|
[class.text-primary]="activeTab() === 'bans'"
|
|
[class.border-b-2]="activeTab() === 'bans'"
|
|
[class.border-primary]="activeTab() === 'bans'"
|
|
[class.text-muted-foreground]="activeTab() !== 'bans'"
|
|
>
|
|
<ng-icon
|
|
name="lucideBan"
|
|
class="w-4 h-4 inline mr-1"
|
|
/>
|
|
Bans
|
|
</button>
|
|
<button
|
|
type="button"
|
|
(click)="activeTab.set('permissions')"
|
|
class="flex-1 px-4 py-2 text-sm font-medium transition-colors"
|
|
[class.text-primary]="activeTab() === 'permissions'"
|
|
[class.border-b-2]="activeTab() === 'permissions'"
|
|
[class.border-primary]="activeTab() === 'permissions'"
|
|
[class.text-muted-foreground]="activeTab() !== 'permissions'"
|
|
>
|
|
<ng-icon
|
|
name="lucideShield"
|
|
class="w-4 h-4 inline mr-1"
|
|
/>
|
|
Perms
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Tab Content -->
|
|
<div class="flex-1 overflow-y-auto p-4">
|
|
@switch (activeTab()) {
|
|
@case ('settings') {
|
|
<div class="space-y-6">
|
|
<h3 class="text-sm font-medium text-foreground">Room Settings</h3>
|
|
|
|
<!-- Room Name -->
|
|
<div>
|
|
<label
|
|
for="room-name-input"
|
|
class="block text-sm text-muted-foreground mb-1"
|
|
>Room Name</label
|
|
>
|
|
<input
|
|
type="text"
|
|
id="room-name-input"
|
|
[(ngModel)]="roomName"
|
|
class="w-full px-3 py-2 bg-secondary rounded border border-border text-foreground focus:outline-none focus:ring-2 focus:ring-primary"
|
|
/>
|
|
</div>
|
|
|
|
<!-- Room Description -->
|
|
<div>
|
|
<label
|
|
for="room-description-input"
|
|
class="block text-sm text-muted-foreground mb-1"
|
|
>Description</label
|
|
>
|
|
<textarea
|
|
id="room-description-input"
|
|
[(ngModel)]="roomDescription"
|
|
rows="3"
|
|
class="w-full px-3 py-2 bg-secondary rounded border border-border text-foreground focus:outline-none focus:ring-2 focus:ring-primary resize-none"
|
|
></textarea>
|
|
</div>
|
|
|
|
<!-- Private Room Toggle -->
|
|
<div class="flex items-center justify-between">
|
|
<div>
|
|
<p class="text-sm font-medium text-foreground">Private Room</p>
|
|
<p class="text-xs text-muted-foreground">Require approval to join</p>
|
|
</div>
|
|
<button
|
|
type="button"
|
|
(click)="togglePrivate()"
|
|
class="p-2 rounded-lg transition-colors"
|
|
[class.bg-primary]="isPrivate()"
|
|
[class.text-primary-foreground]="isPrivate()"
|
|
[class.bg-secondary]="!isPrivate()"
|
|
[class.text-muted-foreground]="!isPrivate()"
|
|
>
|
|
@if (isPrivate()) {
|
|
<ng-icon
|
|
name="lucideLock"
|
|
class="w-4 h-4"
|
|
/>
|
|
} @else {
|
|
<ng-icon
|
|
name="lucideUnlock"
|
|
class="w-4 h-4"
|
|
/>
|
|
}
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Max Users -->
|
|
<div>
|
|
<label
|
|
for="max-users-input"
|
|
class="block text-sm text-muted-foreground mb-1"
|
|
>Max Users (0 = unlimited)</label
|
|
>
|
|
<input
|
|
type="number"
|
|
id="max-users-input"
|
|
[(ngModel)]="maxUsers"
|
|
min="0"
|
|
class="w-full px-3 py-2 bg-secondary rounded border border-border text-foreground focus:outline-none focus:ring-2 focus:ring-primary"
|
|
/>
|
|
</div>
|
|
|
|
<!-- Save Button -->
|
|
<button
|
|
type="button"
|
|
(click)="saveSettings()"
|
|
class="w-full px-4 py-2 bg-primary text-primary-foreground rounded-lg hover:bg-primary/90 transition-colors flex items-center justify-center gap-2"
|
|
>
|
|
<ng-icon
|
|
name="lucideCheck"
|
|
class="w-4 h-4"
|
|
/>
|
|
Save Settings
|
|
</button>
|
|
|
|
<!-- Danger Zone -->
|
|
<div class="pt-4 border-t border-border">
|
|
<h3 class="text-sm font-medium text-destructive mb-4">Danger Zone</h3>
|
|
<button
|
|
type="button"
|
|
(click)="confirmDeleteRoom()"
|
|
class="w-full px-4 py-2 bg-destructive/10 text-destructive border border-destructive/20 rounded-lg hover:bg-destructive/20 transition-colors flex items-center justify-center gap-2"
|
|
>
|
|
<ng-icon
|
|
name="lucideTrash2"
|
|
class="w-4 h-4"
|
|
/>
|
|
Delete Room
|
|
</button>
|
|
</div>
|
|
</div>
|
|
}
|
|
@case ('members') {
|
|
<div class="space-y-4">
|
|
<h3 class="text-sm font-medium text-foreground">Server Members</h3>
|
|
|
|
@if (membersFiltered().length === 0) {
|
|
<p class="text-sm text-muted-foreground text-center py-8">No other members online</p>
|
|
} @else {
|
|
@for (user of membersFiltered(); track user.id) {
|
|
<div class="flex items-center gap-3 p-3 bg-secondary/50 rounded-lg">
|
|
<app-user-avatar
|
|
[name]="user.displayName || '?'"
|
|
size="sm"
|
|
/>
|
|
<div class="flex-1 min-w-0">
|
|
<div class="flex items-center gap-1.5">
|
|
<p class="text-sm font-medium text-foreground truncate">{{ user.displayName }}</p>
|
|
@if (user.role === 'host') {
|
|
<span class="text-[10px] bg-yellow-500/20 text-yellow-400 px-1 py-0.5 rounded">Owner</span>
|
|
} @else if (user.role === 'admin') {
|
|
<span class="text-[10px] bg-blue-500/20 text-blue-400 px-1 py-0.5 rounded">Admin</span>
|
|
} @else if (user.role === 'moderator') {
|
|
<span class="text-[10px] bg-green-500/20 text-green-400 px-1 py-0.5 rounded">Mod</span>
|
|
}
|
|
</div>
|
|
</div>
|
|
<!-- Role actions (only for non-hosts) -->
|
|
@if (user.role !== 'host') {
|
|
<div class="flex items-center gap-1">
|
|
<select
|
|
[ngModel]="user.role"
|
|
(ngModelChange)="changeRole(user, $event)"
|
|
class="text-xs px-2 py-1 bg-secondary rounded border border-border text-foreground focus:outline-none focus:ring-1 focus:ring-primary"
|
|
>
|
|
<option value="member">Member</option>
|
|
<option value="moderator">Moderator</option>
|
|
<option value="admin">Admin</option>
|
|
</select>
|
|
<button
|
|
type="button"
|
|
(click)="kickMember(user)"
|
|
class="p-1 rounded hover:bg-destructive/20 text-muted-foreground hover:text-destructive transition-colors"
|
|
title="Kick"
|
|
>
|
|
<ng-icon
|
|
name="lucideUserX"
|
|
class="w-4 h-4"
|
|
/>
|
|
</button>
|
|
<button
|
|
type="button"
|
|
(click)="banMember(user)"
|
|
class="p-1 rounded hover:bg-destructive/20 text-muted-foreground hover:text-destructive transition-colors"
|
|
title="Ban"
|
|
>
|
|
<ng-icon
|
|
name="lucideBan"
|
|
class="w-4 h-4"
|
|
/>
|
|
</button>
|
|
</div>
|
|
}
|
|
</div>
|
|
}
|
|
}
|
|
</div>
|
|
}
|
|
@case ('bans') {
|
|
<div class="space-y-4">
|
|
<h3 class="text-sm font-medium text-foreground">Banned Users</h3>
|
|
|
|
@if (bannedUsers().length === 0) {
|
|
<p class="text-sm text-muted-foreground text-center py-8">No banned users</p>
|
|
} @else {
|
|
@for (ban of bannedUsers(); track ban.oderId) {
|
|
<div class="flex items-center gap-3 p-3 bg-secondary/50 rounded-lg">
|
|
<div class="w-8 h-8 rounded-full bg-destructive/20 flex items-center justify-center text-destructive font-semibold text-sm">
|
|
{{ ban.displayName?.charAt(0)?.toUpperCase() || '?' }}
|
|
</div>
|
|
<div class="flex-1 min-w-0">
|
|
<p class="text-sm font-medium text-foreground truncate">
|
|
{{ ban.displayName || 'Unknown User' }}
|
|
</p>
|
|
@if (ban.reason) {
|
|
<p class="text-xs text-muted-foreground truncate">Reason: {{ ban.reason }}</p>
|
|
}
|
|
@if (ban.expiresAt) {
|
|
<p class="text-xs text-muted-foreground">Expires: {{ formatExpiry(ban.expiresAt) }}</p>
|
|
} @else {
|
|
<p class="text-xs text-destructive">Permanent</p>
|
|
}
|
|
</div>
|
|
<button
|
|
type="button"
|
|
(click)="unbanUser(ban)"
|
|
class="p-2 hover:bg-secondary rounded-lg transition-colors text-muted-foreground hover:text-foreground"
|
|
>
|
|
<ng-icon
|
|
name="lucideX"
|
|
class="w-4 h-4"
|
|
/>
|
|
</button>
|
|
</div>
|
|
}
|
|
}
|
|
</div>
|
|
}
|
|
@case ('permissions') {
|
|
<div class="space-y-4">
|
|
<h3 class="text-sm font-medium text-foreground">Room Permissions</h3>
|
|
|
|
<!-- Permission Toggles -->
|
|
<div class="space-y-3">
|
|
<div class="flex items-center justify-between p-3 bg-secondary/50 rounded-lg">
|
|
<div>
|
|
<p class="text-sm font-medium text-foreground">Allow Voice Chat</p>
|
|
<p class="text-xs text-muted-foreground">Users can join voice channels</p>
|
|
</div>
|
|
<input
|
|
type="checkbox"
|
|
[(ngModel)]="allowVoice"
|
|
class="w-4 h-4 accent-primary"
|
|
/>
|
|
</div>
|
|
|
|
<div class="flex items-center justify-between p-3 bg-secondary/50 rounded-lg">
|
|
<div>
|
|
<p class="text-sm font-medium text-foreground">Allow Screen Share</p>
|
|
<p class="text-xs text-muted-foreground">Users can share their screen</p>
|
|
</div>
|
|
<input
|
|
type="checkbox"
|
|
[(ngModel)]="allowScreenShare"
|
|
class="w-4 h-4 accent-primary"
|
|
/>
|
|
</div>
|
|
|
|
<div class="flex items-center justify-between p-3 bg-secondary/50 rounded-lg">
|
|
<div>
|
|
<p class="text-sm font-medium text-foreground">Allow File Uploads</p>
|
|
<p class="text-xs text-muted-foreground">Users can upload files</p>
|
|
</div>
|
|
<input
|
|
type="checkbox"
|
|
[(ngModel)]="allowFileUploads"
|
|
class="w-4 h-4 accent-primary"
|
|
/>
|
|
</div>
|
|
|
|
<div class="flex items-center justify-between p-3 bg-secondary/50 rounded-lg">
|
|
<div>
|
|
<p class="text-sm font-medium text-foreground">Slow Mode</p>
|
|
<p class="text-xs text-muted-foreground">Limit message frequency</p>
|
|
</div>
|
|
<select
|
|
[(ngModel)]="slowModeInterval"
|
|
class="px-3 py-1 bg-secondary rounded border border-border text-foreground text-sm focus:outline-none focus:ring-2 focus:ring-primary"
|
|
>
|
|
<option value="0">Off</option>
|
|
<option value="5">5 seconds</option>
|
|
<option value="10">10 seconds</option>
|
|
<option value="30">30 seconds</option>
|
|
<option value="60">1 minute</option>
|
|
</select>
|
|
</div>
|
|
|
|
<!-- Management Permissions -->
|
|
<div class="flex items-center justify-between p-3 bg-secondary/50 rounded-lg">
|
|
<div>
|
|
<p class="text-sm font-medium text-foreground">Admins Can Manage Rooms</p>
|
|
<p class="text-xs text-muted-foreground">Allow admins to create/modify chat & voice rooms</p>
|
|
</div>
|
|
<input
|
|
type="checkbox"
|
|
[(ngModel)]="adminsManageRooms"
|
|
class="w-4 h-4 accent-primary"
|
|
/>
|
|
</div>
|
|
|
|
<div class="flex items-center justify-between p-3 bg-secondary/50 rounded-lg">
|
|
<div>
|
|
<p class="text-sm font-medium text-foreground">Moderators Can Manage Rooms</p>
|
|
<p class="text-xs text-muted-foreground">Allow moderators to create/modify chat & voice rooms</p>
|
|
</div>
|
|
<input
|
|
type="checkbox"
|
|
[(ngModel)]="moderatorsManageRooms"
|
|
class="w-4 h-4 accent-primary"
|
|
/>
|
|
</div>
|
|
|
|
<div class="flex items-center justify-between p-3 bg-secondary/50 rounded-lg">
|
|
<div>
|
|
<p class="text-sm font-medium text-foreground">Admins Can Change Server Icon</p>
|
|
<p class="text-xs text-muted-foreground">Grant icon management to admins</p>
|
|
</div>
|
|
<input
|
|
type="checkbox"
|
|
[(ngModel)]="adminsManageIcon"
|
|
class="w-4 h-4 accent-primary"
|
|
/>
|
|
</div>
|
|
|
|
<div class="flex items-center justify-between p-3 bg-secondary/50 rounded-lg">
|
|
<div>
|
|
<p class="text-sm font-medium text-foreground">Moderators Can Change Server Icon</p>
|
|
<p class="text-xs text-muted-foreground">Grant icon management to moderators</p>
|
|
</div>
|
|
<input
|
|
type="checkbox"
|
|
[(ngModel)]="moderatorsManageIcon"
|
|
class="w-4 h-4 accent-primary"
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Save Permissions -->
|
|
<button
|
|
type="button"
|
|
(click)="savePermissions()"
|
|
class="w-full px-4 py-2 bg-primary text-primary-foreground rounded-lg hover:bg-primary/90 transition-colors flex items-center justify-center gap-2"
|
|
>
|
|
<ng-icon
|
|
name="lucideCheck"
|
|
class="w-4 h-4"
|
|
/>
|
|
Save Permissions
|
|
</button>
|
|
</div>
|
|
}
|
|
}
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Delete Confirmation Modal -->
|
|
@if (showDeleteConfirm()) {
|
|
<app-confirm-dialog
|
|
title="Delete Room"
|
|
confirmLabel="Delete Room"
|
|
variant="danger"
|
|
[widthClass]="'w-96 max-w-[90vw]'"
|
|
(confirmed)="deleteRoom()"
|
|
(cancelled)="showDeleteConfirm.set(false)"
|
|
>
|
|
<p>Are you sure you want to delete this room? This action cannot be undone.</p>
|
|
</app-confirm-dialog>
|
|
}
|
|
} @else {
|
|
<div class="h-full flex items-center justify-center text-muted-foreground">
|
|
<p>You don't have admin permissions</p>
|
|
</div>
|
|
}
|