Add debugging console

This commit is contained in:
2026-03-07 21:59:39 +01:00
parent 66246e4e16
commit 90f067e662
49 changed files with 5962 additions and 139 deletions

View File

@@ -0,0 +1,106 @@
<div class="max-w-3xl space-y-6">
<section class="rounded-xl border border-border bg-card/40 p-5">
<div class="flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between">
<div class="flex items-start gap-3">
<div class="rounded-xl bg-primary/10 p-2 text-primary">
<ng-icon
name="lucideBug"
class="h-5 w-5"
/>
</div>
<div>
<h4 class="text-sm font-semibold text-foreground">App-wide debugging</h4>
<p class="mt-1 text-sm text-muted-foreground">
Capture UI events, navigation activity, console output, and global runtime errors in a live debug console.
</p>
</div>
</div>
<label class="relative inline-flex cursor-pointer items-center">
<input
type="checkbox"
class="peer sr-only"
[checked]="enabled()"
(change)="onEnabledChange($event)"
/>
<div
class="h-5 w-10 rounded-full bg-secondary after:absolute after:left-[2px] after:top-0.5 after:h-4 after:w-4 after:rounded-full after:bg-white after:transition-all after:content-[''] peer-checked:bg-primary peer-checked:after:translate-x-full"
></div>
</label>
</div>
</section>
<section class="grid gap-3 sm:grid-cols-3">
<div class="rounded-xl border border-border bg-secondary/20 p-4">
<div class="flex items-center gap-2 text-muted-foreground">
<ng-icon
name="lucideClock3"
class="h-4 w-4"
/>
<span class="text-xs font-medium uppercase tracking-wide">Captured events</span>
</div>
<p class="mt-3 text-2xl font-semibold text-foreground">{{ entryCount() }}</p>
<p class="mt-1 text-xs text-muted-foreground">Last update: {{ lastUpdatedLabel() }}</p>
</div>
<div class="rounded-xl border border-border bg-secondary/20 p-4">
<div class="flex items-center gap-2 text-muted-foreground">
<ng-icon
name="lucideCircleAlert"
class="h-4 w-4"
/>
<span class="text-xs font-medium uppercase tracking-wide">Errors</span>
</div>
<p class="mt-3 text-2xl font-semibold text-destructive">{{ errorCount() }}</p>
<p class="mt-1 text-xs text-muted-foreground">Unhandled runtime failures and rejected promises.</p>
</div>
<div class="rounded-xl border border-border bg-secondary/20 p-4">
<div class="flex items-center gap-2 text-muted-foreground">
<ng-icon
name="lucideTriangleAlert"
class="h-4 w-4"
/>
<span class="text-xs font-medium uppercase tracking-wide">Warnings</span>
</div>
<p class="mt-3 text-2xl font-semibold text-yellow-400">{{ warningCount() }}</p>
<p class="mt-1 text-xs text-muted-foreground">Navigation cancellations, offline events, and other warnings.</p>
</div>
</section>
<section class="rounded-xl border border-border bg-card/40 p-5">
<div class="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
<div>
<h5 class="text-sm font-semibold text-foreground">Floating debug console</h5>
<p class="mt-1 text-sm text-muted-foreground">
When debugging is enabled, a bug icon appears in the app so you can open the docked console without blocking the rest of the UI.
</p>
</div>
<div class="flex flex-wrap items-center gap-2">
<button
type="button"
(click)="openConsole()"
[disabled]="!enabled()"
class="rounded-lg bg-primary px-3 py-2 text-sm font-medium text-primary-foreground transition-colors hover:bg-primary/90 disabled:cursor-not-allowed disabled:opacity-50"
>
{{ isConsoleOpen() ? 'Console open' : 'Open console' }}
</button>
<button
type="button"
(click)="clearLogs()"
[disabled]="entryCount() === 0"
class="inline-flex items-center gap-1.5 rounded-lg bg-secondary px-3 py-2 text-sm font-medium text-foreground transition-colors hover:bg-secondary/80 disabled:cursor-not-allowed disabled:opacity-50"
>
<ng-icon
name="lucideTrash2"
class="h-4 w-4"
/>
Clear logs
</button>
</div>
</div>
</section>
</div>

View File

