feat: remove DNS and Domains from layout, add DNS and Domains content components
- Removed DNS and Domains entries from the layout navigation. - Added DnsContentComponent for managing DNS records with Cloudflare. - Added DomainsContentComponent for managing domains and SSL certificates. - Introduced TabsComponent and TabComponent for tab navigation. - Updated index.ts to export new TabsComponent and TabComponent.
This commit is contained in:
@@ -21,6 +21,11 @@ import {
|
||||
TableHeadComponent,
|
||||
TableCellComponent,
|
||||
} from '../../ui/table/table.component';
|
||||
import { TabsComponent, TabComponent } from '../../ui/tabs/tabs.component';
|
||||
import { DnsContentComponent } from './dns-content.component';
|
||||
import { DomainsContentComponent } from './domains-content.component';
|
||||
|
||||
type TNetworkTab = 'proxy' | 'dns' | 'domains';
|
||||
|
||||
@Component({
|
||||
selector: 'app-network',
|
||||
@@ -40,6 +45,10 @@ import {
|
||||
TableRowComponent,
|
||||
TableHeadComponent,
|
||||
TableCellComponent,
|
||||
TabsComponent,
|
||||
TabComponent,
|
||||
DnsContentComponent,
|
||||
DomainsContentComponent,
|
||||
],
|
||||
template: `
|
||||
<div class="space-y-6">
|
||||
@@ -47,210 +56,238 @@ import {
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<h1 class="text-3xl font-bold tracking-tight">Network</h1>
|
||||
<p class="text-muted-foreground">Traffic targets and access logs</p>
|
||||
<p class="text-muted-foreground">Manage proxy, DNS, and domains</p>
|
||||
</div>
|
||||
<button uiButton variant="outline" (click)="loadData()" [disabled]="loading()">
|
||||
@if (loading()) {
|
||||
<svg class="animate-spin h-4 w-4 mr-2" fill="none" viewBox="0 0 24 24">
|
||||
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
||||
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"></path>
|
||||
</svg>
|
||||
}
|
||||
Refresh
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@if (loading() && !stats()) {
|
||||
<!-- Loading skeleton -->
|
||||
<div class="grid gap-4 md:grid-cols-4">
|
||||
@for (_ of [1,2,3,4]; track $index) {
|
||||
<!-- Tabs -->
|
||||
<ui-tabs class="block">
|
||||
<ui-tab [active]="activeTab() === 'proxy'" (tabClick)="setTab('proxy')">Proxy</ui-tab>
|
||||
<ui-tab [active]="activeTab() === 'dns'" (tabClick)="setTab('dns')">DNS</ui-tab>
|
||||
<ui-tab [active]="activeTab() === 'domains'" (tabClick)="setTab('domains')">Domains</ui-tab>
|
||||
</ui-tabs>
|
||||
|
||||
<!-- Tab Content -->
|
||||
@switch (activeTab()) {
|
||||
@case ('proxy') {
|
||||
<!-- Proxy Tab Content -->
|
||||
<div class="space-y-6">
|
||||
<div class="flex justify-end">
|
||||
<button uiButton variant="outline" (click)="loadData()" [disabled]="loading()">
|
||||
@if (loading()) {
|
||||
<svg class="animate-spin h-4 w-4 mr-2" fill="none" viewBox="0 0 24 24">
|
||||
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
||||
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"></path>
|
||||
</svg>
|
||||
}
|
||||
Refresh
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@if (loading() && !stats()) {
|
||||
<!-- Loading skeleton -->
|
||||
<div class="grid gap-4 md:grid-cols-4">
|
||||
@for (_ of [1,2,3,4]; track $index) {
|
||||
<ui-card>
|
||||
<ui-card-header class="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||
<ui-skeleton class="h-4 w-24" />
|
||||
</ui-card-header>
|
||||
<ui-card-content>
|
||||
<ui-skeleton class="h-8 w-16" />
|
||||
</ui-card-content>
|
||||
</ui-card>
|
||||
}
|
||||
</div>
|
||||
} @else if (stats()) {
|
||||
<!-- Stats Grid -->
|
||||
<div class="grid gap-4 md:grid-cols-4">
|
||||
<ui-card>
|
||||
<ui-card-header class="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||
<ui-card-title class="text-sm font-medium">Proxy Status</ui-card-title>
|
||||
<svg class="h-4 w-4 text-muted-foreground" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M13 10V3L4 14h7v7l9-11h-7z" />
|
||||
</svg>
|
||||
</ui-card-header>
|
||||
<ui-card-content>
|
||||
<ui-badge [variant]="stats()!.proxy.running ? 'success' : 'secondary'">
|
||||
{{ stats()!.proxy.running ? 'Running' : 'Stopped' }}
|
||||
</ui-badge>
|
||||
</ui-card-content>
|
||||
</ui-card>
|
||||
|
||||
<ui-card>
|
||||
<ui-card-header class="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||
<ui-card-title class="text-sm font-medium">Routes</ui-card-title>
|
||||
<svg class="h-4 w-4 text-muted-foreground" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M9 20l-5.447-2.724A1 1 0 013 16.382V5.618a1 1 0 011.447-.894L9 7m0 13l6-3m-6 3V7m6 10l4.553 2.276A1 1 0 0021 18.382V7.618a1 1 0 00-.553-.894L15 4m0 13V4m0 0L9 7" />
|
||||
</svg>
|
||||
</ui-card-header>
|
||||
<ui-card-content>
|
||||
<div class="text-2xl font-bold">{{ stats()!.proxy.routes }}</div>
|
||||
</ui-card-content>
|
||||
</ui-card>
|
||||
|
||||
<ui-card>
|
||||
<ui-card-header class="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||
<ui-card-title class="text-sm font-medium">Certificates</ui-card-title>
|
||||
<svg class="h-4 w-4 text-muted-foreground" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z" />
|
||||
</svg>
|
||||
</ui-card-header>
|
||||
<ui-card-content>
|
||||
<div class="text-2xl font-bold">{{ stats()!.proxy.certificates }}</div>
|
||||
</ui-card-content>
|
||||
</ui-card>
|
||||
|
||||
<ui-card>
|
||||
<ui-card-header class="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||
<ui-card-title class="text-sm font-medium">Targets</ui-card-title>
|
||||
<svg class="h-4 w-4 text-muted-foreground" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M5 12h14M5 12a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v4a2 2 0 01-2 2M5 12a2 2 0 00-2 2v4a2 2 0 002 2h14a2 2 0 002-2v-4a2 2 0 00-2-2m-2-4h.01M17 16h.01" />
|
||||
</svg>
|
||||
</ui-card-header>
|
||||
<ui-card-content>
|
||||
<div class="text-2xl font-bold">{{ targets().length }}</div>
|
||||
</ui-card-content>
|
||||
</ui-card>
|
||||
</div>
|
||||
}
|
||||
|
||||
<!-- Traffic Targets Table -->
|
||||
<ui-card>
|
||||
<ui-card-header class="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||
<ui-skeleton class="h-4 w-24" />
|
||||
<ui-card-header class="flex flex-col space-y-1.5">
|
||||
<ui-card-title>Traffic Targets</ui-card-title>
|
||||
<ui-card-description>Services, registry, and platform services with their routing info. Click to filter logs.</ui-card-description>
|
||||
</ui-card-header>
|
||||
<ui-card-content>
|
||||
<ui-skeleton class="h-8 w-16" />
|
||||
@if (targets().length === 0 && !loading()) {
|
||||
<p class="text-muted-foreground text-center py-8">No traffic targets configured</p>
|
||||
} @else {
|
||||
<ui-table>
|
||||
<ui-table-header>
|
||||
<ui-table-row>
|
||||
<ui-table-head>Type</ui-table-head>
|
||||
<ui-table-head>Name</ui-table-head>
|
||||
<ui-table-head>Domain</ui-table-head>
|
||||
<ui-table-head>Target</ui-table-head>
|
||||
<ui-table-head>Status</ui-table-head>
|
||||
</ui-table-row>
|
||||
</ui-table-header>
|
||||
<ui-table-body>
|
||||
@for (target of targets(); track target.name) {
|
||||
<ui-table-row [class]="'cursor-pointer ' + (activeFilter() === target.domain ? 'bg-muted' : '')" (click)="onTargetClick(target)">
|
||||
<ui-table-cell>
|
||||
<ui-badge [variant]="getTypeVariant(target.type)">{{ target.type }}</ui-badge>
|
||||
</ui-table-cell>
|
||||
<ui-table-cell class="font-medium">{{ target.name }}</ui-table-cell>
|
||||
<ui-table-cell>
|
||||
@if (target.domain) {
|
||||
<span class="font-mono text-sm">{{ target.domain }}</span>
|
||||
} @else {
|
||||
<span class="text-muted-foreground">-</span>
|
||||
}
|
||||
</ui-table-cell>
|
||||
<ui-table-cell class="font-mono text-sm">{{ target.targetHost }}:{{ target.targetPort }}</ui-table-cell>
|
||||
<ui-table-cell>
|
||||
<ui-badge [variant]="getStatusVariant(target.status)">{{ target.status }}</ui-badge>
|
||||
</ui-table-cell>
|
||||
</ui-table-row>
|
||||
}
|
||||
</ui-table-body>
|
||||
</ui-table>
|
||||
}
|
||||
</ui-card-content>
|
||||
</ui-card>
|
||||
}
|
||||
</div>
|
||||
} @else if (stats()) {
|
||||
<!-- Stats Grid -->
|
||||
<div class="grid gap-4 md:grid-cols-4">
|
||||
<ui-card>
|
||||
<ui-card-header class="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||
<ui-card-title class="text-sm font-medium">Proxy Status</ui-card-title>
|
||||
<svg class="h-4 w-4 text-muted-foreground" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M13 10V3L4 14h7v7l9-11h-7z" />
|
||||
</svg>
|
||||
</ui-card-header>
|
||||
<ui-card-content>
|
||||
<ui-badge [variant]="stats()!.proxy.running ? 'success' : 'secondary'">
|
||||
{{ stats()!.proxy.running ? 'Running' : 'Stopped' }}
|
||||
</ui-badge>
|
||||
</ui-card-content>
|
||||
</ui-card>
|
||||
|
||||
<ui-card>
|
||||
<ui-card-header class="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||
<ui-card-title class="text-sm font-medium">Routes</ui-card-title>
|
||||
<svg class="h-4 w-4 text-muted-foreground" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M9 20l-5.447-2.724A1 1 0 013 16.382V5.618a1 1 0 011.447-.894L9 7m0 13l6-3m-6 3V7m6 10l4.553 2.276A1 1 0 0021 18.382V7.618a1 1 0 00-.553-.894L15 4m0 13V4m0 0L9 7" />
|
||||
</svg>
|
||||
</ui-card-header>
|
||||
<ui-card-content>
|
||||
<div class="text-2xl font-bold">{{ stats()!.proxy.routes }}</div>
|
||||
</ui-card-content>
|
||||
</ui-card>
|
||||
|
||||
<ui-card>
|
||||
<ui-card-header class="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||
<ui-card-title class="text-sm font-medium">Certificates</ui-card-title>
|
||||
<svg class="h-4 w-4 text-muted-foreground" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z" />
|
||||
</svg>
|
||||
</ui-card-header>
|
||||
<ui-card-content>
|
||||
<div class="text-2xl font-bold">{{ stats()!.proxy.certificates }}</div>
|
||||
</ui-card-content>
|
||||
</ui-card>
|
||||
|
||||
<ui-card>
|
||||
<ui-card-header class="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||
<ui-card-title class="text-sm font-medium">Targets</ui-card-title>
|
||||
<svg class="h-4 w-4 text-muted-foreground" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M5 12h14M5 12a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v4a2 2 0 01-2 2M5 12a2 2 0 00-2 2v4a2 2 0 002 2h14a2 2 0 002-2v-4a2 2 0 00-2-2m-2-4h.01M17 16h.01" />
|
||||
</svg>
|
||||
</ui-card-header>
|
||||
<ui-card-content>
|
||||
<div class="text-2xl font-bold">{{ targets().length }}</div>
|
||||
</ui-card-content>
|
||||
</ui-card>
|
||||
</div>
|
||||
}
|
||||
|
||||
<!-- Traffic Targets Table -->
|
||||
<ui-card>
|
||||
<ui-card-header class="flex flex-col space-y-1.5">
|
||||
<ui-card-title>Traffic Targets</ui-card-title>
|
||||
<ui-card-description>Services, registry, and platform services with their routing info. Click to filter logs.</ui-card-description>
|
||||
</ui-card-header>
|
||||
<ui-card-content>
|
||||
@if (targets().length === 0 && !loading()) {
|
||||
<p class="text-muted-foreground text-center py-8">No traffic targets configured</p>
|
||||
} @else {
|
||||
<ui-table>
|
||||
<ui-table-header>
|
||||
<ui-table-row>
|
||||
<ui-table-head>Type</ui-table-head>
|
||||
<ui-table-head>Name</ui-table-head>
|
||||
<ui-table-head>Domain</ui-table-head>
|
||||
<ui-table-head>Target</ui-table-head>
|
||||
<ui-table-head>Status</ui-table-head>
|
||||
</ui-table-row>
|
||||
</ui-table-header>
|
||||
<ui-table-body>
|
||||
@for (target of targets(); track target.name) {
|
||||
<ui-table-row [class]="'cursor-pointer ' + (activeFilter() === target.domain ? 'bg-muted' : '')" (click)="onTargetClick(target)">
|
||||
<ui-table-cell>
|
||||
<ui-badge [variant]="getTypeVariant(target.type)">{{ target.type }}</ui-badge>
|
||||
</ui-table-cell>
|
||||
<ui-table-cell class="font-medium">{{ target.name }}</ui-table-cell>
|
||||
<ui-table-cell>
|
||||
@if (target.domain) {
|
||||
<span class="font-mono text-sm">{{ target.domain }}</span>
|
||||
} @else {
|
||||
<span class="text-muted-foreground">-</span>
|
||||
}
|
||||
</ui-table-cell>
|
||||
<ui-table-cell class="font-mono text-sm">{{ target.targetHost }}:{{ target.targetPort }}</ui-table-cell>
|
||||
<ui-table-cell>
|
||||
<ui-badge [variant]="getStatusVariant(target.status)">{{ target.status }}</ui-badge>
|
||||
</ui-table-cell>
|
||||
</ui-table-row>
|
||||
}
|
||||
</ui-table-body>
|
||||
</ui-table>
|
||||
}
|
||||
</ui-card-content>
|
||||
</ui-card>
|
||||
|
||||
<!-- Access Logs -->
|
||||
<ui-card>
|
||||
<ui-card-header class="flex flex-row items-center justify-between">
|
||||
<div class="flex flex-col space-y-1.5">
|
||||
<ui-card-title>Access Logs</ui-card-title>
|
||||
<ui-card-description>
|
||||
@if (networkLogStream.isStreaming()) {
|
||||
<span class="flex items-center gap-2">
|
||||
<span class="relative flex h-2 w-2">
|
||||
<span class="animate-ping absolute inline-flex h-full w-full rounded-full bg-green-400 opacity-75"></span>
|
||||
<span class="relative inline-flex rounded-full h-2 w-2 bg-green-500"></span>
|
||||
</span>
|
||||
Live streaming
|
||||
@if (activeFilter()) {
|
||||
<span class="text-muted-foreground">- filtered by {{ activeFilter() }}</span>
|
||||
}
|
||||
</span>
|
||||
} @else {
|
||||
Real-time Caddy access logs
|
||||
}
|
||||
</ui-card-description>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
@if (activeFilter()) {
|
||||
<button uiButton variant="ghost" size="sm" (click)="clearFilter()">
|
||||
<svg class="h-4 w-4 mr-1" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
Clear Filter
|
||||
</button>
|
||||
}
|
||||
@if (networkLogStream.isStreaming()) {
|
||||
<button uiButton variant="outline" size="sm" (click)="stopLogStream()">
|
||||
<svg class="h-4 w-4 mr-1" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M9 10a1 1 0 011-1h4a1 1 0 011 1v4a1 1 0 01-1 1h-4a1 1 0 01-1-1v-4z" />
|
||||
</svg>
|
||||
Stop
|
||||
</button>
|
||||
} @else {
|
||||
<button uiButton variant="outline" size="sm" (click)="startLogStream()">
|
||||
<svg class="h-4 w-4 mr-1" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M14.752 11.168l-3.197-2.132A1 1 0 0010 9.87v4.263a1 1 0 001.555.832l3.197-2.132a1 1 0 000-1.664z" />
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
Stream
|
||||
</button>
|
||||
}
|
||||
<button uiButton variant="ghost" size="sm" (click)="clearLogs()" title="Clear logs">
|
||||
<svg class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</ui-card-header>
|
||||
<ui-card-content>
|
||||
<div
|
||||
#logContainer
|
||||
class="bg-zinc-950 text-zinc-100 rounded-md p-4 h-96 overflow-auto font-mono text-xs"
|
||||
>
|
||||
@if (networkLogStream.state().error) {
|
||||
<p class="text-red-400">Error: {{ networkLogStream.state().error }}</p>
|
||||
} @else if (networkLogStream.logs().length > 0) {
|
||||
@for (log of networkLogStream.logs(); track $index) {
|
||||
<div class="whitespace-pre hover:bg-zinc-800/50 py-0.5" [class]="getLogClass(log.status)">
|
||||
{{ formatLog(log) }}
|
||||
<!-- Access Logs -->
|
||||
<ui-card>
|
||||
<ui-card-header class="flex flex-row items-center justify-between">
|
||||
<div class="flex flex-col space-y-1.5">
|
||||
<ui-card-title>Access Logs</ui-card-title>
|
||||
<ui-card-description>
|
||||
@if (networkLogStream.isStreaming()) {
|
||||
<span class="flex items-center gap-2">
|
||||
<span class="relative flex h-2 w-2">
|
||||
<span class="animate-ping absolute inline-flex h-full w-full rounded-full bg-green-400 opacity-75"></span>
|
||||
<span class="relative inline-flex rounded-full h-2 w-2 bg-green-500"></span>
|
||||
</span>
|
||||
Live streaming
|
||||
@if (activeFilter()) {
|
||||
<span class="text-muted-foreground">- filtered by {{ activeFilter() }}</span>
|
||||
}
|
||||
</span>
|
||||
} @else {
|
||||
Real-time Caddy access logs
|
||||
}
|
||||
</ui-card-description>
|
||||
</div>
|
||||
}
|
||||
} @else if (networkLogStream.isStreaming()) {
|
||||
<p class="text-zinc-500">Waiting for access logs...</p>
|
||||
} @else {
|
||||
<p class="text-zinc-500">Click "Stream" to start live access log streaming</p>
|
||||
}
|
||||
<div class="flex items-center gap-2">
|
||||
@if (activeFilter()) {
|
||||
<button uiButton variant="ghost" size="sm" (click)="clearFilter()">
|
||||
<svg class="h-4 w-4 mr-1" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
Clear Filter
|
||||
</button>
|
||||
}
|
||||
@if (networkLogStream.isStreaming()) {
|
||||
<button uiButton variant="outline" size="sm" (click)="stopLogStream()">
|
||||
<svg class="h-4 w-4 mr-1" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M9 10a1 1 0 011-1h4a1 1 0 011 1v4a1 1 0 01-1 1h-4a1 1 0 01-1-1v-4z" />
|
||||
</svg>
|
||||
Stop
|
||||
</button>
|
||||
} @else {
|
||||
<button uiButton variant="outline" size="sm" (click)="startLogStream()">
|
||||
<svg class="h-4 w-4 mr-1" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M14.752 11.168l-3.197-2.132A1 1 0 0010 9.87v4.263a1 1 0 001.555.832l3.197-2.132a1 1 0 000-1.664z" />
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
Stream
|
||||
</button>
|
||||
}
|
||||
<button uiButton variant="ghost" size="sm" (click)="clearLogs()" title="Clear logs">
|
||||
<svg class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</ui-card-header>
|
||||
<ui-card-content>
|
||||
<div
|
||||
#logContainer
|
||||
class="bg-zinc-950 text-zinc-100 rounded-md p-4 h-96 overflow-auto font-mono text-xs"
|
||||
>
|
||||
@if (networkLogStream.state().error) {
|
||||
<p class="text-red-400">Error: {{ networkLogStream.state().error }}</p>
|
||||
} @else if (networkLogStream.logs().length > 0) {
|
||||
@for (log of networkLogStream.logs(); track $index) {
|
||||
<div class="whitespace-pre hover:bg-zinc-800/50 py-0.5" [class]="getLogClass(log.status)">
|
||||
{{ formatLog(log) }}
|
||||
</div>
|
||||
}
|
||||
} @else if (networkLogStream.isStreaming()) {
|
||||
<p class="text-zinc-500">Waiting for access logs...</p>
|
||||
} @else {
|
||||
<p class="text-zinc-500">Click "Stream" to start live access log streaming</p>
|
||||
}
|
||||
</div>
|
||||
</ui-card-content>
|
||||
</ui-card>
|
||||
</div>
|
||||
</ui-card-content>
|
||||
</ui-card>
|
||||
}
|
||||
@case ('dns') {
|
||||
<div class="space-y-6">
|
||||
<app-dns-content />
|
||||
</div>
|
||||
}
|
||||
@case ('domains') {
|
||||
<div class="space-y-6">
|
||||
<app-domains-content />
|
||||
</div>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
`,
|
||||
})
|
||||
@@ -261,6 +298,10 @@ export class NetworkComponent implements OnInit, OnDestroy {
|
||||
|
||||
@ViewChild('logContainer') logContainer!: ElementRef<HTMLDivElement>;
|
||||
|
||||
// Tab state
|
||||
activeTab = signal<TNetworkTab>('proxy');
|
||||
|
||||
// Proxy tab data
|
||||
targets = signal<INetworkTarget[]>([]);
|
||||
stats = signal<INetworkStats | null>(null);
|
||||
loading = signal(false);
|
||||
@@ -287,6 +328,10 @@ export class NetworkComponent implements OnInit, OnDestroy {
|
||||
this.networkLogStream.disconnect();
|
||||
}
|
||||
|
||||
setTab(tab: TNetworkTab): void {
|
||||
this.activeTab.set(tab);
|
||||
}
|
||||
|
||||
async loadData(): Promise<void> {
|
||||
this.loading.set(true);
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user