Use advertised hostnames during pairing
This commit is contained in:
@@ -218,6 +218,7 @@ export function AddHostModal({
|
|||||||
const profile = await upsertDirectConnection({
|
const profile = await upsertDirectConnection({
|
||||||
serverId,
|
serverId,
|
||||||
endpoint,
|
endpoint,
|
||||||
|
label: hostname ?? undefined,
|
||||||
});
|
});
|
||||||
|
|
||||||
onSaved?.({ profile, serverId, hostname, isNewHost });
|
onSaved?.({ profile, serverId, hostname, isNewHost });
|
||||||
|
|||||||
@@ -1,117 +0,0 @@
|
|||||||
import { useCallback, useEffect, useRef, useState } from "react";
|
|
||||||
import { Text, View } from "react-native";
|
|
||||||
import { StyleSheet, useUnistyles } from "react-native-unistyles";
|
|
||||||
import { AdaptiveModalSheet, AdaptiveTextInput } from "./adaptive-modal-sheet";
|
|
||||||
import { Button } from "@/components/ui/button";
|
|
||||||
|
|
||||||
const styles = StyleSheet.create((theme) => ({
|
|
||||||
helper: {
|
|
||||||
color: theme.colors.foregroundMuted,
|
|
||||||
fontSize: theme.fontSize.sm,
|
|
||||||
},
|
|
||||||
field: {
|
|
||||||
marginTop: theme.spacing[3],
|
|
||||||
gap: theme.spacing[2],
|
|
||||||
},
|
|
||||||
label: {
|
|
||||||
color: theme.colors.foregroundMuted,
|
|
||||||
fontSize: theme.fontSize.sm,
|
|
||||||
fontWeight: theme.fontWeight.medium,
|
|
||||||
},
|
|
||||||
input: {
|
|
||||||
backgroundColor: theme.colors.surface2,
|
|
||||||
borderRadius: theme.borderRadius.lg,
|
|
||||||
paddingHorizontal: theme.spacing[4],
|
|
||||||
paddingVertical: theme.spacing[3],
|
|
||||||
color: theme.colors.foreground,
|
|
||||||
borderWidth: 1,
|
|
||||||
borderColor: theme.colors.border,
|
|
||||||
},
|
|
||||||
actions: {
|
|
||||||
flexDirection: "row",
|
|
||||||
gap: theme.spacing[3],
|
|
||||||
marginTop: theme.spacing[4],
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
|
|
||||||
export interface NameHostModalProps {
|
|
||||||
visible: boolean;
|
|
||||||
serverId: string;
|
|
||||||
hostname: string | null;
|
|
||||||
onSkip: () => void;
|
|
||||||
onSave: (label: string) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function NameHostModal({ visible, serverId, hostname, onSkip, onSave }: NameHostModalProps) {
|
|
||||||
const { theme } = useUnistyles();
|
|
||||||
|
|
||||||
const [label, setLabel] = useState("");
|
|
||||||
const hasEditedRef = useRef(false);
|
|
||||||
|
|
||||||
const suggested = (hostname?.trim() || serverId).trim();
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!visible) return;
|
|
||||||
setLabel(suggested);
|
|
||||||
hasEditedRef.current = false;
|
|
||||||
}, [suggested, visible]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!visible) return;
|
|
||||||
if (hasEditedRef.current) return;
|
|
||||||
if (!hostname) return;
|
|
||||||
const trimmed = label.trim();
|
|
||||||
if (trimmed.length === 0 || trimmed === serverId) {
|
|
||||||
setLabel(hostname.trim());
|
|
||||||
}
|
|
||||||
}, [hostname, label, serverId, visible]);
|
|
||||||
|
|
||||||
const handleChange = useCallback((value: string) => {
|
|
||||||
hasEditedRef.current = true;
|
|
||||||
setLabel(value);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const handleSave = useCallback(() => {
|
|
||||||
const trimmed = label.trim();
|
|
||||||
if (!trimmed) {
|
|
||||||
onSkip();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
onSave(trimmed);
|
|
||||||
}, [label, onSave, onSkip]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<AdaptiveModalSheet
|
|
||||||
title="Name this host"
|
|
||||||
visible={visible}
|
|
||||||
onClose={onSkip}
|
|
||||||
testID="name-host-modal"
|
|
||||||
>
|
|
||||||
<Text style={styles.helper}>Optional. You can rename this later in Settings.</Text>
|
|
||||||
|
|
||||||
<View style={styles.field}>
|
|
||||||
<Text style={styles.label}>Label</Text>
|
|
||||||
<AdaptiveTextInput
|
|
||||||
value={label}
|
|
||||||
onChangeText={handleChange}
|
|
||||||
placeholder={suggested}
|
|
||||||
placeholderTextColor={theme.colors.foregroundMuted}
|
|
||||||
style={styles.input}
|
|
||||||
autoCapitalize="none"
|
|
||||||
autoCorrect={false}
|
|
||||||
returnKeyType="done"
|
|
||||||
onSubmitEditing={handleSave}
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
|
|
||||||
<View style={styles.actions}>
|
|
||||||
<Button style={{ flex: 1 }} variant="secondary" onPress={onSkip} testID="name-host-skip">
|
|
||||||
Skip
|
|
||||||
</Button>
|
|
||||||
<Button style={{ flex: 1 }} variant="default" onPress={handleSave} testID="name-host-save">
|
|
||||||
Save
|
|
||||||
</Button>
|
|
||||||
</View>
|
|
||||||
</AdaptiveModalSheet>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -146,7 +146,7 @@ export function PairLinkModal({
|
|||||||
await client.close().catch(() => undefined);
|
await client.close().catch(() => undefined);
|
||||||
|
|
||||||
const isNewHost = !daemons.some((daemon) => daemon.serverId === parsedOffer.serverId);
|
const isNewHost = !daemons.some((daemon) => daemon.serverId === parsedOffer.serverId);
|
||||||
const profile = await upsertDaemonFromOfferUrl(raw);
|
const profile = await upsertDaemonFromOfferUrl(raw, hostname ?? undefined);
|
||||||
onSaved?.({ profile, serverId: parsedOffer.serverId, hostname, isNewHost });
|
onSaved?.({ profile, serverId: parsedOffer.serverId, hostname, isNewHost });
|
||||||
handleClose();
|
handleClose();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -8,14 +8,11 @@ import type { HostProfile } from "@/types/host-connection";
|
|||||||
import {
|
import {
|
||||||
getHostRuntimeStore,
|
getHostRuntimeStore,
|
||||||
isHostRuntimeConnected,
|
isHostRuntimeConnected,
|
||||||
useHostMutations,
|
|
||||||
useHostRuntimeSnapshot,
|
useHostRuntimeSnapshot,
|
||||||
useHosts,
|
useHosts,
|
||||||
} from "@/runtime/host-runtime";
|
} from "@/runtime/host-runtime";
|
||||||
import { useSessionStore } from "@/stores/session-store";
|
|
||||||
import { AddHostModal } from "./add-host-modal";
|
import { AddHostModal } from "./add-host-modal";
|
||||||
import { PairLinkModal } from "./pair-link-modal";
|
import { PairLinkModal } from "./pair-link-modal";
|
||||||
import { NameHostModal } from "./name-host-modal";
|
|
||||||
import { resolveAppVersion } from "@/utils/app-version";
|
import { resolveAppVersion } from "@/utils/app-version";
|
||||||
import { formatVersionWithPrefix } from "@/desktop/updates/desktop-updates";
|
import { formatVersionWithPrefix } from "@/desktop/updates/desktop-updates";
|
||||||
import { buildHostRootRoute } from "@/utils/host-routes";
|
import { buildHostRootRoute } from "@/utils/host-routes";
|
||||||
@@ -237,31 +234,12 @@ export function WelcomeScreen({ onHostAdded }: WelcomeScreenProps) {
|
|||||||
const { theme } = useUnistyles();
|
const { theme } = useUnistyles();
|
||||||
const insets = useSafeAreaInsets();
|
const insets = useSafeAreaInsets();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { renameHost } = useHostMutations();
|
|
||||||
const appVersion = resolveAppVersion();
|
const appVersion = resolveAppVersion();
|
||||||
const appVersionText = formatVersionWithPrefix(appVersion);
|
const appVersionText = formatVersionWithPrefix(appVersion);
|
||||||
const [isDirectOpen, setIsDirectOpen] = useState(false);
|
const [isDirectOpen, setIsDirectOpen] = useState(false);
|
||||||
const [isPasteLinkOpen, setIsPasteLinkOpen] = useState(false);
|
const [isPasteLinkOpen, setIsPasteLinkOpen] = useState(false);
|
||||||
const [pendingNameHost, setPendingNameHost] = useState<{
|
|
||||||
serverId: string;
|
|
||||||
hostname: string | null;
|
|
||||||
} | null>(null);
|
|
||||||
const [pendingRedirectServerId, setPendingRedirectServerId] = useState<string | null>(null);
|
|
||||||
const hosts = useHosts();
|
const hosts = useHosts();
|
||||||
const anyOnlineServerId = useAnyHostOnline(hosts.map((h) => h.serverId));
|
const anyOnlineServerId = useAnyHostOnline(hosts.map((h) => h.serverId));
|
||||||
const pendingNameHostname = useSessionStore(
|
|
||||||
useCallback(
|
|
||||||
(state) => {
|
|
||||||
if (!pendingNameHost) return null;
|
|
||||||
return (
|
|
||||||
state.sessions[pendingNameHost.serverId]?.serverInfo?.hostname ??
|
|
||||||
pendingNameHost.hostname ??
|
|
||||||
null
|
|
||||||
);
|
|
||||||
},
|
|
||||||
[pendingNameHost],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const currentPathname =
|
const currentPathname =
|
||||||
@@ -272,11 +250,8 @@ export function WelcomeScreen({ onHostAdded }: WelcomeScreenProps) {
|
|||||||
if (!anyOnlineServerId) {
|
if (!anyOnlineServerId) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (pendingNameHost) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
router.replace(buildHostRootRoute(anyOnlineServerId));
|
router.replace(buildHostRootRoute(anyOnlineServerId));
|
||||||
}, [anyOnlineServerId, pendingNameHost, router]);
|
}, [anyOnlineServerId, router]);
|
||||||
|
|
||||||
const finishOnboarding = useCallback(
|
const finishOnboarding = useCallback(
|
||||||
(serverId: string) => {
|
(serverId: string) => {
|
||||||
@@ -401,13 +376,8 @@ export function WelcomeScreen({ onHostAdded }: WelcomeScreenProps) {
|
|||||||
<AddHostModal
|
<AddHostModal
|
||||||
visible={isDirectOpen}
|
visible={isDirectOpen}
|
||||||
onClose={() => setIsDirectOpen(false)}
|
onClose={() => setIsDirectOpen(false)}
|
||||||
onSaved={({ profile, serverId, hostname, isNewHost }) => {
|
onSaved={({ profile, serverId }) => {
|
||||||
onHostAdded?.(profile);
|
onHostAdded?.(profile);
|
||||||
setPendingRedirectServerId(serverId);
|
|
||||||
if (isNewHost) {
|
|
||||||
setPendingNameHost({ serverId, hostname });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
finishOnboarding(serverId);
|
finishOnboarding(serverId);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
@@ -415,38 +385,11 @@ export function WelcomeScreen({ onHostAdded }: WelcomeScreenProps) {
|
|||||||
<PairLinkModal
|
<PairLinkModal
|
||||||
visible={isPasteLinkOpen}
|
visible={isPasteLinkOpen}
|
||||||
onClose={() => setIsPasteLinkOpen(false)}
|
onClose={() => setIsPasteLinkOpen(false)}
|
||||||
onSaved={({ profile, serverId, hostname, isNewHost }) => {
|
onSaved={({ profile, serverId }) => {
|
||||||
onHostAdded?.(profile);
|
onHostAdded?.(profile);
|
||||||
setPendingRedirectServerId(serverId);
|
|
||||||
if (isNewHost) {
|
|
||||||
setPendingNameHost({ serverId, hostname });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
finishOnboarding(serverId);
|
finishOnboarding(serverId);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{pendingNameHost && pendingRedirectServerId ? (
|
|
||||||
<NameHostModal
|
|
||||||
visible
|
|
||||||
serverId={pendingNameHost.serverId}
|
|
||||||
hostname={pendingNameHostname}
|
|
||||||
onSkip={() => {
|
|
||||||
const serverId = pendingRedirectServerId;
|
|
||||||
setPendingNameHost(null);
|
|
||||||
setPendingRedirectServerId(null);
|
|
||||||
finishOnboarding(serverId);
|
|
||||||
}}
|
|
||||||
onSave={(label) => {
|
|
||||||
const serverId = pendingRedirectServerId;
|
|
||||||
void renameHost(pendingNameHost.serverId, label).finally(() => {
|
|
||||||
setPendingNameHost(null);
|
|
||||||
setPendingRedirectServerId(null);
|
|
||||||
finishOnboarding(serverId);
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
) : null}
|
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import type {
|
|||||||
FetchAgentsEntry,
|
FetchAgentsEntry,
|
||||||
FetchAgentsOptions,
|
FetchAgentsOptions,
|
||||||
} from "@server/client/daemon-client";
|
} from "@server/client/daemon-client";
|
||||||
|
import type { ConnectionOffer } from "@server/shared/connection-offer";
|
||||||
import type { HostConnection, HostProfile } from "@/types/host-connection";
|
import type { HostConnection, HostProfile } from "@/types/host-connection";
|
||||||
import { useSessionStore, type Agent } from "@/stores/session-store";
|
import { useSessionStore, type Agent } from "@/stores/session-store";
|
||||||
import {
|
import {
|
||||||
@@ -189,6 +190,17 @@ function makeHost(input?: Partial<HostProfile>): HostProfile {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function makeOffer(input?: Partial<ConnectionOffer>): ConnectionOffer {
|
||||||
|
return {
|
||||||
|
v: 2,
|
||||||
|
serverId: input?.serverId ?? "srv_offer",
|
||||||
|
daemonPublicKeyB64: input?.daemonPublicKeyB64 ?? "pk_test_offer",
|
||||||
|
relay: {
|
||||||
|
endpoint: input?.relay?.endpoint ?? "relay.paseo.sh:443",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
function makeDeps(
|
function makeDeps(
|
||||||
latencyByConnectionId: Record<string, number | Error>,
|
latencyByConnectionId: Record<string, number | Error>,
|
||||||
createdClients: FakeDaemonClient[],
|
createdClients: FakeDaemonClient[],
|
||||||
@@ -1288,4 +1300,53 @@ describe("HostRuntimeStore", () => {
|
|||||||
|
|
||||||
store.syncHosts([]);
|
store.syncHosts([]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("uses the advertised hostname when adding a relay host from a pairing offer", async () => {
|
||||||
|
const store = new HostRuntimeStore({
|
||||||
|
deps: {
|
||||||
|
createClient: () => new FakeDaemonClient() as unknown as DaemonClient,
|
||||||
|
connectToDaemon: async ({ host }) => ({
|
||||||
|
client: makeConnectedProbeClient(5) as unknown as DaemonClient,
|
||||||
|
serverId: host.serverId,
|
||||||
|
hostname: host.label ?? null,
|
||||||
|
}),
|
||||||
|
getClientId: async () => "cid_test_runtime",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await store.upsertConnectionFromOffer(makeOffer(), "mbp");
|
||||||
|
|
||||||
|
const pairedHost = store.getHosts().find((host) => host.serverId === "srv_offer");
|
||||||
|
expect(pairedHost?.label).toBe("mbp");
|
||||||
|
|
||||||
|
store.syncHosts([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("keeps a custom host label when re-pairing with an advertised hostname", async () => {
|
||||||
|
const store = new HostRuntimeStore({
|
||||||
|
deps: {
|
||||||
|
createClient: () => new FakeDaemonClient() as unknown as DaemonClient,
|
||||||
|
connectToDaemon: async ({ host }) => ({
|
||||||
|
client: makeConnectedProbeClient(5) as unknown as DaemonClient,
|
||||||
|
serverId: host.serverId,
|
||||||
|
hostname: host.label ?? null,
|
||||||
|
}),
|
||||||
|
getClientId: async () => "cid_test_runtime",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await store.upsertRelayConnection({
|
||||||
|
serverId: "srv_offer",
|
||||||
|
relayEndpoint: "relay.paseo.sh:443",
|
||||||
|
daemonPublicKeyB64: "pk_test_offer",
|
||||||
|
label: "Custom name",
|
||||||
|
});
|
||||||
|
|
||||||
|
await store.upsertConnectionFromOffer(makeOffer(), "mbp");
|
||||||
|
|
||||||
|
const pairedHost = store.getHosts().find((host) => host.serverId === "srv_offer");
|
||||||
|
expect(pairedHost?.label).toBe("Custom name");
|
||||||
|
|
||||||
|
store.syncHosts([]);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1287,15 +1287,22 @@ export class HostRuntimeStore {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async upsertConnectionFromOffer(offer: ConnectionOffer): Promise<HostProfile> {
|
async upsertConnectionFromOffer(
|
||||||
|
offer: ConnectionOffer,
|
||||||
|
label?: string,
|
||||||
|
): Promise<HostProfile> {
|
||||||
return this.upsertRelayConnection({
|
return this.upsertRelayConnection({
|
||||||
serverId: offer.serverId,
|
serverId: offer.serverId,
|
||||||
relayEndpoint: offer.relay.endpoint,
|
relayEndpoint: offer.relay.endpoint,
|
||||||
daemonPublicKeyB64: offer.daemonPublicKeyB64,
|
daemonPublicKeyB64: offer.daemonPublicKeyB64,
|
||||||
|
label,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async upsertConnectionFromOfferUrl(offerUrlOrFragment: string): Promise<HostProfile> {
|
async upsertConnectionFromOfferUrl(
|
||||||
|
offerUrlOrFragment: string,
|
||||||
|
label?: string,
|
||||||
|
): Promise<HostProfile> {
|
||||||
const marker = "#offer=";
|
const marker = "#offer=";
|
||||||
const idx = offerUrlOrFragment.indexOf(marker);
|
const idx = offerUrlOrFragment.indexOf(marker);
|
||||||
if (idx === -1) {
|
if (idx === -1) {
|
||||||
@@ -1307,7 +1314,7 @@ export class HostRuntimeStore {
|
|||||||
}
|
}
|
||||||
const payload = decodeOfferFragmentPayload(encoded);
|
const payload = decodeOfferFragmentPayload(encoded);
|
||||||
const offer = ConnectionOfferSchema.parse(payload);
|
const offer = ConnectionOfferSchema.parse(payload);
|
||||||
return this.upsertConnectionFromOffer(offer);
|
return this.upsertConnectionFromOffer(offer, label);
|
||||||
}
|
}
|
||||||
|
|
||||||
async addConnectionFromListenAndWaitForOnline(input: {
|
async addConnectionFromListenAndWaitForOnline(input: {
|
||||||
@@ -1956,8 +1963,11 @@ export interface HostMutations {
|
|||||||
daemonPublicKeyB64: string;
|
daemonPublicKeyB64: string;
|
||||||
label?: string;
|
label?: string;
|
||||||
}) => Promise<HostProfile>;
|
}) => Promise<HostProfile>;
|
||||||
upsertConnectionFromOffer: (offer: ConnectionOffer) => Promise<HostProfile>;
|
upsertConnectionFromOffer: (offer: ConnectionOffer, label?: string) => Promise<HostProfile>;
|
||||||
upsertConnectionFromOfferUrl: (offerUrlOrFragment: string) => Promise<HostProfile>;
|
upsertConnectionFromOfferUrl: (
|
||||||
|
offerUrlOrFragment: string,
|
||||||
|
label?: string,
|
||||||
|
) => Promise<HostProfile>;
|
||||||
renameHost: (serverId: string, label: string) => Promise<void>;
|
renameHost: (serverId: string, label: string) => Promise<void>;
|
||||||
removeHost: (serverId: string) => Promise<void>;
|
removeHost: (serverId: string) => Promise<void>;
|
||||||
removeConnection: (serverId: string, connectionId: string) => Promise<void>;
|
removeConnection: (serverId: string, connectionId: string) => Promise<void>;
|
||||||
@@ -1969,8 +1979,8 @@ export function useHostMutations(): HostMutations {
|
|||||||
() => ({
|
() => ({
|
||||||
upsertDirectConnection: (input) => store.upsertDirectConnection(input),
|
upsertDirectConnection: (input) => store.upsertDirectConnection(input),
|
||||||
upsertRelayConnection: (input) => store.upsertRelayConnection(input),
|
upsertRelayConnection: (input) => store.upsertRelayConnection(input),
|
||||||
upsertConnectionFromOffer: (offer) => store.upsertConnectionFromOffer(offer),
|
upsertConnectionFromOffer: (offer, label) => store.upsertConnectionFromOffer(offer, label),
|
||||||
upsertConnectionFromOfferUrl: (url) => store.upsertConnectionFromOfferUrl(url),
|
upsertConnectionFromOfferUrl: (url, label) => store.upsertConnectionFromOfferUrl(url, label),
|
||||||
renameHost: (serverId, label) => store.renameHost(serverId, label),
|
renameHost: (serverId, label) => store.renameHost(serverId, label),
|
||||||
removeHost: (serverId) => store.removeHost(serverId),
|
removeHost: (serverId) => store.removeHost(serverId),
|
||||||
removeConnection: (serverId, connectionId) => store.removeConnection(serverId, connectionId),
|
removeConnection: (serverId, connectionId) => store.removeConnection(serverId, connectionId),
|
||||||
|
|||||||
@@ -41,7 +41,6 @@ import { AddHostMethodModal } from "@/components/add-host-method-modal";
|
|||||||
import { AddHostModal } from "@/components/add-host-modal";
|
import { AddHostModal } from "@/components/add-host-modal";
|
||||||
import { PairLinkModal } from "@/components/pair-link-modal";
|
import { PairLinkModal } from "@/components/pair-link-modal";
|
||||||
import { KeyboardShortcutsSection } from "@/screens/settings/keyboard-shortcuts-section";
|
import { KeyboardShortcutsSection } from "@/screens/settings/keyboard-shortcuts-section";
|
||||||
import { NameHostModal } from "@/components/name-host-modal";
|
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { SegmentedControl } from "@/components/ui/segmented-control";
|
import { SegmentedControl } from "@/components/ui/segmented-control";
|
||||||
import {
|
import {
|
||||||
@@ -96,20 +95,20 @@ function getSettingsSections(context: { isDesktopApp: boolean }): SettingsSectio
|
|||||||
const sections: SettingsSectionDef[] = [
|
const sections: SettingsSectionDef[] = [
|
||||||
{ id: "hosts", label: "Hosts", icon: Server },
|
{ id: "hosts", label: "Hosts", icon: Server },
|
||||||
{ id: "general", label: "General", icon: Settings },
|
{ id: "general", label: "General", icon: Settings },
|
||||||
{ id: "shortcuts", label: "Shortcuts", icon: Keyboard },
|
|
||||||
{ id: "permissions", label: "Permissions", icon: Shield },
|
{ id: "permissions", label: "Permissions", icon: Shield },
|
||||||
];
|
];
|
||||||
|
|
||||||
if (context.isDesktopApp) {
|
if (context.isDesktopApp) {
|
||||||
sections.push(
|
sections.push(
|
||||||
|
{ id: "shortcuts", label: "Shortcuts", icon: Keyboard },
|
||||||
{ id: "integrations", label: "Integrations", icon: Puzzle },
|
{ id: "integrations", label: "Integrations", icon: Puzzle },
|
||||||
{ id: "pair-device", label: "Pair device", icon: Smartphone },
|
{ id: "pair-device", label: "Pair device", icon: Smartphone },
|
||||||
{ id: "daemon", label: "Daemon", icon: Settings },
|
{ id: "daemon", label: "Daemon", icon: Settings },
|
||||||
|
{ id: "providers", label: "Providers", icon: Blocks },
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
sections.push(
|
sections.push(
|
||||||
{ id: "providers", label: "Providers", icon: Blocks },
|
|
||||||
{ id: "diagnostics", label: "Diagnostics", icon: Stethoscope },
|
{ id: "diagnostics", label: "Diagnostics", icon: Stethoscope },
|
||||||
{ id: "about", label: "About", icon: Info },
|
{ id: "about", label: "About", icon: Info },
|
||||||
);
|
);
|
||||||
@@ -203,10 +202,6 @@ interface HostsSectionProps {
|
|||||||
goBackToAddConnectionMethods: () => void;
|
goBackToAddConnectionMethods: () => void;
|
||||||
setIsDirectHostVisible: (visible: boolean) => void;
|
setIsDirectHostVisible: (visible: boolean) => void;
|
||||||
setIsPasteLinkVisible: (visible: boolean) => void;
|
setIsPasteLinkVisible: (visible: boolean) => void;
|
||||||
pendingNameHost: { serverId: string; hostname: string | null } | null;
|
|
||||||
setPendingNameHost: (host: { serverId: string; hostname: string | null } | null) => void;
|
|
||||||
pendingNameHostname: string | null;
|
|
||||||
renameHost: (serverId: string, label: string) => Promise<void>;
|
|
||||||
pendingRemoveHost: HostProfile | null;
|
pendingRemoveHost: HostProfile | null;
|
||||||
setPendingRemoveHost: (host: HostProfile | null) => void;
|
setPendingRemoveHost: (host: HostProfile | null) => void;
|
||||||
isRemovingHost: boolean;
|
isRemovingHost: boolean;
|
||||||
@@ -289,38 +284,14 @@ function HostsSection(props: HostsSectionProps) {
|
|||||||
visible={props.isDirectHostVisible}
|
visible={props.isDirectHostVisible}
|
||||||
onClose={props.closeAddConnectionFlow}
|
onClose={props.closeAddConnectionFlow}
|
||||||
onCancel={props.goBackToAddConnectionMethods}
|
onCancel={props.goBackToAddConnectionMethods}
|
||||||
onSaved={({ serverId, hostname, isNewHost }) => {
|
|
||||||
if (isNewHost) {
|
|
||||||
props.setPendingNameHost({ serverId, hostname });
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<PairLinkModal
|
<PairLinkModal
|
||||||
visible={props.isPasteLinkVisible}
|
visible={props.isPasteLinkVisible}
|
||||||
onClose={props.closeAddConnectionFlow}
|
onClose={props.closeAddConnectionFlow}
|
||||||
onCancel={props.goBackToAddConnectionMethods}
|
onCancel={props.goBackToAddConnectionMethods}
|
||||||
onSaved={({ serverId, hostname, isNewHost }) => {
|
|
||||||
if (isNewHost) {
|
|
||||||
props.setPendingNameHost({ serverId, hostname });
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{props.pendingNameHost ? (
|
|
||||||
<NameHostModal
|
|
||||||
visible
|
|
||||||
serverId={props.pendingNameHost.serverId}
|
|
||||||
hostname={props.pendingNameHostname}
|
|
||||||
onSkip={() => props.setPendingNameHost(null)}
|
|
||||||
onSave={(label) => {
|
|
||||||
void props.renameHost(props.pendingNameHost!.serverId, label).finally(() => {
|
|
||||||
props.setPendingNameHost(null);
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
) : null}
|
|
||||||
|
|
||||||
{props.pendingRemoveHost ? (
|
{props.pendingRemoveHost ? (
|
||||||
<AdaptiveModalSheet
|
<AdaptiveModalSheet
|
||||||
title="Remove host"
|
title="Remove host"
|
||||||
@@ -883,10 +854,6 @@ export default function SettingsScreen() {
|
|||||||
const [isAddHostMethodVisible, setIsAddHostMethodVisible] = useState(false);
|
const [isAddHostMethodVisible, setIsAddHostMethodVisible] = useState(false);
|
||||||
const [isDirectHostVisible, setIsDirectHostVisible] = useState(false);
|
const [isDirectHostVisible, setIsDirectHostVisible] = useState(false);
|
||||||
const [isPasteLinkVisible, setIsPasteLinkVisible] = useState(false);
|
const [isPasteLinkVisible, setIsPasteLinkVisible] = useState(false);
|
||||||
const [pendingNameHost, setPendingNameHost] = useState<{
|
|
||||||
serverId: string;
|
|
||||||
hostname: string | null;
|
|
||||||
} | null>(null);
|
|
||||||
const [pendingRemoveHost, setPendingRemoveHost] = useState<HostProfile | null>(null);
|
const [pendingRemoveHost, setPendingRemoveHost] = useState<HostProfile | null>(null);
|
||||||
const [isRemovingHost, setIsRemovingHost] = useState(false);
|
const [isRemovingHost, setIsRemovingHost] = useState(false);
|
||||||
const [editingDaemon, setEditingDaemon] = useState<HostProfile | null>(null);
|
const [editingDaemon, setEditingDaemon] = useState<HostProfile | null>(null);
|
||||||
@@ -904,19 +871,6 @@ export default function SettingsScreen() {
|
|||||||
const editingDaemonLive = editingServerId
|
const editingDaemonLive = editingServerId
|
||||||
? (daemons.find((daemon) => daemon.serverId === editingServerId) ?? null)
|
? (daemons.find((daemon) => daemon.serverId === editingServerId) ?? null)
|
||||||
: null;
|
: null;
|
||||||
const pendingNameHostname = useSessionStore(
|
|
||||||
useCallback(
|
|
||||||
(state) => {
|
|
||||||
if (!pendingNameHost) return null;
|
|
||||||
return (
|
|
||||||
state.sessions[pendingNameHost.serverId]?.serverInfo?.hostname ??
|
|
||||||
pendingNameHost.hostname ??
|
|
||||||
null
|
|
||||||
);
|
|
||||||
},
|
|
||||||
[pendingNameHost],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
return () => {
|
return () => {
|
||||||
@@ -1076,10 +1030,6 @@ export default function SettingsScreen() {
|
|||||||
goBackToAddConnectionMethods,
|
goBackToAddConnectionMethods,
|
||||||
setIsDirectHostVisible,
|
setIsDirectHostVisible,
|
||||||
setIsPasteLinkVisible,
|
setIsPasteLinkVisible,
|
||||||
pendingNameHost,
|
|
||||||
setPendingNameHost,
|
|
||||||
pendingNameHostname,
|
|
||||||
renameHost,
|
|
||||||
pendingRemoveHost,
|
pendingRemoveHost,
|
||||||
setPendingRemoveHost,
|
setPendingRemoveHost,
|
||||||
isRemovingHost,
|
isRemovingHost,
|
||||||
|
|||||||
Reference in New Issue
Block a user