import { Switch } from "@kobalte/core/switch" import { For, Show, createMemo, createSignal, type Component, onMount } from "solid-js" import { toDataURL } from "qrcode" import { ChevronDown, ExternalLink, Link2, Loader2, RefreshCw, Shield, Wifi } from "lucide-solid" import type { NetworkAddress, ServerMeta } from "../../../../server/src/api-types" import { serverApi } from "../../lib/api-client" import { restartCli } from "../../lib/native/cli" import { serverSettings, setListeningMode } from "../../stores/preferences" import { showConfirmDialog } from "../../stores/alerts" import { getLogger } from "../../lib/logger" import { useI18n } from "../../lib/i18n" import { splitRemoteAddresses, type RemoteAddressGroups } from "../../lib/remote-access-addresses" const log = getLogger("actions") export const RemoteAccessSettingsSection: Component = () => { const { t } = useI18n() const [meta, setMeta] = createSignal(null) const [authStatus, setAuthStatus] = createSignal<{ authenticated: boolean username?: string passwordUserProvided?: boolean } | null>(null) const [loading, setLoading] = createSignal(false) const [applyingListeningMode, setApplyingListeningMode] = createSignal(false) const [qrCodes, setQrCodes] = createSignal>({}) const [expandedUrl, setExpandedUrl] = createSignal(null) const [error, setError] = createSignal(null) const [passwordFormOpen, setPasswordFormOpen] = createSignal(false) const [passwordValue, setPasswordValue] = createSignal("") const [passwordConfirm, setPasswordConfirm] = createSignal("") const [passwordError, setPasswordError] = createSignal(null) const [savingPassword, setSavingPassword] = createSignal(false) const [showAllAddresses, setShowAllAddresses] = createSignal(false) const addresses = createMemo(() => meta()?.addresses ?? []) const currentMode = createMemo(() => meta()?.listeningMode ?? serverSettings().listeningMode) const allowExternalConnections = createMemo(() => currentMode() === "all") const displayAddresses = createMemo(() => { const list = addresses() if (!allowExternalConnections()) return { recommended: null, hidden: [] } return splitRemoteAddresses(list) }) const refreshMeta = async () => { setLoading(true) setError(null) setPasswordError(null) try { const [metaResult, authResult] = await Promise.all([serverApi.fetchServerMeta(), serverApi.fetchAuthStatus()]) setMeta(metaResult) setAuthStatus(authResult) setShowAllAddresses(false) } catch (err) { setError(err instanceof Error ? err.message : String(err)) } finally { setLoading(false) } } onMount(() => { void refreshMeta() }) const toggleExpanded = async (url: string) => { if (expandedUrl() === url) { setExpandedUrl(null) return } setExpandedUrl(url) if (!qrCodes()[url]) { try { const dataUrl = await toDataURL(url, { margin: 1, scale: 4 }) setQrCodes((prev) => ({ ...prev, [url]: dataUrl })) } catch (err) { log.error("Failed to generate QR code", err) } } } const handleAllowConnectionsChange = async (checked: boolean) => { const targetMode: "local" | "all" = checked ? "all" : "local" if (targetMode === currentMode() || applyingListeningMode()) return const confirmed = await showConfirmDialog(t("remoteAccess.listeningMode.restartConfirm.message"), { title: checked ? t("remoteAccess.listeningMode.restartConfirm.title.all") : t("remoteAccess.listeningMode.restartConfirm.title.local"), variant: "warning", confirmLabel: t("remoteAccess.listeningMode.restartConfirm.confirmLabel"), cancelLabel: t("remoteAccess.listeningMode.restartConfirm.cancelLabel"), dismissible: false, }) if (!confirmed) return setApplyingListeningMode(true) setError(null) try { await setListeningMode(targetMode) const restarted = await restartCli() if (!restarted) { setError(t("remoteAccess.restart.errorManual")) } else { setMeta((prev) => (prev ? { ...prev, listeningMode: targetMode } : prev)) } } catch (err) { setError(err instanceof Error ? err.message : String(err)) } finally { setApplyingListeningMode(false) } void refreshMeta() } const handleOpenUrl = (url: string) => { try { window.open(url, "_blank", "noopener,noreferrer") } catch (err) { log.error("Failed to open URL", err) } } const handleSubmitPassword = async () => { setPasswordError(null) const next = passwordValue() const confirm = passwordConfirm() if (next.trim().length < 8) { setPasswordError(t("remoteAccess.password.error.tooShort")) return } if (next !== confirm) { setPasswordError(t("remoteAccess.password.error.mismatch")) return } setSavingPassword(true) try { const result = await serverApi.setServerPassword(next) setAuthStatus({ authenticated: true, username: result.username, passwordUserProvided: result.passwordUserProvided, }) setPasswordValue("") setPasswordConfirm("") setPasswordFormOpen(false) } catch (err) { setPasswordError(err instanceof Error ? err.message : String(err)) } finally { setSavingPassword(false) } } return (

{t("remoteAccess.sections.listeningMode.label")}

{t("remoteAccess.sections.listeningMode.help")}

{t("settings.scope.server")}
void handleAllowConnectionsChange(nextChecked)} disabled={loading() || applyingListeningMode()} > {allowExternalConnections() ? t("remoteAccess.toggle.on") : t("remoteAccess.toggle.off")}
{t("remoteAccess.toggle.title")} {allowExternalConnections() ? t("remoteAccess.toggle.caption.all") : t("remoteAccess.toggle.caption.local")}

{t("remoteAccess.toggle.note")}

{t("remoteAccess.sections.serverPassword.label")}

{t("remoteAccess.sections.serverPassword.help")}

{t("settings.scope.server")}
{t("remoteAccess.authStatus.unavailable")}
} >

{t("remoteAccess.username", { username: authStatus()!.username ?? "codenomad" })}

{authStatus()!.passwordUserProvided ? t("remoteAccess.password.status.set") : t("remoteAccess.password.status.unset")}

setPasswordValue(event.currentTarget.value)} placeholder={t("remoteAccess.password.form.placeholder")} />
setPasswordConfirm(event.currentTarget.value)} />
{(message) =>
{message()}
}

{t("remoteAccess.sections.addresses.label")}

{t("remoteAccess.sections.addresses.help")}

{t("settings.scope.server")}
{t("remoteAccess.addresses.loading")}
}> {error()}}> {t("remoteAccess.addresses.none")}} >
{(url) => { const value = () => url() const expandedState = () => expandedUrl() === value() const qr = () => qrCodes()[value()] return (

{value()}

{t("remoteAccess.address.scope.loopback")}

) }}
{(addressAccessor) => { const address = addressAccessor() const url = address.remoteUrl const expandedState = () => expandedUrl() === url const qr = () => qrCodes()[url] const scopeLabel = () => address.scope === "external" ? t("remoteAccess.address.scope.network") : address.scope === "loopback" ? t("remoteAccess.address.scope.loopback") : t("remoteAccess.address.scope.internal") return (

{url}

{address.family.toUpperCase()} - {scopeLabel()} - {address.ip}

) }}
0}>
{(address) => { const url = address.remoteUrl const expandedState = () => expandedUrl() === url const qr = () => qrCodes()[url] const scopeLabel = () => address.scope === "external" ? t("remoteAccess.address.scope.network") : address.scope === "loopback" ? t("remoteAccess.address.scope.loopback") : t("remoteAccess.address.scope.internal") return (

{url}

{address.family.toUpperCase()} - {scopeLabel()} - {address.ip}

) }}
) }