@@ -0,0 +1,70 @@
import {
Component,
computed,
inject
} from '@angular/core';
import { CommonModule } from '@angular/common';
import { NgIcon, provideIcons } from '@ng-icons/core';
import {
lucideBug,
lucideCircleAlert,
lucideClock3,
lucideTrash2,
lucideTriangleAlert
} from '@ng-icons/lucide';
import { DebuggingService } from '../../../../core/services/debugging.service';
@Component({
selector: 'app-debugging-settings',
standalone: true,
imports: [CommonModule, NgIcon],
viewProviders: [
provideIcons({
lucideBug,
lucideCircleAlert,
lucideClock3,
lucideTrash2,
lucideTriangleAlert
})
],
templateUrl: './debugging-settings.component.html'
})
export class DebuggingSettingsComponent {
readonly debugging = inject(DebuggingService);
readonly enabled = this.debugging.enabled;
readonly isConsoleOpen = this.debugging.isConsoleOpen;
readonly entryCount = computed(() => {
return this.debugging.entries().reduce((sum, entry) => sum + entry.count, 0);
});
readonly errorCount = computed(() => {
return this.debugging.entries().reduce((sum, entry) => {
return sum + (entry.level === 'error' ? entry.count : 0);
}, 0);
});
readonly warningCount = computed(() => {
return this.debugging.entries().reduce((sum, entry) => {
return sum + (entry.level === 'warn' ? entry.count : 0);
}, 0);
});
readonly lastUpdatedLabel = computed(() => {
const lastEntry = this.debugging.entries().at(-1);
return lastEntry ? lastEntry.timeLabel : 'No logs yet';
});
onEnabledChange(event: Event): void {
const input = event.target as HTMLInputElement;
this.debugging.setEnabled(input.checked);
}
openConsole(): void {
this.debugging.openConsole();
}
clearLogs(): void {
this.debugging.clear();
}
}

View File

@@ -1,10 +1,10 @@
/* eslint-disable @typescript-eslint/member-ordering */
import {
Component,
effect,
inject,
input,
signal,
computed
signal
} from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
@@ -60,18 +60,21 @@ export class ServerSettingsComponent {
private saveTimeout: ReturnType<typeof setTimeout> | null = null;
/** Reload form fields whenever the server input changes. */
serverData = computed(() => {
const room = this.server();
readonly serverData = this.server;
constructor() {
effect(() => {
const room = this.server();
if (!room)
return;
if (room) {
this.roomName = room.name;
this.roomDescription = room.description || '';
this.isPrivate.set(room.isPrivate);
this.maxUsers = room.maxUsers || 0;
}
return room;
});
});
}
togglePrivate(): void {
this.isPrivate.update((currentValue) => !currentValue);

View File

@@ -122,6 +122,9 @@
@case ('voice') {
Voice & Audio
}
@case ('debugging') {
Debugging
}
@case ('server') {
Server Settings
}
@@ -157,6 +160,9 @@
@case ('voice') {
<app-voice-settings />
}
@case ('debugging') {
<app-debugging-settings />
}
@case ('server') {
<app-server-settings
[server]="selectedServer()"

View File

@@ -14,6 +14,7 @@ import { Store } from '@ngrx/store';
import { NgIcon, provideIcons } from '@ng-icons/core';
import {
lucideX,
lucideBug,
lucideGlobe,
lucideAudioLines,
lucideSettings,
@@ -33,6 +34,7 @@ import { ServerSettingsComponent } from './server-settings/server-settings.compo
import { MembersSettingsComponent } from './members-settings/members-settings.component';
import { BansSettingsComponent } from './bans-settings/bans-settings.component';
import { PermissionsSettingsComponent } from './permissions-settings/permissions-settings.component';
import { DebuggingSettingsComponent } from './debugging-settings/debugging-settings.component';
import { THIRD_PARTY_LICENSES, type ThirdPartyLicense } from './third-party-licenses';
@Component({
@@ -44,6 +46,7 @@ import { THIRD_PARTY_LICENSES, type ThirdPartyLicense } from './third-party-lice
NgIcon,
NetworkSettingsComponent,
VoiceSettingsComponent,
DebuggingSettingsComponent,
ServerSettingsComponent,
MembersSettingsComponent,
BansSettingsComponent,
@@ -52,6 +55,7 @@ import { THIRD_PARTY_LICENSES, type ThirdPartyLicense } from './third-party-lice
viewProviders: [
provideIcons({
lucideX,
lucideBug,
lucideGlobe,
lucideAudioLines,
lucideSettings,
@@ -82,7 +86,10 @@ export class SettingsModalComponent {
icon: 'lucideGlobe' },
{ id: 'voice',
label: 'Voice & Audio',
icon: 'lucideAudioLines' }
icon: 'lucideAudioLines' },
{ id: 'debugging',
label: 'Debugging',
icon: 'lucideBug' }
];
readonly serverPages: { id: SettingsPage; label: string; icon: string }[] = [
{ id: 'server',