Files
Toju/toju-app/src/app/features/settings/settings-modal/ice-server-settings/ice-server-settings.component.html
Myx 44588e8789
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
feat: Add TURN server support
2026-04-18 21:27:04 +02:00

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>