feat: deprecate old notification module and introduce new notifier module
This commit is contained in:
67
ui/src/components/provider/NotifyProviderSelect.tsx
Normal file
67
ui/src/components/provider/NotifyProviderSelect.tsx
Normal file
@@ -0,0 +1,67 @@
|
||||
import { memo, useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Avatar, Select, type SelectProps, Space, Typography } from "antd";
|
||||
|
||||
import { type NotifyProvider, notifyProvidersMap } from "@/domain/provider";
|
||||
|
||||
export type NotifyProviderSelectProps = Omit<
|
||||
SelectProps,
|
||||
"filterOption" | "filterSort" | "labelRender" | "options" | "optionFilterProp" | "optionLabelProp" | "optionRender"
|
||||
> & {
|
||||
filter?: (record: NotifyProvider) => boolean;
|
||||
};
|
||||
|
||||
const NotifyProviderSelect = ({ filter, ...props }: NotifyProviderSelectProps) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const [options, setOptions] = useState<Array<{ key: string; value: string; label: string; data: NotifyProvider }>>([]);
|
||||
useEffect(() => {
|
||||
const allItems = Array.from(notifyProvidersMap.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,
|
||||
}))
|
||||
);
|
||||
}, [filter]);
|
||||
|
||||
const renderOption = (key: string) => {
|
||||
const provider = notifyProvidersMap.get(key);
|
||||
return (
|
||||
<Space className="max-w-full grow overflow-hidden truncate" size={4}>
|
||||
<Avatar src={provider?.icon} size="small" />
|
||||
<Typography.Text className="leading-loose" ellipsis>
|
||||
{t(provider?.name ?? "")}
|
||||
</Typography.Text>
|
||||
</Space>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<Select
|
||||
{...props}
|
||||
filterOption={(inputValue, option) => {
|
||||
if (!option) return false;
|
||||
|
||||
const value = inputValue.toLowerCase();
|
||||
return option.value.toLowerCase().includes(value) || option.label.toLowerCase().includes(value);
|
||||
}}
|
||||
labelRender={({ label, value }) => {
|
||||
if (!label) {
|
||||
return <Typography.Text type="secondary">{props.placeholder}</Typography.Text>;
|
||||
}
|
||||
|
||||
return renderOption(value as string);
|
||||
}}
|
||||
options={options}
|
||||
optionFilterProp={undefined}
|
||||
optionLabelProp={undefined}
|
||||
optionRender={(option) => renderOption(option.data.value)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(NotifyProviderSelect);
|
||||
@@ -193,6 +193,7 @@ const WorkflowRunLogs = ({ runId, runStatus }: { runId: string; runStatus: strin
|
||||
const NEWLINE = "\n";
|
||||
const logstr = listData
|
||||
.map((group) => {
|
||||
const escape = (str: string) => str.replaceAll("\r", "\\r").replaceAll("\n", "\\n");
|
||||
return (
|
||||
group.name +
|
||||
NEWLINE +
|
||||
@@ -200,8 +201,9 @@ const WorkflowRunLogs = ({ runId, runStatus }: { runId: string; runStatus: strin
|
||||
.map((record) => {
|
||||
const datetime = dayjs(record.timestamp).format("YYYY-MM-DDTHH:mm:ss.SSSZ");
|
||||
const level = record.level;
|
||||
const message = record.message.trim().replaceAll("\r", "\\r").replaceAll("\n", "\\n");
|
||||
return `[${datetime}] [${level}] ${message}`;
|
||||
const message = record.message;
|
||||
const data = record.data && Object.keys(record.data).length > 0 ? JSON.stringify(record.data) : "";
|
||||
return `[${datetime}] [${level}] ${escape(message)} ${escape(data)}`.trim();
|
||||
})
|
||||
.join(NEWLINE)
|
||||
);
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { memo, useMemo, useRef, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Flex, Typography } from "antd";
|
||||
import { Avatar, Flex, Typography } from "antd";
|
||||
import { produce } from "immer";
|
||||
|
||||
import { notifyProvidersMap } from "@/domain/provider";
|
||||
import { notifyChannelsMap } from "@/domain/settings";
|
||||
import { type WorkflowNodeConfigForNotify, WorkflowNodeType } from "@/domain/workflow";
|
||||
import { useZustandShallowSelector } from "@/hooks";
|
||||
@@ -39,9 +40,11 @@ const NotifyNode = ({ node, disabled }: NotifyNodeProps) => {
|
||||
|
||||
const config = (node.config as WorkflowNodeConfigForNotify) ?? {};
|
||||
const channel = notifyChannelsMap.get(config.channel as string);
|
||||
const provider = notifyProvidersMap.get(config.provider);
|
||||
return (
|
||||
<Flex className="size-full overflow-hidden" align="center" gap={8}>
|
||||
<Typography.Text className="flex-1 truncate">{t(channel?.name ?? " ")}</Typography.Text>
|
||||
<Avatar src={provider?.icon} size="small" />
|
||||
<Typography.Text className="flex-1 truncate">{t(channel?.name ?? provider?.name ?? " ")}</Typography.Text>
|
||||
<Typography.Text className="truncate" type="secondary">
|
||||
{config.subject ?? ""}
|
||||
</Typography.Text>
|
||||
|
||||
@@ -1,14 +1,19 @@
|
||||
import { forwardRef, memo, useEffect, useImperativeHandle } from "react";
|
||||
import { forwardRef, memo, useEffect, useImperativeHandle, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Link } from "react-router";
|
||||
import { RightOutlined as RightOutlinedIcon } from "@ant-design/icons";
|
||||
import { Button, Form, type FormInstance, Input, Select } from "antd";
|
||||
import { PlusOutlined as PlusOutlinedIcon, RightOutlined as RightOutlinedIcon } from "@ant-design/icons";
|
||||
import { Alert, Button, Form, type FormInstance, Input, Select } from "antd";
|
||||
import { createSchemaFieldRule } from "antd-zod";
|
||||
import { z } from "zod";
|
||||
|
||||
import AccessEditModal from "@/components/access/AccessEditModal";
|
||||
import AccessSelect from "@/components/access/AccessSelect";
|
||||
import NotifyProviderSelect from "@/components/provider/NotifyProviderSelect";
|
||||
import { ACCESS_USAGES, accessProvidersMap, notifyProvidersMap } from "@/domain/provider";
|
||||
import { notifyChannelsMap } from "@/domain/settings";
|
||||
import { type WorkflowNodeConfigForNotify } from "@/domain/workflow";
|
||||
import { useAntdForm, useZustandShallowSelector } from "@/hooks";
|
||||
import { useAccessesStore } from "@/stores/access";
|
||||
import { useNotifyChannelsStore } from "@/stores/notify";
|
||||
|
||||
type NotifyNodeConfigFormFieldValues = Partial<WorkflowNodeConfigForNotify>;
|
||||
@@ -35,6 +40,8 @@ const NotifyNodeConfigForm = forwardRef<NotifyNodeConfigFormInstance, NotifyNode
|
||||
({ className, style, disabled, initialValues, onValuesChange }, ref) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const { accesses } = useAccessesStore(useZustandShallowSelector("accesses"));
|
||||
|
||||
const {
|
||||
channels,
|
||||
loadedAtOnce: channelsLoadedAtOnce,
|
||||
@@ -53,7 +60,11 @@ const NotifyNodeConfigForm = forwardRef<NotifyNodeConfigFormInstance, NotifyNode
|
||||
.string({ message: t("workflow_node.notify.form.message.placeholder") })
|
||||
.min(1, t("workflow_node.notify.form.message.placeholder"))
|
||||
.max(1000, t("common.errmsg.string_max", { max: 1000 })),
|
||||
channel: z.string({ message: t("workflow_node.notify.form.channel.placeholder") }).min(1, t("workflow_node.notify.form.channel.placeholder")),
|
||||
channel: z.string().nullish(),
|
||||
provider: z.string({ message: t("workflow_node.notify.form.provider.placeholder") }).nonempty(t("workflow_node.notify.form.provider.placeholder")),
|
||||
providerAccessId: z
|
||||
.string({ message: t("workflow_node.notify.form.provider_access.placeholder") })
|
||||
.nonempty(t("workflow_node.notify.form.provider_access.placeholder")),
|
||||
});
|
||||
const formRule = createSchemaFieldRule(formSchema);
|
||||
const { form: formInst, formProps } = useAntdForm({
|
||||
@@ -61,6 +72,49 @@ const NotifyNodeConfigForm = forwardRef<NotifyNodeConfigFormInstance, NotifyNode
|
||||
initialValues: initialValues ?? initFormModel(),
|
||||
});
|
||||
|
||||
const fieldProvider = Form.useWatch<string>("provider", { form: formInst, preserve: true });
|
||||
const fieldProviderAccessId = Form.useWatch<string>("providerAccessId", formInst);
|
||||
|
||||
const [showProvider, setShowProvider] = useState(false);
|
||||
useEffect(() => {
|
||||
// 通常情况下每个授权信息只对应一个消息通知提供商,此时无需显示消息通知提供商字段;
|
||||
// 如果对应多个,则显示。
|
||||
if (fieldProviderAccessId) {
|
||||
const access = accesses.find((e) => e.id === fieldProviderAccessId);
|
||||
const providers = Array.from(notifyProvidersMap.values()).filter((e) => e.provider === access?.provider);
|
||||
setShowProvider(providers.length > 1);
|
||||
} else {
|
||||
setShowProvider(false);
|
||||
}
|
||||
}, [accesses, fieldProviderAccessId]);
|
||||
|
||||
const handleProviderSelect = (value: string) => {
|
||||
if (fieldProvider === value) return;
|
||||
|
||||
// 切换消息通知提供商时联动授权信息
|
||||
if (initialValues?.provider === value) {
|
||||
formInst.setFieldValue("providerAccessId", initialValues?.providerAccessId);
|
||||
onValuesChange?.(formInst.getFieldsValue(true));
|
||||
} else {
|
||||
if (notifyProvidersMap.get(fieldProvider)?.provider !== notifyProvidersMap.get(value)?.provider) {
|
||||
formInst.setFieldValue("providerAccessId", undefined);
|
||||
onValuesChange?.(formInst.getFieldsValue(true));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleProviderAccessSelect = (value: string) => {
|
||||
if (fieldProviderAccessId === value) return;
|
||||
|
||||
// 切换授权信息时联动消息通知提供商
|
||||
const access = accesses.find((access) => access.id === value);
|
||||
const provider = Array.from(notifyProvidersMap.values()).find((provider) => provider.provider === access?.provider);
|
||||
if (fieldProvider !== provider?.type) {
|
||||
formInst.setFieldValue("provider", provider?.type);
|
||||
onValuesChange?.(formInst.getFieldsValue(true));
|
||||
}
|
||||
};
|
||||
|
||||
const handleFormChange = (_: unknown, values: z.infer<typeof formSchema>) => {
|
||||
onValuesChange?.(values as NotifyNodeConfigFormFieldValues);
|
||||
};
|
||||
@@ -92,7 +146,7 @@ const NotifyNodeConfigForm = forwardRef<NotifyNodeConfigFormInstance, NotifyNode
|
||||
<Form.Item className="mb-0">
|
||||
<label className="mb-1 block">
|
||||
<div className="flex w-full items-center justify-between gap-4">
|
||||
<div className="max-w-full grow truncate">{t("workflow_node.notify.form.channel.label")}</div>
|
||||
<div className="max-w-full grow truncate line-through">{t("workflow_node.notify.form.channel.label")}</div>
|
||||
<div className="text-right">
|
||||
<Link className="ant-typography" to="/settings/notification" target="_blank">
|
||||
<Button size="small" type="link">
|
||||
@@ -116,6 +170,60 @@ const NotifyNodeConfigForm = forwardRef<NotifyNodeConfigFormInstance, NotifyNode
|
||||
/>
|
||||
</Form.Item>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item name="provider" label={t("workflow_node.notify.form.provider.label")} hidden={!showProvider} rules={[formRule]}>
|
||||
<NotifyProviderSelect
|
||||
disabled={!showProvider}
|
||||
filter={(record) => {
|
||||
if (fieldProviderAccessId) {
|
||||
return accesses.find((e) => e.id === fieldProviderAccessId)?.provider === record.provider;
|
||||
}
|
||||
|
||||
return true;
|
||||
}}
|
||||
placeholder={t("workflow_node.notify.form.provider.placeholder")}
|
||||
showSearch
|
||||
onSelect={handleProviderSelect}
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item className="mb-0">
|
||||
<label className="mb-1 block">
|
||||
<div className="flex w-full items-center justify-between gap-4">
|
||||
<div className="max-w-full grow truncate">
|
||||
<span>{t("workflow_node.notify.form.provider_access.label")}</span>
|
||||
</div>
|
||||
<div className="text-right">
|
||||
<AccessEditModal
|
||||
range="notify-only"
|
||||
scene="add"
|
||||
trigger={
|
||||
<Button size="small" type="link">
|
||||
{t("workflow_node.notify.form.provider_access.button")}
|
||||
<PlusOutlinedIcon className="text-xs" />
|
||||
</Button>
|
||||
}
|
||||
afterSubmit={(record) => {
|
||||
const provider = accessProvidersMap.get(record.provider);
|
||||
if (provider?.usages?.includes(ACCESS_USAGES.NOTIFICATION)) {
|
||||
formInst.setFieldValue("providerAccessId", record.id);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</label>
|
||||
<Form.Item name="providerAccessId" rules={[formRule]}>
|
||||
<AccessSelect
|
||||
filter={(record) => {
|
||||
const provider = accessProvidersMap.get(record.provider);
|
||||
return !!provider?.usages?.includes(ACCESS_USAGES.NOTIFICATION);
|
||||
}}
|
||||
placeholder={t("workflow_node.notify.form.provider_access.placeholder")}
|
||||
onChange={handleProviderAccessSelect}
|
||||
/>
|
||||
</Form.Item>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -201,9 +201,9 @@ export const applyCAProvidersMap: Map<ApplyCAProvider["type"] | string, ApplyCAP
|
||||
|
||||
// #region ApplyDNSProvider
|
||||
/*
|
||||
注意:如果追加新的常量值,请保持以 ASCII 排序。
|
||||
NOTICE: If you add new constant, please keep ASCII order.
|
||||
*/
|
||||
注意:如果追加新的常量值,请保持以 ASCII 排序。
|
||||
NOTICE: If you add new constant, please keep ASCII order.
|
||||
*/
|
||||
export const APPLY_DNS_PROVIDERS = Object.freeze({
|
||||
ACMEHTTPREQ: `${ACCESS_PROVIDERS.ACMEHTTPREQ}`,
|
||||
ALIYUN: `${ACCESS_PROVIDERS.ALIYUN}`, // 兼容旧值,等同于 `ALIYUN_DNS`
|
||||
@@ -255,9 +255,9 @@ export type ApplyDNSProvider = {
|
||||
|
||||
export const applyDNSProvidersMap: Map<ApplyDNSProvider["type"] | string, ApplyDNSProvider> = new Map(
|
||||
/*
|
||||
注意:此处的顺序决定显示在前端的顺序。
|
||||
NOTICE: The following order determines the order displayed at the frontend.
|
||||
*/
|
||||
注意:此处的顺序决定显示在前端的顺序。
|
||||
NOTICE: The following order determines the order displayed at the frontend.
|
||||
*/
|
||||
[
|
||||
[APPLY_DNS_PROVIDERS.ALIYUN_DNS, "provider.aliyun.dns"],
|
||||
[APPLY_DNS_PROVIDERS.TENCENTCLOUD_DNS, "provider.tencentcloud.dns"],
|
||||
@@ -302,9 +302,9 @@ export const applyDNSProvidersMap: Map<ApplyDNSProvider["type"] | string, ApplyD
|
||||
|
||||
// #region DeployProvider
|
||||
/*
|
||||
注意:如果追加新的常量值,请保持以 ASCII 排序。
|
||||
NOTICE: If you add new constant, please keep ASCII order.
|
||||
*/
|
||||
注意:如果追加新的常量值,请保持以 ASCII 排序。
|
||||
NOTICE: If you add new constant, please keep ASCII order.
|
||||
*/
|
||||
export const DEPLOY_PROVIDERS = Object.freeze({
|
||||
["1PANEL_CONSOLE"]: `${ACCESS_PROVIDERS["1PANEL"]}-console`,
|
||||
["1PANEL_SITE"]: `${ACCESS_PROVIDERS["1PANEL"]}-site`,
|
||||
@@ -500,3 +500,38 @@ export const deployProvidersMap: Map<DeployProvider["type"] | string, DeployProv
|
||||
])
|
||||
);
|
||||
// #endregion
|
||||
|
||||
// #region NotifyProvider
|
||||
/*
|
||||
注意:如果追加新的常量值,请保持以 ASCII 排序。
|
||||
NOTICE: If you add new constant, please keep ASCII order.
|
||||
*/
|
||||
export const NOTIFY_PROVIDERS = Object.freeze({
|
||||
WEBHOOK: `${ACCESS_PROVIDERS.WEBHOOK}`,
|
||||
} as const);
|
||||
|
||||
export type NotifyProviderType = (typeof APPLY_CA_PROVIDERS)[keyof typeof APPLY_CA_PROVIDERS];
|
||||
|
||||
export type NotifyProvider = {
|
||||
type: NotifyProviderType;
|
||||
name: string;
|
||||
icon: string;
|
||||
provider: AccessProviderType;
|
||||
};
|
||||
|
||||
export const notifyProvidersMap: Map<NotifyProvider["type"] | string, NotifyProvider> = new Map(
|
||||
/*
|
||||
注意:此处的顺序决定显示在前端的顺序。
|
||||
NOTICE: The following order determines the order displayed at the frontend.
|
||||
*/
|
||||
[[NOTIFY_PROVIDERS.WEBHOOK]].map(([type]) => [
|
||||
type,
|
||||
{
|
||||
type: type as ApplyCAProviderType,
|
||||
name: accessProvidersMap.get(type.split("-")[0])!.name,
|
||||
icon: accessProvidersMap.get(type.split("-")[0])!.icon,
|
||||
provider: type.split("-")[0] as AccessProviderType,
|
||||
},
|
||||
])
|
||||
);
|
||||
// #endregion
|
||||
|
||||
@@ -3,6 +3,9 @@ import { type ApplyCAProviderType } from "./provider";
|
||||
export const SETTINGS_NAMES = Object.freeze({
|
||||
EMAILS: "emails",
|
||||
NOTIFY_TEMPLATES: "notifyTemplates",
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
NOTIFY_CHANNELS: "notifyChannels",
|
||||
SSL_PROVIDER: "sslProvider",
|
||||
PERSISTENCE: "persistence",
|
||||
@@ -38,6 +41,9 @@ export const defaultNotifyTemplate: NotifyTemplate = {
|
||||
// #endregion
|
||||
|
||||
// #region Settings: NotifyChannels
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
export const NOTIFY_CHANNELS = Object.freeze({
|
||||
BARK: "bark",
|
||||
DINGTALK: "dingtalk",
|
||||
@@ -53,8 +59,14 @@ export const NOTIFY_CHANNELS = Object.freeze({
|
||||
WECOM: "wecom",
|
||||
} as const);
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
export type NotifyChannels = (typeof NOTIFY_CHANNELS)[keyof typeof NOTIFY_CHANNELS];
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
export type NotifyChannelsSettingsContent = {
|
||||
/*
|
||||
注意:如果追加新的类型,请保持以 ASCII 排序。
|
||||
@@ -116,7 +128,7 @@ export type MattermostNotifyChannelConfig = {
|
||||
username: string;
|
||||
password: string;
|
||||
enabled?: boolean;
|
||||
}
|
||||
};
|
||||
|
||||
export type PushoverNotifyChannelConfig = {
|
||||
token: string;
|
||||
@@ -155,6 +167,9 @@ export type NotifyChannel = {
|
||||
name: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
export const notifyChannelsMap: Map<NotifyChannel["type"], NotifyChannel> = new Map(
|
||||
[
|
||||
[NOTIFY_CHANNELS.EMAIL, "common.notifier.email"],
|
||||
|
||||
@@ -154,9 +154,15 @@ export type WorkflowNodeConfigForDeploy = {
|
||||
};
|
||||
|
||||
export type WorkflowNodeConfigForNotify = {
|
||||
channel: string;
|
||||
subject: string;
|
||||
message: string;
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
channel?: string;
|
||||
provider: string;
|
||||
providerAccessId: string;
|
||||
providerConfig?: Record<string, unknown>;
|
||||
};
|
||||
|
||||
export type WorkflowNodeConfigForBranch = never;
|
||||
|
||||
@@ -712,9 +712,14 @@
|
||||
"workflow_node.notify.form.subject.placeholder": "Please enter subject",
|
||||
"workflow_node.notify.form.message.label": "Message",
|
||||
"workflow_node.notify.form.message.placeholder": "Please enter message",
|
||||
"workflow_node.notify.form.channel.label": "Channel",
|
||||
"workflow_node.notify.form.channel.label": "Channel (Deprecated)",
|
||||
"workflow_node.notify.form.channel.placeholder": "Please select channel",
|
||||
"workflow_node.notify.form.channel.button": "Configure",
|
||||
"workflow_node.notify.form.provider.label": "Notification channel",
|
||||
"workflow_node.notify.form.provider.placeholder": "Please select notification channel",
|
||||
"workflow_node.notify.form.provider_access.label": "Notification provider authorization",
|
||||
"workflow_node.notify.form.provider_access.placeholder": "Please select an authorization of notification provider",
|
||||
"workflow_node.notify.form.provider_access.button": "Create",
|
||||
|
||||
"workflow_node.end.label": "End",
|
||||
|
||||
|
||||
@@ -711,9 +711,14 @@
|
||||
"workflow_node.notify.form.subject.placeholder": "请输入通知主题",
|
||||
"workflow_node.notify.form.message.label": "通知内容",
|
||||
"workflow_node.notify.form.message.placeholder": "请输入通知内容",
|
||||
"workflow_node.notify.form.channel.label": "通知渠道",
|
||||
"workflow_node.notify.form.channel.label": "通知渠道(已废弃,请使用「通知渠道授权」字段)",
|
||||
"workflow_node.notify.form.channel.placeholder": "请选择通知渠道",
|
||||
"workflow_node.notify.form.channel.button": "去配置",
|
||||
"workflow_node.notify.form.channel.button": "设置",
|
||||
"workflow_node.notify.form.provider.label": "通知渠道",
|
||||
"workflow_node.notify.form.provider.placeholder": "请选择通知渠道",
|
||||
"workflow_node.notify.form.provider_access.label": "通知渠道授权",
|
||||
"workflow_node.notify.form.provider_access.placeholder": "请选择通知渠道授权",
|
||||
"workflow_node.notify.form.provider_access.button": "新建",
|
||||
|
||||
"workflow_node.end.label": "结束",
|
||||
|
||||
|
||||
@@ -185,10 +185,12 @@ const AccessList = () => {
|
||||
|
||||
const handleTabChange = (key: string) => {
|
||||
setFilters((prev) => ({ ...prev, range: key }));
|
||||
setPage(1);
|
||||
};
|
||||
|
||||
const handleSearch = (value: string) => {
|
||||
setFilters((prev) => ({ ...prev, keyword: value }));
|
||||
setPage(1);
|
||||
};
|
||||
|
||||
const handleReloadClick = () => {
|
||||
@@ -251,10 +253,10 @@ const AccessList = () => {
|
||||
key: "ca-only",
|
||||
label: t("access.props.range.ca_only"),
|
||||
},
|
||||
// {
|
||||
// key: "notify-only",
|
||||
// label: t("access.props.range.notify_only"),
|
||||
// },
|
||||
{
|
||||
key: "notify-only",
|
||||
label: t("access.props.range.notify_only"),
|
||||
},
|
||||
]}
|
||||
activeTabKey={filters["range"] as string}
|
||||
onTabChange={(key) => handleTabChange(key)}
|
||||
|
||||
@@ -251,6 +251,7 @@ const CertificateList = () => {
|
||||
|
||||
const handleSearch = (value: string) => {
|
||||
setFilters((prev) => ({ ...prev, keyword: value.trim() }));
|
||||
setPage(1);
|
||||
};
|
||||
|
||||
const handleReloadClick = () => {
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Card, Divider } from "antd";
|
||||
import { Alert, Card, Divider } from "antd";
|
||||
|
||||
import NotifyChannels from "@/components/notification/NotifyChannels";
|
||||
import NotifyTemplate from "@/components/notification/NotifyTemplate";
|
||||
import { useZustandShallowSelector } from "@/hooks";
|
||||
import { useNotifyChannelsStore } from "@/stores/notify";
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
const SettingsNotification = () => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
@@ -22,6 +25,7 @@ const SettingsNotification = () => {
|
||||
<Divider />
|
||||
|
||||
<Card className="shadow" styles={{ body: loadedAtOnce ? { padding: 0 } : {} }} title={t("settings.notification.channels.card.title")}>
|
||||
<Alert type="warning" banner message="本页面相关功能即将在后续版本中废弃,请使用「授权管理」页面来管理通知渠道。" />
|
||||
<NotifyChannels classNames={{ form: "md:max-w-[40rem]" }} />
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
@@ -281,6 +281,7 @@ const WorkflowList = () => {
|
||||
|
||||
const handleSearch = (value: string) => {
|
||||
setFilters((prev) => ({ ...prev, keyword: value.trim() }));
|
||||
setPage(1);
|
||||
};
|
||||
|
||||
const handleCreateClick = () => {
|
||||
|
||||
@@ -4,6 +4,9 @@ import { create } from "zustand";
|
||||
import { type NotifyChannelsSettingsContent, SETTINGS_NAMES, type SettingsModel } from "@/domain/settings";
|
||||
import { get as getSettings, save as saveSettings } from "@/repository/settings";
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
export interface NotifyChannelsState {
|
||||
channels: NotifyChannelsSettingsContent;
|
||||
loading: boolean;
|
||||
@@ -14,6 +17,9 @@ export interface NotifyChannelsState {
|
||||
setChannels: (channels: NotifyChannelsSettingsContent) => Promise<void>;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
export const useNotifyChannelsStore = create<NotifyChannelsState>((set, get) => {
|
||||
let fetcher: Promise<SettingsModel<NotifyChannelsSettingsContent>> | null = null; // 防止多次重复请求
|
||||
let settings: SettingsModel<NotifyChannelsSettingsContent>; // 记录当前设置的其他字段,保存回数据库时用
|
||||
|
||||
Reference in New Issue
Block a user