diff --git a/migrations/1729241998_updated_access.go b/migrations/1729241998_updated_access.go new file mode 100644 index 00000000..65ac594e --- /dev/null +++ b/migrations/1729241998_updated_access.go @@ -0,0 +1,95 @@ +package migrations + +import ( + "encoding/json" + + "github.com/pocketbase/dbx" + "github.com/pocketbase/pocketbase/daos" + m "github.com/pocketbase/pocketbase/migrations" + "github.com/pocketbase/pocketbase/models/schema" +) + +func init() { + m.Register(func(db dbx.Builder) error { + dao := daos.New(db); + + collection, err := dao.FindCollectionByNameOrId("4yzbv8urny5ja1e") + if err != nil { + return err + } + + // update + edit_configType := &schema.SchemaField{} + if err := json.Unmarshal([]byte(`{ + "system": false, + "id": "hwy7m03o", + "name": "configType", + "type": "select", + "required": false, + "presentable": false, + "unique": false, + "options": { + "maxSelect": 1, + "values": [ + "aliyun", + "tencent", + "huaweicloud", + "qiniu", + "aws", + "cloudflare", + "namesilo", + "godaddy", + "local", + "ssh", + "webhook", + "k8s" + ] + } + }`), edit_configType); err != nil { + return err + } + collection.Schema.AddField(edit_configType) + + return dao.SaveCollection(collection) + }, func(db dbx.Builder) error { + dao := daos.New(db); + + collection, err := dao.FindCollectionByNameOrId("4yzbv8urny5ja1e") + if err != nil { + return err + } + + // update + edit_configType := &schema.SchemaField{} + if err := json.Unmarshal([]byte(`{ + "system": false, + "id": "hwy7m03o", + "name": "configType", + "type": "select", + "required": false, + "presentable": false, + "unique": false, + "options": { + "maxSelect": 1, + "values": [ + "aliyun", + "tencent", + "huaweicloud", + "qiniu", + "aws", + "cloudflare", + "namesilo", + "godaddy", + "local", + "ssh", + "webhook" + ] + } + }`), edit_configType); err != nil { + return err + } + collection.Schema.AddField(edit_configType) + + return dao.SaveCollection(collection) + }) +} diff --git a/ui/public/imgs/providers/k8s.svg b/ui/public/imgs/providers/k8s.svg new file mode 100644 index 00000000..b7f555f7 --- /dev/null +++ b/ui/public/imgs/providers/k8s.svg @@ -0,0 +1 @@ + diff --git a/ui/src/components/certimate/AccessEdit.tsx b/ui/src/components/certimate/AccessEdit.tsx index 42017606..b265b15f 100644 --- a/ui/src/components/certimate/AccessEdit.tsx +++ b/ui/src/components/certimate/AccessEdit.tsx @@ -17,6 +17,7 @@ import AccessGodaddyForm from "./AccessGodaddyForm"; import AccessLocalForm from "./AccessLocalForm"; import AccessSSHForm from "./AccessSSHForm"; import AccessWebhookForm from "./AccessWebhookForm"; +import AccessKubernetesForm from "./AccessKubernetesForm"; import { Access, accessTypeMap } from "@/domain/access"; type AccessEditProps = { @@ -157,6 +158,17 @@ const AccessEdit = ({ trigger, op, data, className }: AccessEditProps) => { /> ); break; + case "k8s": + form = ( + { + setOpen(false); + }} + /> + ); + break; } const getOptionCls = (val: string) => { diff --git a/ui/src/components/certimate/AccessKubernetesForm.tsx b/ui/src/components/certimate/AccessKubernetesForm.tsx new file mode 100644 index 00000000..a088cb99 --- /dev/null +++ b/ui/src/components/certimate/AccessKubernetesForm.tsx @@ -0,0 +1,195 @@ +import { useRef, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { useForm } from "react-hook-form"; +import { z } from "zod"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { ClientResponseError } from "pocketbase"; + +import { Access, accessFormType, getUsageByConfigType, KubernetesConfig } from "@/domain/access"; +import { Button } from "@/components/ui/button"; +import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form"; +import { Input } from "@/components/ui/input"; +import { readFileContent } from "@/lib/file"; +import { PbErrorData } from "@/domain/base"; +import { save } from "@/repository/access"; +import { useConfig } from "@/providers/config"; + +type AccessKubernetesFormProps = { + op: "add" | "edit" | "copy"; + data?: Access; + onAfterReq: () => void; +}; + +const AccessKubernetesForm = ({ data, op, onAfterReq }: AccessKubernetesFormProps) => { + const { addAccess, updateAccess } = useConfig(); + + const fileInputRef = useRef(null); + const [fileName, setFileName] = useState(""); + + const { t } = useTranslation(); + + const formSchema = z.object({ + id: z.string().optional(), + name: z + .string() + .min(1, "access.authorization.form.name.placeholder") + .max(64, t("common.errmsg.string_max", { max: 64 })), + configType: accessFormType, + kubeConfig: z + .string() + .min(1, "access.authorization.form.k8s_kubeconfig.placeholder") + .max(20480, t("common.errmsg.string_max", { max: 20480 })), + kubeConfigFile: z.any().optional(), + }); + + let config: KubernetesConfig & { kubeConfigFile?: string } = { + kubeConfig: "", + kubeConfigFile: "", + }; + if (data) config = data.config as typeof config; + + const form = useForm>({ + resolver: zodResolver(formSchema), + defaultValues: { + id: data?.id, + name: data?.name || "", + configType: "k8s", + kubeConfig: config.kubeConfig, + kubeConfigFile: config.kubeConfigFile, + }, + }); + + const onSubmit = async (data: z.infer) => { + const req: Access = { + id: data.id as string, + name: data.name, + configType: data.configType, + usage: getUsageByConfigType(data.configType), + config: { + kubeConfig: data.kubeConfig, + }, + }; + + try { + req.id = op == "copy" ? "" : req.id; + const rs = await save(req); + + onAfterReq(); + + req.id = rs.id; + req.created = rs.created; + req.updated = rs.updated; + if (data.id && op == "edit") { + updateAccess(req); + } else { + addAccess(req); + } + } catch (e) { + const err = e as ClientResponseError; + + Object.entries(err.response.data as PbErrorData).forEach(([key, value]) => { + form.setError(key as keyof z.infer, { + type: "manual", + message: value.message, + }); + }); + + return; + } + }; + + const handleFileChange = async (event: React.ChangeEvent) => { + const file = event.target.files?.[0]; + if (!file) return; + const savedFile = file; + setFileName(savedFile.name); + const content = await readFileContent(savedFile); + form.setValue("kubeConfig", content); + }; + + const handleSelectFileClick = () => { + fileInputRef.current?.click(); + }; + + return ( + <> +
+
+ { + e.stopPropagation(); + form.handleSubmit(onSubmit)(e); + }} + className="space-y-3" + > + ( + + {t("access.authorization.form.name.label")} + + + + + + + )} + /> + + ( + + )} + /> + + ( + + {t("access.authorization.form.k8s_kubeconfig.label")} + +
+ + +
+
+ + +
+ )} + /> + + + +
+ +
+ + +
+ + ); +}; + +export default AccessKubernetesForm; diff --git a/ui/src/domain/access.ts b/ui/src/domain/access.ts index 99fbe9f4..b1ebda88 100644 --- a/ui/src/domain/access.ts +++ b/ui/src/domain/access.ts @@ -12,6 +12,7 @@ export const accessTypeMap: Map = new Map([ ["local", ["common.provider.local", "/imgs/providers/local.svg"]], ["ssh", ["common.provider.ssh", "/imgs/providers/ssh.svg"]], ["webhook", ["common.provider.webhook", "/imgs/providers/webhook.svg"]], + ["k8s", ["common.provider.kubernetes", "/imgs/providers/k8s.svg"]], ]); export const getProviderInfo = (t: string) => { @@ -31,6 +32,7 @@ export const accessFormType = z.union( z.literal("local"), z.literal("ssh"), z.literal("webhook"), + z.literal("k8s"), ], { message: "access.authorization.form.type.placeholder" } ); @@ -54,7 +56,8 @@ export type Access = { | GodaddyConfig | LocalConfig | SSHConfig - | WebhookConfig; + | WebhookConfig + | KubernetesConfig; deleted?: string; created?: string; updated?: string; @@ -117,6 +120,10 @@ export type WebhookConfig = { url: string; }; +export type KubernetesConfig = { + kubeConfig: string; +}; + export const getUsageByConfigType = (configType: string): AccessUsage => { switch (configType) { case "aliyun": @@ -128,8 +135,10 @@ export const getUsageByConfigType = (configType: string): AccessUsage => { case "local": case "ssh": case "webhook": + case "k8s": return "deploy"; + case "aws": case "cloudflare": case "namesilo": case "godaddy": diff --git a/ui/src/i18n/locales/en/nls.access.json b/ui/src/i18n/locales/en/nls.access.json index ee96c626..24785029 100644 --- a/ui/src/i18n/locales/en/nls.access.json +++ b/ui/src/i18n/locales/en/nls.access.json @@ -69,6 +69,9 @@ "access.authorization.form.ssh_command.placeholder": "Please enter command", "access.authorization.form.webhook_url.label": "Webhook URL", "access.authorization.form.webhook_url.placeholder": "Please enter Webhook URL", + "access.authorization.form.k8s_kubeconfig.label": "KubeConfig", + "access.authorization.form.k8s_kubeconfig.placeholder": "Please enter KubeConfig", + "access.authorization.form.k8s_kubeconfig_file.placeholder": "Please select file", "access.group.tab": "Authorization Group", diff --git a/ui/src/i18n/locales/en/nls.common.json b/ui/src/i18n/locales/en/nls.common.json index 9c4238e8..1cf99345 100644 --- a/ui/src/i18n/locales/en/nls.common.json +++ b/ui/src/i18n/locales/en/nls.common.json @@ -68,6 +68,7 @@ "common.provider.local": "Local Deployment", "common.provider.ssh": "SSH Deployment", "common.provider.webhook": "Webhook", + "common.provider.kubernetes": "Kubernetes", "common.provider.dingtalk": "DingTalk", "common.provider.telegram": "Telegram", "common.provider.lark": "Lark" diff --git a/ui/src/i18n/locales/zh/nls.access.json b/ui/src/i18n/locales/zh/nls.access.json index 9531385d..379035c2 100644 --- a/ui/src/i18n/locales/zh/nls.access.json +++ b/ui/src/i18n/locales/zh/nls.access.json @@ -69,6 +69,9 @@ "access.authorization.form.ssh_command.placeholder": "请输入要执行的命令", "access.authorization.form.webhook_url.label": "Webhook URL", "access.authorization.form.webhook_url.placeholder": "请输入 Webhook URL", + "access.authorization.form.k8s_kubeconfig.label": "KubeConfig", + "access.authorization.form.k8s_kubeconfig.placeholder": "请输入 KubeConfig", + "access.authorization.form.k8s_kubeconfig_file.placeholder": "请选择文件", "access.group.tab": "授权组", diff --git a/ui/src/i18n/locales/zh/nls.common.json b/ui/src/i18n/locales/zh/nls.common.json index dd8cddb5..1f8288bd 100644 --- a/ui/src/i18n/locales/zh/nls.common.json +++ b/ui/src/i18n/locales/zh/nls.common.json @@ -68,6 +68,7 @@ "common.provider.local": "本地部署", "common.provider.ssh": "SSH 部署", "common.provider.webhook": "Webhook", + "common.provider.kubernetes": "Kubernetes", "common.provider.dingtalk": "钉钉", "common.provider.telegram": "Telegram", "common.provider.lark": "飞书"