|
1 | 1 | <script lang="ts"> |
2 | 2 | import { onMount } from 'svelte'; |
3 | | - import { listDevices, deleteDevice } from '$lib/api'; |
| 3 | + import { listDevices, deleteDevice, getSettings } from '$lib/api'; |
4 | 4 | import { Button } from '$lib/components/ui/button/index.js'; |
5 | 5 | import { Badge } from '$lib/components/ui/badge/index.js'; |
6 | 6 | import { Skeleton } from '$lib/components/ui/skeleton/index.js'; |
|
12 | 12 | let devices = $state<Device[]>([]); |
13 | 13 | let loading = $state(true); |
14 | 14 | let error = $state(''); |
| 15 | + let passphraseEnabled = $state(false); |
15 | 16 |
|
16 | 17 | onMount(load); |
17 | 18 |
|
|
25 | 26 | } finally { |
26 | 27 | loading = false; |
27 | 28 | } |
| 29 | + // Account-wide passphrase encryption is not per-device; used to label |
| 30 | + // devices without an E2E key. Failure must not break the listing. |
| 31 | + try { |
| 32 | + const settings = await getSettings(); |
| 33 | + passphraseEnabled = Boolean(settings?.encryption?.passphrase); |
| 34 | + } catch { |
| 35 | + passphraseEnabled = false; |
| 36 | + } |
| 37 | + } |
| 38 | +
|
| 39 | + type EncryptionType = 'e2e' | 'passphrase' | 'none'; |
| 40 | +
|
| 41 | + function encryptionType(d: Device): EncryptionType { |
| 42 | + if (d.publicKey) return 'e2e'; |
| 43 | + if (passphraseEnabled) return 'passphrase'; |
| 44 | + return 'none'; |
28 | 45 | } |
29 | 46 |
|
30 | 47 | let deleting = $state<string | null>(null); |
|
87 | 104 | <Th>Name</Th> |
88 | 105 | <Th>ID</Th> |
89 | 106 | <Th>Status</Th> |
| 107 | + <Th>Encryption</Th> |
90 | 108 | <Th>Last Seen</Th> |
91 | 109 | <Th></Th> |
92 | 110 | </Tr> |
|
103 | 121 | <Badge variant="outline">Offline</Badge> |
104 | 122 | {/if} |
105 | 123 | </Td> |
106 | | - <Td class="whitespace-nowrap text-muted-foreground">{timeAgo(d.lastSeen)}</Td> |
| 124 | + <Td> |
| 125 | + {#if encryptionType(d) === 'e2e'} |
| 126 | + <span class="inline-flex items-center gap-1.5" title="End-to-end encryption enabled"> |
| 127 | + <span class="inline-block h-2 w-2 rounded-full bg-emerald-500" aria-hidden="true"></span> |
| 128 | + <Badge variant="secondary">E2E</Badge> |
| 129 | + {#if d.keyVersion != null} |
| 130 | + <span class="font-mono text-xs text-muted-foreground">v{d.keyVersion}</span> |
| 131 | + {/if} |
| 132 | + </span> |
| 133 | + {:else if encryptionType(d) === 'passphrase'} |
| 134 | + <Badge variant="secondary">Passphrase</Badge> |
| 135 | + {:else} |
| 136 | + <Badge variant="outline">None</Badge> |
| 137 | + {/if} |
| 138 | + </Td> |
| 139 | + <Td class="whitespace-nowrap text-muted-foreground">{timeAgo(d.lastSeen)}</Td> |
107 | 140 | <Td> |
108 | 141 | <Button |
109 | 142 | variant="destructive" |
|
0 commit comments