()
| 62 | // secret are app-managed on desktop and exposed read-only. Errors surface via |
| 63 | // toast — never swallowed. |
| 64 | export function useNetworkConfig() { |
| 65 | const api = useControlApi() |
| 66 | const { hasFeature } = useControlInfo() |
| 67 | const { t } = useI18n() |
| 68 | |
| 69 | const available = computed(() => hasFeature('config-sections')) |
| 70 | |
| 71 | const loading = ref(false) |
| 72 | // Per-section busy key so saving one section no longer spins all three |
| 73 | // buttons. `saving` is kept as a derived aggregate for backward compatibility |
| 74 | // (existing callers/tests read it). |
| 75 | const savingKey = ref<'tunnels' | 'sniffer' | 'interface-name' | null>(null) |
| 76 | const saving = computed(() => savingKey.value !== null) |
| 77 | |
| 78 | // tunnels (array editor) |
| 79 | const tunnels = ref<TunnelEntry[]>([]) |
| 80 | |
| 81 | // sniffer (object editor) — common toggles split out; rest preserved. |
| 82 | const sniffer = reactive<SnifferState>({ |
| 83 | enable: false, |
| 84 | overrideDestination: false, |
| 85 | }) |
| 86 | // Everything in the sniffer object EXCEPT the common toggles, preserved so a |
| 87 | // save round-trips losslessly. |
| 88 | let snifferRest: Record<string, unknown> = {} |
| 89 | |
| 90 | // interface-name (string editor — may also hot-apply via PATCH /configs) |
| 91 | const interfaceName = ref('') |
| 92 | |
| 93 | // external-controller / secret (read-only on desktop — app-managed). |
| 94 | const externalController = ref('') |
| 95 | const secret = ref('') |
| 96 | |
| 97 | const load = async () => { |
| 98 | loading.value = true |
| 99 | try { |
| 100 | const [ |
| 101 | tunnelsSection, |
| 102 | snifferSection, |
| 103 | interfaceSection, |
| 104 | controllerSection, |
| 105 | secretSection, |
| 106 | ] = await Promise.all([ |
| 107 | api.getConfigSection<unknown>('tunnels'), |
| 108 | api.getConfigSection<unknown>('sniffer'), |
| 109 | api.getConfigSection<unknown>('interface-name'), |
| 110 | api.getConfigSection<unknown>('external-controller'), |
| 111 | api.getConfigSection<unknown>('secret'), |
| 112 | ]) |
| 113 | |
| 114 | tunnels.value = Array.isArray(tunnelsSection) |
| 115 | ? tunnelsSection.map(parseTunnel) |
| 116 | : [] |
| 117 | |
| 118 | if (isPlainObject(snifferSection)) { |
| 119 | const { |
| 120 | enable, |
| 121 | 'override-destination': overrideDestination, |
no test coverage detected