mirror of
https://github.com/khairul169/vaulterm.git
synced 2026-09-15 17:03:30 +07:00
feat: add keychains
This commit is contained in:
@@ -0,0 +1,87 @@
|
||||
import Icons from "@/components/ui/icons";
|
||||
import Modal from "@/components/ui/modal";
|
||||
import { SelectField } from "@/components/ui/select";
|
||||
import { useZForm } from "@/hooks/useZForm";
|
||||
import { createDisclosure } from "@/lib/utils";
|
||||
import React from "react";
|
||||
import { ScrollView, Sheet, XStack } from "tamagui";
|
||||
import { FormSchema, formSchema, typeOptions } from "../schema/form";
|
||||
import { InputField } from "@/components/ui/input";
|
||||
import FormField from "@/components/ui/form";
|
||||
import { useSaveKeychain } from "../hooks/query";
|
||||
import { ErrorAlert } from "@/components/ui/alert";
|
||||
import Button from "@/components/ui/button";
|
||||
import {
|
||||
UserTypeInputFields,
|
||||
PVETypeInputFields,
|
||||
RSATypeInputFields,
|
||||
CertTypeInputFields,
|
||||
} from "./input-fields";
|
||||
|
||||
export const keyFormModal = createDisclosure<FormSchema>();
|
||||
|
||||
const KeyForm = () => {
|
||||
const { data } = keyFormModal.use();
|
||||
const form = useZForm(formSchema, data);
|
||||
const isEditing = data?.id != null;
|
||||
const type = form.watch("type");
|
||||
|
||||
const saveMutation = useSaveKeychain();
|
||||
|
||||
const onSubmit = form.handleSubmit((values) => {
|
||||
saveMutation.mutate(values, {
|
||||
onSuccess: () => {
|
||||
keyFormModal.onClose();
|
||||
form.reset();
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
return (
|
||||
<Modal
|
||||
disclosure={keyFormModal}
|
||||
title="Keychain"
|
||||
description={`${isEditing ? "Edit" : "Add new"} key.`}
|
||||
>
|
||||
<ErrorAlert mx="$4" mb="$4" error={saveMutation.error} />
|
||||
|
||||
<Sheet.ScrollView
|
||||
contentContainerStyle={{ padding: "$4", pt: 0, gap: "$4" }}
|
||||
>
|
||||
<FormField label="Label">
|
||||
<InputField f={1} form={form} name="label" placeholder="Label..." />
|
||||
</FormField>
|
||||
|
||||
<FormField label="Type">
|
||||
<SelectField form={form} name="type" items={typeOptions} />
|
||||
</FormField>
|
||||
|
||||
{type === "user" ? (
|
||||
<UserTypeInputFields form={form} />
|
||||
) : type === "pve" ? (
|
||||
<PVETypeInputFields form={form} />
|
||||
) : type === "rsa" ? (
|
||||
<RSATypeInputFields form={form} />
|
||||
) : type === "cert" ? (
|
||||
<CertTypeInputFields form={form} />
|
||||
) : null}
|
||||
</Sheet.ScrollView>
|
||||
|
||||
<XStack p="$4" gap="$4">
|
||||
<Button flex={1} onPress={keyFormModal.onClose} bg="$colorTransparent">
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
flex={1}
|
||||
icon={<Icons name="content-save" size={18} />}
|
||||
onPress={onSubmit}
|
||||
isLoading={saveMutation.isPending}
|
||||
>
|
||||
Save
|
||||
</Button>
|
||||
</XStack>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
export default KeyForm;
|
||||
@@ -0,0 +1,68 @@
|
||||
import React, { useMemo } from "react";
|
||||
import { UseFormReturn } from "react-hook-form";
|
||||
import { FormSchema, pveRealms } from "../schema/form";
|
||||
import FormField from "@/components/ui/form";
|
||||
import { InputField, TextAreaField } from "@/components/ui/input";
|
||||
import { SelectField } from "@/components/ui/select";
|
||||
|
||||
type Props = {
|
||||
form: UseFormReturn<FormSchema>;
|
||||
};
|
||||
|
||||
export const UserTypeInputFields = ({ form }: Props) => {
|
||||
return (
|
||||
<>
|
||||
<FormField label="Username">
|
||||
<InputField f={1} form={form} name="data.username" />
|
||||
</FormField>
|
||||
<FormField label="Password">
|
||||
<InputField f={1} form={form} name="data.password" />
|
||||
</FormField>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export const PVETypeInputFields = ({ form }: Props) => {
|
||||
return (
|
||||
<>
|
||||
<FormField label="Username">
|
||||
<InputField f={1} form={form} name="data.username" />
|
||||
</FormField>
|
||||
<FormField label="Realm">
|
||||
<SelectField form={form} name="data.realm" items={pveRealms} />
|
||||
</FormField>
|
||||
<FormField label="Password">
|
||||
<InputField f={1} form={form} name="data.password" />
|
||||
</FormField>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export const RSATypeInputFields = ({ form }: Props) => {
|
||||
return (
|
||||
<>
|
||||
{/* <FormField label="Public Key">
|
||||
<TextAreaField rows={7} f={1} form={form} name="data.public" />
|
||||
</FormField> */}
|
||||
<FormField label="Private Key">
|
||||
<TextAreaField rows={7} f={1} form={form} name="data.private" />
|
||||
</FormField>
|
||||
<FormField label="Passphrase">
|
||||
<InputField f={1} form={form} name="data.passphrase" />
|
||||
</FormField>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export const CertTypeInputFields = ({ form }: Props) => {
|
||||
return (
|
||||
<>
|
||||
<FormField label="Client Certificate">
|
||||
<TextAreaField rows={7} f={1} form={form} name="data.cert" />
|
||||
</FormField>
|
||||
<FormField label="Client Key">
|
||||
<TextAreaField rows={7} f={1} form={form} name="data.key" />
|
||||
</FormField>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,56 @@
|
||||
import { View, Text, Button, Card, XStack } from "tamagui";
|
||||
import React from "react";
|
||||
import Pressable from "@/components/ui/pressable";
|
||||
import Icons from "@/components/ui/icons";
|
||||
|
||||
type KeyItemProps = {
|
||||
data: any;
|
||||
onPress?: () => void;
|
||||
};
|
||||
|
||||
const icons: Record<string, string> = {
|
||||
user: "account",
|
||||
pve: "account-key",
|
||||
rsa: "key",
|
||||
cert: "certificate",
|
||||
};
|
||||
|
||||
const KeyItem = ({ data, onPress }: KeyItemProps) => {
|
||||
return (
|
||||
<Pressable group onPress={onPress}>
|
||||
<Card bordered px="$4" py="$3">
|
||||
<XStack alignItems="center">
|
||||
<Icons
|
||||
name={(icons[data.type] || "key") as never}
|
||||
size={20}
|
||||
mr="$3"
|
||||
/>
|
||||
|
||||
<View flex={1}>
|
||||
<Text textAlign="left">{data.label}</Text>
|
||||
<Text textAlign="left" fontSize="$3" mt="$1">
|
||||
{data.type}
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
<Button
|
||||
circular
|
||||
opacity={0}
|
||||
$sm={{ opacity: 1 }}
|
||||
animation="quickest"
|
||||
animateOnly={["opacity"]}
|
||||
$group-hover={{ opacity: 1 }}
|
||||
onPress={(e) => {
|
||||
e.stopPropagation();
|
||||
onPress?.();
|
||||
}}
|
||||
>
|
||||
<Icons name="pencil" size={16} />
|
||||
</Button>
|
||||
</XStack>
|
||||
</Card>
|
||||
</Pressable>
|
||||
);
|
||||
};
|
||||
|
||||
export default KeyItem;
|
||||
@@ -0,0 +1,60 @@
|
||||
import { View, Text, Spinner } from "tamagui";
|
||||
import React, { useMemo, useState } from "react";
|
||||
import SearchInput from "@/components/ui/search-input";
|
||||
import GridView from "@/components/ui/grid-view";
|
||||
import { useKeychains } from "../hooks/query";
|
||||
import KeyItem from "./key-item";
|
||||
import { keyFormModal } from "./form";
|
||||
|
||||
const KeyList = () => {
|
||||
const [search, setSearch] = useState("");
|
||||
const keys = useKeychains({ withData: true });
|
||||
|
||||
const keyList = useMemo(() => {
|
||||
let items = keys.data || [];
|
||||
|
||||
if (search) {
|
||||
items = items.filter((item: any) => {
|
||||
const q = search.toLowerCase();
|
||||
return item.label.toLowerCase().includes(q);
|
||||
});
|
||||
}
|
||||
|
||||
return items.map((i: any) => ({ ...i, key: i.id }));
|
||||
}, [keys.data, search]);
|
||||
|
||||
const onEdit = (item: any) => {
|
||||
keyFormModal.onOpen(item);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<View p="$4" pb="$3">
|
||||
<SearchInput
|
||||
placeholder="Search key..."
|
||||
value={search}
|
||||
onChangeText={setSearch}
|
||||
/>
|
||||
</View>
|
||||
|
||||
{keys.isLoading ? (
|
||||
<View alignItems="center" justifyContent="center" flex={1}>
|
||||
<Spinner size="large" />
|
||||
<Text mt="$4">Loading...</Text>
|
||||
</View>
|
||||
) : (
|
||||
<GridView
|
||||
data={keyList}
|
||||
columns={{ sm: 2, lg: 3, xl: 4 }}
|
||||
contentContainerStyle={{ p: "$2", pt: 0 }}
|
||||
gap="$2.5"
|
||||
renderItem={(item: any) => (
|
||||
<KeyItem data={item} onPress={() => onEdit(item)} />
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(KeyList);
|
||||
Reference in New Issue
Block a user