mirror of
https://github.com/EthanShoeDev/fressh.git
synced 2026-01-11 14:22:51 +00:00
segmented control
This commit is contained in:
@@ -28,6 +28,7 @@
|
|||||||
"@expo/vector-icons": "^15.0.2",
|
"@expo/vector-icons": "^15.0.2",
|
||||||
"@fressh/assets": "workspace:*",
|
"@fressh/assets": "workspace:*",
|
||||||
"@react-native-picker/picker": "2.11.1",
|
"@react-native-picker/picker": "2.11.1",
|
||||||
|
"@react-native-segmented-control/segmented-control": "2.5.7",
|
||||||
"@react-navigation/bottom-tabs": "^7.4.0",
|
"@react-navigation/bottom-tabs": "^7.4.0",
|
||||||
"@react-navigation/elements": "^2.6.4",
|
"@react-navigation/elements": "^2.6.4",
|
||||||
"@react-navigation/native": "^7.1.8",
|
"@react-navigation/native": "^7.1.8",
|
||||||
|
|||||||
@@ -1,17 +1,10 @@
|
|||||||
import SSHClient, { PtyType } from '@dylankenneally/react-native-ssh-sftp';
|
import SSHClient, { PtyType } from '@dylankenneally/react-native-ssh-sftp';
|
||||||
// Removed inline Picker usage in favor of modal selection
|
import SegmentedControl from '@react-native-segmented-control/segmented-control';
|
||||||
import { useStore } from '@tanstack/react-form';
|
import { useStore } from '@tanstack/react-form';
|
||||||
import { useQuery } from '@tanstack/react-query';
|
import { useMutation, useQuery } from '@tanstack/react-query';
|
||||||
import { useRouter } from 'expo-router';
|
import { useRouter } from 'expo-router';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import {
|
import { Pressable, ScrollView, StyleSheet, Text, View } from 'react-native';
|
||||||
Pressable,
|
|
||||||
ScrollView,
|
|
||||||
StyleSheet,
|
|
||||||
Text,
|
|
||||||
View,
|
|
||||||
Switch,
|
|
||||||
} from 'react-native';
|
|
||||||
import { useAppForm, useFieldContext } from '../components/form-components';
|
import { useAppForm, useFieldContext } from '../components/form-components';
|
||||||
import { KeyManagerModal } from '../components/key-manager-modal';
|
import { KeyManagerModal } from '../components/key-manager-modal';
|
||||||
import {
|
import {
|
||||||
@@ -31,77 +24,80 @@ const defaultValues: ConnectionDetails = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function Index() {
|
const useSshConnMutation = () => {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
// const storedConnectionsQuery = useQuery(
|
|
||||||
// secretsManager.connections.query.list,
|
|
||||||
// );
|
|
||||||
|
|
||||||
|
return useMutation({
|
||||||
|
mutationFn: async (value: ConnectionDetails) => {
|
||||||
|
try {
|
||||||
|
console.log('Connecting to SSH server...');
|
||||||
|
const effective = await (async () => {
|
||||||
|
if (value.security.type === 'password') return value;
|
||||||
|
if (value.security.keyId) return value;
|
||||||
|
const keys = await secretsManager.keys.utils.listEntriesWithValues();
|
||||||
|
const def = keys.find((k) => k.metadata?.isDefault);
|
||||||
|
const pick = def ?? keys[0];
|
||||||
|
if (pick) {
|
||||||
|
return {
|
||||||
|
...value,
|
||||||
|
security: { type: 'key', keyId: pick.id },
|
||||||
|
} as ConnectionDetails;
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
})();
|
||||||
|
|
||||||
|
const sshClientConnection = await (async () => {
|
||||||
|
if (effective.security.type === 'password') {
|
||||||
|
return await SSHClient.connectWithPassword(
|
||||||
|
effective.host,
|
||||||
|
effective.port,
|
||||||
|
effective.username,
|
||||||
|
effective.security.password,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const privateKey = await secretsManager.keys.utils.getPrivateKey(
|
||||||
|
effective.security.keyId,
|
||||||
|
);
|
||||||
|
return await SSHClient.connectWithKey(
|
||||||
|
effective.host,
|
||||||
|
effective.port,
|
||||||
|
effective.username,
|
||||||
|
privateKey.value,
|
||||||
|
);
|
||||||
|
})();
|
||||||
|
|
||||||
|
await secretsManager.connections.utils.upsertConnection({
|
||||||
|
id: 'default',
|
||||||
|
details: effective,
|
||||||
|
priority: 0,
|
||||||
|
});
|
||||||
|
await sshClientConnection.startShell(PtyType.XTERM);
|
||||||
|
const sshConn = sshConnectionManager.addSession({
|
||||||
|
client: sshClientConnection,
|
||||||
|
});
|
||||||
|
console.log('Connected to SSH server', sshConn.sessionId);
|
||||||
|
router.push({
|
||||||
|
pathname: '/shell',
|
||||||
|
params: {
|
||||||
|
sessionId: sshConn.sessionId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error connecting to SSH server', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function Index() {
|
||||||
|
const sshConnMutation = useSshConnMutation();
|
||||||
const connectionForm = useAppForm({
|
const connectionForm = useAppForm({
|
||||||
// https://tanstack.com/form/latest/docs/framework/react/guides/async-initial-values
|
// https://tanstack.com/form/latest/docs/framework/react/guides/async-initial-values
|
||||||
defaultValues,
|
defaultValues,
|
||||||
validators: {
|
validators: {
|
||||||
onChange: connectionDetailsSchema,
|
onChange: connectionDetailsSchema,
|
||||||
onSubmitAsync: async ({ value }) => {
|
onSubmitAsync: async ({ value }) => sshConnMutation.mutateAsync(value),
|
||||||
try {
|
|
||||||
console.log('Connecting to SSH server...');
|
|
||||||
const effective = await (async () => {
|
|
||||||
if (value.security.type === 'password') return value;
|
|
||||||
if (value.security.keyId) return value;
|
|
||||||
const keys =
|
|
||||||
await secretsManager.keys.utils.listEntriesWithValues();
|
|
||||||
const def = keys.find((k) => k.metadata?.isDefault);
|
|
||||||
const pick = def ?? keys[0];
|
|
||||||
if (pick) {
|
|
||||||
return {
|
|
||||||
...value,
|
|
||||||
security: { type: 'key', keyId: pick.id },
|
|
||||||
} as ConnectionDetails;
|
|
||||||
}
|
|
||||||
return value;
|
|
||||||
})();
|
|
||||||
|
|
||||||
const sshClientConnection = await (async () => {
|
|
||||||
if (effective.security.type === 'password') {
|
|
||||||
return await SSHClient.connectWithPassword(
|
|
||||||
effective.host,
|
|
||||||
effective.port,
|
|
||||||
effective.username,
|
|
||||||
effective.security.password,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
const privateKey = await secretsManager.keys.utils.getPrivateKey(
|
|
||||||
effective.security.keyId,
|
|
||||||
);
|
|
||||||
return await SSHClient.connectWithKey(
|
|
||||||
effective.host,
|
|
||||||
effective.port,
|
|
||||||
effective.username,
|
|
||||||
privateKey.value,
|
|
||||||
);
|
|
||||||
})();
|
|
||||||
|
|
||||||
await secretsManager.connections.utils.upsertConnection({
|
|
||||||
id: 'default',
|
|
||||||
details: effective,
|
|
||||||
priority: 0,
|
|
||||||
});
|
|
||||||
await sshClientConnection.startShell(PtyType.XTERM);
|
|
||||||
const sshConn = sshConnectionManager.addSession({
|
|
||||||
client: sshClientConnection,
|
|
||||||
});
|
|
||||||
console.log('Connected to SSH server', sshConn.sessionId);
|
|
||||||
router.push({
|
|
||||||
pathname: '/shell',
|
|
||||||
params: {
|
|
||||||
sessionId: sshConn.sessionId,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error connecting to SSH server', error);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -164,16 +160,17 @@ export default function Index() {
|
|||||||
<connectionForm.AppField name="security.type">
|
<connectionForm.AppField name="security.type">
|
||||||
{(field) => (
|
{(field) => (
|
||||||
<View style={styles.inputGroup}>
|
<View style={styles.inputGroup}>
|
||||||
<Text style={styles.label}>Use Private Key</Text>
|
<SegmentedControl
|
||||||
<View>
|
values={['Password', 'Private Key']}
|
||||||
{/* Map boolean switch to discriminated union */}
|
selectedIndex={field.state.value === 'password' ? 0 : 1}
|
||||||
<Switch
|
onChange={(event) => {
|
||||||
value={field.state.value === 'key'}
|
field.handleChange(
|
||||||
onValueChange={(val) => {
|
event.nativeEvent.selectedSegmentIndex === 0
|
||||||
field.handleChange(val ? 'key' : 'password');
|
? 'password'
|
||||||
}}
|
: 'key',
|
||||||
/>
|
);
|
||||||
</View>
|
}}
|
||||||
|
/>
|
||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
</connectionForm.AppField>
|
</connectionForm.AppField>
|
||||||
|
|||||||
14
pnpm-lock.yaml
generated
14
pnpm-lock.yaml
generated
@@ -47,6 +47,9 @@ importers:
|
|||||||
'@react-native-picker/picker':
|
'@react-native-picker/picker':
|
||||||
specifier: 2.11.1
|
specifier: 2.11.1
|
||||||
version: 2.11.1(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0))(react@19.1.0)
|
version: 2.11.1(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0))(react@19.1.0)
|
||||||
|
'@react-native-segmented-control/segmented-control':
|
||||||
|
specifier: 2.5.7
|
||||||
|
version: 2.5.7(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0))(react@19.1.0)
|
||||||
'@react-navigation/bottom-tabs':
|
'@react-navigation/bottom-tabs':
|
||||||
specifier: ^7.4.0
|
specifier: ^7.4.0
|
||||||
version: 7.4.7(@react-navigation/native@7.1.17(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0))(react@19.1.0))(react-native-safe-area-context@5.6.1(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0))(react@19.1.0))(react-native-screens@4.16.0(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0))(react@19.1.0))(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0))(react@19.1.0)
|
version: 7.4.7(@react-navigation/native@7.1.17(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0))(react@19.1.0))(react-native-safe-area-context@5.6.1(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0))(react@19.1.0))(react-native-screens@4.16.0(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0))(react@19.1.0))(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0))(react@19.1.0)
|
||||||
@@ -1730,6 +1733,12 @@ packages:
|
|||||||
react: '*'
|
react: '*'
|
||||||
react-native: '*'
|
react-native: '*'
|
||||||
|
|
||||||
|
'@react-native-segmented-control/segmented-control@2.5.7':
|
||||||
|
resolution: {integrity: sha512-l84YeVX8xAU3lvOJSvV4nK/NbGhIm2gBfveYolwaoCbRp+/SLXtc6mYrQmM9ScXNwU14mnzjQTpTHWl5YPnkzQ==}
|
||||||
|
peerDependencies:
|
||||||
|
react: '>=16.0'
|
||||||
|
react-native: '>=0.62'
|
||||||
|
|
||||||
'@react-native/assets-registry@0.81.4':
|
'@react-native/assets-registry@0.81.4':
|
||||||
resolution: {integrity: sha512-AMcDadefBIjD10BRqkWw+W/VdvXEomR6aEZ0fhQRAv7igrBzb4PTn4vHKYg+sUK0e3wa74kcMy2DLc/HtnGcMA==}
|
resolution: {integrity: sha512-AMcDadefBIjD10BRqkWw+W/VdvXEomR6aEZ0fhQRAv7igrBzb4PTn4vHKYg+sUK0e3wa74kcMy2DLc/HtnGcMA==}
|
||||||
engines: {node: '>= 20.19.4'}
|
engines: {node: '>= 20.19.4'}
|
||||||
@@ -8692,6 +8701,11 @@ snapshots:
|
|||||||
react: 19.1.0
|
react: 19.1.0
|
||||||
react-native: 0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0)
|
react-native: 0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0)
|
||||||
|
|
||||||
|
'@react-native-segmented-control/segmented-control@2.5.7(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0))(react@19.1.0)':
|
||||||
|
dependencies:
|
||||||
|
react: 19.1.0
|
||||||
|
react-native: 0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0)
|
||||||
|
|
||||||
'@react-native/assets-registry@0.81.4': {}
|
'@react-native/assets-registry@0.81.4': {}
|
||||||
|
|
||||||
'@react-native/babel-plugin-codegen@0.81.4(@babel/core@7.28.3)':
|
'@react-native/babel-plugin-codegen@0.81.4(@babel/core@7.28.3)':
|
||||||
|
|||||||
Reference in New Issue
Block a user