diff --git a/apps/mobile/src/features/connection/ConnectionDiagnosisDetails.tsx b/apps/mobile/src/features/connection/ConnectionDiagnosisDetails.tsx index b3922c4a95eda..e4862b2f367d3 100644 --- a/apps/mobile/src/features/connection/ConnectionDiagnosisDetails.tsx +++ b/apps/mobile/src/features/connection/ConnectionDiagnosisDetails.tsx @@ -4,6 +4,7 @@ import { type ConnectionDiagnosis, connectionDiagnosisText, connectionRoutes, + environmentDisplayLabel, hasRelayRoute, presentConnectionDiagnosis, type EnvironmentPresentation, @@ -37,7 +38,7 @@ export function useConnectionDiagnosis( const relayOnly = connectionRoutes(presentation.entry).length === 1 && hasRelayRoute(presentation.entry); const diagnosis = presentConnectionDiagnosis({ - hostLabel: presentation.entry.target.label, + hostLabel: environmentDisplayLabel(presentation.entry, presentation.serverConfig), state, via: relayOnly ? "relay" : "direct", clientVersion: Constants.expoConfig?.version, diff --git a/apps/mobile/src/features/connection/ConnectionEnvironmentRow.tsx b/apps/mobile/src/features/connection/ConnectionEnvironmentRow.tsx index c7866cea6ce06..fbcb82f7cd8ea 100644 --- a/apps/mobile/src/features/connection/ConnectionEnvironmentRow.tsx +++ b/apps/mobile/src/features/connection/ConnectionEnvironmentRow.tsx @@ -3,7 +3,11 @@ import { ConnectionTraceId } from "./ConnectionTraceId"; import { SymbolView } from "../../components/ui/AppSymbol"; import { hasSavedBearerRoute, hostFreezeNotice } from "@t3tools/client-runtime/connection"; import type { AtomCommandResult } from "@t3tools/client-runtime/state/runtime"; -import { type EnvironmentId, resolveEnvironmentMachineKind } from "@t3tools/contracts"; +import { + type EnvironmentId, + type ServerConfig, + resolveEnvironmentMachineKind, +} from "@t3tools/contracts"; import { useAtomValue } from "@effect/atom-react"; import * as Cause from "effect/Cause"; import { AsyncResult } from "effect/reactivity"; @@ -24,6 +28,7 @@ import { ConnectionDiagnosisDetails, useConnectionDiagnosis } from "./Connection import { ConnectionFormField } from "./ConnectionFormField"; import { ConnectionStatusDot } from "./ConnectionStatusDot"; import { environmentStatusText } from "./environmentStatusText"; +import { environmentCatalog } from "../../connection/catalog"; function connectionStatusLabel(environment: ConnectedEnvironmentSummary): string | null { if (!environment.isEnabled && environment.connectionState !== "unsupported") { @@ -36,7 +41,7 @@ function connectionStatusLabel(environment: ConnectedEnvironmentSummary): string }); } -export function ConnectionEnvironmentRow(props: { +interface ConnectionEnvironmentRowProps { readonly environment: ConnectedEnvironmentSummary; readonly expanded: boolean; readonly opensDetails?: boolean; @@ -48,9 +53,12 @@ export function ConnectionEnvironmentRow(props: { environmentId: EnvironmentId, updates: { readonly label: string; readonly displayUrl: string }, ) => Promise>; -}) { - const [label, setLabel] = useState(props.environment.environmentLabel); - const [url, setUrl] = useState(props.environment.displayUrl); +} + +export function ConnectionEnvironmentRow(props: ConnectionEnvironmentRowProps) { + const catalog = useAtomValue(environmentCatalog.catalogValueAtom); + const isHostName = + catalog.entries.get(props.environment.environmentId)?.target._tag === "RelayConnectionTarget"; const serverConfig = useAtomValue( serverEnvironment.configValueAtom(props.environment.environmentId), ); @@ -74,21 +82,6 @@ export function ConnectionEnvironmentRow(props: { enabled && (props.environment.connectionState === "connecting" || props.environment.connectionState === "reconnecting"); - const handleSave = async () => { - const result = await props.onUpdate(props.environment.environmentId, { - label: label.trim(), - displayUrl: url.trim(), - }); - if (AsyncResult.isSuccess(result)) { - props.onToggle(); - return; - } - const error = Cause.squash(result.cause); - Dialog.alert( - "Could not update environment", - error instanceof Error ? error.message : "The environment could not be updated.", - ); - }; return ( @@ -121,7 +114,7 @@ export function ConnectionEnvironmentRow(props: { {props.environment.environmentLabel} - {!props.environment.isRelayManaged && props.environment.displayUrl.trim() ? ( + {!isHostName && props.environment.displayUrl.trim() ? ( {props.environment.displayUrl} @@ -197,124 +190,192 @@ export function ConnectionEnvironmentRow(props: { {props.expanded ? ( - - {diagnosis ? ( - - ) : null} - {props.environment.isRelayManaged ? ( - - Managed by T3 Connect. Tunnel details update automatically. - - ) : ( - <> - + + ) : null} + + ); +} - - - )} +/** Mounted only while expanded, so each opening starts from the latest saved values. */ +function ConnectionEnvironmentEditor( + props: ConnectionEnvironmentRowProps & { + readonly isHostName: boolean; + readonly enabled: boolean; + readonly serverConfig: ServerConfig | null; + readonly diagnosis: ReturnType["diagnosis"]; + }, +) { + const { isHostName, enabled, serverConfig, diagnosis } = props; + const [draft, setDraft] = useState<{ readonly label?: string; readonly url?: string }>({}); + const [saving, setSaving] = useState(false); + const label = + draft.label ?? + (isHostName + ? (serverConfig?.settings.environmentLabel ?? "") + : props.environment.environmentLabel); + const url = draft.url ?? props.environment.displayUrl; + const canUpdateSettings = useAtomValue( + serverEnvironment.updateSettings.permissionAtom(props.environment.environmentId, { + patch: { environmentLabel: label.trim() }, + }), + ); + const canEditHost = + props.environment.connectionState === "connected" && + serverConfig?.environment.capabilities.environmentLabel === true && + canUpdateSettings; + const canSave = !saving && (!isHostName || canEditHost); + async function handleSave() { + if (!canSave) return; + setSaving(true); + const result = await props + .onUpdate(props.environment.environmentId, { + label: label.trim(), + displayUrl: url.trim(), + }) + .finally(() => setSaving(false)); + if (AsyncResult.isSuccess(result)) { + props.onToggle(); + return; + } + const error = Cause.squash(result.cause); + Dialog.alert( + "Could not update environment", + error instanceof Error ? error.message : "The environment could not be updated.", + ); + } - {Platform.OS === "android" ? ( - - {props.environment.isRelayManaged ? null : ( - -