All checks were successful
Queue Release Build / prepare (push) Successful in 15s
Deploy Web Apps / deploy (push) Successful in 5m35s
Queue Release Build / build-linux (push) Successful in 24m45s
Queue Release Build / build-windows (push) Successful in 13m52s
Queue Release Build / finalize (push) Successful in 23s
162 lines
6.1 KiB
HTML
162 lines
6.1 KiB
HTML
<section data-testid="ice-server-settings">
|
|
<div class="flex items-center justify-between mb-3">
|
|
<div class="flex items-center gap-2">
|
|
<ng-icon
|
|
name="lucideShield"
|
|
class="w-5 h-5 text-muted-foreground"
|
|
/>
|
|
<h4 class="text-sm font-semibold text-foreground">ICE Servers (STUN / TURN)</h4>
|
|
</div>
|
|
<button
|
|
type="button"
|
|
data-testid="ice-restore-defaults"
|
|
(click)="restoreDefaults()"
|
|
class="flex items-center gap-1.5 px-2.5 py-1 text-xs bg-secondary text-foreground rounded-lg hover:bg-secondary/80 transition-colors"
|
|
>
|
|
<ng-icon
|
|
name="lucideRotateCcw"
|
|
class="w-3.5 h-3.5"
|
|
/>
|
|
Restore Defaults
|
|
</button>
|
|
</div>
|
|
|
|
<p class="text-xs text-muted-foreground mb-3">
|
|
ICE servers are used for NAT traversal. STUN discovers your public address; TURN relays traffic when direct connections fail. Higher entries have
|
|
priority.
|
|
</p>
|
|
|
|
<!-- ICE Server List -->
|
|
<div
|
|
class="space-y-2 mb-3"
|
|
data-testid="ice-server-list"
|
|
>
|
|
@for (entry of entries(); track trackEntry($index, entry); let i = $index) {
|
|
<div
|
|
class="flex items-center gap-3 p-2.5 rounded-lg border transition-colors"
|
|
[class.border-blue-500/40]="entry.type === 'turn'"
|
|
[class.bg-blue-500/5]="entry.type === 'turn'"
|
|
[class.border-border]="entry.type === 'stun'"
|
|
[class.bg-secondary/30]="entry.type === 'stun'"
|
|
[attr.data-testid]="'ice-entry-' + i"
|
|
>
|
|
<span
|
|
class="text-[10px] font-bold uppercase tracking-wider px-1.5 py-0.5 rounded"
|
|
[class.bg-muted]="entry.type === 'stun'"
|
|
[class.text-muted-foreground]="entry.type === 'stun'"
|
|
[class.bg-blue-500/15]="entry.type === 'turn'"
|
|
[class.text-blue-400]="entry.type === 'turn'"
|
|
>
|
|
{{ entry.type }}
|
|
</span>
|
|
<div class="flex-1 min-w-0">
|
|
<p class="text-sm text-foreground truncate">{{ entry.urls }}</p>
|
|
@if (entry.type === 'turn' && entry.username) {
|
|
<p class="text-[10px] text-muted-foreground truncate">User: {{ entry.username }}</p>
|
|
}
|
|
</div>
|
|
<div class="flex items-center gap-0.5 flex-shrink-0">
|
|
<button
|
|
type="button"
|
|
(click)="moveUp(i)"
|
|
[disabled]="i === 0"
|
|
class="grid h-7 w-7 place-items-center rounded-lg transition-colors hover:bg-secondary disabled:opacity-30"
|
|
title="Move up (higher priority)"
|
|
>
|
|
<ng-icon
|
|
name="lucideArrowUp"
|
|
class="w-3.5 h-3.5 text-muted-foreground"
|
|
/>
|
|
</button>
|
|
<button
|
|
type="button"
|
|
(click)="moveDown(i)"
|
|
[disabled]="i === entries().length - 1"
|
|
class="grid h-7 w-7 place-items-center rounded-lg transition-colors hover:bg-secondary disabled:opacity-30"
|
|
title="Move down (lower priority)"
|
|
>
|
|
<ng-icon
|
|
name="lucideArrowDown"
|
|
class="w-3.5 h-3.5 text-muted-foreground"
|
|
/>
|
|
</button>
|
|
<button
|
|
type="button"
|
|
(click)="removeEntry(entry.id)"
|
|
class="grid h-7 w-7 place-items-center rounded-lg transition-colors hover:bg-destructive/10"
|
|
title="Remove"
|
|
>
|
|
<ng-icon
|
|
name="lucideTrash2"
|
|
class="w-3.5 h-3.5 text-muted-foreground hover:text-destructive"
|
|
/>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
}
|
|
@if (entries().length === 0) {
|
|
<p class="text-xs text-muted-foreground italic py-2">No ICE servers configured. P2P connections may fail across networks.</p>
|
|
}
|
|
</div>
|
|
|
|
<!-- Add New ICE Server -->
|
|
<div class="border-t border-border pt-3">
|
|
<h4 class="text-xs font-medium text-foreground mb-2">Add ICE Server</h4>
|
|
<div class="space-y-1.5">
|
|
<div class="flex gap-2">
|
|
<select
|
|
[(ngModel)]="newType"
|
|
data-testid="ice-type-select"
|
|
class="px-3 py-1.5 bg-secondary rounded-lg border border-border text-foreground text-sm focus:outline-none focus:ring-2 focus:ring-primary"
|
|
>
|
|
<option value="stun">STUN</option>
|
|
<option value="turn">TURN</option>
|
|
</select>
|
|
<input
|
|
type="text"
|
|
[(ngModel)]="newUrl"
|
|
data-testid="ice-url-input"
|
|
[placeholder]="newType === 'stun' ? 'stun:stun.example.com:19302' : 'turn:turn.example.com:3478'"
|
|
class="flex-1 px-3 py-1.5 bg-secondary rounded-lg border border-border text-foreground text-sm placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-primary"
|
|
/>
|
|
</div>
|
|
@if (newType === 'turn') {
|
|
<div class="flex gap-2">
|
|
<input
|
|
type="text"
|
|
[(ngModel)]="newUsername"
|
|
data-testid="ice-username-input"
|
|
placeholder="Username"
|
|
class="flex-1 px-3 py-1.5 bg-secondary rounded-lg border border-border text-foreground text-sm placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-primary"
|
|
/>
|
|
<input
|
|
type="password"
|
|
[(ngModel)]="newCredential"
|
|
data-testid="ice-credential-input"
|
|
placeholder="Credential"
|
|
class="flex-1 px-3 py-1.5 bg-secondary rounded-lg border border-border text-foreground text-sm placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-primary"
|
|
/>
|
|
</div>
|
|
}
|
|
<div class="flex justify-end">
|
|
<button
|
|
type="button"
|
|
data-testid="ice-add-button"
|
|
(click)="addEntry()"
|
|
[disabled]="!newUrl"
|
|
class="flex items-center gap-1.5 px-3 py-1.5 text-xs bg-primary text-primary-foreground rounded-lg hover:bg-primary/90 transition-colors disabled:cursor-not-allowed disabled:opacity-50"
|
|
>
|
|
<ng-icon
|
|
name="lucidePlus"
|
|
class="w-3.5 h-3.5"
|
|
/>
|
|
Add Server
|
|
</button>
|
|
</div>
|
|
</div>
|
|
@if (addError()) {
|
|
<p class="text-xs text-destructive mt-1.5">{{ addError() }}</p>
|
|
}
|
|
</div>
|
|
</section>
|