mirror of
https://github.com/EthanShoeDev/fressh.git
synced 2026-01-11 06:12:51 +00:00
segmented control
This commit is contained in:
@@ -28,6 +28,7 @@
|
||||
"@expo/vector-icons": "^15.0.2",
|
||||
"@fressh/assets": "workspace:*",
|
||||
"@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/elements": "^2.6.4",
|
||||
"@react-navigation/native": "^7.1.8",
|
||||
|
||||
@@ -1,17 +1,10 @@
|
||||
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 { useQuery } from '@tanstack/react-query';
|
||||
import { useMutation, useQuery } from '@tanstack/react-query';
|
||||
import { useRouter } from 'expo-router';
|
||||
import React from 'react';
|
||||
import {
|
||||
Pressable,
|
||||
ScrollView,
|
||||
StyleSheet,
|
||||
Text,
|
||||
View,
|
||||
Switch,
|
||||
} from 'react-native';
|
||||
import { Pressable, ScrollView, StyleSheet, Text, View } from 'react-native';
|
||||
import { useAppForm, useFieldContext } from '../components/form-components';
|
||||
import { KeyManagerModal } from '../components/key-manager-modal';
|
||||
import {
|
||||
@@ -31,77 +24,80 @@ const defaultValues: ConnectionDetails = {
|
||||
},
|
||||
};
|
||||
|
||||
export default function Index() {
|
||||
const useSshConnMutation = () => {
|
||||
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({
|
||||
// https://tanstack.com/form/latest/docs/framework/react/guides/async-initial-values
|
||||
defaultValues,
|
||||
validators: {
|
||||
onChange: connectionDetailsSchema,
|
||||
onSubmitAsync: async ({ 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;
|
||||
}
|
||||
},
|
||||
onSubmitAsync: async ({ value }) => sshConnMutation.mutateAsync(value),
|
||||
},
|
||||
});
|
||||
|
||||
@@ -164,16 +160,17 @@ export default function Index() {
|
||||
<connectionForm.AppField name="security.type">
|
||||
{(field) => (
|
||||
<View style={styles.inputGroup}>
|
||||
<Text style={styles.label}>Use Private Key</Text>
|
||||
<View>
|
||||
{/* Map boolean switch to discriminated union */}
|
||||
<Switch
|
||||
value={field.state.value === 'key'}
|
||||
onValueChange={(val) => {
|
||||
field.handleChange(val ? 'key' : 'password');
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
<SegmentedControl
|
||||
values={['Password', 'Private Key']}
|
||||
selectedIndex={field.state.value === 'password' ? 0 : 1}
|
||||
onChange={(event) => {
|
||||
field.handleChange(
|
||||
event.nativeEvent.selectedSegmentIndex === 0
|
||||
? 'password'
|
||||
: 'key',
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
)}
|
||||
</connectionForm.AppField>
|
||||
|
||||
14
pnpm-lock.yaml
generated
14
pnpm-lock.yaml
generated
@@ -47,6 +47,9 @@ importers:
|
||||
'@react-native-picker/picker':
|
||||
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)
|
||||
'@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':
|
||||
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)
|
||||
@@ -1730,6 +1733,12 @@ packages:
|
||||
react: '*'
|
||||
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':
|
||||
resolution: {integrity: sha512-AMcDadefBIjD10BRqkWw+W/VdvXEomR6aEZ0fhQRAv7igrBzb4PTn4vHKYg+sUK0e3wa74kcMy2DLc/HtnGcMA==}
|
||||
engines: {node: '>= 20.19.4'}
|
||||
@@ -8692,6 +8701,11 @@ snapshots:
|
||||
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/babel-plugin-codegen@0.81.4(@babel/core@7.28.3)':
|
||||
|
||||
Reference in New Issue
Block a user