chore(deps): migrate to tailwindcss v4

This commit is contained in:
Fu Diwei
2025-07-03 10:29:56 +08:00
parent d6882cbc4f
commit 4341e87929
45 changed files with 920 additions and 1146 deletions
+4 -2
View File
@@ -15,7 +15,7 @@ module.exports = {
"plugin:import/typescript",
"plugin:prettier/recommended",
"plugin:react-hooks/recommended",
"plugin:tailwindcss/recommended",
"plugin:better-tailwindcss/recommended-warn",
],
ignorePatterns: ["dist", ".eslintrc.cjs"],
parser: "@typescript-eslint/parser",
@@ -88,6 +88,9 @@ module.exports = {
},
},
],
"better-tailwindcss": {
entryPoint: "src/global.css",
},
"react-refresh/only-export-components": [
"warn",
{
@@ -100,7 +103,6 @@ module.exports = {
ignoreDeclarationSort: true,
},
],
"tailwindcss/no-custom-classname": "off",
},
settings: {
"import/resolver": {
+647 -834
View File
File diff suppressed because it is too large Load Diff
+5 -6
View File
@@ -37,11 +37,13 @@
"react-dom": "^18.3.1",
"react-i18next": "^15.5.3",
"react-router-dom": "^7.6.2",
"tailwind-merge": "^2.6.0",
"tailwind-merge": "^3.3.1",
"zod": "^3.25.67",
"zustand": "^5.0.5"
},
"devDependencies": {
"@tailwindcss/postcss": "^4.1.11",
"@tailwindcss/vite": "^4.1.11",
"@types/file-saver": "^2.0.7",
"@types/fs-extra": "^11.0.4",
"@types/node": "^22.15.17",
@@ -52,20 +54,17 @@
"@typescript-eslint/parser": "^8.34.1",
"@vitejs/plugin-legacy": "^6.1.1",
"@vitejs/plugin-react": "^4.6.0",
"autoprefixer": "^10.4.21",
"eslint": "^8.57.0",
"eslint-config-prettier": "^10.1.5",
"eslint-import-resolver-typescript": "^3.8.3",
"eslint-plugin-better-tailwindcss": "^3.4.3",
"eslint-plugin-import": "^2.32.0",
"eslint-plugin-prettier": "^5.5.0",
"eslint-plugin-react-hooks": "^5.2.0",
"eslint-plugin-react-refresh": "^0.4.20",
"eslint-plugin-tailwindcss": "^3.18.0",
"fs-extra": "^11.3.0",
"postcss": "^8.5.6",
"prettier": "^3.6.0",
"tailwindcss": "^3.4.17",
"tailwindcss-animate": "^1.0.7",
"tailwindcss": "^4.1.11",
"typescript": "^5.8.3",
"vite": "^6.3.5"
}
-6
View File
@@ -1,6 +0,0 @@
export default {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
};
@@ -95,7 +95,7 @@ const AccessFormEmailConfig = ({ form: formInst, formName, disabled, initialValu
<div className="w-2/5">
<Form.Item name="smtpPort" label={t("access.form.email_smtp_port.label")} rules={[formRule]}>
<InputNumber className="w-full" placeholder={t("access.form.email_smtp_port.placeholder")} min={1} max={65535} />
<InputNumber style={{ width: "100%" }} placeholder={t("access.form.email_smtp_port.placeholder")} min={1} max={65535} />
</Form.Item>
</div>
</div>
@@ -129,7 +129,7 @@ const AccessFormSSHConfig = ({ form: formInst, formName, disabled, initialValues
<div className="w-1/3">
<Form.Item name="port" label={t("access.form.ssh_port.label")} rules={[formRule]}>
<InputNumber className="w-full" min={1} max={65535} placeholder={t("access.form.ssh_port.placeholder")} />
<InputNumber style={{ width: "100%" }} min={1} max={65535} placeholder={t("access.form.ssh_port.placeholder")} />
</Form.Item>
</div>
</div>
@@ -198,7 +198,7 @@ const AccessFormSSHConfig = ({ form: formInst, formName, disabled, initialValues
</div>
<div className="w-1/3">
<Form.Item name={[field.name, "port"]} label={t("access.form.ssh_port.label")} rules={[formRule]}>
<InputNumber className="w-full" placeholder={t("access.form.ssh_port.placeholder")} min={1} max={65535} />
<InputNumber style={{ width: "100%" }} placeholder={t("access.form.ssh_port.placeholder")} min={1} max={65535} />
</Form.Item>
</div>
</div>
@@ -304,7 +304,7 @@ const AccessFormWebhookConfig = ({ form: formInst, formName, disabled, initialVa
</Form.Item>
<Show when={!usage || usage === "deployment"}>
<Form.Item className="mb-0" htmlFor="null">
<Form.Item noStyle>
<label className="mb-1 block">
<div className="flex w-full items-center justify-between gap-4">
<div className="max-w-full grow truncate">
@@ -335,7 +335,7 @@ const AccessFormWebhookConfig = ({ form: formInst, formName, disabled, initialVa
</Show>
<Show when={!usage || usage === "notification"}>
<Form.Item className="mb-0" htmlFor="null">
<Form.Item noStyle>
<label className="mb-1 block">
<div className="flex w-full items-center justify-between gap-4">
<div className="max-w-full grow truncate">
+7 -11
View File
@@ -1,5 +1,5 @@
import { useEffect, useState } from "react";
import { Avatar, Select, type SelectProps, Space, Typography, theme } from "antd";
import { Avatar, Select, type SelectProps, Typography, theme } from "antd";
import { type AccessModel } from "@/domain/access";
import { accessProvidersMap } from "@/domain/provider";
@@ -38,23 +38,19 @@ const AccessSelect = ({ filter, ...props }: AccessTypeSelectProps) => {
const access = accesses.find((e) => e.id === key);
if (!access) {
return (
<Space className="max-w-full grow truncate" size={4}>
<div className="flex items-center gap-2 overflow-hidden truncate">
<Avatar shape="square" size="small" />
<Typography.Text className="leading-loose" ellipsis>
{key}
</Typography.Text>
</Space>
<Typography.Text ellipsis>{key}</Typography.Text>
</div>
);
}
const provider = accessProvidersMap.get(access.provider);
return (
<Space className="max-w-full grow truncate" size={4}>
<div className="flex items-center gap-2 overflow-hidden truncate">
<Avatar shape="square" src={provider?.icon} size="small" />
<Typography.Text className="leading-loose" ellipsis>
{access.name}
</Typography.Text>
</Space>
<Typography.Text ellipsis>{access.name}</Typography.Text>
</div>
);
};
@@ -51,7 +51,7 @@ const NotifyChannelEditFormEmailFields = () => {
<div className="w-2/5">
<Form.Item name="smtpPort" label={t("settings.notification.channel.form.email_smtp_port.label")} rules={[formRule]}>
<InputNumber className="w-full" placeholder={t("settings.notification.channel.form.email_smtp_port.placeholder")} min={1} max={65535} />
<InputNumber style={{ width: "100%" }} placeholder={t("settings.notification.channel.form.email_smtp_port.placeholder")} min={1} max={65535} />
</Form.Item>
</div>
@@ -59,7 +59,7 @@ const ACMEDns01ProviderPicker = ({ className, style, autoFocus, filter, placehol
return (
<Col key={index} xs={24} md={12} span={12}>
<Card
className="h-16 w-full overflow-hidden shadow-sm"
className="h-16 w-full overflow-hidden shadow-xs"
styles={{ body: { height: "100%", padding: "0.5rem 1rem" } }}
hoverable
onClick={() => {
@@ -1,6 +1,6 @@
import { memo, useEffect, useState } from "react";
import { memo, useMemo } from "react";
import { useTranslation } from "react-i18next";
import { Avatar, Select, type SelectProps, Space, Typography, theme } from "antd";
import { Avatar, Select, type SelectProps, Typography, theme } from "antd";
import { type ACMEDns01Provider, acmeDns01ProvidersMap } from "@/domain/provider";
@@ -16,29 +16,30 @@ const ACMEDns01ProviderSelect = ({ filter, ...props }: ACMEDns01ProviderSelectPr
const { token: themeToken } = theme.useToken();
const [options, setOptions] = useState<Array<{ key: string; value: string; label: string; data: ACMEDns01Provider }>>([]);
useEffect(() => {
const allItems = Array.from(acmeDns01ProvidersMap.values());
const filteredItems = filter != null ? allItems.filter(filter) : allItems;
setOptions(
filteredItems.map((item) => ({
key: item.type,
value: item.type,
label: t(item.name),
data: item,
}))
);
const options = useMemo<Array<{ key: string; value: string; label: string; data: ACMEDns01Provider }>>(() => {
return Array.from(acmeDns01ProvidersMap.values())
.filter((provider) => {
if (filter) {
return filter(provider);
}
return true;
})
.map((provider) => ({
key: provider.type,
value: provider.type,
label: t(provider.name),
data: provider,
}));
}, [filter]);
const renderOption = (key: string) => {
const provider = acmeDns01ProvidersMap.get(key);
return (
<Space className="max-w-full grow overflow-hidden truncate" size={4}>
<div className="flex items-center gap-2 overflow-hidden truncate">
<Avatar shape="square" src={provider?.icon} size="small" />
<Typography.Text className="leading-loose" ellipsis>
{t(provider?.name ?? "")}
</Typography.Text>
</Space>
<Typography.Text ellipsis>{t(provider?.name ?? "")}</Typography.Text>
</div>
);
};
@@ -74,7 +74,7 @@ const AccessProviderPicker = ({ className, style, autoFocus, filter, placeholder
return (
<Col key={index} xs={24} md={12} span={8}>
<Card
className={mergeCls("h-20 w-full overflow-hidden shadow-sm", provider.builtin ? " cursor-not-allowed" : "")}
className={mergeCls("h-20 w-full overflow-hidden shadow-xs", provider.builtin ? " cursor-not-allowed" : "")}
styles={{ body: { height: "100%", padding: "0.5rem 1rem" } }}
hoverable
onClick={() => {
@@ -91,7 +91,7 @@ const AccessProviderPicker = ({ className, style, autoFocus, filter, placeholder
<Typography.Text className="mb-1 line-clamp-1" type={provider.builtin ? "secondary" : undefined}>
{t(provider.name)}
</Typography.Text>
<div className="origin-left scale-[75%]">
<div className="origin-left scale-75">
<Show when={provider.builtin}>
<Tag>{t("access.props.provider.builtin")}</Tag>
</Show>
@@ -1,6 +1,6 @@
import { memo, useEffect, useMemo, useState } from "react";
import { memo, useMemo } from "react";
import { useTranslation } from "react-i18next";
import { Avatar, Select, type SelectProps, Space, Tag, Typography, theme } from "antd";
import { Avatar, Select, type SelectProps, Tag, Typography, theme } from "antd";
import Show from "@/components/Show";
import { ACCESS_USAGES, type AccessProvider, type AccessUsageType, accessProvidersMap } from "@/domain/provider";
@@ -18,19 +18,22 @@ const AccessProviderSelect = ({ filter, showOptionTags, ...props }: AccessProvid
const { token: themeToken } = theme.useToken();
const [options, setOptions] = useState<Array<{ key: string; value: string; label: string; data: AccessProvider }>>([]);
useEffect(() => {
const allItems = Array.from(accessProvidersMap.values());
const filteredItems = filter != null ? allItems.filter(filter) : allItems;
setOptions(
filteredItems.map((item) => ({
key: item.type,
value: item.type,
label: t(item.name),
disabled: item.builtin,
data: item,
}))
);
const options = useMemo<Array<{ key: string; value: string; label: string; data: AccessProvider }>>(() => {
return Array.from(accessProvidersMap.values())
.filter((provider) => {
if (filter) {
return filter(provider);
}
return true;
})
.map((provider) => ({
key: provider.type,
value: provider.type,
label: t(provider.name),
disabled: provider.builtin,
data: provider,
}));
}, [filter]);
const showOptionTagForDNS = useMemo(() => {
@@ -50,13 +53,13 @@ const AccessProviderSelect = ({ filter, showOptionTags, ...props }: AccessProvid
const provider = accessProvidersMap.get(key) ?? ({ type: "", name: "", icon: "", usages: [] } as unknown as AccessProvider);
return (
<div className="flex max-w-full items-center justify-between gap-4 overflow-hidden">
<Space className="max-w-full grow truncate" size={4}>
<div className="flex items-center gap-2 overflow-hidden truncate">
<Avatar shape="square" src={provider.icon} size="small" />
<Typography.Text className="leading-loose" type={provider.builtin ? "secondary" : undefined} ellipsis>
<Typography.Text type={provider.builtin ? "secondary" : undefined} ellipsis>
{t(provider.name)}
</Typography.Text>
</Space>
<div className="origin-right scale-[75%]">
</div>
<div className="origin-right scale-75">
<Show when={provider.builtin}>
<Tag>{t("access.props.provider.builtin")}</Tag>
</Show>
+32 -28
View File
@@ -1,6 +1,6 @@
import { memo, useEffect, useState } from "react";
import { memo, useMemo } from "react";
import { useTranslation } from "react-i18next";
import { Avatar, Select, type SelectProps, Space, Typography, theme } from "antd";
import { Avatar, Select, type SelectProps, Typography, theme } from "antd";
import { type CAProvider, caProvidersMap } from "@/domain/provider";
@@ -16,45 +16,49 @@ const CAProviderSelect = ({ filter, ...props }: CAProviderSelectProps) => {
const { token: themeToken } = theme.useToken();
const [options, setOptions] = useState<Array<{ key: string; value: string; label: string; data: CAProvider }>>([]);
useEffect(() => {
const allItems = Array.from(caProvidersMap.values());
const filteredItems = filter != null ? allItems.filter(filter) : allItems;
setOptions([
{
key: "",
value: "",
label: t("provider.default_ca_provider.label"),
data: {} as CAProvider,
},
...filteredItems.map((item) => ({
key: item.type,
value: item.type,
label: t(item.name),
data: item,
})),
]);
const options = useMemo<Array<{ key: string; value: string; label: string; data: CAProvider }>>(() => {
const temp = Array.from(caProvidersMap.values())
.filter((provider) => {
if (filter) {
return filter(provider);
}
return true;
})
.map((provider) => ({
key: provider.type as string,
value: provider.type as string,
label: t(provider.name),
data: provider,
}));
temp.unshift({
key: "",
value: "",
label: t("provider.default_ca_provider.label"),
data: {} as CAProvider,
});
return temp;
}, [filter]);
const renderOption = (key: string) => {
if (key === "") {
return (
<Space className="max-w-full grow overflow-hidden truncate" size={4}>
<Typography.Text className="italic leading-loose" type="secondary" ellipsis italic>
<div className="flex items-center gap-2 overflow-hidden truncate">
<Typography.Text className="italic" ellipsis italic>
{t("provider.default_ca_provider.label")}
</Typography.Text>
</Space>
</div>
);
}
const provider = caProvidersMap.get(key);
return (
<Space className="max-w-full grow overflow-hidden truncate" size={4}>
<div className="flex items-center gap-2 overflow-hidden truncate">
<Avatar shape="square" src={provider?.icon} size="small" />
<Typography.Text className="leading-loose" ellipsis>
{t(provider?.name ?? "")}
</Typography.Text>
</Space>
<Typography.Text ellipsis>{t(provider?.name ?? "")}</Typography.Text>
</div>
);
};
@@ -96,7 +96,7 @@ const DeploymentProviderPicker = ({ className, style, autoFocus, filter, placeho
return (
<Col key={index} xs={24} md={12} span={12}>
<Card
className="h-16 w-full overflow-hidden shadow-sm"
className="h-16 w-full overflow-hidden shadow-xs"
styles={{ body: { height: "100%", padding: "0.5rem 1rem" } }}
hoverable
onClick={() => {
@@ -1,6 +1,6 @@
import { memo, useEffect, useState } from "react";
import { memo, useMemo } from "react";
import { useTranslation } from "react-i18next";
import { Avatar, Select, type SelectProps, Space, Typography, theme } from "antd";
import { Avatar, Select, type SelectProps, Typography, theme } from "antd";
import { type DeploymentProvider, deploymentProvidersMap } from "@/domain/provider";
@@ -16,29 +16,30 @@ const DeploymentProviderSelect = ({ filter, ...props }: DeploymentProviderSelect
const { token: themeToken } = theme.useToken();
const [options, setOptions] = useState<Array<{ key: string; value: string; label: string; data: DeploymentProvider }>>([]);
useEffect(() => {
const allItems = Array.from(deploymentProvidersMap.values());
const filteredItems = filter != null ? allItems.filter(filter) : allItems;
setOptions(
filteredItems.map((item) => ({
key: item.type,
value: item.type,
label: t(item.name),
data: item,
}))
);
const options = useMemo<Array<{ key: string; value: string; label: string; data: DeploymentProvider }>>(() => {
return Array.from(deploymentProvidersMap.values())
.filter((provider) => {
if (filter) {
return filter(provider);
}
return true;
})
.map((provider) => ({
key: provider.type,
value: provider.type,
label: t(provider.name),
data: provider,
}));
}, [filter]);
const renderOption = (key: string) => {
const provider = deploymentProvidersMap.get(key);
return (
<Space className="max-w-full grow overflow-hidden truncate" size={4}>
<div className="flex items-center gap-2 overflow-hidden truncate">
<Avatar shape="square" src={provider?.icon} size="small" />
<Typography.Text className="leading-loose" ellipsis>
{t(provider?.name ?? "")}
</Typography.Text>
</Space>
<Typography.Text ellipsis>{t(provider?.name ?? "")}</Typography.Text>
</div>
);
};
@@ -1,6 +1,6 @@
import { memo, useEffect, useState } from "react";
import { memo, useMemo } from "react";
import { useTranslation } from "react-i18next";
import { Avatar, Select, type SelectProps, Space, Typography, theme } from "antd";
import { Avatar, Select, type SelectProps, Typography, theme } from "antd";
import { type NotificationProvider, notificationProvidersMap } from "@/domain/provider";
@@ -16,29 +16,30 @@ const NotificationProviderSelect = ({ filter, ...props }: NotificationProviderSe
const { token: themeToken } = theme.useToken();
const [options, setOptions] = useState<Array<{ key: string; value: string; label: string; data: NotificationProvider }>>([]);
useEffect(() => {
const allItems = Array.from(notificationProvidersMap.values());
const filteredItems = filter != null ? allItems.filter(filter) : allItems;
setOptions(
filteredItems.map((item) => ({
key: item.type,
value: item.type,
label: t(item.name),
data: item,
}))
);
const options = useMemo<Array<{ key: string; value: string; label: string; data: NotificationProvider }>>(() => {
return Array.from(notificationProvidersMap.values())
.filter((provider) => {
if (filter) {
return filter(provider);
}
return true;
})
.map((provider) => ({
key: provider.type,
value: provider.type,
label: t(provider.name),
data: provider,
}));
}, [filter]);
const renderOption = (key: string) => {
const provider = notificationProvidersMap.get(key);
return (
<Space className="max-w-full grow overflow-hidden truncate" size={4}>
<div className="flex items-center gap-2 overflow-hidden truncate">
<Avatar shape="square" src={provider?.icon} size="small" />
<Typography.Text className="leading-loose" ellipsis>
{t(provider?.name ?? "")}
</Typography.Text>
</Space>
<Typography.Text ellipsis>{t(provider?.name ?? "")}</Typography.Text>
</div>
);
};
@@ -21,7 +21,7 @@ const WorkflowElementsContainer = ({ className, style, disabled }: WorkflowEleme
return (
<div className={mergeCls("relative size-full overflow-hidden", className)} style={style}>
<div className="size-full overflow-auto">
<div className="relative z-[1]">
<div className="relative z-1">
<div className="origin-center transition-transform duration-300" style={{ zoom: `${scale}` }}>
<div className="p-4">
<WorkflowElements disabled={disabled} />
@@ -30,14 +30,16 @@ const WorkflowElementsContainer = ({ className, style, disabled }: WorkflowEleme
</div>
</div>
<Card className="absolute bottom-4 right-6 z-[2] rounded-lg p-2 shadow-lg" styles={{ body: { padding: 0 } }}>
<div className="flex items-center gap-2">
<Button icon={<MinusOutlinedIcon />} disabled={scale <= MIN_SCALE} onClick={() => setScale((s) => Math.max(MIN_SCALE, s - STEP_SCALE))} />
<Typography.Text className="min-w-[3em] text-center">{Math.round(scale * 100)}%</Typography.Text>
<Button icon={<PlusOutlinedIcon />} disabled={scale >= MAX_SCALE} onClick={() => setScale((s) => Math.min(MAX_SCALE, s + STEP_SCALE))} />
<Button icon={<ExpandOutlinedIcon />} onClick={() => setScale(1)} />
</div>
</Card>
<div className="absolute bottom-4 right-6 z-2">
<Card className="rounded-lg p-2 shadow-lg" styles={{ body: { padding: 0 } }}>
<div className="flex items-center gap-2">
<Button icon={<MinusOutlinedIcon />} disabled={scale <= MIN_SCALE} onClick={() => setScale((s) => Math.max(MIN_SCALE, s - STEP_SCALE))} />
<Typography.Text className="min-w-12 text-center">{Math.round(scale * 100)}%</Typography.Text>
<Button icon={<PlusOutlinedIcon />} disabled={scale >= MAX_SCALE} onClick={() => setScale((s) => Math.min(MAX_SCALE, s + STEP_SCALE))} />
<Button icon={<ExpandOutlinedIcon />} onClick={() => setScale(1)} />
</div>
</Card>
</div>
</div>
);
};
+2 -2
View File
@@ -65,8 +65,8 @@ const AddNode = ({ node, disabled }: AddNodeProps) => {
return (
<div className="relative py-6 before:absolute before:left-1/2 before:top-0 before:h-full before:w-[2px] before:-translate-x-1/2 before:bg-stone-200 before:content-['']">
<Dropdown menu={{ items: dropdownMenus }} trigger={["click"]}>
<div className="relative z-[1] flex size-5 cursor-pointer items-center justify-center rounded-full bg-stone-400 hover:bg-stone-500">
<PlusOutlinedIcon className="text-white" />
<div className="relative z-1 flex size-5 cursor-pointer items-center justify-center rounded-full text-white bg-stone-400 hover:bg-stone-500">
<PlusOutlinedIcon />
</div>
</Dropdown>
</div>
@@ -318,7 +318,7 @@ const ApplyNodeConfigForm = forwardRef<ApplyNodeConfigFormInstance, ApplyNodeCon
/>
</Form.Item>
<Form.Item className="mb-0" htmlFor="null">
<Form.Item noStyle>
<label className="mb-1 block">
<div className="flex w-full items-center justify-between gap-4">
<div className="max-w-full grow truncate">
@@ -375,7 +375,7 @@ const ApplyNodeConfigForm = forwardRef<ApplyNodeConfigFormInstance, ApplyNodeCon
</Divider>
<Form className={className} style={style} {...formProps} disabled={disabled} layout="vertical" scrollToFirstError onValuesChange={handleFormChange}>
<Form.Item className="mb-0" htmlFor="null">
<Form.Item noStyle>
<label className="mb-1 block">
<div className="flex w-full items-center justify-between gap-4">
<div className="max-w-full grow truncate">
@@ -404,7 +404,7 @@ const ApplyNodeConfigForm = forwardRef<ApplyNodeConfigFormInstance, ApplyNodeCon
</Form.Item>
</Form.Item>
<Form.Item className="mb-0" htmlFor="null" hidden={!showCAProviderAccess}>
<Form.Item hidden={!showCAProviderAccess} noStyle>
<label className="mb-1 block">
<div className="flex w-full items-center justify-between gap-4">
<div className="max-w-full grow truncate">
+7 -27
View File
@@ -40,7 +40,7 @@ const BranchNode = ({ node, disabled }: BrandNodeProps) => {
}}
>
<Button
className="absolute left-1/2 z-[1] -translate-x-1/2 -translate-y-1/2 text-xs"
className="absolute left-1/2 z-1 -translate-x-1/2 -translate-y-1/2 text-xs"
disabled={disabled}
size="small"
shape="round"
@@ -55,38 +55,18 @@ const BranchNode = ({ node, disabled }: BrandNodeProps) => {
{node.branches?.map((branch, index) => (
<div
key={branch.id}
className="relative flex flex-col items-center before:absolute before:left-1/2 before:top-0 before:h-full before:w-[2px] before:-translate-x-1/2 before:bg-stone-200 before:content-['']"
className="relative flex flex-col items-center before:absolute before:left-1/2 before:top-0 before:h-full before:w-[2px] before:-translate-x-1/2 before:bg-stone-200 before:content-['']"
>
{index == 0 && (
<>
<div
className="absolute -left-px -top-1 h-2 w-1/2"
style={{
backgroundColor: themeToken.colorBgContainer,
}}
></div>
<div
className="absolute -bottom-1 -left-px z-50 h-2 w-1/2"
style={{
backgroundColor: themeToken.colorBgContainer,
}}
></div>
<div className="absolute -left-[1px] -top-1 h-2 w-1/2" style={{ backgroundColor: themeToken.colorBgContainer }}></div>
<div className="absolute -bottom-1 -left-[1px] z-50 h-2 w-1/2" style={{ backgroundColor: themeToken.colorBgContainer }}></div>
</>
)}
{node.branches && index == node.branches.length - 1 && (
{index == node.branches?.length! - 1 && (
<>
<div
className="absolute -right-px -top-1 h-2 w-1/2"
style={{
backgroundColor: themeToken.colorBgContainer,
}}
></div>
<div
className="absolute -bottom-1 -right-px z-50 h-2 w-1/2"
style={{
backgroundColor: themeToken.colorBgContainer,
}}
></div>
<div className="absolute -right-[1px] -top-1 h-2 w-1/2" style={{ backgroundColor: themeToken.colorBgContainer }}></div>
<div className="absolute -bottom-1 -right-[1px] z-50 h-2 w-1/2" style={{ backgroundColor: themeToken.colorBgContainer }}></div>
</>
)}
<div className="relative flex flex-col items-center">{renderBranch(branch, node.id, index)}</div>
@@ -64,24 +64,26 @@ const ConditionNode = ({ node, disabled, branchId, branchIndex }: ConditionNodeP
}
placement="rightTop"
>
<Card className="relative z-[1] mt-10 w-[256px] shadow-md" styles={{ body: { padding: 0 } }} hoverable onClick={() => setDrawerOpen(true)}>
<div className="flex h-[48px] flex-col items-center justify-center truncate px-4 py-2">
<div className="relative w-full overflow-hidden" onClick={(e) => e.stopPropagation()}>
<SharedNode.Title
className="focus:bg-background focus:text-foreground overflow-hidden outline-slate-200 focus:rounded-sm"
node={node}
disabled={disabled}
/>
<div className="absolute right-0 top-1/2 -translate-y-1/2" onClick={() => setDrawerOpen(true)}>
{node.config?.expression ? (
<Button color="primary" icon={<FilterFilledIcon />} variant="link" />
) : (
<Button color="default" icon={<FilterOutlinedIcon />} variant="link" />
)}
<div className="relative w-[256px] z-1 mt-10">
<Card className="shadow-md" styles={{ body: { padding: 0 } }} hoverable onClick={() => setDrawerOpen(true)}>
<div className="flex h-[48px] flex-col items-center justify-center truncate px-4 py-2">
<div className="relative w-full overflow-hidden" onClick={(e) => e.stopPropagation()}>
<SharedNode.Title
className="focus:bg-background focus:text-foreground overflow-hidden outline-slate-200 focus:rounded-xs"
node={node}
disabled={disabled}
/>
<div className="absolute right-0 top-1/2 -translate-y-1/2" onClick={() => setDrawerOpen(true)}>
{node.config?.expression ? (
<Button color="primary" icon={<FilterFilledIcon />} variant="link" />
) : (
<Button color="default" icon={<FilterOutlinedIcon />} variant="link" />
)}
</div>
</div>
</div>
</div>
</Card>
</Card>
</div>
</Popover>
<SharedNode.ConfigDrawer
@@ -269,11 +269,7 @@ const ConditionNodeConfigFormExpressionEditor = forwardRef<ConditionNodeConfigFo
onValuesChange={handleFormChange}
>
<Show when={formModel.conditions?.length > 1}>
<Form.Item
className="mb-2"
name="logicalOperator"
rules={[{ required: true, message: t("workflow_node.condition.form.expression.logical_operator.errmsg") }]}
>
<Form.Item name="logicalOperator" rules={[{ required: true, message: t("workflow_node.condition.form.expression.logical_operator.errmsg") }]}>
<Radio.Group block>
<Radio.Button value="and">{t("workflow_node.condition.form.expression.logical_operator.option.and.label")}</Radio.Button>
<Radio.Button value="or">{t("workflow_node.condition.form.expression.logical_operator.option.or.label")}</Radio.Button>
@@ -475,7 +475,7 @@ const DeployNodeConfigForm = forwardRef<DeployNodeConfigFormInstance, DeployNode
/>
</Form.Item>
<Form.Item className="mb-0" htmlFor="null" hidden={!showProviderAccess}>
<Form.Item hidden={!showProviderAccess} noStyle>
<label className="mb-1 block">
<div className="flex w-full items-center justify-between gap-4">
<div className="max-w-full grow truncate">
@@ -69,7 +69,7 @@ const DeployNodeConfigFormBaotaWAFSiteConfig = ({
</Form.Item>
<Form.Item name="sitePort" label={t("workflow_node.deploy.form.baotawaf_site_port.label")} rules={[formRule]}>
<InputNumber className="w-full" placeholder={t("access.form.ssh_port.placeholder")} min={1} max={65535} />
<InputNumber style={{ width: "100%" }} placeholder={t("access.form.ssh_port.placeholder")} min={1} max={65535} />
</Form.Item>
</Form>
);
@@ -406,7 +406,7 @@ const DeployNodeConfigFormLocalConfig = ({ form: formInst, formName, disabled, i
</Select>
</Form.Item>
<Form.Item className="mb-0" htmlFor="null">
<Form.Item noStyle>
<label className="mb-1 block">
<div className="flex w-full items-center justify-between gap-4">
<div className="max-w-full grow truncate">
@@ -442,7 +442,7 @@ const DeployNodeConfigFormLocalConfig = ({ form: formInst, formName, disabled, i
</Form.Item>
</Form.Item>
<Form.Item className="mb-0" htmlFor="null">
<Form.Item noStyle>
<label className="mb-1 block">
<div className="flex w-full items-center justify-between gap-4">
<div className="max-w-full grow truncate">
@@ -431,7 +431,7 @@ const DeployNodeConfigFormSSHConfig = ({ form: formInst, formName, disabled, ini
</Form.Item>
</Show>
<Form.Item className="mb-0" htmlFor="null">
<Form.Item noStyle>
<label className="mb-1 block">
<div className="flex w-full items-center justify-between gap-4">
<div className="max-w-full grow truncate">
@@ -467,7 +467,7 @@ const DeployNodeConfigFormSSHConfig = ({ form: formInst, formName, disabled, ini
</Form.Item>
</Form.Item>
<Form.Item className="mb-0" htmlFor="null">
<Form.Item noStyle>
<label className="mb-1 block">
<div className="flex w-full items-center justify-between gap-4">
<div className="max-w-full grow truncate">
@@ -28,45 +28,23 @@ const ExecuteResultBranchNode = ({ node, disabled }: BrandNodeProps) => {
<>
<div
className="relative flex gap-x-16 before:absolute before:inset-x-[128px] before:top-0 before:h-[2px] before:bg-stone-200 before:content-[''] after:absolute after:inset-x-[128px] after:bottom-0 after:h-[2px] after:bg-stone-200 after:content-['']"
style={{
backgroundColor: themeToken.colorBgContainer,
}}
style={{ backgroundColor: themeToken.colorBgContainer }}
>
{node.branches?.map((branch, index) => (
<div
key={branch.id}
className="relative flex flex-col items-center before:absolute before:left-1/2 before:top-0 before:h-full before:w-[2px] before:-translate-x-1/2 before:bg-stone-200 before:content-['']"
className="relative flex flex-col items-center before:absolute before:left-1/2 before:top-0 before:h-full before:w-[2px] before:-translate-x-1/2 before:bg-stone-200 before:content-['']"
>
{index == 0 && (
<>
<div
className="absolute -left-px -top-1 h-2 w-1/2"
style={{
backgroundColor: themeToken.colorBgContainer,
}}
></div>
<div
className="absolute -bottom-1 -left-px z-50 h-2 w-1/2"
style={{
backgroundColor: themeToken.colorBgContainer,
}}
></div>
<div className="absolute -left-[1px] -top-1 h-2 w-1/2" style={{ backgroundColor: themeToken.colorBgContainer }}></div>
<div className="absolute -bottom-1 -left-[1px] z-50 h-2 w-1/2" style={{ backgroundColor: themeToken.colorBgContainer }}></div>
</>
)}
{node.branches && index == node.branches.length - 1 && (
<>
<div
className="absolute -right-px -top-1 h-2 w-1/2"
style={{
backgroundColor: themeToken.colorBgContainer,
}}
></div>
<div
className="absolute -bottom-1 -right-px z-50 h-2 w-1/2"
style={{
backgroundColor: themeToken.colorBgContainer,
}}
></div>
<div className="absolute -right-[1px] -top-1 h-2 w-1/2" style={{ backgroundColor: themeToken.colorBgContainer }}></div>
<div className="absolute -bottom-1 -right-[1px] z-50 h-2 w-1/2" style={{ backgroundColor: themeToken.colorBgContainer }}></div>
</>
)}
<div className="relative flex flex-col items-center">{renderBranch(branch, node.id, index)}</div>
@@ -35,22 +35,24 @@ const ExecuteResultNode = ({ node, disabled, branchId, branchIndex }: ConditionN
}
placement="rightTop"
>
<Card className="relative z-[1] mt-10 w-[256px] shadow-md" styles={{ body: { padding: 0 } }} hoverable>
<div className="flex h-[48px] flex-col items-center justify-center truncate px-4 py-2">
<div className="flex items-center space-x-2">
{node.type === WorkflowNodeType.ExecuteSuccess ? (
<CheckCircleOutlinedIcon style={{ color: themeToken.colorSuccess }} />
) : (
<CloseCircleOutlinedIcon style={{ color: themeToken.colorError }} />
)}
<SharedNode.Title
className="focus:bg-background focus:text-foreground overflow-hidden outline-slate-200 focus:rounded-sm"
node={node}
disabled={disabled}
/>
<div className="relative z-1 mt-10 w-[256px]">
<Card className="shadow-md" styles={{ body: { padding: 0 } }} hoverable>
<div className="flex h-[48px] flex-col items-center justify-center truncate px-4 py-2">
<div className="flex items-center space-x-2">
{node.type === WorkflowNodeType.ExecuteSuccess ? (
<CheckCircleOutlinedIcon style={{ color: themeToken.colorSuccess }} />
) : (
<CloseCircleOutlinedIcon style={{ color: themeToken.colorError }} />
)}
<SharedNode.Title
className="focus:bg-background focus:text-foreground overflow-hidden outline-slate-200 focus:rounded-xs"
node={node}
disabled={disabled}
/>
</div>
</div>
</div>
</Card>
</Card>
</div>
</Popover>
<AddNode node={node} disabled={disabled} />
@@ -91,7 +91,7 @@ const MonitorNodeConfigForm = forwardRef<MonitorNodeConfigFormInstance, MonitorN
<div className="w-1/3">
<Form.Item name="port" label={t("workflow_node.monitor.form.port.label")} rules={[formRule]}>
<InputNumber className="w-full" min={1} max={65535} placeholder={t("workflow_node.monitor.form.port.placeholder")} />
<InputNumber style={{ width: "100%" }} min={1} max={65535} placeholder={t("workflow_node.monitor.form.port.placeholder")} />
</Form.Item>
</div>
</div>
@@ -187,7 +187,7 @@ const NotifyNodeConfigForm = forwardRef<NotifyNodeConfigFormInstance, NotifyNode
<Input.TextArea autoSize={{ minRows: 3, maxRows: 10 }} placeholder={t("workflow_node.notify.form.message.placeholder")} />
</Form.Item>
<Form.Item className="mb-0" htmlFor="null">
<Form.Item noStyle>
<label className="mb-1 block">
<div className="flex w-full items-center justify-between gap-4">
<div className="max-w-full grow truncate line-through">{t("workflow_node.notify.form.channel.label")}</div>
@@ -231,7 +231,7 @@ const NotifyNodeConfigForm = forwardRef<NotifyNodeConfigFormInstance, NotifyNode
/>
</Form.Item>
<Form.Item className="mb-0" htmlFor="null">
<Form.Item noStyle>
<label className="mb-1 block">
<div className="flex w-full items-center justify-between gap-4">
<div className="max-w-full grow truncate">
+18 -16
View File
@@ -19,23 +19,25 @@ const UnknownNode = ({ node, disabled }: MonitorNodeProps) => {
return (
<>
<Card className="relative w-[256px] overflow-hidden shadow-md" styles={{ body: { padding: 0 } }} hoverable variant="borderless">
<div className="cursor-pointer ">
<Alert
type="error"
message={
<div className="flex items-center justify-between gap-4 overflow-hidden">
<div className="flex-1 text-center text-xs">
INVALID NODE
<br />
PLEASE REMOVE
<div className="relative w-[256px] overflow-hidden">
<Card className="shadow-md" styles={{ body: { padding: 0 } }} hoverable variant="borderless">
<div className="cursor-pointer ">
<Alert
type="error"
message={
<div className="flex items-center justify-between gap-4 overflow-hidden">
<div className="flex-1 text-center text-xs">
INVALID NODE
<br />
PLEASE REMOVE
</div>
<Button color="primary" icon={<CloseCircleOutlinedIcon />} variant="text" onClick={handleClickRemove} />
</div>
<Button color="primary" icon={<CloseCircleOutlinedIcon />} variant="text" onClick={handleClickRemove} />
</div>
}
/>
</div>
</Card>
}
/>
</div>
</Card>
</div>
<AddNode node={node} disabled={disabled} />
</>
+14 -12
View File
@@ -251,19 +251,21 @@ const SharedNodeBlock = ({ children, node, disabled, onClick }: SharedNodeBlockP
content={<SharedNodeMenu node={node} disabled={disabled} trigger={<Button color="primary" icon={<MoreOutlinedIcon />} variant="text" />} />}
placement="rightTop"
>
<Card className="relative w-[256px] overflow-hidden shadow-md" styles={{ body: { padding: 0 } }} hoverable>
<div className="bg-primary flex h-[48px] flex-col items-center justify-center truncate px-4 py-2 text-white">
<SharedNodeTitle
className="focus:bg-background focus:text-foreground overflow-hidden outline-none focus:rounded-sm"
node={node}
disabled={disabled}
/>
</div>
<div className="relative w-[256px] overflow-hidden">
<Card className="shadow-md" styles={{ body: { padding: 0 } }} hoverable>
<div className="bg-primary flex h-[48px] flex-col items-center justify-center truncate px-4 py-2 text-white">
<SharedNodeTitle
className="focus:bg-background focus:text-foreground overflow-hidden outline-hidden focus:rounded-xs"
node={node}
disabled={disabled}
/>
</div>
<div className="flex cursor-pointer flex-col justify-center px-4 py-2" onClick={handleNodeClick}>
<div className="overflow-hidden text-sm">{children}</div>
</div>
</Card>
<div className="flex cursor-pointer flex-col justify-center px-4 py-2" onClick={handleNodeClick}>
<div className="overflow-hidden text-sm">{children}</div>
</div>
</Card>
</div>
</Popover>
<AddNode node={node} disabled={disabled} />
+2 -3
View File
@@ -1,6 +1,5 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
@import "tailwindcss";
@config "../tailwind.config.js";
@layer base {
:root {
+2 -2
View File
@@ -51,12 +51,12 @@ const ConsoleLayout = () => {
</div>
</Layout.Sider>
<Layout className="flex flex-col overflow-hidden pl-[256px] max-md:pl-0">
<Layout className="flex flex-col overflow-hidden">
<Show when={!isBrowserHappy()}>
<Alert message={t("common.text.happy_browser")} type="warning" showIcon closable />
</Show>
<Layout.Header className="p-0 shadow-sm" style={{ background: themeToken.colorBgContainer }}>
<Layout.Header className="shadow-xs" style={{ background: themeToken.colorBgContainer, padding: 0 }}>
<div className="flex size-full items-center justify-between overflow-hidden px-4">
<div className="flex items-center gap-4">
<SiderMenuDrawer trigger={<Button className="md:hidden" icon={<MenuOutlinedIcon />} size="large" />} />
+2 -2
View File
@@ -55,10 +55,10 @@ const AccessList = () => {
ellipsis: true,
render: (_, record) => {
return (
<Space className="max-w-full truncate" size={4}>
<div className="max-w-full flex items-center gap-2 overflow-hidden truncate">
<Avatar shape="square" src={accessProvidersMap.get(record.provider)?.icon} size="small" />
<Typography.Text ellipsis>{t(accessProvidersMap.get(record.provider)?.name ?? "")}</Typography.Text>
</Space>
</div>
);
},
},
+1 -1
View File
@@ -57,7 +57,7 @@ const SettingsAccount = () => {
{MessageContextHolder}
{NotificationContextHolder}
<div className="md:max-w-[40rem]">
<div className="md:max-w-160">
<Form {...formProps} form={formInst} disabled={formPending} layout="vertical">
<Form.Item name="username" label={t("settings.account.form.email.label")} rules={[formRule]}>
<Input placeholder={t("settings.account.form.email.placeholder")} onChange={handleInputChange} />
@@ -16,17 +16,17 @@ const SettingsNotification = () => {
return (
<div>
<Card className="shadow" title={t("settings.notification.template.card.title")}>
<div className="md:max-w-[40rem]">
<Card className="shadow-sm" title={t("settings.notification.template.card.title")}>
<div className="md:max-w-160">
<NotifyTemplate />
</div>
</Card>
<Divider />
<Card className="shadow" styles={{ body: loadedAtOnce ? { padding: 0 } : {} }} title={t("settings.notification.channels.card.title")}>
<Card className="shadow-sm" styles={{ body: loadedAtOnce ? { padding: 0 } : {} }} title={t("settings.notification.channels.card.title")}>
<Alert type="warning" banner message="本页面相关功能即将在后续版本中废弃,请使用「授权管理」页面来管理通知渠道。" />
<NotifyChannels classNames={{ form: "md:max-w-[40rem]" }} />
<NotifyChannels classNames={{ form: "md:max-w-160" }} />
</Card>
</div>
);
+1 -1
View File
@@ -62,7 +62,7 @@ const SettingsPassword = () => {
{MessageContextHolder}
{NotificationContextHolder}
<div className="md:max-w-[40rem]">
<div className="md:max-w-160">
<Form {...formProps} form={formInst} disabled={formPending} layout="vertical">
<Form.Item name="oldPassword" label={t("settings.password.form.old_password.label")} rules={[formRule]}>
<Input.Password placeholder={t("settings.password.form.old_password.placeholder")} onChange={handleInputChange} />
@@ -83,7 +83,7 @@ const SettingsPersistence = () => {
{NotificationContextHolder}
<Show when={!loading} fallback={<Skeleton active />}>
<div className="md:max-w-[40rem]">
<div className="md:max-w-160">
<Form {...formProps} form={formInst} disabled={formPending} layout="vertical">
<Form.Item
name="workflowRunsMaxDaysRetention"
@@ -92,7 +92,7 @@ const SettingsPersistence = () => {
rules={[formRule]}
>
<InputNumber
className="w-full"
style={{ width: "100%" }}
min={0}
max={36500}
placeholder={t("settings.persistence.form.workflow_runs_max_days.placeholder")}
@@ -108,7 +108,7 @@ const SettingsPersistence = () => {
rules={[formRule]}
>
<InputNumber
className="w-full"
style={{ width: "100%" }}
min={0}
max={36500}
placeholder={t("settings.persistence.form.expired_certificates_max_days.placeholder")}
@@ -430,7 +430,11 @@ const SettingsSSLProvider = () => {
<Show when={!loading} fallback={<Skeleton active />}>
<Form form={formInst} disabled={formPending} layout="vertical" initialValues={{ provider: providerType }}>
<Form.Item className="mb-2" name="provider" label={t("settings.sslprovider.form.provider.label")}>
<Form.Item>
<Alert type="warning" message={<span dangerouslySetInnerHTML={{ __html: t("settings.sslprovider.form.provider.alert") }}></span>} />
</Form.Item>
<Form.Item name="provider" label={t("settings.sslprovider.form.provider.label")}>
<CheckCard.Group className="w-full" onChange={(value) => setProviderType(value as CAProviderType)}>
<CheckCard
avatar={<img src={"/imgs/providers/letsencrypt.svg"} className="size-8" />}
@@ -476,13 +480,9 @@ const SettingsSSLProvider = () => {
/>
</CheckCard.Group>
</Form.Item>
<Form.Item>
<Alert type="warning" message={<span dangerouslySetInnerHTML={{ __html: t("settings.sslprovider.form.provider.alert") }}></span>} />
</Form.Item>
</Form>
<div className="md:max-w-[40rem]">{providerFormEl}</div>
<div className="md:max-w-160">{providerFormEl}</div>
</Show>
</SSLProviderContext.Provider>
);
+1 -1
View File
@@ -270,7 +270,7 @@ const WorkflowDetail = () => {
}}
loading={!initialized}
>
<div className="absolute inset-x-6 top-4 z-[2] flex items-center justify-between gap-4">
<div className="absolute inset-x-6 top-4 z-2 flex items-center justify-between gap-4">
<div className="flex-1 overflow-hidden">
<Show when={workflow.hasDraft!}>
<Alert banner message={<div className="truncate">{t("workflow.detail.orchestration.draft.alert")}</div>} type="warning" />
+1 -1
View File
@@ -119,7 +119,7 @@ const WorkflowNew = () => {
</Card>
<div className="p-4">
<div className="mx-auto max-w-[1600px] px-2">
<div className="mx-auto max-w-400 px-2">
<Typography.Text type="secondary">
<div className="mb-8 mt-4 text-xl">{t("workflow.new.templates.title")}</div>
</Typography.Text>
-5
View File
@@ -7,10 +7,6 @@ module.exports = {
container: {
center: "true",
padding: "2rem",
screens: {
"2xl": "1400px",
"3xl": "1920px",
},
},
extend: {
colors: {
@@ -22,5 +18,4 @@ module.exports = {
},
},
},
plugins: [require("tailwindcss-animate")],
};
+4 -2
View File
@@ -1,6 +1,7 @@
import { type SpawnSyncReturns, execFileSync } from "node:child_process";
import path from "node:path";
import tailwindcssPlugin from "@tailwindcss/vite";
import legacyPlugin from "@vitejs/plugin-legacy";
import reactPlugin from "@vitejs/plugin-react";
import fs from "fs-extra";
@@ -14,7 +15,7 @@ const preserveFilesPlugin = (filesToPreserve: string[]): Plugin => {
// 在构建开始时将要保留的文件或目录移动到临时位置
filesToPreserve.forEach((file) => {
const srcPath = path.resolve(__dirname, file);
const tempPath = path.resolve(__dirname, `node_modules`, `.tmp`, `build_${file}`);
const tempPath = path.resolve(__dirname, `node_modules/.tmp/build/${file}`);
if (fs.existsSync(srcPath)) {
fs.moveSync(srcPath, tempPath, { overwrite: true });
}
@@ -24,7 +25,7 @@ const preserveFilesPlugin = (filesToPreserve: string[]): Plugin => {
// 在构建完成后将临时位置的文件或目录移回原来的位置
filesToPreserve.forEach((file) => {
const srcPath = path.resolve(__dirname, file);
const tempPath = path.resolve(__dirname, `node_modules`, `.tmp`, `build_${file}`);
const tempPath = path.resolve(__dirname, `node_modules/.tmp/build/${file}`);
if (fs.existsSync(tempPath)) {
fs.moveSync(tempPath, srcPath, { overwrite: true });
}
@@ -56,6 +57,7 @@ export default defineConfig(({ mode }) => {
legacyPlugin({
targets: ["defaults", "not IE 11"],
}),
tailwindcssPlugin(),
preserveFilesPlugin(["dist/.gitkeep"]),
],
resolve: {