mirror of
https://github.com/woodchen-ink/certimate.git
synced 2025-07-18 17:31:55 +08:00
Merge branch 'l123wx-i18n'
This commit is contained in:
commit
6b85b4a0c9
313
ui/dist/assets/index-CQVPrK_Y.js
vendored
313
ui/dist/assets/index-CQVPrK_Y.js
vendored
File diff suppressed because one or more lines are too long
1
ui/dist/assets/index-Djc_JtNf.css
vendored
1
ui/dist/assets/index-Djc_JtNf.css
vendored
File diff suppressed because one or more lines are too long
1
ui/dist/assets/index-I--T0qY3.css
vendored
Normal file
1
ui/dist/assets/index-I--T0qY3.css
vendored
Normal file
File diff suppressed because one or more lines are too long
322
ui/dist/assets/index-TzNEc_kS.js
vendored
Normal file
322
ui/dist/assets/index-TzNEc_kS.js
vendored
Normal file
File diff suppressed because one or more lines are too long
4
ui/dist/index.html
vendored
4
ui/dist/index.html
vendored
@ -5,8 +5,8 @@
|
|||||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>Certimate - Your Trusted SSL Automation Partner</title>
|
<title>Certimate - Your Trusted SSL Automation Partner</title>
|
||||||
<script type="module" crossorigin src="/assets/index-CQVPrK_Y.js"></script>
|
<script type="module" crossorigin src="/assets/index-TzNEc_kS.js"></script>
|
||||||
<link rel="stylesheet" crossorigin href="/assets/index-Djc_JtNf.css">
|
<link rel="stylesheet" crossorigin href="/assets/index-I--T0qY3.css">
|
||||||
</head>
|
</head>
|
||||||
<body class="bg-background">
|
<body class="bg-background">
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
|
141
ui/package-lock.json
generated
141
ui/package-lock.json
generated
@ -27,6 +27,9 @@
|
|||||||
"@radix-ui/react-tooltip": "^1.1.2",
|
"@radix-ui/react-tooltip": "^1.1.2",
|
||||||
"class-variance-authority": "^0.7.0",
|
"class-variance-authority": "^0.7.0",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
|
"i18next": "^23.15.1",
|
||||||
|
"i18next-browser-languagedetector": "^8.0.0",
|
||||||
|
"i18next-http-backend": "^2.6.1",
|
||||||
"jszip": "^3.10.1",
|
"jszip": "^3.10.1",
|
||||||
"lucide-react": "^0.417.0",
|
"lucide-react": "^0.417.0",
|
||||||
"moment": "^2.30.1",
|
"moment": "^2.30.1",
|
||||||
@ -34,6 +37,7 @@
|
|||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
"react-dom": "^18.3.1",
|
"react-dom": "^18.3.1",
|
||||||
"react-hook-form": "^7.52.1",
|
"react-hook-form": "^7.52.1",
|
||||||
|
"react-i18next": "^15.0.2",
|
||||||
"react-router-dom": "^6.25.1",
|
"react-router-dom": "^6.25.1",
|
||||||
"tailwind-merge": "^2.4.0",
|
"tailwind-merge": "^2.4.0",
|
||||||
"tailwindcss-animate": "^1.0.7",
|
"tailwindcss-animate": "^1.0.7",
|
||||||
@ -382,6 +386,17 @@
|
|||||||
"@babel/core": "^7.0.0-0"
|
"@babel/core": "^7.0.0-0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@babel/runtime": {
|
||||||
|
"version": "7.25.6",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@babel/runtime/-/runtime-7.25.6.tgz",
|
||||||
|
"integrity": "sha512-VBj9MYyDb9tuLq7yzqjgzt6Q+IBQLrGZfdjOekyEirZPHxXWoTSGUTMrpsfi58Up73d13NfYLv8HT9vmznjzhQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"regenerator-runtime": "^0.14.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6.9.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@babel/template": {
|
"node_modules/@babel/template": {
|
||||||
"version": "7.24.7",
|
"version": "7.24.7",
|
||||||
"resolved": "https://registry.npmmirror.com/@babel/template/-/template-7.24.7.tgz",
|
"resolved": "https://registry.npmmirror.com/@babel/template/-/template-7.24.7.tgz",
|
||||||
@ -2959,6 +2974,14 @@
|
|||||||
"resolved": "https://registry.npmmirror.com/core-util-is/-/core-util-is-1.0.3.tgz",
|
"resolved": "https://registry.npmmirror.com/core-util-is/-/core-util-is-1.0.3.tgz",
|
||||||
"integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ=="
|
"integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ=="
|
||||||
},
|
},
|
||||||
|
"node_modules/cross-fetch": {
|
||||||
|
"version": "4.0.0",
|
||||||
|
"resolved": "https://registry.npmmirror.com/cross-fetch/-/cross-fetch-4.0.0.tgz",
|
||||||
|
"integrity": "sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g==",
|
||||||
|
"dependencies": {
|
||||||
|
"node-fetch": "^2.6.12"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/cross-spawn": {
|
"node_modules/cross-spawn": {
|
||||||
"version": "7.0.3",
|
"version": "7.0.3",
|
||||||
"resolved": "https://registry.npmmirror.com/cross-spawn/-/cross-spawn-7.0.3.tgz",
|
"resolved": "https://registry.npmmirror.com/cross-spawn/-/cross-spawn-7.0.3.tgz",
|
||||||
@ -3697,6 +3720,52 @@
|
|||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/html-parse-stringify": {
|
||||||
|
"version": "3.0.1",
|
||||||
|
"resolved": "https://registry.npmmirror.com/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz",
|
||||||
|
"integrity": "sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==",
|
||||||
|
"dependencies": {
|
||||||
|
"void-elements": "3.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/i18next": {
|
||||||
|
"version": "23.15.1",
|
||||||
|
"resolved": "https://registry.npmmirror.com/i18next/-/i18next-23.15.1.tgz",
|
||||||
|
"integrity": "sha512-wB4abZ3uK7EWodYisHl/asf8UYEhrI/vj/8aoSsrj/ZDxj4/UXPOa1KvFt1Fq5hkUHquNqwFlDprmjZ8iySgYA==",
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "individual",
|
||||||
|
"url": "https://locize.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "individual",
|
||||||
|
"url": "https://locize.com/i18next.html"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "individual",
|
||||||
|
"url": "https://www.i18next.com/how-to/faq#i18next-is-awesome.-how-can-i-support-the-project"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.23.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/i18next-browser-languagedetector": {
|
||||||
|
"version": "8.0.0",
|
||||||
|
"resolved": "https://registry.npmmirror.com/i18next-browser-languagedetector/-/i18next-browser-languagedetector-8.0.0.tgz",
|
||||||
|
"integrity": "sha512-zhXdJXTTCoG39QsrOCiOabnWj2jecouOqbchu3EfhtSHxIB5Uugnm9JaizenOy39h7ne3+fLikIjeW88+rgszw==",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.23.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/i18next-http-backend": {
|
||||||
|
"version": "2.6.1",
|
||||||
|
"resolved": "https://registry.npmmirror.com/i18next-http-backend/-/i18next-http-backend-2.6.1.tgz",
|
||||||
|
"integrity": "sha512-rCilMAnlEQNeKOZY1+x8wLM5IpYOj10guGvEpeC59tNjj6MMreLIjIW8D1RclhD3ifLwn6d/Y9HEM1RUE6DSog==",
|
||||||
|
"dependencies": {
|
||||||
|
"cross-fetch": "4.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/ignore": {
|
"node_modules/ignore": {
|
||||||
"version": "5.3.1",
|
"version": "5.3.1",
|
||||||
"resolved": "https://registry.npmmirror.com/ignore/-/ignore-5.3.1.tgz",
|
"resolved": "https://registry.npmmirror.com/ignore/-/ignore-5.3.1.tgz",
|
||||||
@ -4112,6 +4181,25 @@
|
|||||||
"integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==",
|
"integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/node-fetch": {
|
||||||
|
"version": "2.7.0",
|
||||||
|
"resolved": "https://registry.npmmirror.com/node-fetch/-/node-fetch-2.7.0.tgz",
|
||||||
|
"integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==",
|
||||||
|
"dependencies": {
|
||||||
|
"whatwg-url": "^5.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "4.x || >=6.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"encoding": "^0.1.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"encoding": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/node-releases": {
|
"node_modules/node-releases": {
|
||||||
"version": "2.0.18",
|
"version": "2.0.18",
|
||||||
"resolved": "https://registry.npmmirror.com/node-releases/-/node-releases-2.0.18.tgz",
|
"resolved": "https://registry.npmmirror.com/node-releases/-/node-releases-2.0.18.tgz",
|
||||||
@ -4553,6 +4641,27 @@
|
|||||||
"react": "^16.8.0 || ^17 || ^18 || ^19"
|
"react": "^16.8.0 || ^17 || ^18 || ^19"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/react-i18next": {
|
||||||
|
"version": "15.0.2",
|
||||||
|
"resolved": "https://registry.npmmirror.com/react-i18next/-/react-i18next-15.0.2.tgz",
|
||||||
|
"integrity": "sha512-z0W3/RES9Idv3MmJUcf0mDNeeMOUXe+xoL0kPfQPbDoZHmni/XsIoq5zgT2MCFUiau283GuBUK578uD/mkAbLQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.25.0",
|
||||||
|
"html-parse-stringify": "^3.0.1"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"i18next": ">= 23.2.3",
|
||||||
|
"react": ">= 16.8.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"react-dom": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"react-native": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/react-refresh": {
|
"node_modules/react-refresh": {
|
||||||
"version": "0.14.2",
|
"version": "0.14.2",
|
||||||
"resolved": "https://registry.npmmirror.com/react-refresh/-/react-refresh-0.14.2.tgz",
|
"resolved": "https://registry.npmmirror.com/react-refresh/-/react-refresh-0.14.2.tgz",
|
||||||
@ -4692,6 +4801,11 @@
|
|||||||
"node": ">=8.10.0"
|
"node": ">=8.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/regenerator-runtime": {
|
||||||
|
"version": "0.14.1",
|
||||||
|
"resolved": "https://registry.npmmirror.com/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz",
|
||||||
|
"integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw=="
|
||||||
|
},
|
||||||
"node_modules/resolve": {
|
"node_modules/resolve": {
|
||||||
"version": "1.22.8",
|
"version": "1.22.8",
|
||||||
"resolved": "https://registry.npmmirror.com/resolve/-/resolve-1.22.8.tgz",
|
"resolved": "https://registry.npmmirror.com/resolve/-/resolve-1.22.8.tgz",
|
||||||
@ -5140,6 +5254,11 @@
|
|||||||
"node": ">=8.0"
|
"node": ">=8.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/tr46": {
|
||||||
|
"version": "0.0.3",
|
||||||
|
"resolved": "https://registry.npmmirror.com/tr46/-/tr46-0.0.3.tgz",
|
||||||
|
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="
|
||||||
|
},
|
||||||
"node_modules/ts-api-utils": {
|
"node_modules/ts-api-utils": {
|
||||||
"version": "1.3.0",
|
"version": "1.3.0",
|
||||||
"resolved": "https://registry.npmmirror.com/ts-api-utils/-/ts-api-utils-1.3.0.tgz",
|
"resolved": "https://registry.npmmirror.com/ts-api-utils/-/ts-api-utils-1.3.0.tgz",
|
||||||
@ -5361,6 +5480,28 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/void-elements": {
|
||||||
|
"version": "3.1.0",
|
||||||
|
"resolved": "https://registry.npmmirror.com/void-elements/-/void-elements-3.1.0.tgz",
|
||||||
|
"integrity": "sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/webidl-conversions": {
|
||||||
|
"version": "3.0.1",
|
||||||
|
"resolved": "https://registry.npmmirror.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
|
||||||
|
"integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="
|
||||||
|
},
|
||||||
|
"node_modules/whatwg-url": {
|
||||||
|
"version": "5.0.0",
|
||||||
|
"resolved": "https://registry.npmmirror.com/whatwg-url/-/whatwg-url-5.0.0.tgz",
|
||||||
|
"integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
|
||||||
|
"dependencies": {
|
||||||
|
"tr46": "~0.0.3",
|
||||||
|
"webidl-conversions": "^3.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/which": {
|
"node_modules/which": {
|
||||||
"version": "2.0.2",
|
"version": "2.0.2",
|
||||||
"resolved": "https://registry.npmmirror.com/which/-/which-2.0.2.tgz",
|
"resolved": "https://registry.npmmirror.com/which/-/which-2.0.2.tgz",
|
||||||
|
@ -40,7 +40,11 @@
|
|||||||
"tailwind-merge": "^2.4.0",
|
"tailwind-merge": "^2.4.0",
|
||||||
"tailwindcss-animate": "^1.0.7",
|
"tailwindcss-animate": "^1.0.7",
|
||||||
"vaul": "^0.9.1",
|
"vaul": "^0.9.1",
|
||||||
"zod": "^3.23.8"
|
"zod": "^3.23.8",
|
||||||
|
"i18next": "^23.15.1",
|
||||||
|
"i18next-browser-languagedetector": "^8.0.0",
|
||||||
|
"i18next-http-backend": "^2.6.1",
|
||||||
|
"react-i18next": "^15.0.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^22.0.0",
|
"@types/node": "^22.0.0",
|
||||||
|
33
ui/src/components/LocaleToggle.tsx
Normal file
33
ui/src/components/LocaleToggle.tsx
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
import { Languages } from "lucide-react";
|
||||||
|
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import {
|
||||||
|
DropdownMenu,
|
||||||
|
DropdownMenuContent,
|
||||||
|
DropdownMenuItem,
|
||||||
|
DropdownMenuTrigger,
|
||||||
|
} from "@/components/ui/dropdown-menu";
|
||||||
|
|
||||||
|
export default function LocaleToggle() {
|
||||||
|
const { i18n } = useTranslation()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DropdownMenu>
|
||||||
|
<DropdownMenuTrigger asChild>
|
||||||
|
<Button variant="outline" size="icon">
|
||||||
|
<Languages className="h-[1.2rem] w-[1.2rem] dark:text-white" />
|
||||||
|
<span className="sr-only">Toggle theme</span>
|
||||||
|
</Button>
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
<DropdownMenuContent align="end">
|
||||||
|
{Object.keys(i18n.store.data).map(key => (
|
||||||
|
<DropdownMenuItem onClick={() => i18n.changeLanguage(key)}>
|
||||||
|
{i18n.store.data[key].name as string}
|
||||||
|
</DropdownMenuItem>
|
||||||
|
))}
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
);
|
||||||
|
}
|
@ -1,4 +1,5 @@
|
|||||||
import { Moon, Sun } from "lucide-react";
|
import { Moon, Sun } from "lucide-react";
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import {
|
import {
|
||||||
@ -11,6 +12,8 @@ import { useTheme } from "./ThemeProvider";
|
|||||||
|
|
||||||
export function ThemeToggle() {
|
export function ThemeToggle() {
|
||||||
const { setTheme } = useTheme();
|
const { setTheme } = useTheme();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
@ -23,13 +26,13 @@ export function ThemeToggle() {
|
|||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
<DropdownMenuContent align="end">
|
<DropdownMenuContent align="end">
|
||||||
<DropdownMenuItem onClick={() => setTheme("light")}>
|
<DropdownMenuItem onClick={() => setTheme("light")}>
|
||||||
浅色
|
{t('theme.light')}
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
<DropdownMenuItem onClick={() => setTheme("dark")}>
|
<DropdownMenuItem onClick={() => setTheme("dark")}>
|
||||||
暗黑
|
{t('theme.dark')}
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
<DropdownMenuItem onClick={() => setTheme("system")}>
|
<DropdownMenuItem onClick={() => setTheme("system")}>
|
||||||
系统
|
{t('theme.system')}
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
</DropdownMenuContent>
|
</DropdownMenuContent>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
|
||||||
@ -29,12 +30,13 @@ const AccessAliyunForm = ({
|
|||||||
onAfterReq: () => void;
|
onAfterReq: () => void;
|
||||||
}) => {
|
}) => {
|
||||||
const { addAccess, updateAccess } = useConfig();
|
const { addAccess, updateAccess } = useConfig();
|
||||||
|
const { t } = useTranslation();
|
||||||
const formSchema = z.object({
|
const formSchema = z.object({
|
||||||
id: z.string().optional(),
|
id: z.string().optional(),
|
||||||
name: z.string().min(1).max(64),
|
name: z.string().min(1, 'access.form.name.not.empty').max(64, t('zod.rule.string.max', { max: 64 })),
|
||||||
configType: accessFormType,
|
configType: accessFormType,
|
||||||
accessKeyId: z.string().min(1).max(64),
|
accessKeyId: z.string().min(1, 'access.form.access.key.id.not.empty').max(64, t('zod.rule.string.max', { max: 64 })),
|
||||||
accessSecretId: z.string().min(1).max(64),
|
accessSecretId: z.string().min(1, 'access.form.access.key.secret.not.empty').max(64, t('zod.rule.string.max', { max: 64 })),
|
||||||
});
|
});
|
||||||
|
|
||||||
let config: AliyunConfig = {
|
let config: AliyunConfig = {
|
||||||
@ -47,7 +49,7 @@ const AccessAliyunForm = ({
|
|||||||
resolver: zodResolver(formSchema),
|
resolver: zodResolver(formSchema),
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
id: data?.id,
|
id: data?.id,
|
||||||
name: data?.name,
|
name: data?.name || '',
|
||||||
configType: "aliyun",
|
configType: "aliyun",
|
||||||
accessKeyId: config.accessKeyId,
|
accessKeyId: config.accessKeyId,
|
||||||
accessSecretId: config.accessKeySecret,
|
accessSecretId: config.accessKeySecret,
|
||||||
@ -111,9 +113,9 @@ const AccessAliyunForm = ({
|
|||||||
name="name"
|
name="name"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>名称</FormLabel>
|
<FormLabel>{t('name')}</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input placeholder="请输入授权名称" {...field} />
|
<Input placeholder={t('access.form.name.not.empty')} {...field} />
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
@ -126,7 +128,7 @@ const AccessAliyunForm = ({
|
|||||||
name="id"
|
name="id"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem className="hidden">
|
<FormItem className="hidden">
|
||||||
<FormLabel>配置类型</FormLabel>
|
<FormLabel>{t('access.form.config.field')}</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input {...field} />
|
<Input {...field} />
|
||||||
</FormControl>
|
</FormControl>
|
||||||
@ -141,7 +143,7 @@ const AccessAliyunForm = ({
|
|||||||
name="configType"
|
name="configType"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem className="hidden">
|
<FormItem className="hidden">
|
||||||
<FormLabel>配置类型</FormLabel>
|
<FormLabel>{t('access.form.config.field')}</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input {...field} />
|
<Input {...field} />
|
||||||
</FormControl>
|
</FormControl>
|
||||||
@ -156,9 +158,9 @@ const AccessAliyunForm = ({
|
|||||||
name="accessKeyId"
|
name="accessKeyId"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>AccessKeyId</FormLabel>
|
<FormLabel>{t('access.form.access.key.id')}</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input placeholder="请输入AccessKeyId" {...field} />
|
<Input placeholder={t('access.form.access.key.id.not.empty')} {...field} />
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
@ -171,9 +173,9 @@ const AccessAliyunForm = ({
|
|||||||
name="accessSecretId"
|
name="accessSecretId"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>AccessKeySecret</FormLabel>
|
<FormLabel>{t('access.form.access.key.secret')}</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input placeholder="请输入AccessKeySecret" {...field} />
|
<Input placeholder={t('access.form.access.key.secret.not.empty')} {...field} />
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
@ -184,7 +186,7 @@ const AccessAliyunForm = ({
|
|||||||
<FormMessage />
|
<FormMessage />
|
||||||
|
|
||||||
<div className="flex justify-end">
|
<div className="flex justify-end">
|
||||||
<Button type="submit">保存</Button>
|
<Button type="submit">{t('save')}</Button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</Form>
|
</Form>
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
|
||||||
@ -28,11 +29,12 @@ const AccessCloudflareForm = ({
|
|||||||
onAfterReq: () => void;
|
onAfterReq: () => void;
|
||||||
}) => {
|
}) => {
|
||||||
const { addAccess, updateAccess } = useConfig();
|
const { addAccess, updateAccess } = useConfig();
|
||||||
|
const { t } = useTranslation();
|
||||||
const formSchema = z.object({
|
const formSchema = z.object({
|
||||||
id: z.string().optional(),
|
id: z.string().optional(),
|
||||||
name: z.string().min(1).max(64),
|
name: z.string().min(1, 'access.form.name.not.empty').max(64, t('zod.rule.string.max', { max: 64 })),
|
||||||
configType: accessFormType,
|
configType: accessFormType,
|
||||||
dnsApiToken: z.string().min(1).max(64),
|
dnsApiToken: z.string().min(1, 'access.form.cloud.dns.api.token.not.empty').max(64, t('zod.rule.string.max', { max: 64 })),
|
||||||
});
|
});
|
||||||
|
|
||||||
let config: CloudflareConfig = {
|
let config: CloudflareConfig = {
|
||||||
@ -44,7 +46,7 @@ const AccessCloudflareForm = ({
|
|||||||
resolver: zodResolver(formSchema),
|
resolver: zodResolver(formSchema),
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
id: data?.id,
|
id: data?.id,
|
||||||
name: data?.name,
|
name: data?.name || '',
|
||||||
configType: "cloudflare",
|
configType: "cloudflare",
|
||||||
dnsApiToken: config.dnsApiToken,
|
dnsApiToken: config.dnsApiToken,
|
||||||
},
|
},
|
||||||
@ -106,9 +108,9 @@ const AccessCloudflareForm = ({
|
|||||||
name="name"
|
name="name"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>名称</FormLabel>
|
<FormLabel>{t('name')}</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input placeholder="请输入授权名称" {...field} />
|
<Input placeholder={t('access.form.name.not.empty')} {...field} />
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
@ -121,7 +123,7 @@ const AccessCloudflareForm = ({
|
|||||||
name="id"
|
name="id"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem className="hidden">
|
<FormItem className="hidden">
|
||||||
<FormLabel>配置类型</FormLabel>
|
<FormLabel>{t('access.form.config.field')}</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input {...field} />
|
<Input {...field} />
|
||||||
</FormControl>
|
</FormControl>
|
||||||
@ -136,7 +138,7 @@ const AccessCloudflareForm = ({
|
|||||||
name="configType"
|
name="configType"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem className="hidden">
|
<FormItem className="hidden">
|
||||||
<FormLabel>配置类型</FormLabel>
|
<FormLabel>{t('access.form.config.field')}</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input {...field} />
|
<Input {...field} />
|
||||||
</FormControl>
|
</FormControl>
|
||||||
@ -151,9 +153,9 @@ const AccessCloudflareForm = ({
|
|||||||
name="dnsApiToken"
|
name="dnsApiToken"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>CLOUD_DNS_API_TOKEN</FormLabel>
|
<FormLabel>{t('access.form.cloud.dns.api.token')}</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input placeholder="请输入CLOUD_DNS_API_TOKEN" {...field} />
|
<Input placeholder={t('access.form.cloud.dns.api.token.not.empty')} {...field} />
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
@ -162,7 +164,7 @@ const AccessCloudflareForm = ({
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<div className="flex justify-end">
|
<div className="flex justify-end">
|
||||||
<Button type="submit">保存</Button>
|
<Button type="submit">{t('save')}</Button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</Form>
|
</Form>
|
||||||
|
@ -8,6 +8,7 @@ import {
|
|||||||
import { ScrollArea } from "@/components/ui/scroll-area";
|
import { ScrollArea } from "@/components/ui/scroll-area";
|
||||||
|
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
import AccessTencentForm from "./AccessTencentForm";
|
import AccessTencentForm from "./AccessTencentForm";
|
||||||
|
|
||||||
@ -47,6 +48,7 @@ export function AccessEdit({
|
|||||||
className,
|
className,
|
||||||
}: TargetConfigEditProps) {
|
}: TargetConfigEditProps) {
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const typeKeys = Array.from(accessTypeMap.keys());
|
const typeKeys = Array.from(accessTypeMap.keys());
|
||||||
|
|
||||||
@ -157,25 +159,24 @@ export function AccessEdit({
|
|||||||
</DialogTrigger>
|
</DialogTrigger>
|
||||||
<DialogContent className="sm:max-w-[600px] w-full dark:text-stone-200">
|
<DialogContent className="sm:max-w-[600px] w-full dark:text-stone-200">
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>{op == "add" ? "添加" : "编辑"}授权</DialogTitle>
|
<DialogTitle>{op == "add" ? t('access.add') : t('access.edit')}</DialogTitle>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
<ScrollArea className="max-h-[80vh]">
|
<ScrollArea className="max-h-[80vh]">
|
||||||
<div className="container py-3">
|
<div className="container py-3">
|
||||||
<Label>服务商</Label>
|
<Label>{t('access.type')}</Label>
|
||||||
|
|
||||||
<Select
|
<Select
|
||||||
onValueChange={(val) => {
|
onValueChange={(val) => {
|
||||||
console.log(val);
|
|
||||||
setConfigType(val);
|
setConfigType(val);
|
||||||
}}
|
}}
|
||||||
defaultValue={configType}
|
defaultValue={configType}
|
||||||
>
|
>
|
||||||
<SelectTrigger className="mt-3">
|
<SelectTrigger className="mt-3">
|
||||||
<SelectValue placeholder="请选择服务商" />
|
<SelectValue placeholder={t('access.type.not.empty')} />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
<SelectGroup>
|
<SelectGroup>
|
||||||
<SelectLabel>服务商</SelectLabel>
|
<SelectLabel>{t('access.type')}</SelectLabel>
|
||||||
{typeKeys.map((key) => (
|
{typeKeys.map((key) => (
|
||||||
<SelectItem value={key} key={key}>
|
<SelectItem value={key} key={key}>
|
||||||
<div
|
<div
|
||||||
@ -188,7 +189,7 @@ export function AccessEdit({
|
|||||||
src={accessTypeMap.get(key)?.[1]}
|
src={accessTypeMap.get(key)?.[1]}
|
||||||
className="h-6 w-6"
|
className="h-6 w-6"
|
||||||
/>
|
/>
|
||||||
<div>{accessTypeMap.get(key)?.[0]}</div>
|
<div>{t(accessTypeMap.get(key)?.[0] || '')}</div>
|
||||||
</div>
|
</div>
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
))}
|
))}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
|
||||||
@ -33,12 +34,13 @@ const AccessGodaddyFrom = ({
|
|||||||
onAfterReq: () => void;
|
onAfterReq: () => void;
|
||||||
}) => {
|
}) => {
|
||||||
const { addAccess, updateAccess } = useConfig();
|
const { addAccess, updateAccess } = useConfig();
|
||||||
|
const { t } = useTranslation();
|
||||||
const formSchema = z.object({
|
const formSchema = z.object({
|
||||||
id: z.string().optional(),
|
id: z.string().optional(),
|
||||||
name: z.string().min(1).max(64),
|
name: z.string().min(1, 'access.form.name.not.empty').max(64, t('zod.rule.string.max', { max: 64 })),
|
||||||
configType: accessFormType,
|
configType: accessFormType,
|
||||||
apiKey: z.string().min(1).max(64),
|
apiKey: z.string().min(1, 'access.form.go.daddy.api.key.not.empty').max(64, t('zod.rule.string.max', { max: 64 })),
|
||||||
apiSecret: z.string().min(1).max(64),
|
apiSecret: z.string().min(1, 'access.form.go.daddy.api.secret.not.empty').max(64, t('zod.rule.string.max', { max: 64 })),
|
||||||
});
|
});
|
||||||
|
|
||||||
let config: GodaddyConfig = {
|
let config: GodaddyConfig = {
|
||||||
@ -51,7 +53,7 @@ const AccessGodaddyFrom = ({
|
|||||||
resolver: zodResolver(formSchema),
|
resolver: zodResolver(formSchema),
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
id: data?.id,
|
id: data?.id,
|
||||||
name: data?.name,
|
name: data?.name || '',
|
||||||
configType: "godaddy",
|
configType: "godaddy",
|
||||||
apiKey: config.apiKey,
|
apiKey: config.apiKey,
|
||||||
apiSecret: config.apiSecret,
|
apiSecret: config.apiSecret,
|
||||||
@ -115,9 +117,9 @@ const AccessGodaddyFrom = ({
|
|||||||
name="name"
|
name="name"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>名称</FormLabel>
|
<FormLabel>{t('name')}</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input placeholder="请输入授权名称" {...field} />
|
<Input placeholder={t('access.form.name.not.empty')} {...field} />
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
@ -130,7 +132,7 @@ const AccessGodaddyFrom = ({
|
|||||||
name="id"
|
name="id"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem className="hidden">
|
<FormItem className="hidden">
|
||||||
<FormLabel>配置类型</FormLabel>
|
<FormLabel>{t('access.form.config.field')}</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input {...field} />
|
<Input {...field} />
|
||||||
</FormControl>
|
</FormControl>
|
||||||
@ -145,7 +147,7 @@ const AccessGodaddyFrom = ({
|
|||||||
name="configType"
|
name="configType"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem className="hidden">
|
<FormItem className="hidden">
|
||||||
<FormLabel>配置类型</FormLabel>
|
<FormLabel>{t('access.form.config.field')}</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input {...field} />
|
<Input {...field} />
|
||||||
</FormControl>
|
</FormControl>
|
||||||
@ -160,9 +162,9 @@ const AccessGodaddyFrom = ({
|
|||||||
name="apiKey"
|
name="apiKey"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>GODADDY_API_KEY</FormLabel>
|
<FormLabel>{t('access.form.go.daddy.api.key')}</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input placeholder="请输入GODADDY_API_KEY" {...field} />
|
<Input placeholder={t('access.form.go.daddy.api.key.not.empty')} {...field} />
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
@ -175,9 +177,9 @@ const AccessGodaddyFrom = ({
|
|||||||
name="apiSecret"
|
name="apiSecret"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>GODADDY_API_SECRET</FormLabel>
|
<FormLabel>{t('access.form.go.daddy.api.secret')}</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input placeholder="请输入GODADDY_API_SECRET" {...field} />
|
<Input placeholder={t('access.form.go.daddy.api.secret.not.empty')} {...field} />
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
@ -186,7 +188,7 @@ const AccessGodaddyFrom = ({
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<div className="flex justify-end">
|
<div className="flex justify-end">
|
||||||
<Button type="submit">保存</Button>
|
<Button type="submit">{t('save')}</Button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</Form>
|
</Form>
|
||||||
|
@ -25,6 +25,7 @@ import { update } from "@/repository/access_group";
|
|||||||
import { ClientResponseError } from "pocketbase";
|
import { ClientResponseError } from "pocketbase";
|
||||||
import { PbErrorData } from "@/domain/base";
|
import { PbErrorData } from "@/domain/base";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
type AccessGroupEditProps = {
|
type AccessGroupEditProps = {
|
||||||
className?: string;
|
className?: string;
|
||||||
@ -35,9 +36,10 @@ const AccessGroupEdit = ({ className, trigger }: AccessGroupEditProps) => {
|
|||||||
const { reloadAccessGroups } = useConfig();
|
const { reloadAccessGroups } = useConfig();
|
||||||
|
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const formSchema = z.object({
|
const formSchema = z.object({
|
||||||
name: z.string().min(1).max(64),
|
name: z.string().min(1, 'access.group.name.not.empty').max(64, t('zod.rule.string.max', { max: 64 })),
|
||||||
});
|
});
|
||||||
|
|
||||||
const form = useForm<z.infer<typeof formSchema>>({
|
const form = useForm<z.infer<typeof formSchema>>({
|
||||||
@ -78,14 +80,13 @@ const AccessGroupEdit = ({ className, trigger }: AccessGroupEditProps) => {
|
|||||||
</DialogTrigger>
|
</DialogTrigger>
|
||||||
<DialogContent className="sm:max-w-[600px] w-full dark:text-stone-200">
|
<DialogContent className="sm:max-w-[600px] w-full dark:text-stone-200">
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>添加分组</DialogTitle>
|
<DialogTitle>{t('access.group.add')}</DialogTitle>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
|
|
||||||
<div className="container py-3">
|
<div className="container py-3">
|
||||||
<Form {...form}>
|
<Form {...form}>
|
||||||
<form
|
<form
|
||||||
onSubmit={(e) => {
|
onSubmit={(e) => {
|
||||||
console.log(e);
|
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
form.handleSubmit(onSubmit)(e);
|
form.handleSubmit(onSubmit)(e);
|
||||||
}}
|
}}
|
||||||
@ -96,9 +97,9 @@ const AccessGroupEdit = ({ className, trigger }: AccessGroupEditProps) => {
|
|||||||
name="name"
|
name="name"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>组名</FormLabel>
|
<FormLabel>{t('access.group.name')}</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input placeholder="请输入组名" {...field} type="text" />
|
<Input placeholder={t('access.group.name.not.empty')} {...field} type="text" />
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
@ -107,7 +108,7 @@ const AccessGroupEdit = ({ className, trigger }: AccessGroupEditProps) => {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<div className="flex justify-end">
|
<div className="flex justify-end">
|
||||||
<Button type="submit">保存</Button>
|
<Button type="submit">{t('save')}</Button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</Form>
|
</Form>
|
||||||
|
@ -29,6 +29,7 @@ import { Group } from "lucide-react";
|
|||||||
import { useToast } from "@/components/ui/use-toast";
|
import { useToast } from "@/components/ui/use-toast";
|
||||||
|
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
const AccessGroupList = () => {
|
const AccessGroupList = () => {
|
||||||
const {
|
const {
|
||||||
@ -39,8 +40,7 @@ const AccessGroupList = () => {
|
|||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
|
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
|
||||||
const handleRemoveClick = async (id: string) => {
|
const handleRemoveClick = async (id: string) => {
|
||||||
try {
|
try {
|
||||||
@ -48,7 +48,7 @@ const AccessGroupList = () => {
|
|||||||
reloadAccessGroups();
|
reloadAccessGroups();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
toast({
|
toast({
|
||||||
title: "删除失败",
|
title: t('delete.failed'),
|
||||||
description: getErrMessage(e),
|
description: getErrMessage(e),
|
||||||
variant: "destructive",
|
variant: "destructive",
|
||||||
});
|
});
|
||||||
@ -69,10 +69,10 @@ const AccessGroupList = () => {
|
|||||||
</span>
|
</span>
|
||||||
|
|
||||||
<div className="text-center text-sm text-muted-foreground mt-3">
|
<div className="text-center text-sm text-muted-foreground mt-3">
|
||||||
请添加域名开始部署证书吧。
|
{t('access.group.domain.empty')}
|
||||||
</div>
|
</div>
|
||||||
<AccessGroupEdit
|
<AccessGroupEdit
|
||||||
trigger={<Button>新增授权组</Button>}
|
trigger={<Button>{t('access.group.add')}</Button>}
|
||||||
className="mt-3"
|
className="mt-3"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -86,9 +86,7 @@ const AccessGroupList = () => {
|
|||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle>{accessGroup.name}</CardTitle>
|
<CardTitle>{accessGroup.name}</CardTitle>
|
||||||
<CardDescription>
|
<CardDescription>
|
||||||
共有
|
{t('access.group.total', { total: accessGroup.expand ? accessGroup.expand.access.length : 0 })}
|
||||||
{accessGroup.expand ? accessGroup.expand.access.length : 0}
|
|
||||||
个部署授权配置
|
|
||||||
</CardDescription>
|
</CardDescription>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="min-h-[180px]">
|
<CardContent className="min-h-[180px]">
|
||||||
@ -123,7 +121,7 @@ const AccessGroupList = () => {
|
|||||||
<Group size={40} />
|
<Group size={40} />
|
||||||
</div>
|
</div>
|
||||||
<div className="ml-2">
|
<div className="ml-2">
|
||||||
暂无部署授权配置,请添加后开始使用吧
|
{t('access.group.empty')}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
@ -151,7 +149,7 @@ const AccessGroupList = () => {
|
|||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
所有授权
|
{t('access.all')}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</Show>
|
</Show>
|
||||||
@ -166,7 +164,7 @@ const AccessGroupList = () => {
|
|||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
<Button size="sm" onClick={handleAddAccess}>
|
<Button size="sm" onClick={handleAddAccess}>
|
||||||
新增授权
|
{t('access.add')}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</Show>
|
</Show>
|
||||||
@ -175,21 +173,21 @@ const AccessGroupList = () => {
|
|||||||
<AlertDialog>
|
<AlertDialog>
|
||||||
<AlertDialogTrigger asChild>
|
<AlertDialogTrigger asChild>
|
||||||
<Button variant={"destructive"} size={"sm"}>
|
<Button variant={"destructive"} size={"sm"}>
|
||||||
删除
|
{t('delete')}
|
||||||
</Button>
|
</Button>
|
||||||
</AlertDialogTrigger>
|
</AlertDialogTrigger>
|
||||||
<AlertDialogContent>
|
<AlertDialogContent>
|
||||||
<AlertDialogHeader>
|
<AlertDialogHeader>
|
||||||
<AlertDialogTitle className="dark:text-gray-200">
|
<AlertDialogTitle className="dark:text-gray-200">
|
||||||
删除组
|
{t('access.group.delete')}
|
||||||
</AlertDialogTitle>
|
</AlertDialogTitle>
|
||||||
<AlertDialogDescription>
|
<AlertDialogDescription>
|
||||||
确定要删除部署授权组吗?
|
{t('access.group.delete.confirm')}
|
||||||
</AlertDialogDescription>
|
</AlertDialogDescription>
|
||||||
</AlertDialogHeader>
|
</AlertDialogHeader>
|
||||||
<AlertDialogFooter>
|
<AlertDialogFooter>
|
||||||
<AlertDialogCancel className="dark:text-gray-200">
|
<AlertDialogCancel className="dark:text-gray-200">
|
||||||
取消
|
{t('cancel')}
|
||||||
</AlertDialogCancel>
|
</AlertDialogCancel>
|
||||||
<AlertDialogAction
|
<AlertDialogAction
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
@ -198,7 +196,7 @@ const AccessGroupList = () => {
|
|||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
确认
|
{t('confirm')}
|
||||||
</AlertDialogAction>
|
</AlertDialogAction>
|
||||||
</AlertDialogFooter>
|
</AlertDialogFooter>
|
||||||
</AlertDialogContent>
|
</AlertDialogContent>
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
|
||||||
@ -28,11 +29,12 @@ const AccessNamesiloForm = ({
|
|||||||
onAfterReq: () => void;
|
onAfterReq: () => void;
|
||||||
}) => {
|
}) => {
|
||||||
const { addAccess, updateAccess } = useConfig();
|
const { addAccess, updateAccess } = useConfig();
|
||||||
|
const { t } = useTranslation();
|
||||||
const formSchema = z.object({
|
const formSchema = z.object({
|
||||||
id: z.string().optional(),
|
id: z.string().optional(),
|
||||||
name: z.string().min(1).max(64),
|
name: z.string().min(1, 'access.form.name.not.empty').max(64, t('zod.rule.string.max', { max: 64 })),
|
||||||
configType: accessFormType,
|
configType: accessFormType,
|
||||||
apiKey: z.string().min(1).max(64),
|
apiKey: z.string().min(1, 'access.form.namesilo.api.key.not.empty').max(64, t('zod.rule.string.max', { max: 64 })),
|
||||||
});
|
});
|
||||||
|
|
||||||
let config: NamesiloConfig = {
|
let config: NamesiloConfig = {
|
||||||
@ -44,14 +46,13 @@ const AccessNamesiloForm = ({
|
|||||||
resolver: zodResolver(formSchema),
|
resolver: zodResolver(formSchema),
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
id: data?.id,
|
id: data?.id,
|
||||||
name: data?.name,
|
name: data?.name || '',
|
||||||
configType: "namesilo",
|
configType: "namesilo",
|
||||||
apiKey: config.apiKey,
|
apiKey: config.apiKey,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const onSubmit = async (data: z.infer<typeof formSchema>) => {
|
const onSubmit = async (data: z.infer<typeof formSchema>) => {
|
||||||
console.log(data);
|
|
||||||
const req: Access = {
|
const req: Access = {
|
||||||
id: data.id as string,
|
id: data.id as string,
|
||||||
name: data.name,
|
name: data.name,
|
||||||
@ -106,9 +107,9 @@ const AccessNamesiloForm = ({
|
|||||||
name="name"
|
name="name"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>名称</FormLabel>
|
<FormLabel>{t('name')}</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input placeholder="请输入授权名称" {...field} />
|
<Input placeholder={t('access.form.name.not.empty')} {...field} />
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
@ -121,7 +122,7 @@ const AccessNamesiloForm = ({
|
|||||||
name="id"
|
name="id"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem className="hidden">
|
<FormItem className="hidden">
|
||||||
<FormLabel>配置类型</FormLabel>
|
<FormLabel>{t('access.form.config.field')}</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input {...field} />
|
<Input {...field} />
|
||||||
</FormControl>
|
</FormControl>
|
||||||
@ -136,7 +137,7 @@ const AccessNamesiloForm = ({
|
|||||||
name="configType"
|
name="configType"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem className="hidden">
|
<FormItem className="hidden">
|
||||||
<FormLabel>配置类型</FormLabel>
|
<FormLabel>{t('access.form.config.field')}</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input {...field} />
|
<Input {...field} />
|
||||||
</FormControl>
|
</FormControl>
|
||||||
@ -151,9 +152,9 @@ const AccessNamesiloForm = ({
|
|||||||
name="apiKey"
|
name="apiKey"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>NAMESILO_API_KEY</FormLabel>
|
<FormLabel>{t('access.form.namesilo.api.key')}</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input placeholder="请输入NAMESILO_API_KEY" {...field} />
|
<Input placeholder={t('access.form.namesilo.api.key.not.empty')} {...field} />
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
@ -162,7 +163,7 @@ const AccessNamesiloForm = ({
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<div className="flex justify-end">
|
<div className="flex justify-end">
|
||||||
<Button type="submit">保存</Button>
|
<Button type="submit">{t('save')}</Button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</Form>
|
</Form>
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
|
||||||
@ -29,12 +30,13 @@ const AccessQiniuForm = ({
|
|||||||
onAfterReq: () => void;
|
onAfterReq: () => void;
|
||||||
}) => {
|
}) => {
|
||||||
const { addAccess, updateAccess } = useConfig();
|
const { addAccess, updateAccess } = useConfig();
|
||||||
|
const { t } = useTranslation();
|
||||||
const formSchema = z.object({
|
const formSchema = z.object({
|
||||||
id: z.string().optional(),
|
id: z.string().optional(),
|
||||||
name: z.string().min(1).max(64),
|
name: z.string().min(1, 'access.form.name.not.empty').max(64, t('zod.rule.string.max', { max: 64 })),
|
||||||
configType: accessFormType,
|
configType: accessFormType,
|
||||||
accessKey: z.string().min(1).max(64),
|
accessKey: z.string().min(1, 'access.form.access.key.not.empty').max(64),
|
||||||
secretKey: z.string().min(1).max(64),
|
secretKey: z.string().min(1, 'access.form.secret.key.not.empty').max(64),
|
||||||
});
|
});
|
||||||
|
|
||||||
let config: QiniuConfig = {
|
let config: QiniuConfig = {
|
||||||
@ -47,7 +49,7 @@ const AccessQiniuForm = ({
|
|||||||
resolver: zodResolver(formSchema),
|
resolver: zodResolver(formSchema),
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
id: data?.id,
|
id: data?.id,
|
||||||
name: data?.name,
|
name: data?.name || '',
|
||||||
configType: "qiniu",
|
configType: "qiniu",
|
||||||
accessKey: config.accessKey,
|
accessKey: config.accessKey,
|
||||||
secretKey: config.secretKey,
|
secretKey: config.secretKey,
|
||||||
@ -111,9 +113,9 @@ const AccessQiniuForm = ({
|
|||||||
name="name"
|
name="name"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>名称</FormLabel>
|
<FormLabel>{t('name')}</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input placeholder="请输入授权名称" {...field} />
|
<Input placeholder={t('access.form.name.not.empty')} {...field} />
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
@ -126,7 +128,7 @@ const AccessQiniuForm = ({
|
|||||||
name="id"
|
name="id"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem className="hidden">
|
<FormItem className="hidden">
|
||||||
<FormLabel>配置类型</FormLabel>
|
<FormLabel>{t('access.form.config.field')}</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input {...field} />
|
<Input {...field} />
|
||||||
</FormControl>
|
</FormControl>
|
||||||
@ -141,7 +143,7 @@ const AccessQiniuForm = ({
|
|||||||
name="configType"
|
name="configType"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem className="hidden">
|
<FormItem className="hidden">
|
||||||
<FormLabel>配置类型</FormLabel>
|
<FormLabel>{t('access.form.config.field')}</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input {...field} />
|
<Input {...field} />
|
||||||
</FormControl>
|
</FormControl>
|
||||||
@ -156,9 +158,9 @@ const AccessQiniuForm = ({
|
|||||||
name="accessKey"
|
name="accessKey"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>AccessKey</FormLabel>
|
<FormLabel>{t('access.form.access.key')}</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input placeholder="请输入AccessKey" {...field} />
|
<Input placeholder={t('access.form.access.key.not.empty')} {...field} />
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
@ -171,9 +173,9 @@ const AccessQiniuForm = ({
|
|||||||
name="secretKey"
|
name="secretKey"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>SecretKey</FormLabel>
|
<FormLabel>{t('access.form.secret.key')}</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input placeholder="请输入SecretKey" {...field} />
|
<Input placeholder={t('access.form.secret.key.not.empty')} {...field} />
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
@ -184,7 +186,7 @@ const AccessQiniuForm = ({
|
|||||||
<FormMessage />
|
<FormMessage />
|
||||||
|
|
||||||
<div className="flex justify-end">
|
<div className="flex justify-end">
|
||||||
<Button type="submit">保存</Button>
|
<Button type="submit">{t('save')}</Button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</Form>
|
</Form>
|
||||||
|
@ -24,6 +24,7 @@ import { ClientResponseError } from "pocketbase";
|
|||||||
import { PbErrorData } from "@/domain/base";
|
import { PbErrorData } from "@/domain/base";
|
||||||
import { readFileContent } from "@/lib/file";
|
import { readFileContent } from "@/lib/file";
|
||||||
import { useRef, useState } from "react";
|
import { useRef, useState } from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
import {
|
import {
|
||||||
Select,
|
Select,
|
||||||
SelectContent,
|
SelectContent,
|
||||||
@ -53,6 +54,7 @@ const AccessSSHForm = ({
|
|||||||
const fileInputRef = useRef<HTMLInputElement | null>(null);
|
const fileInputRef = useRef<HTMLInputElement | null>(null);
|
||||||
|
|
||||||
const [fileName, setFileName] = useState("");
|
const [fileName, setFileName] = useState("");
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const originGroup = data ? (data.group ? data.group : "") : "";
|
const originGroup = data ? (data.group ? data.group : "") : "";
|
||||||
|
|
||||||
@ -62,26 +64,27 @@ const AccessSSHForm = ({
|
|||||||
|
|
||||||
const formSchema = z.object({
|
const formSchema = z.object({
|
||||||
id: z.string().optional(),
|
id: z.string().optional(),
|
||||||
name: z.string().min(1).max(64),
|
name: z.string().min(1, 'access.form.name.not.empty').max(64, t('zod.rule.string.max', { max: 64 })),
|
||||||
configType: accessFormType,
|
configType: accessFormType,
|
||||||
host: z.string().refine(
|
host: z.string().refine(
|
||||||
(str) => {
|
(str) => {
|
||||||
return ipReg.test(str) || domainReg.test(str);
|
return ipReg.test(str) || domainReg.test(str);
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
message: "请输入正确的域名或IP",
|
message: "zod.rule.ssh.host",
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
group: z.string().optional(),
|
group: z.string().optional(),
|
||||||
port: z.string().min(1).max(5),
|
port: z.string().min(1, 'access.form.ssh.port.not.empty').max(5, t('zod.rule.string.max', { max: 5 })),
|
||||||
username: z.string().min(1).max(64),
|
username: z.string().min(1, 'username.not.empty').max(64, t('zod.rule.string.max', { max: 64 })),
|
||||||
password: z.string().min(0).max(64),
|
password: z.string().min(0, 'password.not.empty').max(64, t('zod.rule.string.max', { max: 64 })),
|
||||||
key: z.string().min(0).max(20480),
|
key: z.string().min(0, 'access.form.ssh.key.not.empty').max(20480, t('zod.rule.string.max', { max: 20480 })),
|
||||||
keyFile: z.any().optional(),
|
keyFile: z.any().optional(),
|
||||||
command: z.string().min(1).max(2048),
|
|
||||||
preCommand: z.string().min(0).max(2048).optional(),
|
preCommand: z.string().min(0).max(2048).optional(),
|
||||||
certPath: z.string().min(0).max(2048),
|
command: z.string().min(1, 'access.form.ssh.command.not.empty').max(2048, t('zod.rule.string.max', { max: 2048 })),
|
||||||
keyPath: z.string().min(0).max(2048),
|
certPath: z.string().min(0, 'access.form.ssh.cert.path.not.empty').max(2048, t('zod.rule.string.max', { max: 2048 })),
|
||||||
|
keyPath: z.string().min(0, 'access.form.ssh.key.path.not.empty').max(2048, t('zod.rule.string.max', { max: 2048 })),
|
||||||
});
|
});
|
||||||
|
|
||||||
let config: SSHConfig = {
|
let config: SSHConfig = {
|
||||||
@ -102,7 +105,7 @@ const AccessSSHForm = ({
|
|||||||
resolver: zodResolver(formSchema),
|
resolver: zodResolver(formSchema),
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
id: data?.id,
|
id: data?.id,
|
||||||
name: data?.name,
|
name: data?.name || '',
|
||||||
configType: "ssh",
|
configType: "ssh",
|
||||||
group: data?.group,
|
group: data?.group,
|
||||||
host: config.host,
|
host: config.host,
|
||||||
@ -223,9 +226,9 @@ const AccessSSHForm = ({
|
|||||||
name="name"
|
name="name"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>名称</FormLabel>
|
<FormLabel>{t('name')}</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input placeholder="请输入授权名称" {...field} />
|
<Input placeholder={t('access.form.name.not.empty')} {...field} />
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
@ -239,12 +242,12 @@ const AccessSSHForm = ({
|
|||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel className="w-full flex justify-between">
|
<FormLabel className="w-full flex justify-between">
|
||||||
<div>授权配置组(用于将一个域名证书部署到多个 ssh 主机)</div>
|
<div>{t('access.form.ssh.group.label')}</div>
|
||||||
<AccessGroupEdit
|
<AccessGroupEdit
|
||||||
trigger={
|
trigger={
|
||||||
<div className="font-normal text-primary hover:underline cursor-pointer flex items-center">
|
<div className="font-normal text-primary hover:underline cursor-pointer flex items-center">
|
||||||
<Plus size={14} />
|
<Plus size={14} />
|
||||||
新增
|
{t('add')}
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
@ -259,7 +262,7 @@ const AccessSSHForm = ({
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<SelectTrigger>
|
<SelectTrigger>
|
||||||
<SelectValue placeholder="请选择分组" />
|
<SelectValue placeholder={t('access.group.not.empty')} />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
<SelectItem value="emptyId">
|
<SelectItem value="emptyId">
|
||||||
@ -299,7 +302,7 @@ const AccessSSHForm = ({
|
|||||||
name="id"
|
name="id"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem className="hidden">
|
<FormItem className="hidden">
|
||||||
<FormLabel>配置类型</FormLabel>
|
<FormLabel>{t('access.form.config.field')}</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input {...field} />
|
<Input {...field} />
|
||||||
</FormControl>
|
</FormControl>
|
||||||
@ -314,7 +317,7 @@ const AccessSSHForm = ({
|
|||||||
name="configType"
|
name="configType"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem className="hidden">
|
<FormItem className="hidden">
|
||||||
<FormLabel>配置类型</FormLabel>
|
<FormLabel>{t('access.form.config.field')}</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input {...field} />
|
<Input {...field} />
|
||||||
</FormControl>
|
</FormControl>
|
||||||
@ -329,9 +332,9 @@ const AccessSSHForm = ({
|
|||||||
name="host"
|
name="host"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem className="grow">
|
<FormItem className="grow">
|
||||||
<FormLabel>服务器HOST</FormLabel>
|
<FormLabel>{t('access.form.ssh.host')}</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input placeholder="请输入Host" {...field} />
|
<Input placeholder={t('access.form.ssh.host.not.empty')} {...field} />
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
@ -344,10 +347,10 @@ const AccessSSHForm = ({
|
|||||||
name="port"
|
name="port"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>SSH端口</FormLabel>
|
<FormLabel>{t('access.form.ssh.port')}</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input
|
<Input
|
||||||
placeholder="请输入Port"
|
placeholder={t('access.form.ssh.port.not.empty')}
|
||||||
{...field}
|
{...field}
|
||||||
type="number"
|
type="number"
|
||||||
/>
|
/>
|
||||||
@ -364,9 +367,9 @@ const AccessSSHForm = ({
|
|||||||
name="username"
|
name="username"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>用户名</FormLabel>
|
<FormLabel>{t('username')}</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input placeholder="请输入用户名" {...field} />
|
<Input placeholder={t('username.not.empty')} {...field} />
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
@ -379,10 +382,10 @@ const AccessSSHForm = ({
|
|||||||
name="password"
|
name="password"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>密码</FormLabel>
|
<FormLabel>{t('password')}</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input
|
<Input
|
||||||
placeholder="请输入密码"
|
placeholder={t('password.not.empty')}
|
||||||
{...field}
|
{...field}
|
||||||
type="password"
|
type="password"
|
||||||
/>
|
/>
|
||||||
@ -398,9 +401,9 @@ const AccessSSHForm = ({
|
|||||||
name="key"
|
name="key"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem hidden>
|
<FormItem hidden>
|
||||||
<FormLabel>Key(使用证书登录)</FormLabel>
|
<FormLabel>{t('access.form.ssh.key')}</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input placeholder="请输入Key" {...field} />
|
<Input placeholder={t('access.form.ssh.key.not.empty')} {...field} />
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
@ -413,7 +416,7 @@ const AccessSSHForm = ({
|
|||||||
name="keyFile"
|
name="keyFile"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>Key(使用证书登录)</FormLabel>
|
<FormLabel>{t('access.form.ssh.key')}</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<div>
|
<div>
|
||||||
<Button
|
<Button
|
||||||
@ -423,10 +426,10 @@ const AccessSSHForm = ({
|
|||||||
className="w-48"
|
className="w-48"
|
||||||
onClick={handleSelectFileClick}
|
onClick={handleSelectFileClick}
|
||||||
>
|
>
|
||||||
{fileName ? fileName : "请选择文件"}
|
{fileName ? fileName : t('access.form.ssh.key.file.not.empty')}
|
||||||
</Button>
|
</Button>
|
||||||
<Input
|
<Input
|
||||||
placeholder="请输入Key"
|
placeholder={t('access.form.ssh.key.not.empty')}
|
||||||
{...field}
|
{...field}
|
||||||
ref={fileInputRef}
|
ref={fileInputRef}
|
||||||
className="hidden"
|
className="hidden"
|
||||||
@ -447,9 +450,9 @@ const AccessSSHForm = ({
|
|||||||
name="certPath"
|
name="certPath"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>证书上传路径</FormLabel>
|
<FormLabel>{t('access.form.ssh.cert.path')}</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input placeholder="请输入证书上传路径" {...field} />
|
<Input placeholder={t('access.form.ssh.cert.path.not.empty')} {...field} />
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
@ -462,9 +465,9 @@ const AccessSSHForm = ({
|
|||||||
name="keyPath"
|
name="keyPath"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>私钥上传路径</FormLabel>
|
<FormLabel>{t('access.form.ssh.key.path')}</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input placeholder="请输入私钥上传路径" {...field} />
|
<Input placeholder={t('access.form.ssh.key.path.not.empty')} {...field} />
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
@ -492,9 +495,9 @@ const AccessSSHForm = ({
|
|||||||
name="command"
|
name="command"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>Command</FormLabel>
|
<FormLabel>{t('access.form.ssh.command')}</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Textarea placeholder="请输入要执行的命令" {...field} />
|
<Textarea placeholder={t('access.form.ssh.command.not.empty')} {...field} />
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
@ -505,7 +508,7 @@ const AccessSSHForm = ({
|
|||||||
<FormMessage />
|
<FormMessage />
|
||||||
|
|
||||||
<div className="flex justify-end">
|
<div className="flex justify-end">
|
||||||
<Button type="submit">保存</Button>
|
<Button type="submit">{t('save')}</Button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</Form>
|
</Form>
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
|
||||||
@ -28,12 +29,13 @@ const AccessTencentForm = ({
|
|||||||
onAfterReq: () => void;
|
onAfterReq: () => void;
|
||||||
}) => {
|
}) => {
|
||||||
const { addAccess, updateAccess } = useConfig();
|
const { addAccess, updateAccess } = useConfig();
|
||||||
|
const { t } = useTranslation();
|
||||||
const formSchema = z.object({
|
const formSchema = z.object({
|
||||||
id: z.string().optional(),
|
id: z.string().optional(),
|
||||||
name: z.string().min(1).max(64),
|
name: z.string().min(1, 'access.form.name.not.empty').max(64, t('zod.rule.string.max', { max: 64 })),
|
||||||
configType: accessFormType,
|
configType: accessFormType,
|
||||||
secretId: z.string().min(1).max(64),
|
secretId: z.string().min(1, 'access.form.secret.id.not.empty').max(64, t('zod.rule.string.max', { max: 64 })),
|
||||||
secretKey: z.string().min(1).max(64),
|
secretKey: z.string().min(1, 'access.form.secret.key.not.empty').max(64, t('zod.rule.string.max', { max: 64 })),
|
||||||
});
|
});
|
||||||
|
|
||||||
let config: TencentConfig = {
|
let config: TencentConfig = {
|
||||||
@ -46,7 +48,7 @@ const AccessTencentForm = ({
|
|||||||
resolver: zodResolver(formSchema),
|
resolver: zodResolver(formSchema),
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
id: data?.id,
|
id: data?.id,
|
||||||
name: data?.name,
|
name: data?.name || '',
|
||||||
configType: "tencent",
|
configType: "tencent",
|
||||||
secretId: config.secretId,
|
secretId: config.secretId,
|
||||||
secretKey: config.secretKey,
|
secretKey: config.secretKey,
|
||||||
@ -108,9 +110,9 @@ const AccessTencentForm = ({
|
|||||||
name="name"
|
name="name"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>名称</FormLabel>
|
<FormLabel>{t('name')}</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input placeholder="请输入授权名称" {...field} />
|
<Input placeholder={t('access.form.name.not.empty')} {...field} />
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
@ -123,7 +125,7 @@ const AccessTencentForm = ({
|
|||||||
name="id"
|
name="id"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem className="hidden">
|
<FormItem className="hidden">
|
||||||
<FormLabel>配置类型</FormLabel>
|
<FormLabel>{t('access.form.config.field')}</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input {...field} />
|
<Input {...field} />
|
||||||
</FormControl>
|
</FormControl>
|
||||||
@ -138,7 +140,7 @@ const AccessTencentForm = ({
|
|||||||
name="configType"
|
name="configType"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem className="hidden">
|
<FormItem className="hidden">
|
||||||
<FormLabel>配置类型</FormLabel>
|
<FormLabel>{t('access.form.config.field')}</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input {...field} />
|
<Input {...field} />
|
||||||
</FormControl>
|
</FormControl>
|
||||||
@ -153,9 +155,9 @@ const AccessTencentForm = ({
|
|||||||
name="secretId"
|
name="secretId"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>SecretId</FormLabel>
|
<FormLabel>{t('access.form.secret.id')}</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input placeholder="请输入SecretId" {...field} />
|
<Input placeholder={t('access.form.secret.id.not.empty')} {...field} />
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
@ -168,9 +170,9 @@ const AccessTencentForm = ({
|
|||||||
name="secretKey"
|
name="secretKey"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>SecretKey</FormLabel>
|
<FormLabel>{t('access.form.secret.key')}</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input placeholder="请输入SecretKey" {...field} />
|
<Input placeholder={t('access.form.secret.key.not.empty')} {...field} />
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
@ -179,7 +181,7 @@ const AccessTencentForm = ({
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<div className="flex justify-end">
|
<div className="flex justify-end">
|
||||||
<Button type="submit">保存</Button>
|
<Button type="submit">{t('save')}</Button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</Form>
|
</Form>
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
|
||||||
@ -28,11 +29,12 @@ const WebhookForm = ({
|
|||||||
onAfterReq: () => void;
|
onAfterReq: () => void;
|
||||||
}) => {
|
}) => {
|
||||||
const { addAccess, updateAccess } = useConfig();
|
const { addAccess, updateAccess } = useConfig();
|
||||||
|
const { t } = useTranslation();
|
||||||
const formSchema = z.object({
|
const formSchema = z.object({
|
||||||
id: z.string().optional(),
|
id: z.string().optional(),
|
||||||
name: z.string().min(1).max(64),
|
name: z.string().min(1, 'access.form.name.not.empty').max(64, t('zod.rule.string.max', { max: 64 })),
|
||||||
configType: accessFormType,
|
configType: accessFormType,
|
||||||
url: z.string().url(),
|
url: z.string().url('zod.rule.url'),
|
||||||
});
|
});
|
||||||
|
|
||||||
let config: WebhookConfig = {
|
let config: WebhookConfig = {
|
||||||
@ -44,14 +46,13 @@ const WebhookForm = ({
|
|||||||
resolver: zodResolver(formSchema),
|
resolver: zodResolver(formSchema),
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
id: data?.id,
|
id: data?.id,
|
||||||
name: data?.name,
|
name: data?.name || '',
|
||||||
configType: "webhook",
|
configType: "webhook",
|
||||||
url: config.url,
|
url: config.url,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const onSubmit = async (data: z.infer<typeof formSchema>) => {
|
const onSubmit = async (data: z.infer<typeof formSchema>) => {
|
||||||
console.log(data);
|
|
||||||
const req: Access = {
|
const req: Access = {
|
||||||
id: data.id as string,
|
id: data.id as string,
|
||||||
name: data.name,
|
name: data.name,
|
||||||
@ -106,9 +107,9 @@ const WebhookForm = ({
|
|||||||
name="name"
|
name="name"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>名称</FormLabel>
|
<FormLabel>{t('name')}</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input placeholder="请输入授权名称" {...field} />
|
<Input placeholder={t('access.form.name.not.empty')} {...field} />
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
@ -121,7 +122,7 @@ const WebhookForm = ({
|
|||||||
name="id"
|
name="id"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem className="hidden">
|
<FormItem className="hidden">
|
||||||
<FormLabel>配置类型</FormLabel>
|
<FormLabel>{t('access.form.config.field')}</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input {...field} />
|
<Input {...field} />
|
||||||
</FormControl>
|
</FormControl>
|
||||||
@ -136,7 +137,7 @@ const WebhookForm = ({
|
|||||||
name="configType"
|
name="configType"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem className="hidden">
|
<FormItem className="hidden">
|
||||||
<FormLabel>配置类型</FormLabel>
|
<FormLabel>{t('access.form.config.field')}</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input {...field} />
|
<Input {...field} />
|
||||||
</FormControl>
|
</FormControl>
|
||||||
@ -151,9 +152,9 @@ const WebhookForm = ({
|
|||||||
name="url"
|
name="url"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>Webhook Url</FormLabel>
|
<FormLabel>{t('access.form.webhook.url')}</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input placeholder="请输入Webhook Url" {...field} />
|
<Input placeholder={t('access.form.webhook.url.not.empty')} {...field} />
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
@ -162,7 +163,7 @@ const WebhookForm = ({
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<div className="flex justify-end">
|
<div className="flex justify-end">
|
||||||
<Button type="submit">保存</Button>
|
<Button type="submit">{t('save')}</Button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</Form>
|
</Form>
|
||||||
|
@ -1,3 +1,8 @@
|
|||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
|
||||||
import { Separator } from "../ui/separator";
|
import { Separator } from "../ui/separator";
|
||||||
|
|
||||||
type DeployProgressProps = {
|
type DeployProgressProps = {
|
||||||
@ -6,80 +11,63 @@ type DeployProgressProps = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const DeployProgress = ({ phase, phaseSuccess }: DeployProgressProps) => {
|
const DeployProgress = ({ phase, phaseSuccess }: DeployProgressProps) => {
|
||||||
let rs = <> </>;
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
let step = 0;
|
||||||
|
|
||||||
if (phase === "check") {
|
if (phase === "check") {
|
||||||
if (phaseSuccess) {
|
step = 1
|
||||||
rs = (
|
} else if (phase === "apply") {
|
||||||
<div className="flex items-center">
|
step = 2
|
||||||
<div className="text-xs text-nowrap text-green-600">检查 </div>
|
} else if (phase === "deploy") {
|
||||||
<Separator className="h-1 grow" />
|
step = 3
|
||||||
<div className="text-xs text-nowrap text-muted-foreground">获取</div>
|
|
||||||
<Separator className="h-1 grow" />
|
|
||||||
<div className="text-xs text-nowrap text-muted-foreground">部署</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
rs = (
|
|
||||||
<div className="flex items-center">
|
|
||||||
<div className="text-xs text-nowrap text-red-600">检查 </div>
|
|
||||||
<Separator className="h-1 grow" />
|
|
||||||
<div className="text-xs text-nowrap text-muted-foreground">获取</div>
|
|
||||||
<Separator className="h-1 grow" />
|
|
||||||
<div className="text-xs text-nowrap text-muted-foreground">部署</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (phase === "apply") {
|
return (
|
||||||
if (phaseSuccess) {
|
|
||||||
rs = (
|
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
<div className="text-xs text-nowrap text-green-600">检查 </div>
|
<div className={
|
||||||
<Separator className="h-1 grow bg-green-600" />
|
cn(
|
||||||
<div className="text-xs text-nowrap text-green-600">获取</div>
|
"text-xs text-nowrap",
|
||||||
<Separator className="h-1 grow" />
|
step === 1 ? phaseSuccess ? "text-green-600" : "text-red-600" : "",
|
||||||
<div className="text-xs text-nowrap text-muted-foreground">部署</div>
|
step > 1 ? "text-green-600" : "",
|
||||||
|
)
|
||||||
|
}>
|
||||||
|
{t('deploy.progress.check')}
|
||||||
</div>
|
</div>
|
||||||
);
|
<Separator className={
|
||||||
} else {
|
cn(
|
||||||
rs = (
|
"h-1 grow max-w-[60px]",
|
||||||
<div className="flex items-center">
|
step > 1 ? "bg-green-600" : "",
|
||||||
<div className="text-xs text-nowrap text-green-600">检查 </div>
|
)
|
||||||
<Separator className="h-1 grow bg-green-600" />
|
} />
|
||||||
<div className="text-xs text-nowrap text-red-600">获取</div>
|
<div className={
|
||||||
<Separator className="h-1 grow" />
|
cn(
|
||||||
<div className="text-xs text-nowrap text-muted-foreground">部署</div>
|
"text-xs text-nowrap",
|
||||||
|
step < 2 ? "text-muted-foreground" : "",
|
||||||
|
step === 2 ? phaseSuccess ? "text-green-600" : "text-red-600" : "",
|
||||||
|
step > 2 ? "text-green-600" : "",
|
||||||
|
)
|
||||||
|
}>
|
||||||
|
{t('deploy.progress.apply')}
|
||||||
</div>
|
</div>
|
||||||
);
|
<Separator className={
|
||||||
}
|
cn(
|
||||||
}
|
"h-1 grow max-w-[60px]",
|
||||||
|
step > 2 ? "bg-green-600" : "",
|
||||||
if (phase === "deploy") {
|
)
|
||||||
if (phaseSuccess) {
|
} />
|
||||||
rs = (
|
<div className={
|
||||||
<div className="flex items-center">
|
cn(
|
||||||
<div className="text-xs text-nowrap text-green-600">检查 </div>
|
"text-xs text-nowrap",
|
||||||
<Separator className="h-1 grow bg-green-600" />
|
step < 3 ? "text-muted-foreground" : "",
|
||||||
<div className="text-xs text-nowrap text-green-600">获取</div>
|
step === 3 ? phaseSuccess ? "text-green-600" : "text-red-600" : "",
|
||||||
<Separator className="h-1 grow bg-green-600" />
|
step > 3 ? "text-green-600" : "",
|
||||||
<div className="text-xs text-nowrap text-green-600">部署</div>
|
)
|
||||||
|
}>
|
||||||
|
{t('deploy.progress.deploy')}
|
||||||
</div>
|
</div>
|
||||||
);
|
|
||||||
} else {
|
|
||||||
rs = (
|
|
||||||
<div className="flex items-center">
|
|
||||||
<div className="text-xs text-nowrap text-green-600">检查 </div>
|
|
||||||
<Separator className="h-1 grow bg-green-600" />
|
|
||||||
<div className="text-xs text-nowrap text-green-600">获取</div>
|
|
||||||
<Separator className="h-1 grow bg-green-600" />
|
|
||||||
<div className="text-xs text-nowrap text-red-600">部署</div>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
)
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return rs;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default DeployProgress;
|
export default DeployProgress;
|
||||||
|
@ -10,6 +10,7 @@ import {
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
import {
|
import {
|
||||||
Form,
|
Form,
|
||||||
FormControl,
|
FormControl,
|
||||||
@ -39,9 +40,10 @@ const EmailsEdit = ({ className, trigger }: EmailsEditProps) => {
|
|||||||
} = useConfig();
|
} = useConfig();
|
||||||
|
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const formSchema = z.object({
|
const formSchema = z.object({
|
||||||
email: z.string().email(),
|
email: z.string().email("email.valid.message"),
|
||||||
});
|
});
|
||||||
|
|
||||||
const form = useForm<z.infer<typeof formSchema>>({
|
const form = useForm<z.infer<typeof formSchema>>({
|
||||||
@ -54,7 +56,7 @@ const EmailsEdit = ({ className, trigger }: EmailsEditProps) => {
|
|||||||
const onSubmit = async (data: z.infer<typeof formSchema>) => {
|
const onSubmit = async (data: z.infer<typeof formSchema>) => {
|
||||||
if ((emails.content as EmailsSetting).emails.includes(data.email)) {
|
if ((emails.content as EmailsSetting).emails.includes(data.email)) {
|
||||||
form.setError("email", {
|
form.setError("email", {
|
||||||
message: "邮箱已存在",
|
message: "email.already.exist",
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -100,7 +102,7 @@ const EmailsEdit = ({ className, trigger }: EmailsEditProps) => {
|
|||||||
</DialogTrigger>
|
</DialogTrigger>
|
||||||
<DialogContent className="sm:max-w-[600px] w-full dark:text-stone-200">
|
<DialogContent className="sm:max-w-[600px] w-full dark:text-stone-200">
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>添加邮箱</DialogTitle>
|
<DialogTitle>{t('email.add')}</DialogTitle>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
|
|
||||||
<div className="container py-3">
|
<div className="container py-3">
|
||||||
@ -118,9 +120,9 @@ const EmailsEdit = ({ className, trigger }: EmailsEditProps) => {
|
|||||||
name="email"
|
name="email"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>邮箱</FormLabel>
|
<FormLabel>{t('email')}</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input placeholder="请输入邮箱" {...field} type="email" />
|
<Input placeholder={t('email.not.empty.message')} {...field} type="email" />
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
@ -129,7 +131,7 @@ const EmailsEdit = ({ className, trigger }: EmailsEditProps) => {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<div className="flex justify-end">
|
<div className="flex justify-end">
|
||||||
<Button type="submit">保存</Button>
|
<Button type="submit">{t('save')}</Button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</Form>
|
</Form>
|
||||||
|
@ -1,9 +1,12 @@
|
|||||||
import { BookOpen } from "lucide-react";
|
import { BookOpen } from "lucide-react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
import { Separator } from "../ui/separator";
|
import { Separator } from "../ui/separator";
|
||||||
import { version } from "@/domain/version";
|
import { version } from "@/domain/version";
|
||||||
|
|
||||||
const Version = () => {
|
const Version = () => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="fixed right-0 bottom-0 w-full flex justify-between p-5">
|
<div className="fixed right-0 bottom-0 w-full flex justify-between p-5">
|
||||||
<div className=""></div>
|
<div className=""></div>
|
||||||
@ -14,7 +17,7 @@ const Version = () => {
|
|||||||
className="flex items-center"
|
className="flex items-center"
|
||||||
>
|
>
|
||||||
<BookOpen size={16} />
|
<BookOpen size={16} />
|
||||||
<div className="ml-1">文档</div>
|
<div className="ml-1">{t('document')}</div>
|
||||||
</a>
|
</a>
|
||||||
<Separator orientation="vertical" className="mx-2" />
|
<Separator orientation="vertical" className="mx-2" />
|
||||||
<a
|
<a
|
||||||
|
@ -8,6 +8,7 @@ import { useEffect, useState } from "react";
|
|||||||
import { update } from "@/repository/settings";
|
import { update } from "@/repository/settings";
|
||||||
import { getErrMessage } from "@/lib/error";
|
import { getErrMessage } from "@/lib/error";
|
||||||
import { useToast } from "../ui/use-toast";
|
import { useToast } from "../ui/use-toast";
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
type DingTalkSetting = {
|
type DingTalkSetting = {
|
||||||
id: string;
|
id: string;
|
||||||
@ -17,6 +18,7 @@ type DingTalkSetting = {
|
|||||||
|
|
||||||
const DingTalk = () => {
|
const DingTalk = () => {
|
||||||
const { config, setChannels } = useNotify();
|
const { config, setChannels } = useNotify();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const [dingtalk, setDingtalk] = useState<DingTalkSetting>({
|
const [dingtalk, setDingtalk] = useState<DingTalkSetting>({
|
||||||
id: config.id ?? "",
|
id: config.id ?? "",
|
||||||
@ -70,15 +72,15 @@ const DingTalk = () => {
|
|||||||
|
|
||||||
setChannels(resp);
|
setChannels(resp);
|
||||||
toast({
|
toast({
|
||||||
title: "保存成功",
|
title: t('save.succeed'),
|
||||||
description: "配置保存成功",
|
description: t('setting.notify.config.save.succeed'),
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
const msg = getErrMessage(e);
|
const msg = getErrMessage(e);
|
||||||
|
|
||||||
toast({
|
toast({
|
||||||
title: "保存失败",
|
title: t('save.failed'),
|
||||||
description: "配置保存失败:" + msg,
|
description: `${t('setting.notify.config.save.failed')}: ${msg}`,
|
||||||
variant: "destructive",
|
variant: "destructive",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -100,7 +102,7 @@ const DingTalk = () => {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Input
|
<Input
|
||||||
placeholder="加签的签名"
|
placeholder={t('access.form.ding.access.token.placeholder')}
|
||||||
className="mt-2"
|
className="mt-2"
|
||||||
value={dingtalk.data.secret}
|
value={dingtalk.data.secret}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
@ -127,7 +129,7 @@ const DingTalk = () => {
|
|||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Label htmlFor="airplane-mode">是否启用</Label>
|
<Label htmlFor="airplane-mode">{t('setting.notify.config.enable')}</Label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex justify-end mt-2">
|
<div className="flex justify-end mt-2">
|
||||||
@ -136,7 +138,7 @@ const DingTalk = () => {
|
|||||||
handleSaveClick();
|
handleSaveClick();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
保存
|
{t('save')}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -9,6 +9,7 @@ import {
|
|||||||
} from "@/domain/settings";
|
} from "@/domain/settings";
|
||||||
import { getSetting, update } from "@/repository/settings";
|
import { getSetting, update } from "@/repository/settings";
|
||||||
import { useToast } from "../ui/use-toast";
|
import { useToast } from "../ui/use-toast";
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
const NotifyTemplate = () => {
|
const NotifyTemplate = () => {
|
||||||
const [id, setId] = useState("");
|
const [id, setId] = useState("");
|
||||||
@ -17,6 +18,7 @@ const NotifyTemplate = () => {
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const featchData = async () => {
|
const featchData = async () => {
|
||||||
@ -66,8 +68,8 @@ const NotifyTemplate = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
toast({
|
toast({
|
||||||
title: "保存成功",
|
title: t('save.succeed'),
|
||||||
description: "通知模板保存成功",
|
description: t('setting.notify.template.save.succeed'),
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -81,7 +83,7 @@ const NotifyTemplate = () => {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<div className="text-muted-foreground text-sm mt-1">
|
<div className="text-muted-foreground text-sm mt-1">
|
||||||
可选的变量, COUNT:即将过期张数
|
{t('setting.notify.template.variables.tips.title')}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Textarea
|
<Textarea
|
||||||
@ -92,10 +94,10 @@ const NotifyTemplate = () => {
|
|||||||
}}
|
}}
|
||||||
></Textarea>
|
></Textarea>
|
||||||
<div className="text-muted-foreground text-sm mt-1">
|
<div className="text-muted-foreground text-sm mt-1">
|
||||||
可选的变量, COUNT:即将过期张数,DOMAINS:域名列表
|
{t('setting.notify.template.variables.tips.content')}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex justify-end mt-2">
|
<div className="flex justify-end mt-2">
|
||||||
<Button onClick={handleSaveClick}>保存</Button>
|
<Button onClick={handleSaveClick}>{t('save')}</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -8,6 +8,7 @@ import { useEffect, useState } from "react";
|
|||||||
import { update } from "@/repository/settings";
|
import { update } from "@/repository/settings";
|
||||||
import { getErrMessage } from "@/lib/error";
|
import { getErrMessage } from "@/lib/error";
|
||||||
import { useToast } from "../ui/use-toast";
|
import { useToast } from "../ui/use-toast";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
type TelegramSetting = {
|
type TelegramSetting = {
|
||||||
id: string;
|
id: string;
|
||||||
@ -17,6 +18,7 @@ type TelegramSetting = {
|
|||||||
|
|
||||||
const Telegram = () => {
|
const Telegram = () => {
|
||||||
const { config, setChannels } = useNotify();
|
const { config, setChannels } = useNotify();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const [telegram, setTelegram] = useState<TelegramSetting>({
|
const [telegram, setTelegram] = useState<TelegramSetting>({
|
||||||
id: config.id ?? "",
|
id: config.id ?? "",
|
||||||
@ -70,15 +72,15 @@ const Telegram = () => {
|
|||||||
|
|
||||||
setChannels(resp);
|
setChannels(resp);
|
||||||
toast({
|
toast({
|
||||||
title: "保存成功",
|
title: t('save.succeed'),
|
||||||
description: "配置保存成功",
|
description: t('setting.notify.config.save.succeed'),
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
const msg = getErrMessage(e);
|
const msg = getErrMessage(e);
|
||||||
|
|
||||||
toast({
|
toast({
|
||||||
title: "保存失败",
|
title: t('save.failed'),
|
||||||
description: "配置保存失败:" + msg,
|
description: `${t('setting.notify.config.save.failed')}: ${msg}`,
|
||||||
variant: "destructive",
|
variant: "destructive",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -128,7 +130,7 @@ const Telegram = () => {
|
|||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Label htmlFor="airplane-mode">是否启用</Label>
|
<Label htmlFor="airplane-mode">{t('setting.notify.config.enable')}</Label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex justify-end mt-2">
|
<div className="flex justify-end mt-2">
|
||||||
@ -137,7 +139,7 @@ const Telegram = () => {
|
|||||||
handleSaveClick();
|
handleSaveClick();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
保存
|
{t('save')}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -9,6 +9,7 @@ import { update } from "@/repository/settings";
|
|||||||
import { getErrMessage } from "@/lib/error";
|
import { getErrMessage } from "@/lib/error";
|
||||||
import { useToast } from "../ui/use-toast";
|
import { useToast } from "../ui/use-toast";
|
||||||
import { isValidURL } from "@/lib/url";
|
import { isValidURL } from "@/lib/url";
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
type WebhookSetting = {
|
type WebhookSetting = {
|
||||||
id: string;
|
id: string;
|
||||||
@ -18,6 +19,7 @@ type WebhookSetting = {
|
|||||||
|
|
||||||
const Webhook = () => {
|
const Webhook = () => {
|
||||||
const { config, setChannels } = useNotify();
|
const { config, setChannels } = useNotify();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const [webhook, setWebhook] = useState<WebhookSetting>({
|
const [webhook, setWebhook] = useState<WebhookSetting>({
|
||||||
id: config.id ?? "",
|
id: config.id ?? "",
|
||||||
@ -59,8 +61,8 @@ const Webhook = () => {
|
|||||||
webhook.data.url = webhook.data.url.trim();
|
webhook.data.url = webhook.data.url.trim();
|
||||||
if (!isValidURL(webhook.data.url)) {
|
if (!isValidURL(webhook.data.url)) {
|
||||||
toast({
|
toast({
|
||||||
title: "保存失败",
|
title: t('save.failed'),
|
||||||
description: "Url格式不正确",
|
description: t('setting.notify.config.save.failed.url.not.valid'),
|
||||||
variant: "destructive",
|
variant: "destructive",
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
@ -79,15 +81,15 @@ const Webhook = () => {
|
|||||||
|
|
||||||
setChannels(resp);
|
setChannels(resp);
|
||||||
toast({
|
toast({
|
||||||
title: "保存成功",
|
title: t('save.succeed'),
|
||||||
description: "配置保存成功",
|
description: t('setting.notify.config.save.succeed'),
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
const msg = getErrMessage(e);
|
const msg = getErrMessage(e);
|
||||||
|
|
||||||
toast({
|
toast({
|
||||||
title: "保存失败",
|
title: t('save.failed'),
|
||||||
description: "配置保存失败:" + msg,
|
description: `${t('setting.notify.config.save.failed')}: ${msg}`,
|
||||||
variant: "destructive",
|
variant: "destructive",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -123,7 +125,7 @@ const Webhook = () => {
|
|||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Label htmlFor="airplane-mode">是否启用</Label>
|
<Label htmlFor="airplane-mode">{t('setting.notify.config.enable')}</Label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex justify-end mt-2">
|
<div className="flex justify-end mt-2">
|
||||||
@ -132,7 +134,7 @@ const Webhook = () => {
|
|||||||
handleSaveClick();
|
handleSaveClick();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
保存
|
{t('save')}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -12,6 +12,7 @@ import {
|
|||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils"
|
||||||
import { Label } from "@/components/ui/label"
|
import { Label } from "@/components/ui/label"
|
||||||
|
import { useTranslation } from "react-i18next"
|
||||||
|
|
||||||
const Form = FormProvider
|
const Form = FormProvider
|
||||||
|
|
||||||
@ -145,7 +146,9 @@ const FormMessage = React.forwardRef<
|
|||||||
React.HTMLAttributes<HTMLParagraphElement>
|
React.HTMLAttributes<HTMLParagraphElement>
|
||||||
>(({ className, children, ...props }, ref) => {
|
>(({ className, children, ...props }, ref) => {
|
||||||
const { error, formMessageId } = useFormField()
|
const { error, formMessageId } = useFormField()
|
||||||
const body = error ? String(error?.message) : children
|
const { t } = useTranslation()
|
||||||
|
|
||||||
|
const body = error ? t(String(error?.message)) : children
|
||||||
|
|
||||||
if (!body) {
|
if (!body) {
|
||||||
return null
|
return null
|
||||||
|
@ -3,6 +3,7 @@ import { ChevronLeft, ChevronRight, MoreHorizontal } from "lucide-react";
|
|||||||
|
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { ButtonProps, buttonVariants } from "@/components/ui/button";
|
import { ButtonProps, buttonVariants } from "@/components/ui/button";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
const Pagination = ({ className, ...props }: React.ComponentProps<"nav">) => (
|
const Pagination = ({ className, ...props }: React.ComponentProps<"nav">) => (
|
||||||
<nav
|
<nav
|
||||||
@ -62,7 +63,10 @@ PaginationLink.displayName = "PaginationLink";
|
|||||||
const PaginationPrevious = ({
|
const PaginationPrevious = ({
|
||||||
className,
|
className,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof PaginationLink>) => (
|
}: React.ComponentProps<typeof PaginationLink>) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
|
return (
|
||||||
<PaginationLink
|
<PaginationLink
|
||||||
aria-label="Go to previous page"
|
aria-label="Go to previous page"
|
||||||
size="default"
|
size="default"
|
||||||
@ -70,25 +74,30 @@ const PaginationPrevious = ({
|
|||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
<ChevronLeft className="h-4 w-4" />
|
<ChevronLeft className="h-4 w-4" />
|
||||||
<span>上一页</span>
|
<span>{t('pagination.prev')}</span>
|
||||||
</PaginationLink>
|
</PaginationLink>
|
||||||
);
|
)
|
||||||
|
};
|
||||||
PaginationPrevious.displayName = "PaginationPrevious";
|
PaginationPrevious.displayName = "PaginationPrevious";
|
||||||
|
|
||||||
const PaginationNext = ({
|
const PaginationNext = ({
|
||||||
className,
|
className,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof PaginationLink>) => (
|
}: React.ComponentProps<typeof PaginationLink>) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
|
return (
|
||||||
<PaginationLink
|
<PaginationLink
|
||||||
aria-label="Go to next page"
|
aria-label="Go to next page"
|
||||||
size="default"
|
size="default"
|
||||||
className={cn("gap-1 pr-2.5", className)}
|
className={cn("gap-1 pr-2.5", className)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
<span>下一页</span>
|
<span>{t('pagination.next')}</span>
|
||||||
<ChevronRight className="h-4 w-4" />
|
<ChevronRight className="h-4 w-4" />
|
||||||
</PaginationLink>
|
</PaginationLink>
|
||||||
);
|
)
|
||||||
|
};
|
||||||
PaginationNext.displayName = "PaginationNext";
|
PaginationNext.displayName = "PaginationNext";
|
||||||
|
|
||||||
const PaginationEllipsis = ({
|
const PaginationEllipsis = ({
|
||||||
|
@ -1,15 +1,15 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
export const accessTypeMap: Map<string, [string, string]> = new Map([
|
export const accessTypeMap: Map<string, [string, string]> = new Map([
|
||||||
["tencent", ["腾讯云", "/imgs/providers/tencent.svg"]],
|
["tencent", ["tencent", "/imgs/providers/tencent.svg"]],
|
||||||
["aliyun", ["阿里云", "/imgs/providers/aliyun.svg"]],
|
["aliyun", ["aliyun", "/imgs/providers/aliyun.svg"]],
|
||||||
["cloudflare", ["Cloudflare", "/imgs/providers/cloudflare.svg"]],
|
["cloudflare", ["cloudflare", "/imgs/providers/cloudflare.svg"]],
|
||||||
["namesilo", ["Namesilo", "/imgs/providers/namesilo.svg"]],
|
["namesilo", ["namesilo", "/imgs/providers/namesilo.svg"]],
|
||||||
["godaddy", ["GoDaddy", "/imgs/providers/godaddy.svg"]],
|
["godaddy", ["go.daddy", "/imgs/providers/godaddy.svg"]],
|
||||||
["qiniu", ["七牛云", "/imgs/providers/qiniu.svg"]],
|
["qiniu", ["qiniu", "/imgs/providers/qiniu.svg"]],
|
||||||
["ssh", ["SSH部署", "/imgs/providers/ssh.svg"]],
|
["ssh", ["ssh", "/imgs/providers/ssh.svg"]],
|
||||||
["webhook", ["Webhook", "/imgs/providers/webhook.svg"]],
|
["webhook", ["webhook", "/imgs/providers/webhook.svg"]],
|
||||||
["local", ["本地部署", "/imgs/providers/local.svg"]],
|
["local", ["local", "/imgs/providers/local.svg"]],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
export const getProviderInfo = (t: string) => {
|
export const getProviderInfo = (t: string) => {
|
||||||
@ -28,7 +28,7 @@ export const accessFormType = z.union(
|
|||||||
z.literal("godaddy"),
|
z.literal("godaddy"),
|
||||||
z.literal("local"),
|
z.literal("local"),
|
||||||
],
|
],
|
||||||
{ message: "请选择云服务商" }
|
{ message: "access.not.empty" }
|
||||||
);
|
);
|
||||||
|
|
||||||
type AccessUsage = "apply" | "deploy" | "all";
|
type AccessUsage = "apply" | "deploy" | "all";
|
||||||
|
@ -40,14 +40,14 @@ export const getLastDeployment = (domain: Domain): Deployment | undefined => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const targetTypeMap: Map<string, [string, string]> = new Map([
|
export const targetTypeMap: Map<string, [string, string]> = new Map([
|
||||||
["aliyun-cdn", ["阿里云-CDN", "/imgs/providers/aliyun.svg"]],
|
["aliyun-cdn", ["aliyun.cdn", "/imgs/providers/aliyun.svg"]],
|
||||||
["aliyun-oss", ["阿里云-OSS", "/imgs/providers/aliyun.svg"]],
|
["aliyun-oss", ["aliyun.oss", "/imgs/providers/aliyun.svg"]],
|
||||||
["aliyun-dcdn", ["阿里云-DCDN", "/imgs/providers/aliyun.svg"]],
|
["aliyun-dcdn", ["aliyun.dcdn", "/imgs/providers/aliyun.svg"]],
|
||||||
["tencent-cdn", ["腾讯云-CDN", "/imgs/providers/tencent.svg"]],
|
["tencent-cdn", ["tencent.cdn", "/imgs/providers/tencent.svg"]],
|
||||||
["ssh", ["SSH部署", "/imgs/providers/ssh.svg"]],
|
["ssh", ["ssh", "/imgs/providers/ssh.svg"]],
|
||||||
["qiniu-cdn", ["七牛云-CDN", "/imgs/providers/qiniu.svg"]],
|
["qiniu-cdn", ["qiniu.cdn", "/imgs/providers/qiniu.svg"]],
|
||||||
["webhook", ["Webhook", "/imgs/providers/webhook.svg"]],
|
["webhook", ["webhook", "/imgs/providers/webhook.svg"]],
|
||||||
["local", ["本地部署", "/imgs/providers/local.svg"]],
|
["local", ["local", "/imgs/providers/local.svg"]],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
export const targetTypeKeys = Array.from(targetTypeMap.keys());
|
export const targetTypeKeys = Array.from(targetTypeMap.keys());
|
||||||
|
22
ui/src/i18n/index.ts
Normal file
22
ui/src/i18n/index.ts
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import i18n from 'i18next';
|
||||||
|
import { initReactI18next } from 'react-i18next';
|
||||||
|
import LanguageDetector from 'i18next-browser-languagedetector';
|
||||||
|
|
||||||
|
import resources from './locales'
|
||||||
|
|
||||||
|
i18n
|
||||||
|
.use(LanguageDetector)
|
||||||
|
.use(initReactI18next)
|
||||||
|
.init({
|
||||||
|
resources,
|
||||||
|
fallbackLng: 'zh',
|
||||||
|
debug: true,
|
||||||
|
interpolation: {
|
||||||
|
escapeValue: false,
|
||||||
|
},
|
||||||
|
backend: {
|
||||||
|
loadPath: '/locales/{{lng}}.json',
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export default i18n;
|
210
ui/src/i18n/locales/en.json
Normal file
210
ui/src/i18n/locales/en.json
Normal file
@ -0,0 +1,210 @@
|
|||||||
|
{
|
||||||
|
"ca": "Certificate Authority",
|
||||||
|
"username": "Username",
|
||||||
|
"username.not.empty": "Please enter username",
|
||||||
|
"password": "Password",
|
||||||
|
"password.not.empty": "Please enter password",
|
||||||
|
"email": "Email",
|
||||||
|
"logout": "Logout",
|
||||||
|
"setting": "Settings",
|
||||||
|
"account": "Account",
|
||||||
|
"template": "Template",
|
||||||
|
"save": "Save",
|
||||||
|
"no.data": "No data available",
|
||||||
|
"status": "Status",
|
||||||
|
"operation": "Operation",
|
||||||
|
"enable": "Enable",
|
||||||
|
"disable": "Disable",
|
||||||
|
"deploy": "Deploy",
|
||||||
|
"download": "Download",
|
||||||
|
"delete": "Delete",
|
||||||
|
"cancel": "Cancel",
|
||||||
|
"confirm": "Confirm",
|
||||||
|
"edit": "Edit",
|
||||||
|
"succeed": "Successful",
|
||||||
|
"add": "Add",
|
||||||
|
"document": "Document",
|
||||||
|
"variables": "Variables",
|
||||||
|
"dns": "Domain Name Server",
|
||||||
|
"name": "Name",
|
||||||
|
"create.time": "CreateTime",
|
||||||
|
"update.time": "UpdateTime",
|
||||||
|
"created.in": "Created in",
|
||||||
|
"updated.in": "Updated in",
|
||||||
|
"basic.setting": "Basic Settings",
|
||||||
|
"advanced.setting": "Advanced Settings",
|
||||||
|
"operation.succeed": "Operation Successful",
|
||||||
|
"save.succeed": "Save Successful",
|
||||||
|
"save.failed": "Save Failed",
|
||||||
|
"update.succeed": "Update Successful",
|
||||||
|
"update.failed": "Update Failed",
|
||||||
|
"delete.failed": "Delete Failed",
|
||||||
|
"ding.talk": "Ding Talk",
|
||||||
|
"telegram": "Telegram",
|
||||||
|
"webhook": "Webhook",
|
||||||
|
"local": "Local Deployment",
|
||||||
|
"tencent": "Tencent",
|
||||||
|
"tencent.cdn": "Tencent-CDN",
|
||||||
|
"aliyun": "Alibaba Cloud",
|
||||||
|
"aliyun.cdn": "Alibaba Cloud-CDN",
|
||||||
|
"aliyun.oss": "Alibaba Cloud-OSS",
|
||||||
|
"aliyun.dcdn": "Alibaba Cloud-DCDN",
|
||||||
|
"qiniu": "Qiniu",
|
||||||
|
"qiniu.cdn": "Qiniu-CDN",
|
||||||
|
"cloudflare": "Cloudflare",
|
||||||
|
"namesilo": "Namesilo",
|
||||||
|
"go.daddy": "GoDaddy",
|
||||||
|
"ssh": "SSH Deployment",
|
||||||
|
"zod.rule.string.max": "Please enter no more than {{max}} characters",
|
||||||
|
"zod.rule.url": "Please enter a valid URL",
|
||||||
|
"zod.rule.ssh.host": "Please enter the correct domain name or IP",
|
||||||
|
"login.submit": "Log In",
|
||||||
|
"login.username.no.empty.message": "Please enter a valid email address",
|
||||||
|
"login.password.length.message": "Password should be at least 10 characters",
|
||||||
|
"menu.auth.management": "Authorization Management",
|
||||||
|
"theme.light": "Light",
|
||||||
|
"theme.dark": "Dark",
|
||||||
|
"theme.system": "System",
|
||||||
|
"dashboard": "Dashboard",
|
||||||
|
"dashboard.all": "All",
|
||||||
|
"dashboard.near.expired": "About to Expire",
|
||||||
|
"dashboard.enabled": "Enabled",
|
||||||
|
"dashboard.not.enabled": "Not Enabled",
|
||||||
|
"dashboard.unit": "Unit",
|
||||||
|
"deployment.log.name": "Deployment History",
|
||||||
|
"deployment.log.empty": "You have not created any deployments yet, please add a domain to start deployment!",
|
||||||
|
"deployment.log.status": "Status",
|
||||||
|
"deployment.log.stage": "Stage",
|
||||||
|
"deployment.log.last.execution.time": "Last Execution Time",
|
||||||
|
"deployment.log.detail.button.text": "Log",
|
||||||
|
"deployment.log.detail": "Deployment Details",
|
||||||
|
"pagination.next": "Next",
|
||||||
|
"pagination.prev": "Previous",
|
||||||
|
"domain": "Domain",
|
||||||
|
"domain.add": "Add Domain",
|
||||||
|
"domain.delete": "Delete Domain",
|
||||||
|
"domain.not.empty.verify.message": "Please enter domain",
|
||||||
|
"domain.management.name": "Domain List",
|
||||||
|
"domain.management.start.deploy.succeed.tips": "Deployment initiated, please check the deployment log later.",
|
||||||
|
"domain.management.execution.failed": "Execution Failed",
|
||||||
|
"domain.management.execution.failed.tips": "Execution failed, please check the details in <1>Deployment History</1>.",
|
||||||
|
"domain.management.empty": "Please add a domain to start deploying the certificate.",
|
||||||
|
"domain.management.expiry.date": "Validity Period",
|
||||||
|
"domain.management.expiry.date1": "Valid for {{date}} days",
|
||||||
|
"domain.management.expiry.date2": "Expiry on {{date}}",
|
||||||
|
"domain.management.last.execution.time": "Last Execution Time",
|
||||||
|
"domain.management.last.execution.status": "Last Execution Status",
|
||||||
|
"domain.management.last.execution.stage": "Last Execution Stage",
|
||||||
|
"domain.management.enable": "Enable",
|
||||||
|
"domain.management.start.deploying": "Deploy Now",
|
||||||
|
"domain.management.forced.deployment": "Force Deployment",
|
||||||
|
"domain.management.delete.confirm": "Are you sure you want to delete this domain?",
|
||||||
|
"domain.management.edit.title": "Edit Domain",
|
||||||
|
"domain.management.edit.dns.access.label": "DNS Provider Authorization Configuration",
|
||||||
|
"domain.management.edit.dns.access.not.empty.message": "Please select DNS provider authorization configuration",
|
||||||
|
"domain.management.edit.access.label": "Provider Authorization Configuration",
|
||||||
|
"domain.management.edit.access.not.empty.message": "Please select authorization configuration",
|
||||||
|
"domain.management.edit.target.type": "Deployment Service Type",
|
||||||
|
"domain.management.edit.target.type.not.empty.message": "Please select deployment service type",
|
||||||
|
"domain.management.edit.succeed.tips": "Successful domain editing",
|
||||||
|
"domain.management.edit.target.access": "Deployment Service Provider Authorization Configuration",
|
||||||
|
"domain.management.edit.target.access.content.label": "Provider Authorization Configuration",
|
||||||
|
"domain.management.edit.target.access.not.empty.message": "Please select authorization configuration",
|
||||||
|
"domain.management.edit.target.access.verify.msg": "At least one of the deployment authorization and deployment authorization group must be selected",
|
||||||
|
"domain.management.edit.group.label": "Deployment Configuration Group (used to deploy a domain certificate to multiple ssh hosts)",
|
||||||
|
"domain.management.edit.group.not.empty.message": "Please select group",
|
||||||
|
"domain.management.edit.email.not.empty.message": "Please select email",
|
||||||
|
"domain.management.edit.email.description": "(A email is required to apply for a certificate)",
|
||||||
|
"domain.management.edit.variables.placeholder": "It can be used in SSH deployment, like:\nkey=val;\nkey2=val2;",
|
||||||
|
"domain.management.edit.dns.placeholder": "Custom domain name server, separates multiple entries with semicolon, like:\n8.8.8.8;\n8.8.4.4;",
|
||||||
|
"domain.management.add.succeed.tips": "Domain added successfully",
|
||||||
|
"email.add": "Add Email",
|
||||||
|
"email.list": "Email List",
|
||||||
|
"email.valid.message": "Please enter a valid email address",
|
||||||
|
"email.already.exist": "Email already exists",
|
||||||
|
"email.not.empty.message": "Please enter email",
|
||||||
|
"setting.notify.menu": "Notification Push",
|
||||||
|
"setting.submit": "Confirm Changes",
|
||||||
|
"setting.account.email.valid.message": "Please enter a valid email address",
|
||||||
|
"setting.account.email.placeholder": "Please enter email",
|
||||||
|
"setting.account.email.change.succeed": "Account email altered successfully",
|
||||||
|
"setting.account.email.change.failed": "Account email alteration failed",
|
||||||
|
"setting.account.log.back.in": "Please login again",
|
||||||
|
"setting.password.length.message": "Password should be at least 10 characters",
|
||||||
|
"setting.password.not.match": "Passwords do not match",
|
||||||
|
"setting.password.change.succeed": "Password changed successfully",
|
||||||
|
"setting.password.change.failed": "Password change failed",
|
||||||
|
"setting.password.current.password": "Current Password",
|
||||||
|
"setting.password.new.password": "New Password",
|
||||||
|
"setting.password.confirm.password": "Confirm Password",
|
||||||
|
"setting.notify.template.save.succeed": "Notification template saved successfully",
|
||||||
|
"setting.notify.template.variables.tips.title": "Optional variables, COUNT: number of expiring soon",
|
||||||
|
"setting.notify.template.variables.tips.content": "Optional variables, COUNT: number of expiring soon, DOMAINS: Domain list",
|
||||||
|
"setting.notify.config.enable": "Enable",
|
||||||
|
"setting.notify.config.save.succeed": "Configuration saved successfully",
|
||||||
|
"setting.notify.config.save.failed": "Configuration save failed",
|
||||||
|
"setting.notify.config.save.failed.url.not.valid": "Invalid Url format",
|
||||||
|
"setting.ca.not.empty": "Please select a Certificate Authority",
|
||||||
|
"setting.ca.eab_kid.not.empty": "Please enter EAB_KID",
|
||||||
|
"setting.ca.eab_hmac_key.not.empty": "Please enter EAB_HMAC_KEY.",
|
||||||
|
"setting.ca.eab_kid_hmac_key.not.empty": "Please enter EAB_KID and EAB_HMAC_KEY",
|
||||||
|
"deploy.progress.check": "Check",
|
||||||
|
"deploy.progress.apply": "Apply",
|
||||||
|
"deploy.progress.deploy": "Deploy",
|
||||||
|
"access.management": "Authorization Management",
|
||||||
|
"access.add": "Add Authorization",
|
||||||
|
"access.edit": "Edit Authorization",
|
||||||
|
"access.all": "All Authorizations",
|
||||||
|
"access.list": "Authorization List",
|
||||||
|
"access.type": "Provider",
|
||||||
|
"access.type.not.empty": "Please select a provider",
|
||||||
|
"access.not.empty": "Please select a cloud provider",
|
||||||
|
"access.empty": "Please add authorization to start deploying certificate.",
|
||||||
|
"access.group.management": "Authorization Group Management",
|
||||||
|
"access.group.add": "Add Authorization Group",
|
||||||
|
"access.group.not.empty": "Please select a group",
|
||||||
|
"access.group.name": "Group Name",
|
||||||
|
"access.group.name.not.empty": "Please enter group name",
|
||||||
|
"access.group.delete": "Delete Group",
|
||||||
|
"access.group.delete.confirm": "Are you sure you want to delete the deployment authorization group?",
|
||||||
|
"access.group.domain.empty": "Please add a domain to start deploying the certificate.",
|
||||||
|
"access.group.empty": "No deployment authorization configuration yet, please add after starting use.",
|
||||||
|
"access.group.total": "Totally {{total}} deployment authorization configuration",
|
||||||
|
"access.form.name.not.empty": "Please enter authorization name",
|
||||||
|
"access.form.config.field": "Configuration Type",
|
||||||
|
"access.form.access.key.id": "AccessKeyId",
|
||||||
|
"access.form.access.key.id.not.empty": "Please enter AccessKeyId",
|
||||||
|
"access.form.access.key.secret": "AccessKeySecret",
|
||||||
|
"access.form.access.key.secret.not.empty": "Please enter AccessKeySecret",
|
||||||
|
"access.form.cloud.dns.api.token": "CLOUD_DNS_API_TOKEN",
|
||||||
|
"access.form.cloud.dns.api.token.not.empty": "Please enter CLOUD_DNS_API_TOKEN",
|
||||||
|
"access.form.go.daddy.api.key": "GO_DADDY_API_KEY",
|
||||||
|
"access.form.go.daddy.api.key.not.empty": "Please enter GO_DADDY_API_KEY",
|
||||||
|
"access.form.go.daddy.api.secret": "GO_DADDY_API_SECRET",
|
||||||
|
"access.form.go.daddy.api.secret.not.empty": "Please enter GO_DADDY_API_SECRET",
|
||||||
|
"access.form.namesilo.api.key": "NAMESILO_API_KEY",
|
||||||
|
"access.form.namesilo.api.key.not.empty": "Please enter NAMESILO_API_KEY",
|
||||||
|
"access.form.secret.id": "SecretId",
|
||||||
|
"access.form.secret.id.not.empty": "Please enter SecretId",
|
||||||
|
"access.form.secret.key": "SecretKey",
|
||||||
|
"access.form.secret.key.not.empty": "Please enter SecretKey",
|
||||||
|
"access.form.access.key": "AccessKey",
|
||||||
|
"access.form.access.key.not.empty": "Please enter AccessKey",
|
||||||
|
"access.form.webhook.url": "Webhook URL",
|
||||||
|
"access.form.webhook.url.not.empty": "Please enter Webhook URL",
|
||||||
|
"access.form.ssh.group.label": "Authorization Configuration Group (used to deploy a single domain certificate to multiple SSH hosts)",
|
||||||
|
"access.form.ssh.host": "Server Host",
|
||||||
|
"access.form.ssh.host.not.empty": "Please enter Host",
|
||||||
|
"access.form.ssh.port": "SSH Port",
|
||||||
|
"access.form.ssh.port.not.empty": "Please enter Port",
|
||||||
|
"access.form.ssh.key": "Key (Log in using certificate)",
|
||||||
|
"access.form.ssh.key.not.empty": "Please enter Key",
|
||||||
|
"access.form.ssh.key.file.not.empty": "Please select file",
|
||||||
|
"access.form.ssh.cert.path": "Certificate Upload Path",
|
||||||
|
"access.form.ssh.cert.path.not.empty": "Please enter certificate upload path",
|
||||||
|
"access.form.ssh.key.path": "Private Key Upload Path",
|
||||||
|
"access.form.ssh.key.path.not.empty": "Please enter private key upload path",
|
||||||
|
"access.form.ssh.command": "Command",
|
||||||
|
"access.form.ssh.command.not.empty": "Please enter command",
|
||||||
|
"access.form.ding.access.token.placeholder": "Signature for signed addition"
|
||||||
|
}
|
17
ui/src/i18n/locales/index.ts
Normal file
17
ui/src/i18n/locales/index.ts
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import { Resource } from 'i18next'
|
||||||
|
|
||||||
|
import zh from './zh.json'
|
||||||
|
import en from './en.json'
|
||||||
|
|
||||||
|
const resources: Resource = {
|
||||||
|
zh: {
|
||||||
|
name: '简体中文',
|
||||||
|
translation: zh
|
||||||
|
},
|
||||||
|
en: {
|
||||||
|
name: 'English',
|
||||||
|
translation: en
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default resources;
|
210
ui/src/i18n/locales/zh.json
Normal file
210
ui/src/i18n/locales/zh.json
Normal file
@ -0,0 +1,210 @@
|
|||||||
|
{
|
||||||
|
"ca": "证书颁发机构",
|
||||||
|
"username": "用户名",
|
||||||
|
"username.not.empty": "请输入用户名",
|
||||||
|
"password": "密码",
|
||||||
|
"password.not.empty": "请输入密码",
|
||||||
|
"email": "邮箱",
|
||||||
|
"logout": "退出登录",
|
||||||
|
"setting": "设置",
|
||||||
|
"account": "账户",
|
||||||
|
"template": "模版",
|
||||||
|
"save": "保存",
|
||||||
|
"no.data": "暂无数据",
|
||||||
|
"status": "状态",
|
||||||
|
"operation": "操作",
|
||||||
|
"enable": "启用",
|
||||||
|
"disable": "禁用",
|
||||||
|
"deploy": "部署",
|
||||||
|
"download": "下载",
|
||||||
|
"delete": "删除",
|
||||||
|
"cancel": "取消",
|
||||||
|
"confirm": "确认",
|
||||||
|
"edit": "编辑",
|
||||||
|
"succeed": "成功",
|
||||||
|
"add": "新增",
|
||||||
|
"document": "文档",
|
||||||
|
"variables": "变量",
|
||||||
|
"dns": "域名服务器",
|
||||||
|
"name": "名称",
|
||||||
|
"create.time": "创建时间",
|
||||||
|
"update.time": "更新时间",
|
||||||
|
"created.in": "创建于",
|
||||||
|
"updated.in": "更新于",
|
||||||
|
"basic.setting": "基础设置",
|
||||||
|
"advanced.setting": "高级设置",
|
||||||
|
"operation.succeed": "操作成功",
|
||||||
|
"save.succeed": "保存成功",
|
||||||
|
"save.failed": "保存失败",
|
||||||
|
"update.succeed": "修改成功",
|
||||||
|
"update.failed": "修改失败",
|
||||||
|
"delete.failed": "删除失败",
|
||||||
|
"ding.talk": "钉钉",
|
||||||
|
"telegram": "Telegram",
|
||||||
|
"webhook": "Webhook",
|
||||||
|
"local": "本地部署",
|
||||||
|
"tencent": "腾讯云",
|
||||||
|
"tencent.cdn": "腾讯云-CDN",
|
||||||
|
"aliyun": "阿里云",
|
||||||
|
"aliyun.cdn": "阿里云-CDN",
|
||||||
|
"aliyun.oss": "阿里云-OSS",
|
||||||
|
"aliyun.dcdn": "阿里云-DCDN",
|
||||||
|
"qiniu": "七牛云",
|
||||||
|
"qiniu.cdn": "七牛云-CDN",
|
||||||
|
"cloudflare": "Cloudflare",
|
||||||
|
"namesilo": "Namesilo",
|
||||||
|
"go.daddy": "GoDaddy",
|
||||||
|
"ssh": "SSH 部署",
|
||||||
|
"zod.rule.string.max": "请输入不超过 {{max}} 个字符",
|
||||||
|
"zod.rule.url": "请输入有效的 url 地址",
|
||||||
|
"zod.rule.ssh.host": "请输入正确的域名或IP",
|
||||||
|
"login.submit": "登录",
|
||||||
|
"login.username.no.empty.message": "请输入正确的邮箱地址",
|
||||||
|
"login.password.length.message": "密码至少10个字符",
|
||||||
|
"menu.auth.management": "授权管理",
|
||||||
|
"theme.light": "浅色",
|
||||||
|
"theme.dark": "暗黑",
|
||||||
|
"theme.system": "系统",
|
||||||
|
"dashboard": "控制面板",
|
||||||
|
"dashboard.all": "所有",
|
||||||
|
"dashboard.near.expired": "即将过期",
|
||||||
|
"dashboard.enabled": "启用中",
|
||||||
|
"dashboard.not.enabled": "未启用",
|
||||||
|
"dashboard.unit": "个",
|
||||||
|
"deployment.log.name": "部署历史",
|
||||||
|
"deployment.log.empty": "你暂未创建任何部署,请先添加域名进行部署吧!",
|
||||||
|
"deployment.log.status": "状态",
|
||||||
|
"deployment.log.stage": "阶段",
|
||||||
|
"deployment.log.last.execution.time": "最近执行时间",
|
||||||
|
"deployment.log.detail.button.text": "日志",
|
||||||
|
"deployment.log.detail": "部署详情",
|
||||||
|
"pagination.next": "下一页",
|
||||||
|
"pagination.prev": "上一页",
|
||||||
|
"domain": "域名",
|
||||||
|
"domain.add": "新增域名",
|
||||||
|
"domain.delete": "删除域名",
|
||||||
|
"domain.not.empty.verify.message": "请输入域名",
|
||||||
|
"domain.management.name": "域名列表",
|
||||||
|
"domain.management.start.deploy.succeed.tips": "已发起部署,请稍后查看部署日志。",
|
||||||
|
"domain.management.execution.failed": "执行失败",
|
||||||
|
"domain.management.execution.failed.tips": "执行失败,请在 <1>部署历史</1> 查看详情。",
|
||||||
|
"domain.management.empty": "请添加域名开始部署证书吧。",
|
||||||
|
"domain.management.expiry.date": "有效期限",
|
||||||
|
"domain.management.expiry.date1": "有效期 {{date}} 天",
|
||||||
|
"domain.management.expiry.date2": "{{date}} 到期",
|
||||||
|
"domain.management.last.execution.time": "最近执行时间",
|
||||||
|
"domain.management.last.execution.status": "最近执行状态",
|
||||||
|
"domain.management.last.execution.stage": "最近执行阶段",
|
||||||
|
"domain.management.enable": "是否启用",
|
||||||
|
"domain.management.start.deploying": "立即部署",
|
||||||
|
"domain.management.forced.deployment": "强行部署",
|
||||||
|
"domain.management.delete.confirm": "确定要删除域名吗?",
|
||||||
|
"domain.management.edit.title": "编辑域名",
|
||||||
|
"domain.management.edit.dns.access.label": "DNS 服务商授权配置",
|
||||||
|
"domain.management.edit.dns.access.not.empty.message": "请选择DNS服务商授权配置",
|
||||||
|
"domain.management.edit.access.label": "服务商授权配置",
|
||||||
|
"domain.management.edit.access.not.empty.message": "请选择授权配置",
|
||||||
|
"domain.management.edit.target.type": "部署服务类型",
|
||||||
|
"domain.management.edit.target.type.not.empty.message": "请选择部署服务类型",
|
||||||
|
"domain.management.edit.succeed.tips": "域名编辑成功",
|
||||||
|
"domain.management.edit.target.access": "部署服务商授权配置",
|
||||||
|
"domain.management.edit.target.access.content.label": "服务商授权配置",
|
||||||
|
"domain.management.edit.target.access.not.empty.message": "请选择授权配置",
|
||||||
|
"domain.management.edit.target.access.verify.msg": "部署授权和部署授权组至少选一个",
|
||||||
|
"domain.management.edit.group.label": "部署配置组(用于将一个域名证书部署到多个 ssh 主机)",
|
||||||
|
"domain.management.edit.group.not.empty.message": "请选择分组",
|
||||||
|
"domain.management.edit.email.not.empty.message": "请选择邮箱",
|
||||||
|
"domain.management.edit.email.description": "(申请证书需要提供邮箱)",
|
||||||
|
"domain.management.edit.variables.placeholder": "可在SSH部署中使用,形如:\nkey=val;\nkey2=val2;",
|
||||||
|
"domain.management.edit.dns.placeholder": "自定义域名服务器,多个用分号隔开,如:\n8.8.8.8;\n8.8.4.4;",
|
||||||
|
"domain.management.add.succeed.tips": "域名添加成功",
|
||||||
|
"email.add": "添加邮箱",
|
||||||
|
"email.list": "邮箱列表",
|
||||||
|
"email.valid.message": "请输入正确的邮箱地址",
|
||||||
|
"email.already.exist": "邮箱已存在",
|
||||||
|
"email.not.empty.message": "请输入邮箱",
|
||||||
|
"setting.notify.menu": "消息推送",
|
||||||
|
"setting.submit": "确认修改",
|
||||||
|
"setting.account.email.valid.message": "请输入正确的邮箱地址",
|
||||||
|
"setting.account.email.placeholder": "请输入邮箱",
|
||||||
|
"setting.account.email.change.succeed": "修改账户邮箱成功",
|
||||||
|
"setting.account.email.change.failed": "修改账户邮箱失败",
|
||||||
|
"setting.account.log.back.in": "请重新登录",
|
||||||
|
"setting.password.length.message": "密码至少10个字符",
|
||||||
|
"setting.password.not.match": "两次密码不一致",
|
||||||
|
"setting.password.change.succeed": "修改密码成功",
|
||||||
|
"setting.password.change.failed": "修改密码失败",
|
||||||
|
"setting.password.current.password": "当前密码",
|
||||||
|
"setting.password.new.password": "新密码",
|
||||||
|
"setting.password.confirm.password": "确认密码",
|
||||||
|
"setting.notify.template.save.succeed": "通知模板保存成功",
|
||||||
|
"setting.notify.template.variables.tips.title": "可选的变量, COUNT:即将过期张数",
|
||||||
|
"setting.notify.template.variables.tips.content": "可选的变量, COUNT:即将过期张数,DOMAINS:域名列表",
|
||||||
|
"setting.notify.config.enable": "是否启用",
|
||||||
|
"setting.notify.config.save.succeed": "配置保存成功",
|
||||||
|
"setting.notify.config.save.failed": "配置保存失败",
|
||||||
|
"setting.notify.config.save.failed.url.not.valid": "Url格式不正确",
|
||||||
|
"setting.ca.not.empty": "请选择证书分发机构",
|
||||||
|
"setting.ca.eab_kid.not.empty": "请输入EAB_KID",
|
||||||
|
"setting.ca.eab_hmac_key.not.empty": "请输入EAB_HMAC_KEY",
|
||||||
|
"setting.ca.eab_kid_hmac_key.not.empty": "请输入EAB_KID和EAB_HMAC_KEY",
|
||||||
|
"deploy.progress.check": "检查",
|
||||||
|
"deploy.progress.apply": "获取",
|
||||||
|
"deploy.progress.deploy": "部署",
|
||||||
|
"access.management": "授权管理",
|
||||||
|
"access.add": "添加授权",
|
||||||
|
"access.edit": "编辑授权",
|
||||||
|
"access.all": "所有授权",
|
||||||
|
"access.list": "授权列表",
|
||||||
|
"access.type": "服务商",
|
||||||
|
"access.type.not.empty": "请选择服务商",
|
||||||
|
"access.not.empty": "请选择云服务商",
|
||||||
|
"access.empty": "请添加授权开始部署证书吧。",
|
||||||
|
"access.group.management": "授权组管理",
|
||||||
|
"access.group.add": "添加授权组",
|
||||||
|
"access.group.not.empty": "请选择分组",
|
||||||
|
"access.group.name": "组名",
|
||||||
|
"access.group.name.not.empty": "请输入组名",
|
||||||
|
"access.group.delete": "删除组",
|
||||||
|
"access.group.delete.confirm": "确定要删除部署授权组吗?",
|
||||||
|
"access.group.domain.empty": "请添加域名开始部署证书吧。",
|
||||||
|
"access.group.empty": "暂无部署授权配置,请添加后开始使用吧",
|
||||||
|
"access.group.total": "共有 {{total}} 个部署授权配置",
|
||||||
|
"access.form.name.not.empty": "请输入授权名称",
|
||||||
|
"access.form.config.field": "配置类型",
|
||||||
|
"access.form.access.key.id": "AccessKeyId",
|
||||||
|
"access.form.access.key.id.not.empty": "请输入 AccessKeyId",
|
||||||
|
"access.form.access.key.secret": "AccessKeySecret",
|
||||||
|
"access.form.access.key.secret.not.empty": "请输入 AccessKeySecret",
|
||||||
|
"access.form.cloud.dns.api.token": "CLOUD_DNS_API_TOKEN",
|
||||||
|
"access.form.cloud.dns.api.token.not.empty": "请输入 CLOUD_DNS_API_TOKEN",
|
||||||
|
"access.form.go.daddy.api.key": "GO_DADDY_API_KEY",
|
||||||
|
"access.form.go.daddy.api.key.not.empty": "请输入 GO_DADDY_API_KEY",
|
||||||
|
"access.form.go.daddy.api.secret": "GO_DADDY_API_SECRET",
|
||||||
|
"access.form.go.daddy.api.secret.not.empty": "请输入 GO_DADDY_API_SECRET",
|
||||||
|
"access.form.namesilo.api.key": "NAMESILO_API_KEY",
|
||||||
|
"access.form.namesilo.api.key.not.empty": "请输入 NAMESILO_API_KEY",
|
||||||
|
"access.form.secret.id": "SecretId",
|
||||||
|
"access.form.secret.id.not.empty": "请输入 SecretId",
|
||||||
|
"access.form.secret.key": "SecretKey",
|
||||||
|
"access.form.secret.key.not.empty": "请输入 SecretKey",
|
||||||
|
"access.form.access.key": "AccessKey",
|
||||||
|
"access.form.access.key.not.empty": "请输入 AccessKey",
|
||||||
|
"access.form.webhook.url": "Webhook URL",
|
||||||
|
"access.form.webhook.url.not.empty": "请输入 Webhook URL",
|
||||||
|
"access.form.ssh.group.label": "授权配置组(用于将一个域名证书部署到多个 ssh 主机)",
|
||||||
|
"access.form.ssh.host": "服务器 Host",
|
||||||
|
"access.form.ssh.host.not.empty": "请输入 Host",
|
||||||
|
"access.form.ssh.port": "SSH 端口",
|
||||||
|
"access.form.ssh.port.not.empty": "请输入 Port",
|
||||||
|
"access.form.ssh.key": "Key(使用证书登录)",
|
||||||
|
"access.form.ssh.key.not.empty": "请输入 Key",
|
||||||
|
"access.form.ssh.key.file.not.empty": "请选择文件",
|
||||||
|
"access.form.ssh.cert.path": "证书上传路径",
|
||||||
|
"access.form.ssh.cert.path.not.empty": "请输入证书上传路径",
|
||||||
|
"access.form.ssh.key.path": "私钥上传路径",
|
||||||
|
"access.form.ssh.key.path.not.empty": "请输入私钥上传路径",
|
||||||
|
"access.form.ssh.command": "Command",
|
||||||
|
"access.form.ssh.command.not.empty": "请输入要执行的命令",
|
||||||
|
"access.form.ding.access.token.placeholder": "加签的签名"
|
||||||
|
}
|
@ -4,6 +4,7 @@ import "./global.css";
|
|||||||
import { RouterProvider } from "react-router-dom";
|
import { RouterProvider } from "react-router-dom";
|
||||||
import { router } from "./router.tsx";
|
import { router } from "./router.tsx";
|
||||||
import { ThemeProvider } from "./components/ThemeProvider.tsx";
|
import { ThemeProvider } from "./components/ThemeProvider.tsx";
|
||||||
|
import "@/i18n";
|
||||||
|
|
||||||
ReactDOM.createRoot(document.getElementById("root")!).render(
|
ReactDOM.createRoot(document.getElementById("root")!).render(
|
||||||
<React.StrictMode>
|
<React.StrictMode>
|
||||||
|
@ -6,6 +6,7 @@ import {
|
|||||||
useNavigate,
|
useNavigate,
|
||||||
} from "react-router-dom";
|
} from "react-router-dom";
|
||||||
import { CircleUser, Earth, History, Home, Menu, Server } from "lucide-react";
|
import { CircleUser, Earth, History, Home, Menu, Server } from "lucide-react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
|
|
||||||
@ -22,12 +23,14 @@ import { cn } from "@/lib/utils";
|
|||||||
import { ConfigProvider } from "@/providers/config";
|
import { ConfigProvider } from "@/providers/config";
|
||||||
import { getPb } from "@/repository/api";
|
import { getPb } from "@/repository/api";
|
||||||
import { ThemeToggle } from "@/components/ThemeToggle";
|
import { ThemeToggle } from "@/components/ThemeToggle";
|
||||||
|
import LocaleToggle from "@/components/LocaleToggle";
|
||||||
|
|
||||||
import Version from "@/components/certimate/Version";
|
import Version from "@/components/certimate/Version";
|
||||||
|
|
||||||
export default function Dashboard() {
|
export default function Dashboard() {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
if (!getPb().authStore.isValid || !getPb().authStore.isAdmin) {
|
if (!getPb().authStore.isValid || !getPb().authStore.isAdmin) {
|
||||||
return <Navigate to="/login" />;
|
return <Navigate to="/login" />;
|
||||||
@ -70,7 +73,7 @@ export default function Dashboard() {
|
|||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<Home className="h-4 w-4" />
|
<Home className="h-4 w-4" />
|
||||||
控制面板
|
{t('dashboard')}
|
||||||
</Link>
|
</Link>
|
||||||
<Link
|
<Link
|
||||||
to="/domains"
|
to="/domains"
|
||||||
@ -80,7 +83,7 @@ export default function Dashboard() {
|
|||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<Earth className="h-4 w-4" />
|
<Earth className="h-4 w-4" />
|
||||||
域名列表
|
{t('domain.management.name')}
|
||||||
</Link>
|
</Link>
|
||||||
<Link
|
<Link
|
||||||
to="/access"
|
to="/access"
|
||||||
@ -90,7 +93,7 @@ export default function Dashboard() {
|
|||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<Server className="h-4 w-4" />
|
<Server className="h-4 w-4" />
|
||||||
授权管理
|
{t('menu.auth.management')}
|
||||||
</Link>
|
</Link>
|
||||||
|
|
||||||
<Link
|
<Link
|
||||||
@ -101,7 +104,7 @@ export default function Dashboard() {
|
|||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<History className="h-4 w-4" />
|
<History className="h-4 w-4" />
|
||||||
部署历史
|
{t('deployment.log.name')}
|
||||||
</Link>
|
</Link>
|
||||||
</nav>
|
</nav>
|
||||||
</div>
|
</div>
|
||||||
@ -138,7 +141,7 @@ export default function Dashboard() {
|
|||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<Home className="h-5 w-5" />
|
<Home className="h-5 w-5" />
|
||||||
控制面板
|
{t('dashboard')}
|
||||||
</Link>
|
</Link>
|
||||||
<Link
|
<Link
|
||||||
to="/domains"
|
to="/domains"
|
||||||
@ -148,7 +151,7 @@ export default function Dashboard() {
|
|||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<Earth className="h-5 w-5" />
|
<Earth className="h-5 w-5" />
|
||||||
域名列表
|
{t('domain.management.name')}
|
||||||
</Link>
|
</Link>
|
||||||
<Link
|
<Link
|
||||||
to="/access"
|
to="/access"
|
||||||
@ -158,7 +161,7 @@ export default function Dashboard() {
|
|||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<Server className="h-5 w-5" />
|
<Server className="h-5 w-5" />
|
||||||
授权管理
|
{t('menu.auth.management')}
|
||||||
</Link>
|
</Link>
|
||||||
|
|
||||||
<Link
|
<Link
|
||||||
@ -169,13 +172,14 @@ export default function Dashboard() {
|
|||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<History className="h-5 w-5" />
|
<History className="h-5 w-5" />
|
||||||
部署历史
|
{t('deployment.log.name')}
|
||||||
</Link>
|
</Link>
|
||||||
</nav>
|
</nav>
|
||||||
</SheetContent>
|
</SheetContent>
|
||||||
</Sheet>
|
</Sheet>
|
||||||
<div className="w-full flex-1"></div>
|
<div className="w-full flex-1"></div>
|
||||||
<ThemeToggle />
|
<ThemeToggle />
|
||||||
|
<LocaleToggle />
|
||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
<DropdownMenuTrigger asChild>
|
<DropdownMenuTrigger asChild>
|
||||||
<Button
|
<Button
|
||||||
@ -188,15 +192,15 @@ export default function Dashboard() {
|
|||||||
</Button>
|
</Button>
|
||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
<DropdownMenuContent align="end">
|
<DropdownMenuContent align="end">
|
||||||
<DropdownMenuLabel>账户</DropdownMenuLabel>
|
<DropdownMenuLabel>{t('account')}</DropdownMenuLabel>
|
||||||
<DropdownMenuSeparator />
|
<DropdownMenuSeparator />
|
||||||
|
|
||||||
<DropdownMenuItem onClick={handleSettingClick}>
|
<DropdownMenuItem onClick={handleSettingClick}>
|
||||||
偏好设置
|
{t('setting')}
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
|
|
||||||
<DropdownMenuItem onClick={handleLogoutClick}>
|
<DropdownMenuItem onClick={handleLogoutClick}>
|
||||||
退出
|
{t('logout')}
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
</DropdownMenuContent>
|
</DropdownMenuContent>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
|
@ -4,11 +4,13 @@ import { KeyRound, Megaphone, ShieldCheck, UserRound } from "lucide-react";
|
|||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
|
|
||||||
import { Outlet, useLocation, useNavigate } from "react-router-dom";
|
import { Outlet, useLocation, useNavigate } from "react-router-dom";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
const SettingLayout = () => {
|
const SettingLayout = () => {
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const [tabValue, setTabValue] = useState("account");
|
const [tabValue, setTabValue] = useState("account");
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const pathname = location.pathname;
|
const pathname = location.pathname;
|
||||||
@ -20,7 +22,7 @@ const SettingLayout = () => {
|
|||||||
<div>
|
<div>
|
||||||
<Toaster />
|
<Toaster />
|
||||||
<div className="text-muted-foreground border-b dark:border-stone-500 py-5">
|
<div className="text-muted-foreground border-b dark:border-stone-500 py-5">
|
||||||
偏好设置
|
{t("setting")}
|
||||||
</div>
|
</div>
|
||||||
<div className="w-full mt-5 p-0 md:p-3 flex justify-center">
|
<div className="w-full mt-5 p-0 md:p-3 flex justify-center">
|
||||||
<Tabs defaultValue="account" className="w-full" value={tabValue}>
|
<Tabs defaultValue="account" className="w-full" value={tabValue}>
|
||||||
@ -33,7 +35,7 @@ const SettingLayout = () => {
|
|||||||
className="px-5"
|
className="px-5"
|
||||||
>
|
>
|
||||||
<UserRound size={14} />
|
<UserRound size={14} />
|
||||||
<div className="ml-1">账户</div>
|
<div className="ml-1">{t("account")}</div>
|
||||||
</TabsTrigger>
|
</TabsTrigger>
|
||||||
<TabsTrigger
|
<TabsTrigger
|
||||||
value="password"
|
value="password"
|
||||||
@ -43,7 +45,7 @@ const SettingLayout = () => {
|
|||||||
className="px-5"
|
className="px-5"
|
||||||
>
|
>
|
||||||
<KeyRound size={14} />
|
<KeyRound size={14} />
|
||||||
<div className="ml-1">密码</div>
|
<div className="ml-1">{t("password")}</div>
|
||||||
</TabsTrigger>
|
</TabsTrigger>
|
||||||
|
|
||||||
<TabsTrigger
|
<TabsTrigger
|
||||||
@ -54,7 +56,7 @@ const SettingLayout = () => {
|
|||||||
className="px-5"
|
className="px-5"
|
||||||
>
|
>
|
||||||
<Megaphone size={14} />
|
<Megaphone size={14} />
|
||||||
<div className="ml-1">消息推送</div>
|
<div className="ml-1">{t("setting.notify.menu")}</div>
|
||||||
</TabsTrigger>
|
</TabsTrigger>
|
||||||
<TabsTrigger
|
<TabsTrigger
|
||||||
value="ssl-provider"
|
value="ssl-provider"
|
||||||
@ -64,7 +66,7 @@ const SettingLayout = () => {
|
|||||||
className="px-5"
|
className="px-5"
|
||||||
>
|
>
|
||||||
<ShieldCheck size={14} />
|
<ShieldCheck size={14} />
|
||||||
<div className="ml-1">证书厂商</div>
|
<div className="ml-1">{t("ca")}</div>
|
||||||
</TabsTrigger>
|
</TabsTrigger>
|
||||||
</TabsList>
|
</TabsList>
|
||||||
<TabsContent value={tabValue}>
|
<TabsContent value={tabValue}>
|
||||||
|
@ -9,6 +9,7 @@ import { Access as AccessType, accessTypeMap } from "@/domain/access";
|
|||||||
import { convertZulu2Beijing } from "@/lib/time";
|
import { convertZulu2Beijing } from "@/lib/time";
|
||||||
import { useConfig } from "@/providers/config";
|
import { useConfig } from "@/providers/config";
|
||||||
import { remove } from "@/repository/access";
|
import { remove } from "@/repository/access";
|
||||||
|
import { t } from "i18next";
|
||||||
import { Key } from "lucide-react";
|
import { Key } from "lucide-react";
|
||||||
import { useLocation, useNavigate } from "react-router-dom";
|
import { useLocation, useNavigate } from "react-router-dom";
|
||||||
|
|
||||||
@ -46,11 +47,11 @@ const Access = () => {
|
|||||||
return (
|
return (
|
||||||
<div className="">
|
<div className="">
|
||||||
<div className="flex justify-between items-center">
|
<div className="flex justify-between items-center">
|
||||||
<div className="text-muted-foreground">授权管理</div>
|
<div className="text-muted-foreground">{t("access.management")}</div>
|
||||||
{tab != "access_group" ? (
|
{tab != "access_group" ? (
|
||||||
<AccessEdit trigger={<Button>添加授权</Button>} op="add" />
|
<AccessEdit trigger={<Button>{t("access.add")}</Button>} op="add" />
|
||||||
) : (
|
) : (
|
||||||
<AccessGroupEdit trigger={<Button>添加授权组</Button>} />
|
<AccessGroupEdit trigger={<Button>{t("access.group.add")}</Button>} />
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -66,7 +67,7 @@ const Access = () => {
|
|||||||
handleTabItemClick("access");
|
handleTabItemClick("access");
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
授权管理
|
{t("access.management")}
|
||||||
</TabsTrigger>
|
</TabsTrigger>
|
||||||
<TabsTrigger
|
<TabsTrigger
|
||||||
value="access_group"
|
value="access_group"
|
||||||
@ -74,7 +75,7 @@ const Access = () => {
|
|||||||
handleTabItemClick("access_group");
|
handleTabItemClick("access_group");
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
授权组管理
|
{t("access.group.management")}
|
||||||
</TabsTrigger>
|
</TabsTrigger>
|
||||||
</TabsList>
|
</TabsList>
|
||||||
<TabsContent value="access">
|
<TabsContent value="access">
|
||||||
@ -85,10 +86,10 @@ const Access = () => {
|
|||||||
</span>
|
</span>
|
||||||
|
|
||||||
<div className="text-center text-sm text-muted-foreground mt-3">
|
<div className="text-center text-sm text-muted-foreground mt-3">
|
||||||
请添加授权开始部署证书吧。
|
{t("access.empty")}
|
||||||
</div>
|
</div>
|
||||||
<AccessEdit
|
<AccessEdit
|
||||||
trigger={<Button>添加授权</Button>}
|
trigger={<Button>{t("access.add")}</Button>}
|
||||||
op="add"
|
op="add"
|
||||||
className="mt-3"
|
className="mt-3"
|
||||||
/>
|
/>
|
||||||
@ -96,15 +97,15 @@ const Access = () => {
|
|||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<div className="hidden sm:flex sm:flex-row text-muted-foreground text-sm border-b dark:border-stone-500 sm:p-2 mt-5">
|
<div className="hidden sm:flex sm:flex-row text-muted-foreground text-sm border-b dark:border-stone-500 sm:p-2 mt-5">
|
||||||
<div className="w-48">名称</div>
|
<div className="w-48">{t("name")}</div>
|
||||||
<div className="w-48">服务商</div>
|
<div className="w-48">{t("access.type")}</div>
|
||||||
|
|
||||||
<div className="w-52">创建时间</div>
|
<div className="w-60">{t("create.time")}</div>
|
||||||
<div className="w-52">更新时间</div>
|
<div className="w-60">{t("update.time")}</div>
|
||||||
<div className="grow">操作</div>
|
<div className="grow">{t("operation")}</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="sm:hidden flex text-sm text-muted-foreground">
|
<div className="sm:hidden flex text-sm text-muted-foreground">
|
||||||
授权列表
|
{t("access.list")}
|
||||||
</div>
|
</div>
|
||||||
{accesses
|
{accesses
|
||||||
.filter((item) => {
|
.filter((item) => {
|
||||||
@ -124,22 +125,24 @@ const Access = () => {
|
|||||||
src={accessTypeMap.get(access.configType)?.[1]}
|
src={accessTypeMap.get(access.configType)?.[1]}
|
||||||
className="w-6"
|
className="w-6"
|
||||||
/>
|
/>
|
||||||
<div>{accessTypeMap.get(access.configType)?.[0]}</div>
|
<div>
|
||||||
|
{t(accessTypeMap.get(access.configType)?.[0] || "")}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="sm:w-52 w-full pt-1 sm:pt-0 flex items-center">
|
<div className="sm:w-60 w-full pt-1 sm:pt-0 flex items-center">
|
||||||
创建于{" "}
|
{t("created.in")}{" "}
|
||||||
{access.created && convertZulu2Beijing(access.created)}
|
{access.created && convertZulu2Beijing(access.created)}
|
||||||
</div>
|
</div>
|
||||||
<div className="sm:w-52 w-full pt-1 sm:pt-0 flex items-center">
|
<div className="sm:w-60 w-full pt-1 sm:pt-0 flex items-center">
|
||||||
更新于{" "}
|
{t("updated.in")}{" "}
|
||||||
{access.updated && convertZulu2Beijing(access.updated)}
|
{access.updated && convertZulu2Beijing(access.updated)}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center grow justify-start pt-1 sm:pt-0">
|
<div className="flex items-center grow justify-start pt-1 sm:pt-0">
|
||||||
<AccessEdit
|
<AccessEdit
|
||||||
trigger={
|
trigger={
|
||||||
<Button variant={"link"} className="p-0">
|
<Button variant={"link"} className="p-0">
|
||||||
编辑
|
{t("edit")}
|
||||||
</Button>
|
</Button>
|
||||||
}
|
}
|
||||||
op="edit"
|
op="edit"
|
||||||
@ -153,7 +156,7 @@ const Access = () => {
|
|||||||
handleDelete(access);
|
handleDelete(access);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
删除
|
{t("delete")}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -24,12 +24,14 @@ import {
|
|||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { Link, useNavigate } from "react-router-dom";
|
import { Link, useNavigate } from "react-router-dom";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
const Dashboard = () => {
|
const Dashboard = () => {
|
||||||
const [statistic, setStatistic] = useState<Statistic>();
|
const [statistic, setStatistic] = useState<Statistic>();
|
||||||
const [deployments, setDeployments] = useState<Deployment[]>();
|
const [deployments, setDeployments] = useState<Deployment[]>();
|
||||||
|
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchStatistic = async () => {
|
const fetchStatistic = async () => {
|
||||||
@ -55,7 +57,7 @@ const Dashboard = () => {
|
|||||||
return (
|
return (
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
<div className="flex justify-between items-center">
|
<div className="flex justify-between items-center">
|
||||||
<div className="text-muted-foreground">控制面板</div>
|
<div className="text-muted-foreground">{t('dashboard')}</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex mt-10 gap-5 flex-col flex-wrap md:flex-row">
|
<div className="flex mt-10 gap-5 flex-col flex-wrap md:flex-row">
|
||||||
<div className="w-full md:w-[250px] 3xl:w-[300px] flex items-center rounded-md p-3 shadow-lg border">
|
<div className="w-full md:w-[250px] 3xl:w-[300px] flex items-center rounded-md p-3 shadow-lg border">
|
||||||
@ -63,7 +65,9 @@ const Dashboard = () => {
|
|||||||
<SquareSigma size={48} strokeWidth={1} className="text-blue-400" />
|
<SquareSigma size={48} strokeWidth={1} className="text-blue-400" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div className="text-muted-foreground font-semibold">所有</div>
|
<div className="text-muted-foreground font-semibold">
|
||||||
|
{t('dashboard.all')}
|
||||||
|
</div>
|
||||||
<div className="flex items-baseline">
|
<div className="flex items-baseline">
|
||||||
<div className="text-3xl text-stone-700 dark:text-stone-200">
|
<div className="text-3xl text-stone-700 dark:text-stone-200">
|
||||||
{statistic?.total ? (
|
{statistic?.total ? (
|
||||||
@ -74,7 +78,9 @@ const Dashboard = () => {
|
|||||||
0
|
0
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="ml-1 text-stone-700 dark:text-stone-200">个</div>
|
<div className="ml-1 text-stone-700 dark:text-stone-200">
|
||||||
|
{t("dashboard.unit")}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -84,7 +90,9 @@ const Dashboard = () => {
|
|||||||
<CalendarX2 size={48} strokeWidth={1} className="text-red-400" />
|
<CalendarX2 size={48} strokeWidth={1} className="text-red-400" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div className="text-muted-foreground font-semibold">即将过期</div>
|
<div className="text-muted-foreground font-semibold">
|
||||||
|
{t('dashboard.near.expired')}
|
||||||
|
</div>
|
||||||
<div className="flex items-baseline">
|
<div className="flex items-baseline">
|
||||||
<div className="text-3xl text-stone-700 dark:text-stone-200">
|
<div className="text-3xl text-stone-700 dark:text-stone-200">
|
||||||
{statistic?.expired ? (
|
{statistic?.expired ? (
|
||||||
@ -95,7 +103,9 @@ const Dashboard = () => {
|
|||||||
0
|
0
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="ml-1 text-stone-700 dark:text-stone-200">个</div>
|
<div className="ml-1 text-stone-700 dark:text-stone-200">
|
||||||
|
{t("dashboard.unit")}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -109,7 +119,9 @@ const Dashboard = () => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div className="text-muted-foreground font-semibold">启用中</div>
|
<div className="text-muted-foreground font-semibold">
|
||||||
|
{t('dashboard.enabled')}
|
||||||
|
</div>
|
||||||
<div className="flex items-baseline">
|
<div className="flex items-baseline">
|
||||||
<div className="text-3xl text-stone-700 dark:text-stone-200">
|
<div className="text-3xl text-stone-700 dark:text-stone-200">
|
||||||
{statistic?.enabled ? (
|
{statistic?.enabled ? (
|
||||||
@ -120,7 +132,9 @@ const Dashboard = () => {
|
|||||||
0
|
0
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="ml-1 text-stone-700 dark:text-stone-200">个</div>
|
<div className="ml-1 text-stone-700 dark:text-stone-200">
|
||||||
|
{t("dashboard.unit")}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -130,7 +144,7 @@ const Dashboard = () => {
|
|||||||
<Ban size={48} strokeWidth={1} className="text-gray-400" />
|
<Ban size={48} strokeWidth={1} className="text-gray-400" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div className="text-muted-foreground font-semibold">未启用</div>
|
<div className="text-muted-foreground font-semibold">{t('dashboard.not.enabled')}</div>
|
||||||
<div className="flex items-baseline">
|
<div className="flex items-baseline">
|
||||||
<div className="text-3xl text-stone-700 dark:text-stone-200">
|
<div className="text-3xl text-stone-700 dark:text-stone-200">
|
||||||
{statistic?.disabled ? (
|
{statistic?.disabled ? (
|
||||||
@ -144,19 +158,23 @@ const Dashboard = () => {
|
|||||||
0
|
0
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="ml-1 text-stone-700 dark:text-stone-200">个</div>
|
<div className="ml-1 text-stone-700 dark:text-stone-200">
|
||||||
|
{t("dashboard.unit")}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<div className="text-muted-foreground mt-5 text-sm">部署历史</div>
|
<div className="text-muted-foreground mt-5 text-sm">
|
||||||
|
{t('deployment.log.name')}
|
||||||
|
</div>
|
||||||
|
|
||||||
{deployments?.length == 0 ? (
|
{deployments?.length == 0 ? (
|
||||||
<>
|
<>
|
||||||
<Alert className="max-w-[40em] mt-10">
|
<Alert className="max-w-[40em] mt-10">
|
||||||
<AlertTitle>暂无数据</AlertTitle>
|
<AlertTitle>{t('no.data')}</AlertTitle>
|
||||||
<AlertDescription>
|
<AlertDescription>
|
||||||
<div className="flex items-center mt-5">
|
<div className="flex items-center mt-5">
|
||||||
<div>
|
<div>
|
||||||
@ -164,7 +182,7 @@ const Dashboard = () => {
|
|||||||
</div>
|
</div>
|
||||||
<div className="ml-2">
|
<div className="ml-2">
|
||||||
{" "}
|
{" "}
|
||||||
你暂未创建任何部署,请先添加域名进行部署吧!
|
{t('deployment.log.empty')}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-2 flex justify-end">
|
<div className="mt-2 flex justify-end">
|
||||||
@ -173,7 +191,7 @@ const Dashboard = () => {
|
|||||||
navigate("/edit");
|
navigate("/edit");
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
添加域名
|
{t('domain.add')}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</AlertDescription>
|
</AlertDescription>
|
||||||
@ -182,16 +200,16 @@ const Dashboard = () => {
|
|||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<div className="hidden sm:flex sm:flex-row text-muted-foreground text-sm border-b dark:border-stone-500 sm:p-2 mt-5">
|
<div className="hidden sm:flex sm:flex-row text-muted-foreground text-sm border-b dark:border-stone-500 sm:p-2 mt-5">
|
||||||
<div className="w-48">域名</div>
|
<div className="w-48">{t('domain')}</div>
|
||||||
|
|
||||||
<div className="w-24">状态</div>
|
<div className="w-24">{t('deployment.log.status')}</div>
|
||||||
<div className="w-56">阶段</div>
|
<div className="w-56">{t('deployment.log.stage')}</div>
|
||||||
<div className="w-56 sm:ml-2 text-center">最近执行时间</div>
|
<div className="w-56 sm:ml-2 text-center">{t('deployment.log.last.execution.time')}</div>
|
||||||
|
|
||||||
<div className="grow">操作</div>
|
<div className="grow">{t('operation')}</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="sm:hidden flex text-sm text-muted-foreground">
|
<div className="sm:hidden flex text-sm text-muted-foreground">
|
||||||
部署历史
|
{t('deployment.log.name')}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{deployments?.map((deployment) => (
|
{deployments?.map((deployment) => (
|
||||||
@ -218,14 +236,14 @@ const Dashboard = () => {
|
|||||||
<Sheet>
|
<Sheet>
|
||||||
<SheetTrigger asChild>
|
<SheetTrigger asChild>
|
||||||
<Button variant={"link"} className="p-0">
|
<Button variant={"link"} className="p-0">
|
||||||
日志
|
{t('deployment.log.detail.button.text')}
|
||||||
</Button>
|
</Button>
|
||||||
</SheetTrigger>
|
</SheetTrigger>
|
||||||
<SheetContent className="sm:max-w-5xl">
|
<SheetContent className="sm:max-w-5xl">
|
||||||
<SheetHeader>
|
<SheetHeader>
|
||||||
<SheetTitle>
|
<SheetTitle>
|
||||||
{deployment.expand.domain?.domain}-{deployment.id}
|
{deployment.expand.domain?.domain}-{deployment.id}
|
||||||
部署详情
|
{t('deployment.log.detail')}
|
||||||
</SheetTitle>
|
</SheetTitle>
|
||||||
</SheetHeader>
|
</SheetHeader>
|
||||||
<div className="bg-gray-950 text-stone-100 p-5 text-sm h-[80dvh]">
|
<div className="bg-gray-950 text-stone-100 p-5 text-sm h-[80dvh]">
|
||||||
|
@ -38,6 +38,7 @@ import EmailsEdit from "@/components/certimate/EmailsEdit";
|
|||||||
import { Textarea } from "@/components/ui/textarea";
|
import { Textarea } from "@/components/ui/textarea";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { EmailsSetting } from "@/domain/settings";
|
import { EmailsSetting } from "@/domain/settings";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
const Edit = () => {
|
const Edit = () => {
|
||||||
const {
|
const {
|
||||||
@ -47,6 +48,7 @@ const Edit = () => {
|
|||||||
const [domain, setDomain] = useState<Domain>();
|
const [domain, setDomain] = useState<Domain>();
|
||||||
|
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const [tab, setTab] = useState<"base" | "advance">("base");
|
const [tab, setTab] = useState<"base" | "advance">("base");
|
||||||
|
|
||||||
@ -69,15 +71,15 @@ const Edit = () => {
|
|||||||
const formSchema = z.object({
|
const formSchema = z.object({
|
||||||
id: z.string().optional(),
|
id: z.string().optional(),
|
||||||
domain: z.string().regex(/^(?:\*\.)?([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}$/, {
|
domain: z.string().regex(/^(?:\*\.)?([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}$/, {
|
||||||
message: "请输入正确的域名",
|
message: 'domain.not.empty.verify.message',
|
||||||
}),
|
}),
|
||||||
email: z.string().email().optional(),
|
email: z.string().email('email.valid.message').optional(),
|
||||||
access: z.string().regex(/^[a-zA-Z0-9]+$/, {
|
access: z.string().regex(/^[a-zA-Z0-9]+$/, {
|
||||||
message: "请选择DNS服务商授权配置",
|
message: 'domain.management.edit.dns.access.not.empty.message',
|
||||||
}),
|
}),
|
||||||
targetAccess: z.string().optional(),
|
targetAccess: z.string().optional(),
|
||||||
targetType: z.string().regex(/^[a-zA-Z0-9-]+$/, {
|
targetType: z.string().regex(/^[a-zA-Z0-9-]+$/, {
|
||||||
message: "请选择部署服务类型",
|
message: 'domain.management.edit.target.type.not.empty.message',
|
||||||
}),
|
}),
|
||||||
variables: z.string().optional(),
|
variables: z.string().optional(),
|
||||||
group: z.string().optional(),
|
group: z.string().optional(),
|
||||||
@ -138,11 +140,11 @@ const Edit = () => {
|
|||||||
if (group == "" && targetAccess == "") {
|
if (group == "" && targetAccess == "") {
|
||||||
form.setError("group", {
|
form.setError("group", {
|
||||||
type: "manual",
|
type: "manual",
|
||||||
message: "部署授权和部署授权组至少选一个",
|
message: 'domain.management.edit.target.access.verify.msg',
|
||||||
});
|
});
|
||||||
form.setError("targetAccess", {
|
form.setError("targetAccess", {
|
||||||
type: "manual",
|
type: "manual",
|
||||||
message: "部署授权和部署授权组至少选一个",
|
message: 'domain.management.edit.target.access.verify.msg',
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -162,13 +164,13 @@ const Edit = () => {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
await save(req);
|
await save(req);
|
||||||
let description = "域名编辑成功";
|
let description = t('domain.management.edit.succeed.tips');
|
||||||
if (req.id == "") {
|
if (req.id == "") {
|
||||||
description = "域名添加成功";
|
description = t('domain.management.add.succeed.tips');
|
||||||
}
|
}
|
||||||
|
|
||||||
toast({
|
toast({
|
||||||
title: "成功",
|
title: t('succeed'),
|
||||||
description,
|
description,
|
||||||
});
|
});
|
||||||
navigate("/domains");
|
navigate("/domains");
|
||||||
@ -193,7 +195,7 @@ const Edit = () => {
|
|||||||
<div className="">
|
<div className="">
|
||||||
<Toaster />
|
<Toaster />
|
||||||
<div className=" h-5 text-muted-foreground">
|
<div className=" h-5 text-muted-foreground">
|
||||||
{domain?.id ? "编辑" : "新增"}域名
|
{domain?.id ? t('domain.edit') : t('domain.add')}
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-5 flex w-full justify-center md:space-x-10 flex-col md:flex-row">
|
<div className="mt-5 flex w-full justify-center md:space-x-10 flex-col md:flex-row">
|
||||||
<div className="w-full md:w-[200px] text-muted-foreground space-x-3 md:space-y-3 flex-row md:flex-col flex">
|
<div className="w-full md:w-[200px] text-muted-foreground space-x-3 md:space-y-3 flex-row md:flex-col flex">
|
||||||
@ -206,7 +208,7 @@ const Edit = () => {
|
|||||||
setTab("base");
|
setTab("base");
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
基础设置
|
{t('basic.setting')}
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
@ -217,7 +219,7 @@ const Edit = () => {
|
|||||||
setTab("advance");
|
setTab("advance");
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
高级设置
|
{t('advanced.setting')}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -232,9 +234,9 @@ const Edit = () => {
|
|||||||
name="domain"
|
name="domain"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem hidden={tab != "base"}>
|
<FormItem hidden={tab != "base"}>
|
||||||
<FormLabel>域名</FormLabel>
|
<FormLabel>{t('domain')}</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input placeholder="请输入域名" {...field} />
|
<Input placeholder={t('domain.not.empty.verify.message')} {...field} />
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
@ -247,12 +249,12 @@ const Edit = () => {
|
|||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem hidden={tab != "base"}>
|
<FormItem hidden={tab != "base"}>
|
||||||
<FormLabel className="flex w-full justify-between">
|
<FormLabel className="flex w-full justify-between">
|
||||||
<div>Email(申请证书需要提供邮箱)</div>
|
<div>{t('email') + t('domain.management.edit.email.description')}</div>
|
||||||
<EmailsEdit
|
<EmailsEdit
|
||||||
trigger={
|
trigger={
|
||||||
<div className="font-normal text-primary hover:underline cursor-pointer flex items-center">
|
<div className="font-normal text-primary hover:underline cursor-pointer flex items-center">
|
||||||
<Plus size={14} />
|
<Plus size={14} />
|
||||||
新增
|
{t('add')}
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
@ -266,11 +268,11 @@ const Edit = () => {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<SelectTrigger>
|
<SelectTrigger>
|
||||||
<SelectValue placeholder="请选择邮箱" />
|
<SelectValue placeholder={t('domain.management.edit.email.not.empty.message')} />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
<SelectGroup>
|
<SelectGroup>
|
||||||
<SelectLabel>邮箱列表</SelectLabel>
|
<SelectLabel>{t('email.list')}</SelectLabel>
|
||||||
{(emails.content as EmailsSetting).emails.map(
|
{(emails.content as EmailsSetting).emails.map(
|
||||||
(item) => (
|
(item) => (
|
||||||
<SelectItem key={item} value={item}>
|
<SelectItem key={item} value={item}>
|
||||||
@ -293,12 +295,12 @@ const Edit = () => {
|
|||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem hidden={tab != "base"}>
|
<FormItem hidden={tab != "base"}>
|
||||||
<FormLabel className="flex w-full justify-between">
|
<FormLabel className="flex w-full justify-between">
|
||||||
<div>DNS 服务商授权配置</div>
|
<div>{t('domain.management.edit.dns.access.label')}</div>
|
||||||
<AccessEdit
|
<AccessEdit
|
||||||
trigger={
|
trigger={
|
||||||
<div className="font-normal text-primary hover:underline cursor-pointer flex items-center">
|
<div className="font-normal text-primary hover:underline cursor-pointer flex items-center">
|
||||||
<Plus size={14} />
|
<Plus size={14} />
|
||||||
新增
|
{t('add')}
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
op="add"
|
op="add"
|
||||||
@ -313,11 +315,11 @@ const Edit = () => {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<SelectTrigger>
|
<SelectTrigger>
|
||||||
<SelectValue placeholder="请选择授权配置" />
|
<SelectValue placeholder={t('domain.management.edit.access.not.empty.message')} />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
<SelectGroup>
|
<SelectGroup>
|
||||||
<SelectLabel>服务商授权配置</SelectLabel>
|
<SelectLabel>{t('domain.management.edit.access.label')}</SelectLabel>
|
||||||
{accesses
|
{accesses
|
||||||
.filter((item) => item.usage != "deploy")
|
.filter((item) => item.usage != "deploy")
|
||||||
.map((item) => (
|
.map((item) => (
|
||||||
@ -349,7 +351,7 @@ const Edit = () => {
|
|||||||
name="targetType"
|
name="targetType"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem hidden={tab != "base"}>
|
<FormItem hidden={tab != "base"}>
|
||||||
<FormLabel>部署服务类型</FormLabel>
|
<FormLabel>{t('domain.management.edit.target.type')}</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Select
|
<Select
|
||||||
{...field}
|
{...field}
|
||||||
@ -359,11 +361,11 @@ const Edit = () => {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<SelectTrigger>
|
<SelectTrigger>
|
||||||
<SelectValue placeholder="请选择部署服务类型" />
|
<SelectValue placeholder={t('domain.management.edit.target.type.not.empty.message')} />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
<SelectGroup>
|
<SelectGroup>
|
||||||
<SelectLabel>部署服务类型</SelectLabel>
|
<SelectLabel>{t('domain.management.edit.target.type')}</SelectLabel>
|
||||||
{targetTypeKeys.map((key) => (
|
{targetTypeKeys.map((key) => (
|
||||||
<SelectItem key={key} value={key}>
|
<SelectItem key={key} value={key}>
|
||||||
<div className="flex items-center space-x-2">
|
<div className="flex items-center space-x-2">
|
||||||
@ -371,7 +373,7 @@ const Edit = () => {
|
|||||||
className="w-6"
|
className="w-6"
|
||||||
src={targetTypeMap.get(key)?.[1]}
|
src={targetTypeMap.get(key)?.[1]}
|
||||||
/>
|
/>
|
||||||
<div>{targetTypeMap.get(key)?.[0]}</div>
|
<div>{t(targetTypeMap.get(key)?.[0] || '')}</div>
|
||||||
</div>
|
</div>
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
))}
|
))}
|
||||||
@ -390,12 +392,12 @@ const Edit = () => {
|
|||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem hidden={tab != "base"}>
|
<FormItem hidden={tab != "base"}>
|
||||||
<FormLabel className="w-full flex justify-between">
|
<FormLabel className="w-full flex justify-between">
|
||||||
<div>部署服务商授权配置</div>
|
<div>{t('domain.management.edit.target.access')}</div>
|
||||||
<AccessEdit
|
<AccessEdit
|
||||||
trigger={
|
trigger={
|
||||||
<div className="font-normal text-primary hover:underline cursor-pointer flex items-center">
|
<div className="font-normal text-primary hover:underline cursor-pointer flex items-center">
|
||||||
<Plus size={14} />
|
<Plus size={14} />
|
||||||
新增
|
{t('add')}
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
op="add"
|
op="add"
|
||||||
@ -409,12 +411,12 @@ const Edit = () => {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<SelectTrigger>
|
<SelectTrigger>
|
||||||
<SelectValue placeholder="请选择授权配置" />
|
<SelectValue placeholder={t('domain.management.edit.target.access.not.empty.message')} />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
<SelectGroup>
|
<SelectGroup>
|
||||||
<SelectLabel>
|
<SelectLabel>
|
||||||
服务商授权配置{form.getValues().targetAccess}
|
{t('domain.management.edit.target.access.content.label')} {form.getValues().targetAccess}
|
||||||
</SelectLabel>
|
</SelectLabel>
|
||||||
<SelectItem value="emptyId">
|
<SelectItem value="emptyId">
|
||||||
<div className="flex items-center space-x-2">
|
<div className="flex items-center space-x-2">
|
||||||
@ -451,7 +453,7 @@ const Edit = () => {
|
|||||||
<FormItem hidden={tab != "advance" || targetType != "ssh"}>
|
<FormItem hidden={tab != "advance" || targetType != "ssh"}>
|
||||||
<FormLabel className="w-full flex justify-between">
|
<FormLabel className="w-full flex justify-between">
|
||||||
<div>
|
<div>
|
||||||
部署配置组(用于将一个域名证书部署到多个 ssh 主机)
|
{t('domain.management.edit.group.label')}
|
||||||
</div>
|
</div>
|
||||||
</FormLabel>
|
</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
@ -464,7 +466,7 @@ const Edit = () => {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<SelectTrigger>
|
<SelectTrigger>
|
||||||
<SelectValue placeholder="请选择分组" />
|
<SelectValue placeholder={t('domain.management.edit.group.not.empty.message')} />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
<SelectItem value="emptyId">
|
<SelectItem value="emptyId">
|
||||||
@ -509,10 +511,10 @@ const Edit = () => {
|
|||||||
name="variables"
|
name="variables"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem hidden={tab != "advance"}>
|
<FormItem hidden={tab != "advance"}>
|
||||||
<FormLabel>变量</FormLabel>
|
<FormLabel>{t('variables')}</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Textarea
|
<Textarea
|
||||||
placeholder={`可在SSH部署中使用,形如:\nkey=val;\nkey2=val2;`}
|
placeholder={t('domain.management.edit.variables.placeholder')}
|
||||||
{...field}
|
{...field}
|
||||||
className="placeholder:whitespace-pre-wrap"
|
className="placeholder:whitespace-pre-wrap"
|
||||||
/>
|
/>
|
||||||
@ -528,10 +530,10 @@ const Edit = () => {
|
|||||||
name="nameservers"
|
name="nameservers"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem hidden={tab != "advance"}>
|
<FormItem hidden={tab != "advance"}>
|
||||||
<FormLabel>域名服务器</FormLabel>
|
<FormLabel>{t('dns')}</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Textarea
|
<Textarea
|
||||||
placeholder={`自定义域名服务器,多个用分号隔开,如:\n8.8.8.8;\n8.8.4.4;`}
|
placeholder={t('domain.management.edit.dns.placeholder')}
|
||||||
{...field}
|
{...field}
|
||||||
className="placeholder:whitespace-pre-wrap"
|
className="placeholder:whitespace-pre-wrap"
|
||||||
/>
|
/>
|
||||||
@ -543,7 +545,7 @@ const Edit = () => {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<div className="flex justify-end">
|
<div className="flex justify-end">
|
||||||
<Button type="submit">保存</Button>
|
<Button type="submit">{t('save')}</Button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</Form>
|
</Form>
|
||||||
|
@ -35,11 +35,13 @@ import { TooltipContent, TooltipProvider } from "@radix-ui/react-tooltip";
|
|||||||
import { Earth } from "lucide-react";
|
import { Earth } from "lucide-react";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { Link, useLocation, useNavigate } from "react-router-dom";
|
import { Link, useLocation, useNavigate } from "react-router-dom";
|
||||||
|
import { useTranslation, Trans } from "react-i18next";
|
||||||
|
|
||||||
const Home = () => {
|
const Home = () => {
|
||||||
const toast = useToast();
|
const toast = useToast();
|
||||||
|
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const query = new URLSearchParams(location.search);
|
const query = new URLSearchParams(location.search);
|
||||||
@ -127,23 +129,22 @@ const Home = () => {
|
|||||||
await save(domain);
|
await save(domain);
|
||||||
|
|
||||||
toast.toast({
|
toast.toast({
|
||||||
title: "操作成功",
|
title: t('operation.succeed'),
|
||||||
description: "已发起部署,请稍后查看部署日志。",
|
description: t('domain.management.start.deploy.succeed.tips'),
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
toast.toast({
|
toast.toast({
|
||||||
title: "执行失败",
|
title: t('domain.management.execution.failed'),
|
||||||
description: (
|
description: (
|
||||||
<>
|
// 这里的 text 只是占位作用,实际文案在 src/i18n/locales/[lang].json
|
||||||
执行失败,请查看
|
<Trans i18nKey="domain.management.execution.failed.tips">
|
||||||
|
text1
|
||||||
<Link
|
<Link
|
||||||
to={`/history?domain=${domain.id}`}
|
to={`/history?domain=${domain.id}`}
|
||||||
className="underline text-blue-500"
|
className="underline text-blue-500"
|
||||||
>
|
>text2</Link>
|
||||||
部署日志
|
text3
|
||||||
</Link>
|
</Trans>
|
||||||
查看详情。
|
|
||||||
</>
|
|
||||||
),
|
),
|
||||||
variant: "destructive",
|
variant: "destructive",
|
||||||
});
|
});
|
||||||
@ -175,8 +176,10 @@ const Home = () => {
|
|||||||
<div className="">
|
<div className="">
|
||||||
<Toaster />
|
<Toaster />
|
||||||
<div className="flex justify-between items-center">
|
<div className="flex justify-between items-center">
|
||||||
<div className="text-muted-foreground">域名列表</div>
|
<div className="text-muted-foreground">{t('domain.management.name')}</div>
|
||||||
<Button onClick={handleCreateClick}>新增域名</Button>
|
<Button onClick={handleCreateClick}>
|
||||||
|
{t('domain.add')}
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{!domains.length ? (
|
{!domains.length ? (
|
||||||
@ -187,26 +190,26 @@ const Home = () => {
|
|||||||
</span>
|
</span>
|
||||||
|
|
||||||
<div className="text-center text-sm text-muted-foreground mt-3">
|
<div className="text-center text-sm text-muted-foreground mt-3">
|
||||||
请添加域名开始部署证书吧。
|
{t('domain.management.empty')}
|
||||||
</div>
|
</div>
|
||||||
<Button onClick={handleCreateClick} className="mt-3">
|
<Button onClick={handleCreateClick} className="mt-3">
|
||||||
添加域名
|
{t('domain.add')}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<div className="hidden sm:flex sm:flex-row text-muted-foreground text-sm border-b dark:border-stone-500 sm:p-2 mt-5">
|
<div className="hidden sm:flex sm:flex-row text-muted-foreground text-sm border-b dark:border-stone-500 sm:p-2 mt-5">
|
||||||
<div className="w-36">域名</div>
|
<div className="w-36">{t('domain')}</div>
|
||||||
<div className="w-40">有效期限</div>
|
<div className="w-40">{t('domain.management.expiry.date')}</div>
|
||||||
<div className="w-32">最近执行状态</div>
|
<div className="w-32">{t('domain.management.last.execution.status')}</div>
|
||||||
<div className="w-64">最近执行阶段</div>
|
<div className="w-64">{t('domain.management.last.execution.stage')}</div>
|
||||||
<div className="w-40 sm:ml-2">最近执行时间</div>
|
<div className="w-40 sm:ml-2">{t('domain.management.last.execution.time')}</div>
|
||||||
<div className="w-24">是否启用</div>
|
<div className="w-24">{t('domain.management.enable')}</div>
|
||||||
<div className="grow">操作</div>
|
<div className="grow">{t('operation')}</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="sm:hidden flex text-sm text-muted-foreground">
|
<div className="sm:hidden flex text-sm text-muted-foreground">
|
||||||
域名
|
{t('domain')}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{domains.map((domain) => (
|
{domains.map((domain) => (
|
||||||
@ -221,8 +224,8 @@ const Home = () => {
|
|||||||
<div>
|
<div>
|
||||||
{domain.expiredAt ? (
|
{domain.expiredAt ? (
|
||||||
<>
|
<>
|
||||||
<div>有效期90天</div>
|
<div>{t('domain.management.expiry.date1', { date: 90 })}</div>
|
||||||
<div>{getDate(domain.expiredAt)}到期</div>
|
<div>{t('domain.management.expiry.date2', { date: getDate(domain.expiredAt) })}</div>
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
"---"
|
"---"
|
||||||
@ -266,7 +269,7 @@ const Home = () => {
|
|||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent>
|
<TooltipContent>
|
||||||
<div className="border rounded-sm px-3 bg-background text-muted-foreground text-xs">
|
<div className="border rounded-sm px-3 bg-background text-muted-foreground text-xs">
|
||||||
{domain.enabled ? "禁用" : "启用"}
|
{domain.enabled ? t('disable') : t('enable')}
|
||||||
</div>
|
</div>
|
||||||
</TooltipContent>
|
</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
@ -278,7 +281,7 @@ const Home = () => {
|
|||||||
className="p-0"
|
className="p-0"
|
||||||
onClick={() => handleHistoryClick(domain.id)}
|
onClick={() => handleHistoryClick(domain.id)}
|
||||||
>
|
>
|
||||||
部署历史
|
{t('deployment.log.name')}
|
||||||
</Button>
|
</Button>
|
||||||
<Show when={domain.enabled ? true : false}>
|
<Show when={domain.enabled ? true : false}>
|
||||||
<Separator orientation="vertical" className="h-4 mx-2" />
|
<Separator orientation="vertical" className="h-4 mx-2" />
|
||||||
@ -287,7 +290,7 @@ const Home = () => {
|
|||||||
className="p-0"
|
className="p-0"
|
||||||
onClick={() => handleRightNowClick(domain)}
|
onClick={() => handleRightNowClick(domain)}
|
||||||
>
|
>
|
||||||
立即部署
|
{t('domain.management.start.deploying')}
|
||||||
</Button>
|
</Button>
|
||||||
</Show>
|
</Show>
|
||||||
|
|
||||||
@ -304,7 +307,7 @@ const Home = () => {
|
|||||||
className="p-0"
|
className="p-0"
|
||||||
onClick={() => handleForceClick(domain)}
|
onClick={() => handleForceClick(domain)}
|
||||||
>
|
>
|
||||||
强行部署
|
{t('domain.management.forced.deployment')}
|
||||||
</Button>
|
</Button>
|
||||||
</Show>
|
</Show>
|
||||||
|
|
||||||
@ -315,7 +318,7 @@ const Home = () => {
|
|||||||
className="p-0"
|
className="p-0"
|
||||||
onClick={() => handleDownloadClick(domain)}
|
onClick={() => handleDownloadClick(domain)}
|
||||||
>
|
>
|
||||||
下载
|
{t('download')}
|
||||||
</Button>
|
</Button>
|
||||||
</Show>
|
</Show>
|
||||||
|
|
||||||
@ -325,24 +328,24 @@ const Home = () => {
|
|||||||
<AlertDialog>
|
<AlertDialog>
|
||||||
<AlertDialogTrigger asChild>
|
<AlertDialogTrigger asChild>
|
||||||
<Button variant={"link"} className="p-0">
|
<Button variant={"link"} className="p-0">
|
||||||
删除
|
{t('delete')}
|
||||||
</Button>
|
</Button>
|
||||||
</AlertDialogTrigger>
|
</AlertDialogTrigger>
|
||||||
<AlertDialogContent>
|
<AlertDialogContent>
|
||||||
<AlertDialogHeader>
|
<AlertDialogHeader>
|
||||||
<AlertDialogTitle>删除域名</AlertDialogTitle>
|
<AlertDialogTitle>{t('domain.delete')}</AlertDialogTitle>
|
||||||
<AlertDialogDescription>
|
<AlertDialogDescription>
|
||||||
确定要删除域名吗?
|
{t('domain.management.delete.confirm')}
|
||||||
</AlertDialogDescription>
|
</AlertDialogDescription>
|
||||||
</AlertDialogHeader>
|
</AlertDialogHeader>
|
||||||
<AlertDialogFooter>
|
<AlertDialogFooter>
|
||||||
<AlertDialogCancel>取消</AlertDialogCancel>
|
<AlertDialogCancel>{t('cancel')}</AlertDialogCancel>
|
||||||
<AlertDialogAction
|
<AlertDialogAction
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
handleDeleteClick(domain.id);
|
handleDeleteClick(domain.id);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
确认
|
{t('confirm')}
|
||||||
</AlertDialogAction>
|
</AlertDialogAction>
|
||||||
</AlertDialogFooter>
|
</AlertDialogFooter>
|
||||||
</AlertDialogContent>
|
</AlertDialogContent>
|
||||||
@ -354,7 +357,7 @@ const Home = () => {
|
|||||||
className="p-0"
|
className="p-0"
|
||||||
onClick={() => handleEditClick(domain.id)}
|
onClick={() => handleEditClick(domain.id)}
|
||||||
>
|
>
|
||||||
编辑
|
{t('edit')}
|
||||||
</Button>
|
</Button>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
@ -17,11 +17,13 @@ import { list } from "@/repository/deployment";
|
|||||||
import { Smile } from "lucide-react";
|
import { Smile } from "lucide-react";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { useNavigate, useSearchParams } from "react-router-dom";
|
import { useNavigate, useSearchParams } from "react-router-dom";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
const History = () => {
|
const History = () => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const [deployments, setDeployments] = useState<Deployment[]>();
|
const [deployments, setDeployments] = useState<Deployment[]>();
|
||||||
const [searchParams] = useSearchParams();
|
const [searchParams] = useSearchParams();
|
||||||
|
const { t } = useTranslation();
|
||||||
const domain = searchParams.get("domain");
|
const domain = searchParams.get("domain");
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -38,11 +40,11 @@ const History = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<ScrollArea className="h-[80vh] overflow-hidden">
|
<ScrollArea className="h-[80vh] overflow-hidden">
|
||||||
<div className="text-muted-foreground">部署历史</div>
|
<div className="text-muted-foreground">{t('deployment.log.name')}</div>
|
||||||
{!deployments?.length ? (
|
{!deployments?.length ? (
|
||||||
<>
|
<>
|
||||||
<Alert className="max-w-[40em] mx-auto mt-20">
|
<Alert className="max-w-[40em] mx-auto mt-20">
|
||||||
<AlertTitle>暂无数据</AlertTitle>
|
<AlertTitle>{t('no.data')}</AlertTitle>
|
||||||
<AlertDescription>
|
<AlertDescription>
|
||||||
<div className="flex items-center mt-5">
|
<div className="flex items-center mt-5">
|
||||||
<div>
|
<div>
|
||||||
@ -50,7 +52,7 @@ const History = () => {
|
|||||||
</div>
|
</div>
|
||||||
<div className="ml-2">
|
<div className="ml-2">
|
||||||
{" "}
|
{" "}
|
||||||
你暂未创建任何部署,请先添加域名进行部署吧!
|
{t('deployment.log.empty')}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-2 flex justify-end">
|
<div className="mt-2 flex justify-end">
|
||||||
@ -59,7 +61,7 @@ const History = () => {
|
|||||||
navigate("/");
|
navigate("/");
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
添加域名
|
{t('domain.add')}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</AlertDescription>
|
</AlertDescription>
|
||||||
@ -68,16 +70,16 @@ const History = () => {
|
|||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<div className="hidden sm:flex sm:flex-row text-muted-foreground text-sm border-b dark:border-stone-500 sm:p-2 mt-5">
|
<div className="hidden sm:flex sm:flex-row text-muted-foreground text-sm border-b dark:border-stone-500 sm:p-2 mt-5">
|
||||||
<div className="w-48">域名</div>
|
<div className="w-48">{t('domain')}</div>
|
||||||
|
|
||||||
<div className="w-24">状态</div>
|
<div className="w-24">{t('deployment.log.status')}</div>
|
||||||
<div className="w-56">阶段</div>
|
<div className="w-56">{t('deployment.log.stage')}</div>
|
||||||
<div className="w-56 sm:ml-2 text-center">最近执行时间</div>
|
<div className="w-56 sm:ml-2 text-center">{t('deployment.log.last.execution.time')}</div>
|
||||||
|
|
||||||
<div className="grow">操作</div>
|
<div className="grow">{t('operation')}</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="sm:hidden flex text-sm text-muted-foreground">
|
<div className="sm:hidden flex text-sm text-muted-foreground">
|
||||||
部署历史
|
{t('deployment.log.name')}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{deployments?.map((deployment) => (
|
{deployments?.map((deployment) => (
|
||||||
@ -104,14 +106,14 @@ const History = () => {
|
|||||||
<Sheet>
|
<Sheet>
|
||||||
<SheetTrigger asChild>
|
<SheetTrigger asChild>
|
||||||
<Button variant={"link"} className="p-0">
|
<Button variant={"link"} className="p-0">
|
||||||
日志
|
{t('deployment.log.detail.button.text')}
|
||||||
</Button>
|
</Button>
|
||||||
</SheetTrigger>
|
</SheetTrigger>
|
||||||
<SheetContent className="sm:max-w-5xl">
|
<SheetContent className="sm:max-w-5xl">
|
||||||
<SheetHeader>
|
<SheetHeader>
|
||||||
<SheetTitle>
|
<SheetTitle>
|
||||||
{deployment.expand.domain?.domain}-{deployment.id}
|
{deployment.expand.domain?.domain}-{deployment.id}
|
||||||
部署详情
|
{t('deployment.log.detail')}
|
||||||
</SheetTitle>
|
</SheetTitle>
|
||||||
</SheetHeader>
|
</SheetHeader>
|
||||||
<div className="bg-gray-950 text-stone-100 p-5 text-sm h-[80dvh]">
|
<div className="bg-gray-950 text-stone-100 p-5 text-sm h-[80dvh]">
|
||||||
|
@ -1,3 +1,8 @@
|
|||||||
|
import { useForm } from "react-hook-form";
|
||||||
|
import { useNavigate } from "react-router-dom";
|
||||||
|
import { z } from "zod";
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import {
|
import {
|
||||||
Form,
|
Form,
|
||||||
@ -11,20 +16,19 @@ import { Input } from "@/components/ui/input";
|
|||||||
import { getErrMessage } from "@/lib/error";
|
import { getErrMessage } from "@/lib/error";
|
||||||
import { getPb } from "@/repository/api";
|
import { getPb } from "@/repository/api";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
import { useForm } from "react-hook-form";
|
|
||||||
import { useNavigate } from "react-router-dom";
|
|
||||||
import { z } from "zod";
|
|
||||||
|
|
||||||
const formSchema = z.object({
|
const formSchema = z.object({
|
||||||
username: z.string().email({
|
username: z.string().email({
|
||||||
message: "请输入正确的邮箱地址",
|
message: "login.username.no.empty.message",
|
||||||
}),
|
}),
|
||||||
password: z.string().min(10, {
|
password: z.string().min(10, {
|
||||||
message: "密码至少10个字符",
|
message: "login.password.length.message",
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
const Login = () => {
|
const Login = () => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
const form = useForm<z.infer<typeof formSchema>>({
|
const form = useForm<z.infer<typeof formSchema>>({
|
||||||
resolver: zodResolver(formSchema),
|
resolver: zodResolver(formSchema),
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
@ -61,7 +65,7 @@ const Login = () => {
|
|||||||
name="username"
|
name="username"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>用户名</FormLabel>
|
<FormLabel>{t('username')}</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input placeholder="email" {...field} />
|
<Input placeholder="email" {...field} />
|
||||||
</FormControl>
|
</FormControl>
|
||||||
@ -76,7 +80,7 @@ const Login = () => {
|
|||||||
name="password"
|
name="password"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>密码</FormLabel>
|
<FormLabel>{t('password')}</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input placeholder="password" {...field} type="password" />
|
<Input placeholder="password" {...field} type="password" />
|
||||||
</FormControl>
|
</FormControl>
|
||||||
@ -86,7 +90,7 @@ const Login = () => {
|
|||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
<div className="flex justify-end">
|
<div className="flex justify-end">
|
||||||
<Button type="submit">登录</Button>
|
<Button type="submit">{t('login.submit')}</Button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</Form>
|
</Form>
|
||||||
|
@ -15,16 +15,18 @@ import { zodResolver } from "@hookform/resolvers/zod";
|
|||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
const formSchema = z.object({
|
const formSchema = z.object({
|
||||||
email: z.string().email("请输入正确的邮箱"),
|
email: z.string().email("setting.account.email.valid.message"),
|
||||||
});
|
});
|
||||||
|
|
||||||
const Account = () => {
|
const Account = () => {
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const [changed, setChanged] = useState(false);
|
const [changed, setChanged] = useState(false);
|
||||||
|
|
||||||
@ -43,8 +45,8 @@ const Account = () => {
|
|||||||
|
|
||||||
getPb().authStore.clear();
|
getPb().authStore.clear();
|
||||||
toast({
|
toast({
|
||||||
title: "修改账户邮箱功",
|
title: t("setting.account.email.change.succeed"),
|
||||||
description: "请重新登录",
|
description: t("setting.account.log.back.in"),
|
||||||
});
|
});
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
navigate("/login");
|
navigate("/login");
|
||||||
@ -52,7 +54,7 @@ const Account = () => {
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
const message = getErrMessage(e);
|
const message = getErrMessage(e);
|
||||||
toast({
|
toast({
|
||||||
title: "修改账户邮箱失败",
|
title: t("setting.account.email.change.failed"),
|
||||||
description: message,
|
description: message,
|
||||||
variant: "destructive",
|
variant: "destructive",
|
||||||
});
|
});
|
||||||
@ -72,10 +74,10 @@ const Account = () => {
|
|||||||
name="email"
|
name="email"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>邮箱</FormLabel>
|
<FormLabel>{t('email')}</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input
|
<Input
|
||||||
placeholder="请输入邮箱"
|
placeholder={t('setting.email.placeholder')}
|
||||||
{...field}
|
{...field}
|
||||||
type="email"
|
type="email"
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
@ -92,10 +94,10 @@ const Account = () => {
|
|||||||
|
|
||||||
<div className="flex justify-end">
|
<div className="flex justify-end">
|
||||||
{changed ? (
|
{changed ? (
|
||||||
<Button type="submit">确认修改</Button>
|
<Button type="submit">{t('setting.submit')}</Button>
|
||||||
) : (
|
) : (
|
||||||
<Button type="submit" disabled variant={"secondary"}>
|
<Button type="submit" disabled variant={"secondary"}>
|
||||||
确认修改
|
{t('setting.submit')}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
@ -9,15 +9,18 @@ import {
|
|||||||
AccordionTrigger,
|
AccordionTrigger,
|
||||||
} from "@/components/ui/accordion";
|
} from "@/components/ui/accordion";
|
||||||
import { NotifyProvider } from "@/providers/notify";
|
import { NotifyProvider } from "@/providers/notify";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
const Notify = () => {
|
const Notify = () => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<NotifyProvider>
|
<NotifyProvider>
|
||||||
<div className="border rounded-sm p-5 shadow-lg">
|
<div className="border rounded-sm p-5 shadow-lg">
|
||||||
<Accordion type={"multiple"} className="dark:text-stone-200">
|
<Accordion type={"multiple"} className="dark:text-stone-200">
|
||||||
<AccordionItem value="item-1" className="dark:border-stone-200">
|
<AccordionItem value="item-1" className="dark:border-stone-200">
|
||||||
<AccordionTrigger>模板</AccordionTrigger>
|
<AccordionTrigger>{t('template')}</AccordionTrigger>
|
||||||
<AccordionContent>
|
<AccordionContent>
|
||||||
<NotifyTemplate />
|
<NotifyTemplate />
|
||||||
</AccordionContent>
|
</AccordionContent>
|
||||||
@ -27,21 +30,21 @@ const Notify = () => {
|
|||||||
<div className="border rounded-md p-5 mt-7 shadow-lg">
|
<div className="border rounded-md p-5 mt-7 shadow-lg">
|
||||||
<Accordion type={"single"} className="dark:text-stone-200">
|
<Accordion type={"single"} className="dark:text-stone-200">
|
||||||
<AccordionItem value="item-2" className="dark:border-stone-200">
|
<AccordionItem value="item-2" className="dark:border-stone-200">
|
||||||
<AccordionTrigger>钉钉</AccordionTrigger>
|
<AccordionTrigger>{t('ding.talk')}</AccordionTrigger>
|
||||||
<AccordionContent>
|
<AccordionContent>
|
||||||
<DingTalk />
|
<DingTalk />
|
||||||
</AccordionContent>
|
</AccordionContent>
|
||||||
</AccordionItem>
|
</AccordionItem>
|
||||||
|
|
||||||
<AccordionItem value="item-4" className="dark:border-stone-200">
|
<AccordionItem value="item-4" className="dark:border-stone-200">
|
||||||
<AccordionTrigger>Telegram</AccordionTrigger>
|
<AccordionTrigger>{t('telegram')}</AccordionTrigger>
|
||||||
<AccordionContent>
|
<AccordionContent>
|
||||||
<Telegram />
|
<Telegram />
|
||||||
</AccordionContent>
|
</AccordionContent>
|
||||||
</AccordionItem>
|
</AccordionItem>
|
||||||
|
|
||||||
<AccordionItem value="item-5" className="dark:border-stone-200">
|
<AccordionItem value="item-5" className="dark:border-stone-200">
|
||||||
<AccordionTrigger>Webhook</AccordionTrigger>
|
<AccordionTrigger>{t('webhook')}</AccordionTrigger>
|
||||||
<AccordionContent>
|
<AccordionContent>
|
||||||
<Webhook />
|
<Webhook />
|
||||||
</AccordionContent>
|
</AccordionContent>
|
||||||
|
@ -14,29 +14,31 @@ import { getPb } from "@/repository/api";
|
|||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
const formSchema = z
|
const formSchema = z
|
||||||
.object({
|
.object({
|
||||||
oldPassword: z.string().min(10, {
|
oldPassword: z.string().min(10, {
|
||||||
message: "密码至少10个字符",
|
message: "setting.password.length.message",
|
||||||
}),
|
}),
|
||||||
newPassword: z.string().min(10, {
|
newPassword: z.string().min(10, {
|
||||||
message: "密码至少10个字符",
|
message: "setting.password.length.message",
|
||||||
}),
|
}),
|
||||||
confirmPassword: z.string().min(10, {
|
confirmPassword: z.string().min(10, {
|
||||||
message: "密码至少10个字符",
|
message: "setting.password.length.message",
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
.refine((data) => data.newPassword === data.confirmPassword, {
|
.refine((data) => data.newPassword === data.confirmPassword, {
|
||||||
message: "两次密码不一致",
|
message: "setting.password.not.match",
|
||||||
path: ["confirmPassword"],
|
path: ["confirmPassword"],
|
||||||
});
|
});
|
||||||
|
|
||||||
const Password = () => {
|
const Password = () => {
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const form = useForm<z.infer<typeof formSchema>>({
|
const form = useForm<z.infer<typeof formSchema>>({
|
||||||
resolver: zodResolver(formSchema),
|
resolver: zodResolver(formSchema),
|
||||||
@ -66,8 +68,8 @@ const Password = () => {
|
|||||||
|
|
||||||
getPb().authStore.clear();
|
getPb().authStore.clear();
|
||||||
toast({
|
toast({
|
||||||
title: "修改密码成功",
|
title: t('setting.password.change.succeed'),
|
||||||
description: "请重新登录",
|
description: t("setting.account.log.back.in"),
|
||||||
});
|
});
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
navigate("/login");
|
navigate("/login");
|
||||||
@ -75,7 +77,7 @@ const Password = () => {
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
const message = getErrMessage(e);
|
const message = getErrMessage(e);
|
||||||
toast({
|
toast({
|
||||||
title: "修改密码失败",
|
title: t('setting.password.change.failed'),
|
||||||
description: message,
|
description: message,
|
||||||
variant: "destructive",
|
variant: "destructive",
|
||||||
});
|
});
|
||||||
@ -95,9 +97,9 @@ const Password = () => {
|
|||||||
name="oldPassword"
|
name="oldPassword"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>当前密码</FormLabel>
|
<FormLabel>{t('setting.password.current.password')}</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input placeholder="当前密码" {...field} type="password" />
|
<Input placeholder={t('setting.password.current.password')} {...field} type="password" />
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
@ -110,7 +112,7 @@ const Password = () => {
|
|||||||
name="newPassword"
|
name="newPassword"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>新密码</FormLabel>
|
<FormLabel>{t('setting.password.new.password')}</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input
|
<Input
|
||||||
placeholder="newPassword"
|
placeholder="newPassword"
|
||||||
@ -129,7 +131,7 @@ const Password = () => {
|
|||||||
name="confirmPassword"
|
name="confirmPassword"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>确认密码</FormLabel>
|
<FormLabel>{t('setting.password.confirm.password')}</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input
|
<Input
|
||||||
placeholder="confirmPassword"
|
placeholder="confirmPassword"
|
||||||
@ -143,7 +145,7 @@ const Password = () => {
|
|||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
<div className="flex justify-end">
|
<div className="flex justify-end">
|
||||||
<Button type="submit">确认修改</Button>
|
<Button type="submit">{t('setting.submit')}</Button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</Form>
|
</Form>
|
||||||
|
@ -26,18 +26,21 @@ import { zodResolver } from "@hookform/resolvers/zod";
|
|||||||
|
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
const formSchema = z.object({
|
const SSLProvider = () => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const formSchema = z.object({
|
||||||
provider: z.enum(["letsencrypt", "zerossl"], {
|
provider: z.enum(["letsencrypt", "zerossl"], {
|
||||||
message: "请选择SSL提供商",
|
message: t("setting.ca.not.empty"),
|
||||||
}),
|
}),
|
||||||
eabKid: z.string().optional(),
|
eabKid: z.string().optional(),
|
||||||
eabHmacKey: z.string().optional(),
|
eabHmacKey: z.string().optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
const SSLProvider = () => {
|
|
||||||
const form = useForm<z.infer<typeof formSchema>>({
|
const form = useForm<z.infer<typeof formSchema>>({
|
||||||
resolver: zodResolver(formSchema),
|
resolver: zodResolver(formSchema),
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
@ -86,12 +89,12 @@ const SSLProvider = () => {
|
|||||||
if (values.provider === "zerossl") {
|
if (values.provider === "zerossl") {
|
||||||
if (!values.eabKid) {
|
if (!values.eabKid) {
|
||||||
form.setError("eabKid", {
|
form.setError("eabKid", {
|
||||||
message: "请输入EAB_KID和EAB_HMAC_KEY",
|
message: t("setting.ca.eab_kid_hmac_key.not.empty"),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (!values.eabHmacKey) {
|
if (!values.eabHmacKey) {
|
||||||
form.setError("eabHmacKey", {
|
form.setError("eabHmacKey", {
|
||||||
message: "请输入EAB_KID和EAB_HMAC_KEY",
|
message: t("setting.ca.eab_kid_hmac_key.not.empty"),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (!values.eabKid || !values.eabHmacKey) {
|
if (!values.eabKid || !values.eabHmacKey) {
|
||||||
@ -117,13 +120,13 @@ const SSLProvider = () => {
|
|||||||
try {
|
try {
|
||||||
await update(setting);
|
await update(setting);
|
||||||
toast({
|
toast({
|
||||||
title: "修改成功",
|
title: t("update.succeed"),
|
||||||
description: "修改成功",
|
description: t("update.succeed"),
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
const message = getErrMessage(e);
|
const message = getErrMessage(e);
|
||||||
toast({
|
toast({
|
||||||
title: "修改失败",
|
title: t("update.failed"),
|
||||||
description: message,
|
description: message,
|
||||||
variant: "destructive",
|
variant: "destructive",
|
||||||
});
|
});
|
||||||
@ -143,7 +146,7 @@ const SSLProvider = () => {
|
|||||||
name="provider"
|
name="provider"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>证书厂商</FormLabel>
|
<FormLabel>{t("ca")}</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<RadioGroup
|
<RadioGroup
|
||||||
{...field}
|
{...field}
|
||||||
@ -199,7 +202,7 @@ const SSLProvider = () => {
|
|||||||
<FormLabel>EAB_KID</FormLabel>
|
<FormLabel>EAB_KID</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input
|
<Input
|
||||||
placeholder="请输入EAB_KID"
|
placeholder={t("setting.ca.eab_kid.not.empty")}
|
||||||
{...field}
|
{...field}
|
||||||
type="text"
|
type="text"
|
||||||
/>
|
/>
|
||||||
@ -218,7 +221,7 @@ const SSLProvider = () => {
|
|||||||
<FormLabel>EAB_HMAC_KEY</FormLabel>
|
<FormLabel>EAB_HMAC_KEY</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input
|
<Input
|
||||||
placeholder="请输入EAB_HMAC_KEY"
|
placeholder={t("setting.ca.eab_hmac_key.not.empty")}
|
||||||
{...field}
|
{...field}
|
||||||
type="text"
|
type="text"
|
||||||
/>
|
/>
|
||||||
@ -235,7 +238,7 @@ const SSLProvider = () => {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<div className="flex justify-end">
|
<div className="flex justify-end">
|
||||||
<Button type="submit">确认修改</Button>
|
<Button type="submit">{t("setting.submit")}</Button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</Form>
|
</Form>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user