File
Blob: ui/operator/ClientTunnels.svelte
| 1 | <script lang="ts"> |
| 2 | import Table from './Table.svelte'; |
| 3 | import SnapshotTime from './SnapshotTime.svelte'; |
| 4 | import type { TunnelDetail } from './types'; |
| 5 | |
| 6 | let { data }: { data: TunnelDetail } = $props(); |
| 7 | </script> |
| 8 | |
| 9 | <p class="text-muted wrap-anywhere">Client <span class="ml-1 font-mono text-[0.92em] text-foreground">{data.identity}</span></p> |
| 10 | {#if data.configurationError} |
| 11 | <section class="mt-5 rounded-md border border-line bg-surface px-4 py-3" aria-labelledby="configuration-error"> |
| 12 | <h2 id="configuration-error" class="text-[15px] font-semibold text-warning">Configuration unavailable</h2> |
| 13 | <details class="mt-1"> |
| 14 | <summary class="min-h-8 cursor-pointer py-1 text-muted">Lookup error</summary> |
| 15 | <p class="mt-1 max-w-[72ch] font-mono text-xs wrap-anywhere">{data.configurationError}</p> |
| 16 | </details> |
| 17 | </section> |
| 18 | {/if} |
| 19 | {#if data.registrationError} |
| 20 | <section class="mt-5 rounded-md border border-line bg-surface px-4 py-3" aria-labelledby="registration-error"> |
| 21 | <h2 id="registration-error" class="text-[15px] font-semibold text-warning">Registration unavailable</h2> |
| 22 | <details class="mt-1"> |
| 23 | <summary class="min-h-8 cursor-pointer py-1 text-muted">Lookup error</summary> |
| 24 | <p class="mt-1 max-w-[72ch] font-mono text-xs wrap-anywhere">{data.registrationError}</p> |
| 25 | </details> |
| 26 | </section> |
| 27 | {/if} |
| 28 | |
| 29 | <div class="mt-6 mb-2.5 flex flex-wrap items-baseline justify-between gap-x-6 gap-y-1"> |
| 30 | <h2 class="text-[15px] font-semibold">Hostnames</h2> |
| 31 | <p class="text-[13px] text-muted">Observed <SnapshotTime value={data.observedAt} /></p> |
| 32 | </div> |
| 33 | {#if data.tunnels.length} |
| 34 | <Table label="Client tunnel configuration and registration"> |
| 35 | <thead><tr><th scope="col">Hostname</th><th scope="col">Configured target</th><th scope="col">Configured</th><th scope="col">Registered</th></tr></thead> |
| 36 | <tbody> |
| 37 | {#each data.tunnels as tunnel (tunnel.hostname)} |
| 38 | <tr> |
| 39 | <th scope="row" class="font-mono text-[0.92em]">{tunnel.hostname}</th> |
| 40 | <td> |
| 41 | {#if tunnel.target} |
| 42 | <span class="font-mono text-[0.92em]">{tunnel.target}</span> |
| 43 | {:else} |
| 44 | <span class="text-muted">{tunnel.configured === 'No' ? 'Not configured' : tunnel.configured === 'Unknown' ? 'Unknown' : 'No target reported'}</span> |
| 45 | {/if} |
| 46 | </td> |
| 47 | <td><span class="text-[13px] whitespace-nowrap" class:text-warning={tunnel.configured === 'Unknown'}>{tunnel.configured}</span></td> |
| 48 | <td><span class="text-[13px] whitespace-nowrap" class:text-warning={tunnel.registered === 'Unknown'}>{tunnel.registered}</span></td> |
| 49 | </tr> |
| 50 | {/each} |
| 51 | </tbody> |
| 52 | </Table> |
| 53 | {:else} |
| 54 | <p class="border-y border-line py-6 text-muted">{data.configurationError || data.registrationError ? 'No tunnel details available.' : 'No configured tunnels or registered hostnames.'}</p> |
| 55 | {/if} |
| 56 | <details class="mt-2.5 text-[13px]"> |
| 57 | <summary class="min-h-10 cursor-pointer py-2 text-muted">State key</summary> |
| 58 | <dl class="mt-1 grid grid-cols-[max-content_minmax(0,1fr)] gap-x-4 gap-y-1"> |
| 59 | <dt>Configured</dt><dd class="text-muted">Current client configuration</dd> |
| 60 | <dt>Registered</dt><dd class="text-muted">Hostname ownership record</dd> |
| 61 | <dt>Unknown</dt><dd class="text-muted">Lookup unavailable</dd> |
| 62 | </dl> |
| 63 | </details> |