Files
Toju/src/app/shared/components/debug-console/debug-console-toolbar/debug-console-toolbar.component.html
2026-03-13 00:48:33 +01:00

209 lines
8.1 KiB
HTML

<div class="border-b border-border bg-card/90 px-4 py-3">
<div class="flex flex-col gap-3 lg:flex-row lg:items-center lg:justify-between">
<div>
<div class="flex items-center gap-2">
<span class="text-sm font-semibold text-foreground">Debug Console</span>
@if (activeTab() === 'logs') {
<span class="rounded-full bg-secondary px-2 py-0.5 text-[11px] font-medium text-muted-foreground">{{ visibleCount() }} visible</span>
} @else {
<span class="rounded-full bg-secondary px-2 py-0.5 text-[11px] font-medium text-muted-foreground"
>{{ networkSummary().clientCount }} clients · {{ networkSummary().peerConnectionCount }} links</span
>
}
</div>
<p class="mt-1 text-xs text-muted-foreground">
{{
activeTab() === 'logs'
? 'Search logs, filter by level or source, and inspect timestamps inline.'
: 'Visualize signaling, peer links, typing, speaking, streaming, and grouped traffic directly from captured debug data.'
}}
</p>
</div>
<div class="flex flex-wrap items-center gap-2">
<button
type="button"
(click)="toggleDetached()"
class="inline-flex items-center gap-1.5 rounded-lg bg-secondary px-2.5 py-2 text-xs font-medium text-foreground transition-colors hover:bg-secondary/80"
>
{{ getDetachLabel() }}
</button>
<button
type="button"
(click)="toggleAutoScroll()"
class="inline-flex items-center gap-1.5 rounded-lg bg-secondary px-2.5 py-2 text-xs font-medium text-foreground transition-colors hover:bg-secondary/80"
[attr.aria-pressed]="autoScroll()"
>
<ng-icon
[name]="autoScroll() ? 'lucidePause' : 'lucidePlay'"
class="h-3.5 w-3.5"
/>
{{ autoScroll() ? 'Pause auto-scroll' : 'Resume auto-scroll' }}
</button>
<!-- Export dropdown -->
<div
class="relative"
data-export-menu
>
<button
type="button"
(click)="toggleExportMenu()"
class="inline-flex items-center gap-1.5 rounded-lg bg-secondary px-2.5 py-2 text-xs font-medium text-foreground transition-colors hover:bg-secondary/80"
[attr.aria-expanded]="exportMenuOpen()"
aria-haspopup="true"
title="Export logs"
>
<ng-icon
name="lucideDownload"
class="h-3.5 w-3.5"
/>
Export
</button>
@if (exportMenuOpen()) {
<div class="absolute right-0 top-full z-10 mt-1 min-w-[11rem] rounded-lg border border-border bg-card p-1 shadow-xl">
@if (activeTab() === 'logs') {
<p class="px-2.5 py-1.5 text-[10px] font-semibold uppercase tracking-wider text-muted-foreground">Logs</p>
<button
type="button"
(click)="exportLogs('csv')"
class="flex w-full items-center gap-2 rounded-md px-2.5 py-1.5 text-xs text-foreground transition-colors hover:bg-secondary"
>
Export as CSV
</button>
<button
type="button"
(click)="exportLogs('txt')"
class="flex w-full items-center gap-2 rounded-md px-2.5 py-1.5 text-xs text-foreground transition-colors hover:bg-secondary"
>
Export as TXT
</button>
} @else {
<p class="px-2.5 py-1.5 text-[10px] font-semibold uppercase tracking-wider text-muted-foreground">Network</p>
<button
type="button"
(click)="exportNetwork('csv')"
class="flex w-full items-center gap-2 rounded-md px-2.5 py-1.5 text-xs text-foreground transition-colors hover:bg-secondary"
>
Export as CSV
</button>
<button
type="button"
(click)="exportNetwork('txt')"
class="flex w-full items-center gap-2 rounded-md px-2.5 py-1.5 text-xs text-foreground transition-colors hover:bg-secondary"
>
Export as TXT
</button>
}
</div>
}
</div>
<button
type="button"
(click)="clear()"
class="inline-flex items-center gap-1.5 rounded-lg bg-secondary px-2.5 py-2 text-xs font-medium text-foreground transition-colors hover:bg-secondary/80"
>
<ng-icon
name="lucideTrash2"
class="h-3.5 w-3.5"
/>
Clear
</button>
<button
type="button"
(click)="close()"
class="inline-flex items-center gap-1.5 rounded-lg bg-secondary px-2.5 py-2 text-xs font-medium text-foreground transition-colors hover:bg-secondary/80"
>
<ng-icon
name="lucideX"
class="h-3.5 w-3.5"
/>
Close
</button>
</div>
</div>
<div class="mt-3 flex flex-wrap items-center gap-2">
@for (tab of tabs; track tab) {
<button
type="button"
(click)="setActiveTab(tab)"
[class]="getTabButtonClass(tab)"
[attr.aria-pressed]="activeTab() === tab"
>
{{ getTabLabel(tab) }}
</button>
}
</div>
@if (activeTab() === 'logs') {
<div class="mt-3 grid gap-3 xl:grid-cols-[minmax(0,1fr)_12rem]">
<label class="relative block">
<span class="sr-only">Search logs</span>
<ng-icon
name="lucideSearch"
class="pointer-events-none absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground"
/>
<input
type="search"
class="w-full rounded-lg border border-border bg-background py-2 pl-9 pr-3 text-sm text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-primary"
placeholder="Search messages, payloads, timestamps, and sources"
[value]="searchTerm()"
(input)="onSearchInput($event)"
/>
</label>
<label class="relative block">
<span class="sr-only">Filter by source</span>
<select
class="w-full rounded-lg border border-border bg-background px-3 py-2 text-sm text-foreground focus:outline-none focus:ring-2 focus:ring-primary"
[value]="selectedSource()"
(change)="onSourceChange($event)"
>
<option value="all">All sources</option>
@for (source of sourceOptions(); track source) {
<option [value]="source">{{ source }}</option>
}
</select>
</label>
</div>
<div class="mt-3 flex flex-wrap items-center gap-2">
<div class="mr-1 inline-flex items-center gap-1.5 text-[11px] font-medium uppercase tracking-wide text-muted-foreground">
<ng-icon
name="lucideFilter"
class="h-3.5 w-3.5"
/>
Levels
</div>
@for (level of levels; track level) {
<button
type="button"
(click)="toggleLevel(level)"
[class]="getLevelButtonClass(level)"
[attr.aria-pressed]="levelState()[level]"
>
{{ getLevelLabel(level) }}
<span class="ml-1 text-[11px] opacity-80">{{ levelCounts()[level] }}</span>
</button>
}
</div>
} @else {
<div class="mt-3 rounded-xl border border-border/80 bg-background/70 px-3 py-3 text-xs text-muted-foreground">
<p>Traffic is grouped by edge and message type to keep signaling, voice-state, and screen-state chatter readable.</p>
<div class="mt-2 flex flex-wrap gap-2 text-[11px]">
<span class="rounded-full border border-border bg-secondary/70 px-2 py-0.5">{{ networkSummary().typingCount }} typing</span>
<span class="rounded-full border border-border bg-secondary/70 px-2 py-0.5">{{ networkSummary().speakingCount }} speaking</span>
<span class="rounded-full border border-border bg-secondary/70 px-2 py-0.5">{{ networkSummary().streamingCount }} streaming</span>
<span class="rounded-full border border-border bg-secondary/70 px-2 py-0.5">{{ networkSummary().membershipCount }} memberships</span>
</div>
</div>
}
</div>