This commit is contained in:
yoan
2024-10-11 21:53:54 +08:00
parent 3b06c7b0a6
commit 7d74e1d41e
39 changed files with 6948 additions and 178 deletions

View File

@@ -1,7 +1,18 @@
import { createContext, useContext, useEffect, useState } from "react";
import {
createContext,
useCallback,
useContext,
useEffect,
useState,
} from "react";
import { Button } from "../ui/button";
import { EditIcon, Plus, Trash2 } from "lucide-react";
import { DeployConfig, targetTypeKeys, targetTypeMap } from "@/domain/domain";
import {
DeployConfig,
KVType,
targetTypeKeys,
targetTypeMap,
} from "@/domain/domain";
import Show from "../Show";
import { Alert, AlertDescription } from "../ui/alert";
import {
@@ -25,23 +36,25 @@ import {
SelectTrigger,
SelectValue,
} from "../ui/select";
import { Access, accessTypeMap } from "@/domain/access";
import { accessTypeMap } from "@/domain/access";
import { useTranslation } from "react-i18next";
import { AccessEdit } from "./AccessEdit";
import { Input } from "../ui/input";
import { Textarea } from "../ui/textarea";
import KVList from "./KVList";
import { produce } from "immer";
type DeployListContextProps = {
deploys: DeployConfig[];
type DeployEditContextProps = {
deploy: DeployConfig;
setDeploy: (deploy: DeployConfig) => void;
};
const DeployListContext = createContext<DeployListContextProps>({
deploys: [],
});
const DeployEditContext = createContext<DeployEditContextProps>(
{} as DeployEditContextProps
);
export const useDeployListContext = () => {
return useContext(DeployListContext);
export const useDeployEditContext = () => {
return useContext(DeployEditContext);
};
type DeployListProps = {
@@ -57,74 +70,98 @@ const DeployList = ({ deploys }: DeployListProps) => {
return (
<>
<DeployListContext.Provider value={{ deploys: deploys }}>
<Show
when={list.length > 0}
fallback={
<Alert className="w-full">
<AlertDescription className="flex flex-col items-center">
<div></div>
<div className="flex justify-end mt-2">
<DeployEditDialog
trigger={<Button size={"sm"}></Button>}
/>
</div>
</AlertDescription>
</Alert>
}
>
<div className="flex justify-end py-2 border-b">
<DeployEditDialog trigger={<Button size={"sm"}></Button>} />
</div>
<Show
when={list.length > 0}
fallback={
<Alert className="w-full">
<AlertDescription className="flex flex-col items-center">
<div></div>
<div className="flex justify-end mt-2">
<DeployEditDialog
trigger={<Button size={"sm"}></Button>}
/>
</div>
</AlertDescription>
</Alert>
}
>
<div className="flex justify-end py-2 border-b">
<DeployEditDialog trigger={<Button size={"sm"}></Button>} />
</div>
<div className="w-full md:w-[35em] rounded mt-5 border">
<div className="">
<div className="flex justify-between text-sm p-3 items-center text-stone-700">
<div className="flex space-x-2 items-center">
<div>
<img src="/imgs/providers/ssh.svg" className="w-9"></img>
</div>
<div className="text-stone-600 flex-col flex space-y-0">
<div>ssh部署</div>
<div></div>
</div>
<div className="w-full md:w-[35em] rounded mt-5 border">
<div className="">
<div className="flex justify-between text-sm p-3 items-center text-stone-700">
<div className="flex space-x-2 items-center">
<div>
<img src="/imgs/providers/ssh.svg" className="w-9"></img>
</div>
<div className="flex space-x-2">
<EditIcon size={16} className="cursor-pointer" />
<Trash2 size={16} className="cursor-pointer" />
<div className="text-stone-600 flex-col flex space-y-0">
<div>ssh部署</div>
<div></div>
</div>
</div>
<div className="flex space-x-2">
<EditIcon size={16} className="cursor-pointer" />
<Trash2 size={16} className="cursor-pointer" />
</div>
</div>
</div>
</Show>
</DeployListContext.Provider>
</div>
</Show>
</>
);
};
type DeployEditDialogProps = {
trigger: React.ReactNode;
deployConfig?: DeployConfig;
};
const DeployEditDialog = ({ trigger }: DeployEditDialogProps) => {
const DeployEditDialog = ({ trigger, deployConfig }: DeployEditDialogProps) => {
const {
config: { accesses },
} = useConfig();
const [access, setAccess] = useState<Access>();
const [deployType, setDeployType] = useState<TargetType>();
const [accessType, setAccessType] = useState("");
const [locDeployConfig, setLocDeployConfig] = useState<DeployConfig>({
access: "",
type: "",
});
useEffect(() => {
const temp = accessType.split("-");
if (deployConfig) {
setLocDeployConfig({ ...deployConfig });
} else {
setLocDeployConfig({
access: "",
type: "",
});
}
}, [deployConfig]);
useEffect(() => {
const temp = locDeployConfig.type.split("-");
console.log(temp);
let t;
if (temp && temp.length > 1) {
t = temp[1];
} else {
t = accessType;
t = locDeployConfig.type;
}
setDeployType(t as TargetType);
}, [accessType]);
}, [locDeployConfig.type]);
const setDeploy = useCallback(
(deploy: DeployConfig) => {
if (deploy.type !== locDeployConfig.type) {
setLocDeployConfig({ ...deploy, access: "", config: {} });
} else {
setLocDeployConfig({ ...deploy });
}
},
[locDeployConfig.type]
);
const { t } = useTranslation();
@@ -133,114 +170,121 @@ const DeployEditDialog = ({ trigger }: DeployEditDialogProps) => {
return false;
}
if (accessType == "") {
if (locDeployConfig.type == "") {
return true;
}
const types = accessType.split("-");
const types = locDeployConfig.type.split("-");
return item.configType === types[0];
});
return (
<Dialog>
<DialogTrigger>{trigger}</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle></DialogTitle>
<DialogDescription></DialogDescription>
</DialogHeader>
{/* 授权类型 */}
<div>
<Label></Label>
<DeployEditContext.Provider
value={{
deploy: locDeployConfig,
setDeploy: setDeploy,
}}
>
<Dialog>
<DialogTrigger>{trigger}</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle></DialogTitle>
<DialogDescription></DialogDescription>
</DialogHeader>
{/* 授权类型 */}
<div>
<Label></Label>
<Select
value={accessType}
onValueChange={(val: string) => {
setAccessType(val);
}}
>
<SelectTrigger className="mt-2">
<SelectValue
placeholder={t(
"domain.management.edit.access.not.empty.message"
)}
<Select
value={locDeployConfig.type}
onValueChange={(val: string) => {
setDeploy({ ...locDeployConfig, type: val });
}}
>
<SelectTrigger className="mt-2">
<SelectValue
placeholder={t(
"domain.management.edit.access.not.empty.message"
)}
/>
</SelectTrigger>
<SelectContent>
<SelectGroup>
<SelectLabel>
{t("domain.management.edit.access.label")}
</SelectLabel>
{targetTypeKeys.map((item) => (
<SelectItem key={item} value={item}>
<div className="flex items-center space-x-2">
<img
className="w-6"
src={targetTypeMap.get(item)?.[1]}
/>
<div>{t(targetTypeMap.get(item)?.[0] ?? "")}</div>
</div>
</SelectItem>
))}
</SelectGroup>
</SelectContent>
</Select>
</div>
{/* 授权 */}
<div>
<Label className="flex justify-between">
<div></div>
<AccessEdit
trigger={
<div className="font-normal text-primary hover:underline cursor-pointer flex items-center">
<Plus size={14} />
{t("add")}
</div>
}
op="add"
/>
</SelectTrigger>
<SelectContent>
<SelectGroup>
<SelectLabel>
{t("domain.management.edit.access.label")}
</SelectLabel>
{targetTypeKeys.map((item) => (
<SelectItem key={item} value={item}>
<div className="flex items-center space-x-2">
<img className="w-6" src={targetTypeMap.get(item)?.[1]} />
<div>{t(targetTypeMap.get(item)?.[0] ?? "")}</div>
</div>
</SelectItem>
))}
</SelectGroup>
</SelectContent>
</Select>
</div>
{/* 授权 */}
<div>
<Label className="flex justify-between">
<div></div>
<AccessEdit
trigger={
<div className="font-normal text-primary hover:underline cursor-pointer flex items-center">
<Plus size={14} />
{t("add")}
</div>
}
op="add"
/>
</Label>
</Label>
<Select
value={access?.id}
onValueChange={(val: string) => {
const temp = accesses.find((access) => {
return access.id == val;
});
setAccess(temp!);
}}
>
<SelectTrigger className="mt-2">
<SelectValue
placeholder={t(
"domain.management.edit.access.not.empty.message"
)}
/>
</SelectTrigger>
<SelectContent>
<SelectGroup>
<SelectLabel>
{t("domain.management.edit.access.label")}
</SelectLabel>
{targetAccesses.map((item) => (
<SelectItem key={item.id} value={item.id}>
<div className="flex items-center space-x-2">
<img
className="w-6"
src={accessTypeMap.get(item.configType)?.[1]}
/>
<div>{item.name}</div>
</div>
</SelectItem>
))}
</SelectGroup>
</SelectContent>
</Select>
</div>
<Select
value={locDeployConfig.access}
onValueChange={(val: string) => {
setDeploy({ ...locDeployConfig, access: val });
}}
>
<SelectTrigger className="mt-2">
<SelectValue
placeholder={t(
"domain.management.edit.access.not.empty.message"
)}
/>
</SelectTrigger>
<SelectContent>
<SelectGroup>
<SelectLabel>
{t("domain.management.edit.access.label")}
</SelectLabel>
{targetAccesses.map((item) => (
<SelectItem key={item.id} value={item.id}>
<div className="flex items-center space-x-2">
<img
className="w-6"
src={accessTypeMap.get(item.configType)?.[1]}
/>
<div>{item.name}</div>
</div>
</SelectItem>
))}
</SelectGroup>
</SelectContent>
</Select>
</div>
<DeployEdit type={deployType!} />
<DeployEdit type={deployType!} />
<DialogFooter>
<Button></Button>
</DialogFooter>
</DialogContent>
</Dialog>
<DialogFooter>
<Button></Button>
</DialogFooter>
</DialogContent>
</Dialog>
</DeployEditContext.Provider>
);
};
@@ -248,36 +292,33 @@ type TargetType = "ssh" | "cdn" | "webhook" | "local" | "oss" | "dcdn";
type DeployEditProps = {
type: TargetType;
data?: DeployConfig;
};
const DeployEdit = ({ type, data }: DeployEditProps) => {
const DeployEdit = ({ type }: DeployEditProps) => {
const getDeploy = () => {
switch (type) {
case "ssh":
return <DeploySSH data={data} />;
return <DeploySSH />;
case "local":
return <DeploySSH data={data} />;
return <DeploySSH />;
case "cdn":
return <DeployCDN data={data} />;
return <DeployCDN />;
case "dcdn":
return <DeployCDN data={data} />;
return <DeployCDN />;
case "oss":
return <DeployCDN data={data} />;
return <DeployCDN />;
case "webhook":
return <DeployWebhook data={data} />;
return <DeployWebhook />;
default:
return <DeployCDN data={data} />;
return <DeployCDN />;
}
};
return getDeploy();
};
type DeployProps = {
data?: DeployConfig;
};
const DeploySSH = ({ data }: DeployProps) => {
const DeploySSH = () => {
const { t } = useTranslation();
const { deploy: data, setDeploy } = useDeployEditContext();
return (
<>
<div className="flex flex-col space-y-2">
@@ -287,6 +328,15 @@ const DeploySSH = ({ data }: DeployProps) => {
placeholder={t("access.form.ssh.cert.path")}
className="w-full mt-1"
value={data?.config?.certPath}
onChange={(e) => {
const newData = produce(data, (draft) => {
if (!draft.config) {
draft.config = {};
}
draft.config.certPath = e.target.value;
});
setDeploy(newData);
}}
/>
</div>
<div>
@@ -295,24 +345,58 @@ const DeploySSH = ({ data }: DeployProps) => {
placeholder={t("access.form.ssh.key.path")}
className="w-full mt-1"
value={data?.config?.keyPath}
onChange={(e) => {
const newData = produce(data, (draft) => {
if (!draft.config) {
draft.config = {};
}
draft.config.keyPath = e.target.value;
});
setDeploy(newData);
}}
/>
</div>
<div>
<Label></Label>
<Textarea className="mt-1"></Textarea>
<Textarea
className="mt-1"
value={data?.config?.preCommand}
onChange={(e) => {
const newData = produce(data, (draft) => {
if (!draft.config) {
draft.config = {};
}
draft.config.preCommand = e.target.value;
});
setDeploy(newData);
}}
></Textarea>
</div>
<div>
<Label></Label>
<Textarea className="mt-1"></Textarea>
<Label></Label>
<Textarea
className="mt-1"
value={data?.config?.command}
onChange={(e) => {
const newData = produce(data, (draft) => {
if (!draft.config) {
draft.config = {};
}
draft.config.command = e.target.value;
});
setDeploy(newData);
}}
></Textarea>
</div>
</div>
</>
);
};
const DeployCDN = ({ data }: DeployProps) => {
const DeployCDN = () => {
const { deploy: data, setDeploy } = useDeployEditContext();
return (
<div className="flex flex-col space-y-2">
<div>
@@ -321,16 +405,38 @@ const DeployCDN = ({ data }: DeployProps) => {
placeholder="部署至域名"
className="w-full mt-1"
value={data?.config?.domain}
onChange={(e) => {
const newData = produce(data, (draft) => {
if (!draft.config) {
draft.config = {};
}
draft.config.domain = e.target.value;
});
setDeploy(newData);
}}
/>
</div>
</div>
);
};
const DeployWebhook = ({ data }: DeployProps) => {
const DeployWebhook = () => {
const { deploy: data, setDeploy } = useDeployEditContext();
return (
<>
<KVList variables={data?.config?.variables} />
<KVList
variables={data?.config?.variables}
onValueChange={(variables: KVType[]) => {
const newData = produce(data, (draft) => {
if (!draft.config) {
draft.config = {};
}
draft.config.variables = variables;
});
setDeploy(newData);
}}
/>
</>
);
};

View File

@@ -15,11 +15,14 @@ import {
import { Input } from "../ui/input";
import { Button } from "../ui/button";
import { produce } from "immer";
type KVListProps = {
variables?: KVType[];
onValueChange?: (variables: KVType[]) => void;
};
const KVList = ({ variables }: KVListProps) => {
const KVList = ({ variables, onValueChange }: KVListProps) => {
const [locVariables, setLocVariables] = useState<KVType[]>([]);
const { t } = useTranslation();
@@ -36,25 +39,33 @@ const KVList = ({ variables }: KVListProps) => {
return item.key === variable.key;
});
if (index === -1) {
setLocVariables([...locVariables, variable]);
} else {
const newList = [...locVariables];
newList[index] = variable;
setLocVariables(newList);
}
const newList = produce(locVariables, (draft) => {
if (index === -1) {
draft.push(variable);
} else {
draft[index] = variable;
}
});
setLocVariables(newList);
onValueChange?.(newList);
};
const handleDeleteClick = (index: number) => {
const newList = [...locVariables];
newList.splice(index, 1);
setLocVariables(newList);
onValueChange?.(newList);
};
const handleEditClick = (index: number, variable: KVType) => {
const newList = [...locVariables];
newList[index] = variable;
setLocVariables(newList);
onValueChange?.(newList);
};
return (
@@ -159,6 +170,30 @@ const KVEdit = ({ variable, trigger, onSave }: KVEditProps) => {
const [open, setOpen] = useState<boolean>(false);
const [err, setErr] = useState<Record<string, string>>({});
const handleSaveClick = () => {
if (!locVariable.key) {
setErr({
key: t("name.required"),
});
return;
}
if (!locVariable.value) {
setErr({
value: t("value.required"),
});
return;
}
onSave?.(locVariable);
setOpen(false);
setErr({});
};
return (
<Dialog
open={open}
@@ -181,6 +216,7 @@ const KVEdit = ({ variable, trigger, onSave }: KVEditProps) => {
}}
className="w-full mt-1"
/>
<div className="text-red-500 text-sm mt-1">{err?.key}</div>
</div>
<div className="pt-2 flex flex-col items-start">
@@ -193,14 +229,15 @@ const KVEdit = ({ variable, trigger, onSave }: KVEditProps) => {
}}
className="w-full mt-1"
/>
<div className="text-red-500 text-sm mt-1">{err?.value}</div>
</div>
</DialogHeader>
<DialogFooter>
<div className="flex justify-end">
<Button
onClick={() => {
onSave?.(locVariable);
setOpen(false);
handleSaveClick();
}}
>
{t("save")}