mirror of
https://github.com/khairul169/vaulterm.git
synced 2026-09-15 17:03:30 +07:00
feat: update
This commit is contained in:
@@ -0,0 +1,16 @@
|
||||
import Icons from "@/components/ui/icons";
|
||||
import React from "react";
|
||||
import { Button, Label, XStack } from "tamagui";
|
||||
|
||||
export default function CredentialsSection() {
|
||||
return (
|
||||
<XStack gap="$3">
|
||||
<Label flex={1} h="$3">
|
||||
Credentials
|
||||
</Label>
|
||||
<Button size="$3" icon={<Icons size={16} name="plus" />}>
|
||||
Add
|
||||
</Button>
|
||||
</XStack>
|
||||
);
|
||||
}
|
||||
@@ -1,22 +1,19 @@
|
||||
import Icons from "@/components/ui/icons";
|
||||
import Modal from "@/components/ui/modal";
|
||||
import { SelectField } from "@/components/ui/select";
|
||||
import { useZForm, UseZFormReturn } from "@/hooks/useZForm";
|
||||
import api from "@/lib/api";
|
||||
import { useZForm } from "@/hooks/useZForm";
|
||||
import { createDisclosure } from "@/lib/utils";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import React from "react";
|
||||
import { Button, Label, ScrollView, XStack } from "tamagui";
|
||||
import {
|
||||
FormSchema,
|
||||
formSchema,
|
||||
incusTypes,
|
||||
pveTypes,
|
||||
typeOptions,
|
||||
} from "../schema/form";
|
||||
import { ScrollView, XStack } from "tamagui";
|
||||
import { FormSchema, formSchema, typeOptions } from "../schema/form";
|
||||
import { InputField } from "@/components/ui/input";
|
||||
import FormField from "@/components/ui/form";
|
||||
import { useKeychains, useSaveHost } from "../hooks/query";
|
||||
import { useSaveHost } from "../hooks/query";
|
||||
import { ErrorAlert } from "@/components/ui/alert";
|
||||
import Button from "@/components/ui/button";
|
||||
import { PVEFormFields } from "./pve";
|
||||
import { IncusFormFields } from "./incus";
|
||||
import { SSHFormFields } from "./ssh";
|
||||
|
||||
export const hostFormModal = createDisclosure<FormSchema>();
|
||||
|
||||
@@ -26,7 +23,6 @@ const HostForm = () => {
|
||||
const isEditing = data?.id != null;
|
||||
const type = form.watch("type");
|
||||
|
||||
const keys = useKeychains();
|
||||
const saveMutation = useSaveHost();
|
||||
|
||||
const onSubmit = form.handleSubmit((values) => {
|
||||
@@ -44,6 +40,8 @@ const HostForm = () => {
|
||||
title="Host"
|
||||
description={`${isEditing ? "Edit" : "Add new"} host.`}
|
||||
>
|
||||
<ErrorAlert mx="$4" mb="$4" error={saveMutation.error} />
|
||||
|
||||
<ScrollView contentContainerStyle={{ padding: "$4", pt: 0, gap: "$4" }}>
|
||||
<FormField label="Label">
|
||||
<InputField f={1} form={form} name="label" placeholder="Label..." />
|
||||
@@ -62,47 +60,17 @@ const HostForm = () => {
|
||||
form={form}
|
||||
name="port"
|
||||
keyboardType="number-pad"
|
||||
placeholder="SSH Port"
|
||||
placeholder="Port"
|
||||
/>
|
||||
</FormField>
|
||||
|
||||
{type === "pve" && <PVEFormFields form={form} />}
|
||||
{type === "incus" && <IncusFormFields form={form} />}
|
||||
|
||||
<XStack gap="$3">
|
||||
<Label flex={1} h="$3">
|
||||
Credentials
|
||||
</Label>
|
||||
<Button size="$3" icon={<Icons size={16} name="plus" />}>
|
||||
Add
|
||||
</Button>
|
||||
</XStack>
|
||||
|
||||
<FormField label="User">
|
||||
<SelectField
|
||||
form={form}
|
||||
name="keyId"
|
||||
placeholder="Select User"
|
||||
items={keys.data?.map((key: any) => ({
|
||||
label: key.label,
|
||||
value: key.id,
|
||||
}))}
|
||||
/>
|
||||
</FormField>
|
||||
|
||||
{type === "ssh" && (
|
||||
<FormField label="Private Key">
|
||||
<SelectField
|
||||
form={form}
|
||||
name="altKeyId"
|
||||
placeholder="Select Private Key"
|
||||
items={keys.data?.map((key: any) => ({
|
||||
label: key.label,
|
||||
value: key.id,
|
||||
}))}
|
||||
/>
|
||||
</FormField>
|
||||
)}
|
||||
{type === "ssh" ? (
|
||||
<SSHFormFields form={form} />
|
||||
) : type === "pve" ? (
|
||||
<PVEFormFields form={form} />
|
||||
) : type === "incus" ? (
|
||||
<IncusFormFields form={form} />
|
||||
) : null}
|
||||
</ScrollView>
|
||||
|
||||
<XStack p="$4" gap="$4">
|
||||
@@ -113,6 +81,7 @@ const HostForm = () => {
|
||||
flex={1}
|
||||
icon={<Icons name="content-save" size={18} />}
|
||||
onPress={onSubmit}
|
||||
isLoading={saveMutation.isPending}
|
||||
>
|
||||
Save
|
||||
</Button>
|
||||
@@ -121,72 +90,4 @@ const HostForm = () => {
|
||||
);
|
||||
};
|
||||
|
||||
type MiscFormFieldProps = {
|
||||
form: UseZFormReturn<FormSchema>;
|
||||
};
|
||||
|
||||
const PVEFormFields = ({ form }: MiscFormFieldProps) => {
|
||||
return (
|
||||
<>
|
||||
<FormField label="Node">
|
||||
<InputField form={form} name="metadata.node" placeholder="pve" />
|
||||
</FormField>
|
||||
<FormField label="Type">
|
||||
<SelectField
|
||||
form={form}
|
||||
name="metadata.type"
|
||||
placeholder="Select Type"
|
||||
items={pveTypes}
|
||||
/>
|
||||
</FormField>
|
||||
<FormField label="VMID">
|
||||
<InputField
|
||||
form={form}
|
||||
name="metadata.vmid"
|
||||
keyboardType="number-pad"
|
||||
placeholder="VMID"
|
||||
/>
|
||||
</FormField>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const IncusFormFields = ({ form }: MiscFormFieldProps) => {
|
||||
const type = form.watch("metadata.type");
|
||||
|
||||
return (
|
||||
<>
|
||||
<FormField label="Type">
|
||||
<SelectField
|
||||
form={form}
|
||||
name="metadata.type"
|
||||
placeholder="Select Type"
|
||||
items={incusTypes}
|
||||
/>
|
||||
</FormField>
|
||||
<FormField label="Instance ID">
|
||||
<InputField
|
||||
form={form}
|
||||
name="metadata.instance"
|
||||
placeholder="myinstance"
|
||||
/>
|
||||
</FormField>
|
||||
{type === "lxc" && (
|
||||
<>
|
||||
<FormField label="User ID">
|
||||
<InputField
|
||||
form={form}
|
||||
keyboardType="number-pad"
|
||||
name="metadata.user"
|
||||
/>
|
||||
</FormField>
|
||||
<FormField label="Shell">
|
||||
<InputField form={form} name="metadata.shell" placeholder="bash" />
|
||||
</FormField>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default HostForm;
|
||||
|
||||
@@ -8,8 +8,13 @@ import Icons from "@/components/ui/icons";
|
||||
import SearchInput from "@/components/ui/search-input";
|
||||
import { useTermSession } from "@/stores/terminal-sessions";
|
||||
import { hostFormModal } from "./form";
|
||||
import OSIcons from "@/components/ui/os-icons";
|
||||
|
||||
const HostsList = () => {
|
||||
type HostsListProps = {
|
||||
allowEdit?: boolean;
|
||||
};
|
||||
|
||||
const HostsList = ({ allowEdit = true }: HostsListProps) => {
|
||||
const openSession = useTermSession((i) => i.push);
|
||||
const navigation = useNavigation();
|
||||
const [search, setSearch] = useState("");
|
||||
@@ -37,6 +42,7 @@ const HostsList = () => {
|
||||
}, [hosts.data, search]);
|
||||
|
||||
const onOpen = (host: any) => {
|
||||
if (!allowEdit) return;
|
||||
hostFormModal.onOpen(host);
|
||||
};
|
||||
|
||||
@@ -88,9 +94,9 @@ const HostsList = () => {
|
||||
flexBasis="100%"
|
||||
cursor="pointer"
|
||||
$gtXs={{ flexBasis: "50%" }}
|
||||
$gtSm={{ flexBasis: "33.3%" }}
|
||||
$gtMd={{ flexBasis: "25%" }}
|
||||
$gtLg={{ flexBasis: "20%" }}
|
||||
$gtMd={{ flexBasis: "33.3%" }}
|
||||
$gtLg={{ flexBasis: "25%" }}
|
||||
$gtXl={{ flexBasis: "20%" }}
|
||||
p="$2"
|
||||
group
|
||||
numberOfTaps={2}
|
||||
@@ -99,6 +105,13 @@ const HostsList = () => {
|
||||
>
|
||||
<Card bordered p="$4">
|
||||
<XStack>
|
||||
<OSIcons
|
||||
name={host.os}
|
||||
size={18}
|
||||
mr="$2"
|
||||
fallback="desktop-classic"
|
||||
/>
|
||||
|
||||
<View flex={1}>
|
||||
<Text>{host.label}</Text>
|
||||
<Text fontSize="$3" mt="$2">
|
||||
@@ -106,18 +119,20 @@ const HostsList = () => {
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
<Button
|
||||
circular
|
||||
display="none"
|
||||
$sm={{ display: "block" }}
|
||||
$group-hover={{ display: "block" }}
|
||||
onPress={(e) => {
|
||||
e.stopPropagation();
|
||||
onOpen(host);
|
||||
}}
|
||||
>
|
||||
<Icons name="pencil" size={16} />
|
||||
</Button>
|
||||
{allowEdit && (
|
||||
<Button
|
||||
circular
|
||||
display="none"
|
||||
$sm={{ display: "block" }}
|
||||
$group-hover={{ display: "block" }}
|
||||
onPress={(e) => {
|
||||
e.stopPropagation();
|
||||
onOpen(host);
|
||||
}}
|
||||
>
|
||||
<Icons name="pencil" size={16} />
|
||||
</Button>
|
||||
)}
|
||||
</XStack>
|
||||
</Card>
|
||||
</MultiTapPressable>
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
import FormField from "@/components/ui/form";
|
||||
import { MiscFormFieldProps } from "../types";
|
||||
import { InputField } from "@/components/ui/input";
|
||||
import { SelectField } from "@/components/ui/select";
|
||||
import { incusTypes } from "../schema/form";
|
||||
import CredentialsSection from "./credentials-section";
|
||||
import { useKeychainsOptions } from "../hooks/query";
|
||||
|
||||
export const IncusFormFields = ({ form }: MiscFormFieldProps) => {
|
||||
const keys = useKeychainsOptions();
|
||||
const type = form.watch("metadata.type");
|
||||
|
||||
return (
|
||||
<>
|
||||
<FormField label="Type">
|
||||
<SelectField
|
||||
form={form}
|
||||
name="metadata.type"
|
||||
placeholder="Select Type"
|
||||
items={incusTypes}
|
||||
/>
|
||||
</FormField>
|
||||
<FormField label="Instance ID">
|
||||
<InputField
|
||||
form={form}
|
||||
name="metadata.instance"
|
||||
placeholder="myinstance"
|
||||
/>
|
||||
</FormField>
|
||||
{type === "lxc" && (
|
||||
<>
|
||||
<FormField label="User ID">
|
||||
<InputField
|
||||
form={form}
|
||||
keyboardType="number-pad"
|
||||
name="metadata.user"
|
||||
/>
|
||||
</FormField>
|
||||
<FormField label="Shell">
|
||||
<InputField form={form} name="metadata.shell" placeholder="bash" />
|
||||
</FormField>
|
||||
</>
|
||||
)}
|
||||
|
||||
<CredentialsSection />
|
||||
|
||||
<FormField label="Client Certificate">
|
||||
<SelectField
|
||||
form={form}
|
||||
name="keyId"
|
||||
placeholder="Select Certificate"
|
||||
items={keys.filter((i) => i.type === "cert")}
|
||||
/>
|
||||
</FormField>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,46 @@
|
||||
import FormField from "@/components/ui/form";
|
||||
import { MiscFormFieldProps } from "../types";
|
||||
import { InputField } from "@/components/ui/input";
|
||||
import { SelectField } from "@/components/ui/select";
|
||||
import { pveTypes } from "../schema/form";
|
||||
import { useKeychainsOptions } from "../hooks/query";
|
||||
import CredentialsSection from "./credentials-section";
|
||||
|
||||
export const PVEFormFields = ({ form }: MiscFormFieldProps) => {
|
||||
const keys = useKeychainsOptions();
|
||||
|
||||
return (
|
||||
<>
|
||||
<FormField label="Node">
|
||||
<InputField form={form} name="metadata.node" placeholder="pve" />
|
||||
</FormField>
|
||||
<FormField label="Type">
|
||||
<SelectField
|
||||
form={form}
|
||||
name="metadata.type"
|
||||
placeholder="Select Type"
|
||||
items={pveTypes}
|
||||
/>
|
||||
</FormField>
|
||||
<FormField label="VMID">
|
||||
<InputField
|
||||
form={form}
|
||||
name="metadata.vmid"
|
||||
keyboardType="number-pad"
|
||||
placeholder="VMID"
|
||||
/>
|
||||
</FormField>
|
||||
|
||||
<CredentialsSection />
|
||||
|
||||
<FormField label="Account">
|
||||
<SelectField
|
||||
form={form}
|
||||
name="keyId"
|
||||
placeholder="Select Account"
|
||||
items={keys.filter((i) => i.type === "pve")}
|
||||
/>
|
||||
</FormField>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,33 @@
|
||||
import FormField from "@/components/ui/form";
|
||||
import { MiscFormFieldProps } from "../types";
|
||||
import { SelectField } from "@/components/ui/select";
|
||||
import CredentialsSection from "./credentials-section";
|
||||
import { useKeychainsOptions } from "../hooks/query";
|
||||
|
||||
export const SSHFormFields = ({ form }: MiscFormFieldProps) => {
|
||||
const keys = useKeychainsOptions();
|
||||
|
||||
return (
|
||||
<>
|
||||
<CredentialsSection />
|
||||
|
||||
<FormField label="User">
|
||||
<SelectField
|
||||
form={form}
|
||||
name="keyId"
|
||||
placeholder="Select User"
|
||||
items={keys.filter((i) => i.type === "user")}
|
||||
/>
|
||||
</FormField>
|
||||
|
||||
<FormField label="Private Key">
|
||||
<SelectField
|
||||
form={form}
|
||||
name="altKeyId"
|
||||
placeholder="Select Private Key"
|
||||
items={keys.filter((i) => i.type === "rsa")}
|
||||
/>
|
||||
</FormField>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -1,6 +1,7 @@
|
||||
import { useMutation, useQuery } from "@tanstack/react-query";
|
||||
import { FormSchema } from "../schema/form";
|
||||
import api, { queryClient } from "@/lib/api";
|
||||
import { useMemo } from "react";
|
||||
|
||||
export const useKeychains = () => {
|
||||
return useQuery({
|
||||
@@ -10,6 +11,22 @@ export const useKeychains = () => {
|
||||
});
|
||||
};
|
||||
|
||||
export const useKeychainsOptions = () => {
|
||||
const keys = useKeychains();
|
||||
|
||||
const data = useMemo(() => {
|
||||
const items: any[] = keys.data || [];
|
||||
|
||||
return items.map((key: any) => ({
|
||||
type: key.type,
|
||||
label: key.label,
|
||||
value: key.id,
|
||||
}));
|
||||
}, [keys.data]);
|
||||
|
||||
return data;
|
||||
};
|
||||
|
||||
export const useSaveHost = () => {
|
||||
return useMutation({
|
||||
mutationFn: async (body: FormSchema) => {
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
import { UseZFormReturn } from "@/hooks/useZForm";
|
||||
import { FormSchema } from "./schema/form";
|
||||
|
||||
export type MiscFormFieldProps = {
|
||||
form: UseZFormReturn<FormSchema>;
|
||||
};
|
||||
Reference in New Issue
Block a user