mirror of
https://github.com/woodchen-ink/certimate.git
synced 2025-07-19 01:41:55 +08:00
feat(ui): new SettingsSSLProvider using antd
This commit is contained in:
parent
9e1e0dee1d
commit
a917d6c2c5
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.7 KiB |
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
1
ui/public/imgs/acme/zerossl.svg
Normal file
1
ui/public/imgs/acme/zerossl.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg viewBox="0 0 1113 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" width="200" height="200"><path d="M107.287 512.928c0-79.047-0.045-158.094 0.03-237.141 0.02-21.15 1.256-42.135 9.203-62.181 23.74-59.883 67.166-95.237 130.865-105.518 8.582-1.385 17.221-1.667 25.889-1.667 159.718 0.009 319.436-0.021 479.155 0.019 74.315 0.019 133.182 41.364 157.993 111.108 6.12 17.204 8.542 35.132 8.54 53.403-0.022 161.072 0.122 322.144-0.072 483.215-0.091 75.418-50.342 141.59-119.876 158.868-15.044 3.738-30.331 5.422-45.807 5.423-160.26 0.014-320.521 0.29-480.78-0.12-71.188-0.182-121.825-33.848-152.353-97.864-10.72-22.48-12.842-46.672-12.817-71.218 0.081-78.774 0.03-157.551 0.03-236.327z m283.66-4.813v136.454H254.062v138.583h138.402V645.64h138.004v137.437h138.576V644.705h137.443V505.977H668.339V367.658H530.496V229.98H391.685v138.154H254.216c-0.365 1.798-0.66 2.576-0.661 3.354-0.035 43.047 0.079 86.094-0.188 129.139-0.042 6.738 3.542 6.504 8.205 6.495 39.798-0.073 79.596-0.06 119.394-0.007 3.173 0.005 6.462-0.676 9.981 1z" fill="#4D70D4"></path></svg>
|
After Width: | Height: | Size: 1.0 KiB |
@ -1 +0,0 @@
|
|||||||
<svg class="icon" viewBox="0 0 1113 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="18956" width="200" height="200"><path d="M107.287 512.928c0-79.047-0.045-158.094 0.03-237.141 0.02-21.15 1.256-42.135 9.203-62.181 23.74-59.883 67.166-95.237 130.865-105.518 8.582-1.385 17.221-1.667 25.889-1.667 159.718 0.009 319.436-0.021 479.155 0.019 74.315 0.019 133.182 41.364 157.993 111.108 6.12 17.204 8.542 35.132 8.54 53.403-0.022 161.072 0.122 322.144-0.072 483.215-0.091 75.418-50.342 141.59-119.876 158.868-15.044 3.738-30.331 5.422-45.807 5.423-160.26 0.014-320.521 0.29-480.78-0.12-71.188-0.182-121.825-33.848-152.353-97.864-10.72-22.48-12.842-46.672-12.817-71.218 0.081-78.774 0.03-157.551 0.03-236.327z m283.66-4.813v136.454H254.062v138.583h138.402V645.64h138.004v137.437h138.576V644.705h137.443V505.977H668.339V367.658H530.496V229.98H391.685v138.154H254.216c-0.365 1.798-0.66 2.576-0.661 3.354-0.035 43.047 0.079 86.094-0.188 129.139-0.042 6.738 3.542 6.504 8.205 6.495 39.798-0.073 79.596-0.06 119.394-0.007 3.173 0.005 6.462-0.676 9.981 1z" fill="#4D70D4" p-id="18957"></path></svg>
|
|
Before Width: | Height: | Size: 1.1 KiB |
@ -1,7 +1,7 @@
|
|||||||
import { useEffect, useRef, useState } from "react";
|
import { useEffect, useRef, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { useDeepCompareMemo } from "@ant-design/pro-components";
|
import { useDeepCompareMemo } from "@ant-design/pro-components";
|
||||||
import { Button, Collapse, message, notification, Skeleton, Space, Switch, Tooltip, type CollapseProps } from "antd";
|
import { Button, Collapse, message, notification, Skeleton, Space, Switch, type CollapseProps } from "antd";
|
||||||
|
|
||||||
import NotifyChannelEditForm, { type NotifyChannelEditFormInstance } from "./NotifyChannelEditForm";
|
import NotifyChannelEditForm, { type NotifyChannelEditFormInstance } from "./NotifyChannelEditForm";
|
||||||
import NotifyTestButton from "./NotifyTestButton";
|
import NotifyTestButton from "./NotifyTestButton";
|
||||||
@ -45,9 +45,9 @@ const NotifyChannel = ({ className, style, channel }: NotifyChannelProps) => {
|
|||||||
{MessageContextHolder}
|
{MessageContextHolder}
|
||||||
{NotificationContextHolder}
|
{NotificationContextHolder}
|
||||||
|
|
||||||
<NotifyChannelEditForm ref={channelFormRef} channel={channel} model={channelConfig} onModelChange={() => setChannelFormChanged(true)} />
|
<NotifyChannelEditForm ref={channelFormRef} className="mt-2" channel={channel} model={channelConfig} onModelChange={() => setChannelFormChanged(true)} />
|
||||||
|
|
||||||
<Space>
|
<Space className="mb-2">
|
||||||
<Button type="primary" disabled={!channelFormChanged} onClick={handleClickSubmit}>
|
<Button type="primary" disabled={!channelFormChanged} onClick={handleClickSubmit}>
|
||||||
{t("common.button.save")}
|
{t("common.button.save")}
|
||||||
</Button>
|
</Button>
|
||||||
|
@ -41,6 +41,8 @@ export const ACCESS_PROVIDER_TYPES = Object.freeze({
|
|||||||
WEBHOOK: ACCESS_PROVIDER_TYPE_WEBHOOK,
|
WEBHOOK: ACCESS_PROVIDER_TYPE_WEBHOOK,
|
||||||
} as const);
|
} as const);
|
||||||
|
|
||||||
|
export type AccessProviderTypes = (typeof ACCESS_PROVIDER_TYPES)[keyof typeof ACCESS_PROVIDER_TYPES];
|
||||||
|
|
||||||
export const ACCESS_PROVIDER_USAGE_ALL = "all" as const;
|
export const ACCESS_PROVIDER_USAGE_ALL = "all" as const;
|
||||||
export const ACCESS_PROVIDER_USAGE_APPLY = "apply" as const;
|
export const ACCESS_PROVIDER_USAGE_APPLY = "apply" as const;
|
||||||
export const ACCESS_PROVIDER_USAGE_DEPLOY = "deploy" as const;
|
export const ACCESS_PROVIDER_USAGE_DEPLOY = "deploy" as const;
|
||||||
@ -50,11 +52,13 @@ export const ACCESS_PROVIDER_USAGES = Object.freeze({
|
|||||||
DEPLOY: ACCESS_PROVIDER_USAGE_DEPLOY,
|
DEPLOY: ACCESS_PROVIDER_USAGE_DEPLOY,
|
||||||
} as const);
|
} as const);
|
||||||
|
|
||||||
|
export type AccessProviderUsages = (typeof ACCESS_PROVIDER_USAGES)[keyof typeof ACCESS_PROVIDER_USAGES];
|
||||||
|
|
||||||
// #region AccessModel
|
// #region AccessModel
|
||||||
export interface AccessModel extends BaseModel {
|
export interface AccessModel extends BaseModel {
|
||||||
name: string;
|
name: string;
|
||||||
configType: string;
|
configType: string;
|
||||||
usage: AccessUsages;
|
usage: AccessProviderUsages;
|
||||||
config: /*
|
config: /*
|
||||||
注意:如果追加新的类型,请保持以 ASCII 排序。
|
注意:如果追加新的类型,请保持以 ASCII 排序。
|
||||||
NOTICE: If you add new type, please keep ASCII order.
|
NOTICE: If you add new type, please keep ASCII order.
|
||||||
@ -136,7 +140,7 @@ export type KubernetesAccessConfig = {
|
|||||||
kubeConfig?: string;
|
kubeConfig?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type LocalAccessConfig = never;
|
export type LocalAccessConfig = NonNullable<unknown>;
|
||||||
|
|
||||||
export type NameSiloAccessConfig = {
|
export type NameSiloAccessConfig = {
|
||||||
apiKey: string;
|
apiKey: string;
|
||||||
@ -177,13 +181,11 @@ export type WebhookAccessConfig = {
|
|||||||
// #endregion
|
// #endregion
|
||||||
|
|
||||||
// #region AccessProvider
|
// #region AccessProvider
|
||||||
export type AccessUsages = (typeof ACCESS_PROVIDER_USAGES)[keyof typeof ACCESS_PROVIDER_USAGES];
|
|
||||||
|
|
||||||
export type AccessProvider = {
|
export type AccessProvider = {
|
||||||
type: string;
|
type: string;
|
||||||
name: string;
|
name: string;
|
||||||
icon: string;
|
icon: string;
|
||||||
usage: AccessUsages;
|
usage: AccessProviderUsages;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const accessProvidersMap: Map<AccessProvider["type"], AccessProvider> = new Map(
|
export const accessProvidersMap: Map<AccessProvider["type"], AccessProvider> = new Map(
|
||||||
@ -210,6 +212,6 @@ export const accessProvidersMap: Map<AccessProvider["type"], AccessProvider> = n
|
|||||||
[ACCESS_PROVIDER_TYPE_WEBHOOK, "common.provider.webhook", "/imgs/providers/webhook.svg", "deploy"],
|
[ACCESS_PROVIDER_TYPE_WEBHOOK, "common.provider.webhook", "/imgs/providers/webhook.svg", "deploy"],
|
||||||
[ACCESS_PROVIDER_TYPE_KUBERNETES, "common.provider.kubernetes", "/imgs/providers/kubernetes.svg", "deploy"],
|
[ACCESS_PROVIDER_TYPE_KUBERNETES, "common.provider.kubernetes", "/imgs/providers/kubernetes.svg", "deploy"],
|
||||||
[ACCESS_PROVIDER_TYPE_ACMEHTTPREQ, "common.provider.acmehttpreq", "/imgs/providers/acmehttpreq.svg", "apply"],
|
[ACCESS_PROVIDER_TYPE_ACMEHTTPREQ, "common.provider.acmehttpreq", "/imgs/providers/acmehttpreq.svg", "apply"],
|
||||||
].map(([type, name, icon, usage]) => [type, { type, name, icon, usage: usage as AccessUsages }])
|
].map(([type, name, icon, usage]) => [type, { type, name, icon, usage: usage as AccessProviderUsages }])
|
||||||
);
|
);
|
||||||
// #endregion
|
// #endregion
|
||||||
|
@ -9,7 +9,9 @@ export const SETTINGS_NAMES = Object.freeze({
|
|||||||
SSL_PROVIDER: SETTINGS_NAME_SSLPROVIDER,
|
SSL_PROVIDER: SETTINGS_NAME_SSLPROVIDER,
|
||||||
} as const);
|
} as const);
|
||||||
|
|
||||||
export interface SettingsModel<T> extends BaseModel {
|
export type SettingsNames = (typeof SETTINGS_NAMES)[keyof typeof SETTINGS_NAMES];
|
||||||
|
|
||||||
|
export interface SettingsModel<T extends NonNullable<unknown> = NonNullable<unknown>> extends BaseModel {
|
||||||
name: string;
|
name: string;
|
||||||
content: T;
|
content: T;
|
||||||
}
|
}
|
||||||
@ -115,14 +117,36 @@ export const notifyChannelsMap: Map<NotifyChannel["type"], NotifyChannel> = new
|
|||||||
// #endregion
|
// #endregion
|
||||||
|
|
||||||
// #region Settings: SSLProvider
|
// #region Settings: SSLProvider
|
||||||
export type SSLProvider = "letsencrypt" | "zerossl" | "gts";
|
export const SSLPROVIDER_LETSENCRYPT = "letsencrypt" as const;
|
||||||
|
export const SSLPROVIDER_ZEROSSL = "zerossl" as const;
|
||||||
|
export const SSLPROVIDER_GOOGLETRUSTSERVICES = "gts" as const;
|
||||||
|
export const SSLPROVIDERS = Object.freeze({
|
||||||
|
LETS_ENCRYPT: SSLPROVIDER_LETSENCRYPT,
|
||||||
|
ZERO_SSL: SSLPROVIDER_ZEROSSL,
|
||||||
|
GOOGLE_TRUST_SERVICES: SSLPROVIDER_GOOGLETRUSTSERVICES,
|
||||||
|
} as const);
|
||||||
|
|
||||||
export type SSLProviderSetting = {
|
export type SSLProviders = (typeof SSLPROVIDERS)[keyof typeof SSLPROVIDERS];
|
||||||
provider: SSLProvider;
|
|
||||||
|
export type SSLProviderSettingsContent = {
|
||||||
|
provider: (typeof SSLPROVIDERS)[keyof typeof SSLPROVIDERS];
|
||||||
config: {
|
config: {
|
||||||
[key: string]: {
|
[key: string]: Record<string, unknown> | undefined;
|
||||||
[key: string]: string;
|
letsencrypt?: SSLProviderLetsEncryptConfig;
|
||||||
};
|
zerossl?: SSLProviderZeroSSLConfig;
|
||||||
|
gts?: SSLProviderGoogleTrustServicesConfig;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type SSLProviderLetsEncryptConfig = NonNullable<unknown>;
|
||||||
|
|
||||||
|
export type SSLProviderZeroSSLConfig = {
|
||||||
|
eabKid: string;
|
||||||
|
eabHmacKey: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type SSLProviderGoogleTrustServicesConfig = {
|
||||||
|
eabKid: string;
|
||||||
|
eabHmacKey: string;
|
||||||
|
};
|
||||||
// #endregion
|
// #endregion
|
||||||
|
@ -36,10 +36,10 @@
|
|||||||
"settings.notification.channel.form.bark_device_key.tooltip": "For more information, see <a href=\"https://bark.day.app/\" target=\"_blank\">https://bark.day.app/</a>",
|
"settings.notification.channel.form.bark_device_key.tooltip": "For more information, see <a href=\"https://bark.day.app/\" target=\"_blank\">https://bark.day.app/</a>",
|
||||||
"settings.notification.channel.form.dingtalk_access_token.label": "Robot AccessToken",
|
"settings.notification.channel.form.dingtalk_access_token.label": "Robot AccessToken",
|
||||||
"settings.notification.channel.form.dingtalk_access_token.placeholder": "Please enter Robot Access Token",
|
"settings.notification.channel.form.dingtalk_access_token.placeholder": "Please enter Robot Access Token",
|
||||||
"settings.notification.channel.form.dingtalk_access_token.tooltip": "For more information, see <a href=\"https://open.dingtalk.com/document/orgapp/custom-bot-to-send-group-chat-messages\" target=\"_blank\">https://open.dingtalk.com/document/orgapp/custom-bot-to-send-group-chat-messages</a>",
|
"settings.notification.channel.form.dingtalk_access_token.tooltip": "For more information, see <a href=\"https://open.dingtalk.com/document/orgapp/obtain-the-webhook-address-of-a-custom-robot\" target=\"_blank\">https://open.dingtalk.com/document/orgapp/obtain-the-webhook-address-of-a-custom-robot</a>",
|
||||||
"settings.notification.channel.form.dingtalk_secret.label": "Robot Secret",
|
"settings.notification.channel.form.dingtalk_secret.label": "Robot Secret",
|
||||||
"settings.notification.channel.form.dingtalk_secret.placeholder": "Please enter Robot Secret",
|
"settings.notification.channel.form.dingtalk_secret.placeholder": "Please enter Robot Secret",
|
||||||
"settings.notification.channel.form.dingtalk_secret.tooltip": "For more information, see <a href=\"https://open.dingtalk.com/document/orgapp/custom-bot-to-send-group-chat-messages\" target=\"_blank\">https://open.dingtalk.com/document/orgapp/custom-bot-to-send-group-chat-messages</a>",
|
"settings.notification.channel.form.dingtalk_secret.tooltip": "For more information, see <a href=\"https://open.dingtalk.com/document/orgapp/customize-robot-security-settings\" target=\"_blank\">https://open.dingtalk.com/document/orgapp/customize-robot-security-settings</a>",
|
||||||
"settings.notification.channel.form.email_smtp_host.label": "SMTP Host",
|
"settings.notification.channel.form.email_smtp_host.label": "SMTP Host",
|
||||||
"settings.notification.channel.form.email_smtp_host.placeholder": "Please enter SMTP host",
|
"settings.notification.channel.form.email_smtp_host.placeholder": "Please enter SMTP host",
|
||||||
"settings.notification.channel.form.email_smtp_port.label": "SMTP Port",
|
"settings.notification.channel.form.email_smtp_port.label": "SMTP Port",
|
||||||
@ -68,9 +68,19 @@
|
|||||||
"settings.notification.channel.form.webhook_url.label": "Webhook URL",
|
"settings.notification.channel.form.webhook_url.label": "Webhook URL",
|
||||||
"settings.notification.channel.form.webhook_url.placeholder": "Please enter Webhook URL",
|
"settings.notification.channel.form.webhook_url.placeholder": "Please enter Webhook URL",
|
||||||
|
|
||||||
"settings.ca.tab": "Certificate Authority",
|
"settings.sslprovider.tab": "Certificate Authority",
|
||||||
"settings.ca.provider.errmsg.empty": "Please select a Certificate Authority",
|
"settings.sslprovider.form.provider.label": "ACME Provider",
|
||||||
"settings.ca.eab_kid.errmsg.empty": "Please enter EAB_KID",
|
"settings.sslprovider.provider.errmsg.empty": "Please select a Certificate Authority",
|
||||||
"settings.ca.eab_hmac_key.errmsg.empty": "Please enter EAB_HMAC_KEY.",
|
"settings.sslprovider.form.zerossl_eab_kid.label": "EAB KID",
|
||||||
"settings.ca.eab_kid_hmac_key.errmsg.empty": "Please enter EAB_KID and EAB_HMAC_KEY"
|
"settings.sslprovider.form.zerossl_eab_kid.placeholder": "Please enter EAB KID",
|
||||||
|
"settings.sslprovider.form.zerossl_eab_kid.tooltip": "For more information, see <a href=\"https://zerossl.com/documentation/acme/\" target=\"_blank\">https://zerossl.com/documentation/acme/</a>",
|
||||||
|
"settings.sslprovider.form.zerossl_eab_hmac_key.label": "EAB HMAC Key",
|
||||||
|
"settings.sslprovider.form.zerossl_eab_hmac_key.placeholder": "Please enter EAB HMAC Key",
|
||||||
|
"settings.sslprovider.form.zerossl_eab_hmac_key.tooltip": "For more information, see <a href=\"https://zerossl.com/documentation/acme/\" target=\"_blank\">https://zerossl.com/documentation/acme/</a>",
|
||||||
|
"settings.sslprovider.form.gts_eab_kid.label": "EAB KID",
|
||||||
|
"settings.sslprovider.form.gts_eab_kid.placeholder": "Please enter EAB KID",
|
||||||
|
"settings.sslprovider.form.gts_eab_kid.tooltip": "For more information, see <a href=\"https://cloud.google.com/certificate-manager/docs/public-ca-tutorial\" target=\"_blank\">https://cloud.google.com/certificate-manager/docs/public-ca-tutorial</a>",
|
||||||
|
"settings.sslprovider.form.gts_eab_hmac_key.label": "EAB HMAC Key",
|
||||||
|
"settings.sslprovider.form.gts_eab_hmac_key.placeholder": "Please enter EAB HMAC Key",
|
||||||
|
"settings.sslprovider.form.gts_eab_hmac_key.tooltip": "For more information, see <a href=\"https://cloud.google.com/certificate-manager/docs/public-ca-tutorial\" target=\"_blank\">https://cloud.google.com/certificate-manager/docs/public-ca-tutorial</a>"
|
||||||
}
|
}
|
||||||
|
@ -88,7 +88,7 @@
|
|||||||
"common.notifier.dingtalk": "钉钉",
|
"common.notifier.dingtalk": "钉钉",
|
||||||
"common.notifier.email": "电子邮件",
|
"common.notifier.email": "电子邮件",
|
||||||
"common.notifier.lark": "飞书",
|
"common.notifier.lark": "飞书",
|
||||||
"common.notifier.serverchan": "Server酱",
|
"common.notifier.serverchan": "Server 酱",
|
||||||
"common.notifier.telegram": "Telegram",
|
"common.notifier.telegram": "Telegram",
|
||||||
"common.notifier.webhook": "Webhook"
|
"common.notifier.webhook": "Webhook"
|
||||||
}
|
}
|
||||||
|
@ -36,10 +36,10 @@
|
|||||||
"settings.notification.channel.form.bark_device_key.tooltip": "这是什么?请参阅 <a href=\"https://bark.day.app/\" target=\"_blank\">https://bark.day.app/</a>",
|
"settings.notification.channel.form.bark_device_key.tooltip": "这是什么?请参阅 <a href=\"https://bark.day.app/\" target=\"_blank\">https://bark.day.app/</a>",
|
||||||
"settings.notification.channel.form.dingtalk_access_token.label": "机器人 AccessToken",
|
"settings.notification.channel.form.dingtalk_access_token.label": "机器人 AccessToken",
|
||||||
"settings.notification.channel.form.dingtalk_access_token.placeholder": "请输入机器人 AccessToken",
|
"settings.notification.channel.form.dingtalk_access_token.placeholder": "请输入机器人 AccessToken",
|
||||||
"settings.notification.channel.form.dingtalk_access_token.tooltip": "这是什么?请参阅 <a href=\"https://open.dingtalk.com/document/orgapp/custom-bot-to-send-group-chat-messages\" target=\"_blank\">https://open.dingtalk.com/document/orgapp/custom-bot-to-send-group-chat-messages</a>",
|
"settings.notification.channel.form.dingtalk_access_token.tooltip": "这是什么?请参阅 <a href=\"https://open.dingtalk.com/document/orgapp/obtain-the-webhook-address-of-a-custom-robot\" target=\"_blank\">https://open.dingtalk.com/document/orgapp/obtain-the-webhook-address-of-a-custom-robot</a>",
|
||||||
"settings.notification.channel.form.dingtalk_secret.label": "机器人加签密钥",
|
"settings.notification.channel.form.dingtalk_secret.label": "机器人加签密钥",
|
||||||
"settings.notification.channel.form.dingtalk_secret.placeholder": "请输入机器人加签密钥",
|
"settings.notification.channel.form.dingtalk_secret.placeholder": "请输入机器人加签密钥",
|
||||||
"settings.notification.channel.form.dingtalk_secret.tooltip": "这是什么?请参阅 <a href=\"https://open.dingtalk.com/document/orgapp/custom-bot-to-send-group-chat-messages\" target=\"_blank\">https://open.dingtalk.com/document/orgapp/custom-bot-to-send-group-chat-messages</a>",
|
"settings.notification.channel.form.dingtalk_secret.tooltip": "这是什么?请参阅 <a href=\"https://open.dingtalk.com/document/orgapp/customize-robot-security-settings\" target=\"_blank\">https://open.dingtalk.com/document/orgapp/customize-robot-security-settings</a>",
|
||||||
"settings.notification.channel.form.email_smtp_host.label": "SMTP 服务器地址",
|
"settings.notification.channel.form.email_smtp_host.label": "SMTP 服务器地址",
|
||||||
"settings.notification.channel.form.email_smtp_host.placeholder": "请输入 SMTP 服务器地址",
|
"settings.notification.channel.form.email_smtp_host.placeholder": "请输入 SMTP 服务器地址",
|
||||||
"settings.notification.channel.form.email_smtp_port.label": "SMTP 服务器端口",
|
"settings.notification.channel.form.email_smtp_port.label": "SMTP 服务器端口",
|
||||||
@ -68,9 +68,19 @@
|
|||||||
"settings.notification.channel.form.webhook_url.label": "Webhook 回调地址",
|
"settings.notification.channel.form.webhook_url.label": "Webhook 回调地址",
|
||||||
"settings.notification.channel.form.webhook_url.placeholder": "请输入 Webhook 回调地址",
|
"settings.notification.channel.form.webhook_url.placeholder": "请输入 Webhook 回调地址",
|
||||||
|
|
||||||
"settings.ca.tab": "证书颁发机构(CA)",
|
"settings.sslprovider.tab": "证书颁发机构(CA)",
|
||||||
"settings.ca.provider.errmsg.empty": "请选择证书分发机构",
|
"settings.sslprovider.form.provider.label": "ACME 提供商",
|
||||||
"settings.ca.eab_kid.errmsg.empty": "请输入EAB_KID",
|
"settings.sslprovider.provider.errmsg.empty": "请选择证书分发机构",
|
||||||
"settings.ca.eab_hmac_key.errmsg.empty": "请输入EAB_HMAC_KEY",
|
"settings.sslprovider.form.zerossl_eab_kid.label": "EAB KID",
|
||||||
"settings.ca.eab_kid_hmac_key.errmsg.empty": "请输入EAB_KID和EAB_HMAC_KEY"
|
"settings.sslprovider.form.zerossl_eab_kid.placeholder": "请输入 EAB KID",
|
||||||
|
"settings.sslprovider.form.zerossl_eab_kid.tooltip": "这是什么?请参阅 <a href=\"https://zerossl.com/documentation/acme/\" target=\"_blank\">https://zerossl.com/documentation/acme/</a>",
|
||||||
|
"settings.sslprovider.form.zerossl_eab_hmac_key.label": "EAB HMAC Key",
|
||||||
|
"settings.sslprovider.form.zerossl_eab_hmac_key.placeholder": "请输入 EAB HMAC Key",
|
||||||
|
"settings.sslprovider.form.zerossl_eab_hmac_key.tooltip": "这是什么?请参阅 <a href=\"https://zerossl.com/documentation/acme/\" target=\"_blank\">https://zerossl.com/documentation/acme/</a>",
|
||||||
|
"settings.sslprovider.form.gts_eab_kid.label": "EAB KID",
|
||||||
|
"settings.sslprovider.form.gts_eab_kid.placeholder": "请输入 EAB KID",
|
||||||
|
"settings.sslprovider.form.gts_eab_kid.tooltip": "这是什么?请参阅 <a href=\"https://cloud.google.com/certificate-manager/docs/public-ca-tutorial\" target=\"_blank\">https://cloud.google.com/certificate-manager/docs/public-ca-tutorial</a>",
|
||||||
|
"settings.sslprovider.form.gts_eab_hmac_key.label": "EAB HMAC Key",
|
||||||
|
"settings.sslprovider.form.gts_eab_hmac_key.placeholder": "请输入 EAB HMAC Key",
|
||||||
|
"settings.sslprovider.form.gts_eab_hmac_key.tooltip": "这是什么?请参阅 <a href=\"https://cloud.google.com/certificate-manager/docs/public-ca-tutorial\" target=\"_blank\">https://cloud.google.com/certificate-manager/docs/public-ca-tutorial</a>"
|
||||||
}
|
}
|
||||||
|
@ -1,445 +0,0 @@
|
|||||||
import { useContext, useEffect, useState, createContext } from "react";
|
|
||||||
import { useForm } from "react-hook-form";
|
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
import { z } from "zod";
|
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
|
||||||
import { produce } from "immer";
|
|
||||||
|
|
||||||
import { cn } from "@/components/ui/utils";
|
|
||||||
import { Button } from "@/components/ui/button";
|
|
||||||
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form";
|
|
||||||
import { Input } from "@/components/ui/input";
|
|
||||||
import { Label } from "@/components/ui/label";
|
|
||||||
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
|
|
||||||
import { useToast } from "@/components/ui/use-toast";
|
|
||||||
import { SETTINGS_NAMES, SSLProvider as SSLProviderType, SSLProviderSetting, SettingsModel } from "@/domain/settings";
|
|
||||||
import { get, save } from "@/repository/settings";
|
|
||||||
import { getErrMsg } from "@/utils/error";
|
|
||||||
|
|
||||||
type SSLProviderContext = {
|
|
||||||
setting: SettingsModel<SSLProviderSetting>;
|
|
||||||
onSubmit: (data: SettingsModel<SSLProviderSetting>) => void;
|
|
||||||
setConfig: (config: SettingsModel<SSLProviderSetting>) => void;
|
|
||||||
};
|
|
||||||
|
|
||||||
const Context = createContext({} as SSLProviderContext);
|
|
||||||
|
|
||||||
export const useSSLProviderContext = () => {
|
|
||||||
return useContext(Context);
|
|
||||||
};
|
|
||||||
|
|
||||||
const getConfigStr = (content: SSLProviderSetting, kind: string, key: string) => {
|
|
||||||
if (!content.config) {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
if (!content.config[kind]) {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
return content.config[kind][key] ?? "";
|
|
||||||
};
|
|
||||||
|
|
||||||
const SSLProvider = () => {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
|
|
||||||
const [config, setConfig] = useState<SettingsModel<SSLProviderSetting>>({
|
|
||||||
content: {
|
|
||||||
provider: "letsencrypt",
|
|
||||||
config: {},
|
|
||||||
},
|
|
||||||
} as SettingsModel<SSLProviderSetting>);
|
|
||||||
|
|
||||||
const { toast } = useToast();
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const fetchData = async () => {
|
|
||||||
const setting = await get<SSLProviderSetting>(SETTINGS_NAMES.SSL_PROVIDER);
|
|
||||||
|
|
||||||
if (setting) {
|
|
||||||
setConfig(setting);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
fetchData();
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const setProvider = (val: SSLProviderType) => {
|
|
||||||
const newData = produce(config, (draft) => {
|
|
||||||
if (draft.content) {
|
|
||||||
draft.content.provider = val;
|
|
||||||
} else {
|
|
||||||
draft.content = {
|
|
||||||
provider: val,
|
|
||||||
config: {},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
});
|
|
||||||
setConfig(newData);
|
|
||||||
};
|
|
||||||
|
|
||||||
const getOptionCls = (val: string) => {
|
|
||||||
if (config.content?.provider === val) {
|
|
||||||
return "border-primary dark:border-primary";
|
|
||||||
}
|
|
||||||
|
|
||||||
return "";
|
|
||||||
};
|
|
||||||
|
|
||||||
const onSubmit = async (data: SettingsModel<SSLProviderSetting>) => {
|
|
||||||
try {
|
|
||||||
console.log(data);
|
|
||||||
const resp = await save({ ...data });
|
|
||||||
setConfig(resp);
|
|
||||||
toast({
|
|
||||||
title: t("common.text.operation_succeeded"),
|
|
||||||
description: t("common.text.operation_succeeded"),
|
|
||||||
});
|
|
||||||
} catch (e) {
|
|
||||||
const message = getErrMsg(e);
|
|
||||||
toast({
|
|
||||||
title: t("common.text.operation_failed"),
|
|
||||||
description: message,
|
|
||||||
variant: "destructive",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Context.Provider value={{ onSubmit, setConfig, setting: config }}>
|
|
||||||
<div className="md:max-w-[40rem]">
|
|
||||||
<Label className="dark:text-stone-200">{t("common.text.ca")}</Label>
|
|
||||||
<RadioGroup
|
|
||||||
className="flex mt-3 dark:text-stone-200"
|
|
||||||
onValueChange={(val) => {
|
|
||||||
setProvider(val as SSLProviderType);
|
|
||||||
}}
|
|
||||||
value={config.content?.provider}
|
|
||||||
>
|
|
||||||
<div className="flex items-center space-x-2 ">
|
|
||||||
<RadioGroupItem value="letsencrypt" id="letsencrypt" />
|
|
||||||
<Label htmlFor="letsencrypt">
|
|
||||||
<div className={cn("flex items-center space-x-2 border p-2 rounded cursor-pointer dark:border-stone-700", getOptionCls("letsencrypt"))}>
|
|
||||||
<img src={"/imgs/providers/letsencrypt.svg"} className="h-6" />
|
|
||||||
<div>{"Let's Encrypt"}</div>
|
|
||||||
</div>
|
|
||||||
</Label>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center space-x-2 ">
|
|
||||||
<RadioGroupItem value="zerossl" id="zerossl" />
|
|
||||||
<Label htmlFor="zerossl">
|
|
||||||
<div className={cn("flex items-center space-x-2 border p-2 rounded cursor-pointer dark:border-stone-700", getOptionCls("zerossl"))}>
|
|
||||||
<img src={"/imgs/providers/zerossl.svg"} className="h-6" />
|
|
||||||
<div>{"ZeroSSL"}</div>
|
|
||||||
</div>
|
|
||||||
</Label>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex items-center space-x-2">
|
|
||||||
<RadioGroupItem value="gts" id="gts" />
|
|
||||||
<Label htmlFor="gts">
|
|
||||||
<div className={cn("flex items-center space-x-2 border p-2 rounded cursor-pointer dark:border-stone-700", getOptionCls("gts"))}>
|
|
||||||
<img src={"/imgs/providers/google.svg"} className="h-6" />
|
|
||||||
<div>{"Google Trust Services"}</div>
|
|
||||||
</div>
|
|
||||||
</Label>
|
|
||||||
</div>
|
|
||||||
</RadioGroup>
|
|
||||||
|
|
||||||
<SSLProviderForm kind={config.content?.provider ?? ""} />
|
|
||||||
</div>
|
|
||||||
</Context.Provider>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const SSLProviderForm = ({ kind }: { kind: string }) => {
|
|
||||||
const getForm = () => {
|
|
||||||
switch (kind) {
|
|
||||||
case "zerossl":
|
|
||||||
return <SSLProviderZeroSSLForm />;
|
|
||||||
case "gts":
|
|
||||||
return <SSLProviderGoogleTrustServicesForm />;
|
|
||||||
default:
|
|
||||||
return <SSLProviderLetsEncryptForm />;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<div className="mt-5">{getForm()}</div>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const SSLProviderLetsEncryptForm = () => {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
|
|
||||||
const { setting, onSubmit } = useSSLProviderContext();
|
|
||||||
|
|
||||||
const formSchema = z.object({
|
|
||||||
kind: z.literal("letsencrypt"),
|
|
||||||
});
|
|
||||||
|
|
||||||
const form = useForm<z.infer<typeof formSchema>>({
|
|
||||||
resolver: zodResolver(formSchema),
|
|
||||||
defaultValues: {
|
|
||||||
kind: "letsencrypt",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const onLocalSubmit = async (data: z.infer<typeof formSchema>) => {
|
|
||||||
const newData = produce(setting, (draft) => {
|
|
||||||
if (!draft.content) {
|
|
||||||
draft.content = {
|
|
||||||
provider: data.kind,
|
|
||||||
config: {
|
|
||||||
letsencrypt: {},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
});
|
|
||||||
onSubmit(newData);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Form {...form}>
|
|
||||||
<form onSubmit={form.handleSubmit(onLocalSubmit)} className="space-y-8 dark:text-stone-200">
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name="kind"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem hidden>
|
|
||||||
<FormLabel>kind</FormLabel>
|
|
||||||
<FormControl>
|
|
||||||
<Input {...field} type="text" />
|
|
||||||
</FormControl>
|
|
||||||
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<FormMessage />
|
|
||||||
|
|
||||||
<div className="flex justify-end">
|
|
||||||
<Button type="submit">{t("common.button.save")}</Button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</Form>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const SSLProviderZeroSSLForm = () => {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
|
|
||||||
const { setting, onSubmit } = useSSLProviderContext();
|
|
||||||
|
|
||||||
const formSchema = z.object({
|
|
||||||
kind: z.literal("zerossl"),
|
|
||||||
eabKid: z.string().min(1, { message: t("settings.ca.eab_kid_hmac_key.errmsg.empty") }),
|
|
||||||
eabHmacKey: z.string().min(1, { message: t("settings.ca.eab_kid_hmac_key.errmsg.empty") }),
|
|
||||||
});
|
|
||||||
|
|
||||||
const form = useForm<z.infer<typeof formSchema>>({
|
|
||||||
resolver: zodResolver(formSchema),
|
|
||||||
defaultValues: {
|
|
||||||
kind: "zerossl",
|
|
||||||
eabKid: "",
|
|
||||||
eabHmacKey: "",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (setting.content) {
|
|
||||||
const content = setting.content;
|
|
||||||
|
|
||||||
form.reset({
|
|
||||||
eabKid: getConfigStr(content, "zerossl", "eabKid"),
|
|
||||||
eabHmacKey: getConfigStr(content, "zerossl", "eabHmacKey"),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}, [setting]);
|
|
||||||
|
|
||||||
const onLocalSubmit = async (data: z.infer<typeof formSchema>) => {
|
|
||||||
const newData = produce(setting, (draft) => {
|
|
||||||
if (!draft.content) {
|
|
||||||
draft.content = {
|
|
||||||
provider: "zerossl",
|
|
||||||
config: {
|
|
||||||
zerossl: {},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
draft.content.config.zerossl = {
|
|
||||||
eabKid: data.eabKid,
|
|
||||||
eabHmacKey: data.eabHmacKey,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
onSubmit(newData);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Form {...form}>
|
|
||||||
<form onSubmit={form.handleSubmit(onLocalSubmit)} className="space-y-8 dark:text-stone-200">
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name="kind"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem hidden>
|
|
||||||
<FormLabel>kind</FormLabel>
|
|
||||||
<FormControl>
|
|
||||||
<Input {...field} type="text" />
|
|
||||||
</FormControl>
|
|
||||||
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name="eabKid"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem>
|
|
||||||
<FormLabel>EAB_KID</FormLabel>
|
|
||||||
<FormControl>
|
|
||||||
<Input placeholder={t("settings.ca.eab_kid.errmsg.empty")} {...field} type="text" />
|
|
||||||
</FormControl>
|
|
||||||
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name="eabHmacKey"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem>
|
|
||||||
<FormLabel>EAB_HMAC_KEY</FormLabel>
|
|
||||||
<FormControl>
|
|
||||||
<Input placeholder={t("settings.ca.eab_hmac_key.errmsg.empty")} {...field} type="text" />
|
|
||||||
</FormControl>
|
|
||||||
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<FormMessage />
|
|
||||||
|
|
||||||
<div className="flex justify-end">
|
|
||||||
<Button type="submit">{t("common.button.save")}</Button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</Form>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const SSLProviderGoogleTrustServicesForm = () => {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
|
|
||||||
const { setting, onSubmit } = useSSLProviderContext();
|
|
||||||
|
|
||||||
const formSchema = z.object({
|
|
||||||
kind: z.literal("gts"),
|
|
||||||
eabKid: z.string().min(1, { message: t("settings.ca.eab_kid_hmac_key.errmsg.empty") }),
|
|
||||||
eabHmacKey: z.string().min(1, { message: t("settings.ca.eab_kid_hmac_key.errmsg.empty") }),
|
|
||||||
});
|
|
||||||
|
|
||||||
const form = useForm<z.infer<typeof formSchema>>({
|
|
||||||
resolver: zodResolver(formSchema),
|
|
||||||
defaultValues: {
|
|
||||||
kind: "gts",
|
|
||||||
eabKid: "",
|
|
||||||
eabHmacKey: "",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (setting.content) {
|
|
||||||
const content = setting.content;
|
|
||||||
|
|
||||||
form.reset({
|
|
||||||
eabKid: getConfigStr(content, "gts", "eabKid"),
|
|
||||||
eabHmacKey: getConfigStr(content, "gts", "eabHmacKey"),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}, [setting]);
|
|
||||||
|
|
||||||
const onLocalSubmit = async (data: z.infer<typeof formSchema>) => {
|
|
||||||
const newData = produce(setting, (draft) => {
|
|
||||||
if (!draft.content) {
|
|
||||||
draft.content = {
|
|
||||||
provider: "gts",
|
|
||||||
config: {
|
|
||||||
zerossl: {},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
draft.content.config.gts = {
|
|
||||||
eabKid: data.eabKid,
|
|
||||||
eabHmacKey: data.eabHmacKey,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
onSubmit(newData);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Form {...form}>
|
|
||||||
<form onSubmit={form.handleSubmit(onLocalSubmit)} className="space-y-8 dark:text-stone-200">
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name="kind"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem hidden>
|
|
||||||
<FormLabel>kind</FormLabel>
|
|
||||||
<FormControl>
|
|
||||||
<Input {...field} type="text" />
|
|
||||||
</FormControl>
|
|
||||||
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name="eabKid"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem>
|
|
||||||
<FormLabel>EAB_KID</FormLabel>
|
|
||||||
<FormControl>
|
|
||||||
<Input placeholder={t("settings.ca.eab_kid.errmsg.empty")} {...field} type="text" />
|
|
||||||
</FormControl>
|
|
||||||
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name="eabHmacKey"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem>
|
|
||||||
<FormLabel>EAB_HMAC_KEY</FormLabel>
|
|
||||||
<FormControl>
|
|
||||||
<Input placeholder={t("settings.ca.eab_hmac_key.errmsg.empty")} {...field} type="text" />
|
|
||||||
</FormControl>
|
|
||||||
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<FormMessage />
|
|
||||||
|
|
||||||
<div className="flex justify-end">
|
|
||||||
<Button type="submit">{t("common.button.save")}</Button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</Form>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default SSLProvider;
|
|
@ -60,7 +60,7 @@ const Settings = () => {
|
|||||||
label: (
|
label: (
|
||||||
<Space>
|
<Space>
|
||||||
<ShieldCheckIcon size={14} />
|
<ShieldCheckIcon size={14} />
|
||||||
<label>{t("settings.ca.tab")}</label>
|
<label>{t("settings.sslprovider.tab")}</label>
|
||||||
</Space>
|
</Space>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
301
ui/src/pages/settings/SettingsSSLProvider.tsx
Normal file
301
ui/src/pages/settings/SettingsSSLProvider.tsx
Normal file
@ -0,0 +1,301 @@
|
|||||||
|
import { createContext, useContext, useEffect, useMemo, useState } from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { Button, Form, Input, message, notification, Skeleton } from "antd";
|
||||||
|
import { CheckCard } from "@ant-design/pro-components";
|
||||||
|
import { createSchemaFieldRule } from "antd-zod";
|
||||||
|
import { produce } from "immer";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { SETTINGS_NAMES, SSLPROVIDERS, type SettingsModel, type SSLProviderSettingsContent, type SSLProviders } from "@/domain/settings";
|
||||||
|
import { get as getSettings, save as saveSettings } from "@/repository/settings";
|
||||||
|
import { getErrMsg } from "@/utils/error";
|
||||||
|
import { useDeepCompareEffect } from "ahooks";
|
||||||
|
|
||||||
|
const SSLProviderContext = createContext(
|
||||||
|
{} as {
|
||||||
|
pending: boolean;
|
||||||
|
settings: SettingsModel<SSLProviderSettingsContent>;
|
||||||
|
updateSettings: (settings: MaybeModelRecordWithId<SettingsModel<SSLProviderSettingsContent>>) => Promise<void>;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const SSLProviderEditFormLetsEncryptConfig = () => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const { pending, settings, updateSettings } = useContext(SSLProviderContext);
|
||||||
|
|
||||||
|
const [form] = Form.useForm();
|
||||||
|
|
||||||
|
const [initialValues, setInitialValues] = useState(settings?.content?.config?.[SSLPROVIDERS.LETS_ENCRYPT]);
|
||||||
|
const [initialChanged, setInitialChanged] = useState(false);
|
||||||
|
useDeepCompareEffect(() => {
|
||||||
|
setInitialValues(settings?.content?.config?.[SSLPROVIDERS.LETS_ENCRYPT]);
|
||||||
|
setInitialChanged(settings?.content?.provider !== SSLPROVIDERS.LETS_ENCRYPT);
|
||||||
|
}, [settings]);
|
||||||
|
|
||||||
|
const handleFormChange = () => {
|
||||||
|
setInitialChanged(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleFormFinish = async (fields: NonNullable<unknown>) => {
|
||||||
|
const newSettings = produce(settings, (draft) => {
|
||||||
|
draft.content ??= {} as SSLProviderSettingsContent;
|
||||||
|
draft.content.provider = SSLPROVIDERS.LETS_ENCRYPT;
|
||||||
|
|
||||||
|
draft.content.config ??= {} as SSLProviderSettingsContent["config"];
|
||||||
|
draft.content.config[SSLPROVIDERS.LETS_ENCRYPT] = fields;
|
||||||
|
});
|
||||||
|
await updateSettings(newSettings);
|
||||||
|
|
||||||
|
setInitialChanged(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Form form={form} disabled={pending} layout="vertical" initialValues={initialValues} onFinish={handleFormFinish} onValuesChange={handleFormChange}>
|
||||||
|
<Form.Item>
|
||||||
|
<Button type="primary" htmlType="submit" disabled={!initialChanged} loading={pending}>
|
||||||
|
{t("common.button.save")}
|
||||||
|
</Button>
|
||||||
|
</Form.Item>
|
||||||
|
</Form>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const SSLProviderEditFormZeroSSLConfig = () => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const { pending, settings, updateSettings } = useContext(SSLProviderContext);
|
||||||
|
|
||||||
|
const formSchema = z.object({
|
||||||
|
eabKid: z
|
||||||
|
.string({ message: t("settings.sslprovider.form.zerossl_eab_kid.placeholder") })
|
||||||
|
.min(1, t("settings.sslprovider.form.zerossl_eab_kid.placeholder"))
|
||||||
|
.max(256, t("common.errmsg.string_max", { max: 256 })),
|
||||||
|
eabHmacKey: z
|
||||||
|
.string({ message: t("settings.sslprovider.form.zerossl_eab_hmac_key.placeholder") })
|
||||||
|
.min(1, t("settings.sslprovider.form.zerossl_eab_hmac_key.placeholder"))
|
||||||
|
.max(256, t("common.errmsg.string_max", { max: 256 })),
|
||||||
|
});
|
||||||
|
const formRule = createSchemaFieldRule(formSchema);
|
||||||
|
const [form] = Form.useForm<z.infer<typeof formSchema>>();
|
||||||
|
|
||||||
|
const [initialValues, setInitialValues] = useState(settings?.content?.config?.[SSLPROVIDERS.ZERO_SSL]);
|
||||||
|
const [initialChanged, setInitialChanged] = useState(false);
|
||||||
|
useDeepCompareEffect(() => {
|
||||||
|
setInitialValues(settings?.content?.config?.[SSLPROVIDERS.ZERO_SSL]);
|
||||||
|
setInitialChanged(settings?.content?.provider !== SSLPROVIDERS.ZERO_SSL);
|
||||||
|
}, [settings]);
|
||||||
|
|
||||||
|
const handleFormChange = () => {
|
||||||
|
setInitialChanged(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleFormFinish = async (fields: z.infer<typeof formSchema>) => {
|
||||||
|
const newSettings = produce(settings, (draft) => {
|
||||||
|
draft.content ??= {} as SSLProviderSettingsContent;
|
||||||
|
draft.content.provider = SSLPROVIDERS.ZERO_SSL;
|
||||||
|
|
||||||
|
draft.content.config ??= {} as SSLProviderSettingsContent["config"];
|
||||||
|
draft.content.config[SSLPROVIDERS.ZERO_SSL] = fields;
|
||||||
|
});
|
||||||
|
await updateSettings(newSettings);
|
||||||
|
|
||||||
|
setInitialChanged(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Form form={form} disabled={pending} layout="vertical" initialValues={initialValues} onFinish={handleFormFinish} onValuesChange={handleFormChange}>
|
||||||
|
<Form.Item
|
||||||
|
name="eabKid"
|
||||||
|
label={t("settings.sslprovider.form.zerossl_eab_kid.label")}
|
||||||
|
rules={[formRule]}
|
||||||
|
tooltip={<span dangerouslySetInnerHTML={{ __html: t("settings.sslprovider.form.zerossl_eab_kid.tooltip") }}></span>}
|
||||||
|
>
|
||||||
|
<Input autoComplete="new-password" placeholder={t("settings.sslprovider.form.zerossl_eab_kid.placeholder")} />
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item
|
||||||
|
name="eabHmacKey"
|
||||||
|
label={t("settings.sslprovider.form.zerossl_eab_hmac_key.label")}
|
||||||
|
rules={[formRule]}
|
||||||
|
tooltip={<span dangerouslySetInnerHTML={{ __html: t("settings.sslprovider.form.zerossl_eab_hmac_key.tooltip") }}></span>}
|
||||||
|
>
|
||||||
|
<Input.Password autoComplete="new-password" placeholder={t("settings.sslprovider.form.zerossl_eab_hmac_key.placeholder")} />
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item>
|
||||||
|
<Button type="primary" htmlType="submit" disabled={!initialChanged} loading={pending}>
|
||||||
|
{t("common.button.save")}
|
||||||
|
</Button>
|
||||||
|
</Form.Item>
|
||||||
|
</Form>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const SSLProviderEditFormGoogleTrustServicesConfig = () => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const { pending, settings, updateSettings } = useContext(SSLProviderContext);
|
||||||
|
|
||||||
|
const formSchema = z.object({
|
||||||
|
eabKid: z
|
||||||
|
.string({ message: t("settings.sslprovider.form.gts_eab_kid.placeholder") })
|
||||||
|
.min(1, t("settings.sslprovider.form.gts_eab_kid.placeholder"))
|
||||||
|
.max(256, t("common.errmsg.string_max", { max: 256 })),
|
||||||
|
eabHmacKey: z
|
||||||
|
.string({ message: t("settings.sslprovider.form.gts_eab_hmac_key.placeholder") })
|
||||||
|
.min(1, t("settings.sslprovider.form.gts_eab_hmac_key.placeholder"))
|
||||||
|
.max(256, t("common.errmsg.string_max", { max: 256 })),
|
||||||
|
});
|
||||||
|
const formRule = createSchemaFieldRule(formSchema);
|
||||||
|
const [form] = Form.useForm<z.infer<typeof formSchema>>();
|
||||||
|
|
||||||
|
const [initialValues, setInitialValues] = useState(settings?.content?.config?.[SSLPROVIDERS.GOOGLE_TRUST_SERVICES]);
|
||||||
|
const [initialChanged, setInitialChanged] = useState(false);
|
||||||
|
useDeepCompareEffect(() => {
|
||||||
|
setInitialValues(settings?.content?.config?.[SSLPROVIDERS.GOOGLE_TRUST_SERVICES]);
|
||||||
|
setInitialChanged(settings?.content?.provider !== SSLPROVIDERS.GOOGLE_TRUST_SERVICES);
|
||||||
|
}, [settings]);
|
||||||
|
|
||||||
|
const handleFormChange = () => {
|
||||||
|
setInitialChanged(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleFormFinish = async (fields: z.infer<typeof formSchema>) => {
|
||||||
|
const newSettings = produce(settings, (draft) => {
|
||||||
|
draft.content ??= {} as SSLProviderSettingsContent;
|
||||||
|
draft.content.provider = SSLPROVIDERS.GOOGLE_TRUST_SERVICES;
|
||||||
|
|
||||||
|
draft.content.config ??= {} as SSLProviderSettingsContent["config"];
|
||||||
|
draft.content.config[SSLPROVIDERS.GOOGLE_TRUST_SERVICES] = fields;
|
||||||
|
});
|
||||||
|
await updateSettings(newSettings);
|
||||||
|
|
||||||
|
setInitialChanged(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Form form={form} disabled={pending} layout="vertical" initialValues={initialValues} onFinish={handleFormFinish} onValuesChange={handleFormChange}>
|
||||||
|
<Form.Item
|
||||||
|
name="eabKid"
|
||||||
|
label={t("settings.sslprovider.form.gts_eab_kid.label")}
|
||||||
|
rules={[formRule]}
|
||||||
|
tooltip={<span dangerouslySetInnerHTML={{ __html: t("settings.sslprovider.form.gts_eab_kid.tooltip") }}></span>}
|
||||||
|
>
|
||||||
|
<Input autoComplete="new-password" placeholder={t("settings.sslprovider.form.gts_eab_kid.placeholder")} />
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item
|
||||||
|
name="eabHmacKey"
|
||||||
|
label={t("settings.sslprovider.form.gts_eab_hmac_key.label")}
|
||||||
|
rules={[formRule]}
|
||||||
|
tooltip={<span dangerouslySetInnerHTML={{ __html: t("settings.sslprovider.form.gts_eab_hmac_key.tooltip") }}></span>}
|
||||||
|
>
|
||||||
|
<Input.Password autoComplete="new-password" placeholder={t("settings.sslprovider.form.gts_eab_hmac_key.placeholder")} />
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item>
|
||||||
|
<Button type="primary" htmlType="submit" disabled={!initialChanged} loading={pending}>
|
||||||
|
{t("common.button.save")}
|
||||||
|
</Button>
|
||||||
|
</Form.Item>
|
||||||
|
</Form>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const SettingsSSLProvider = () => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const [messageApi, MessageContextHolder] = message.useMessage();
|
||||||
|
const [notificationApi, NotificationContextHolder] = notification.useNotification();
|
||||||
|
|
||||||
|
const [form] = Form.useForm();
|
||||||
|
const [formPending, setFormPending] = useState(false);
|
||||||
|
|
||||||
|
const [settings, setSettings] = useState<SettingsModel<SSLProviderSettingsContent>>();
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchData = async () => {
|
||||||
|
setLoading(true);
|
||||||
|
|
||||||
|
const settings = await getSettings<SSLProviderSettingsContent>(SETTINGS_NAMES.SSL_PROVIDER);
|
||||||
|
setSettings(settings);
|
||||||
|
setFormProviderType(settings.content?.provider);
|
||||||
|
|
||||||
|
setLoading(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
fetchData();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const [providerType, setFormProviderType] = useState<SSLProviders>();
|
||||||
|
const providerFormComponent = useMemo(() => {
|
||||||
|
switch (providerType) {
|
||||||
|
case SSLPROVIDERS.LETS_ENCRYPT:
|
||||||
|
return <SSLProviderEditFormLetsEncryptConfig />;
|
||||||
|
case SSLPROVIDERS.ZERO_SSL:
|
||||||
|
return <SSLProviderEditFormZeroSSLConfig />;
|
||||||
|
case SSLPROVIDERS.GOOGLE_TRUST_SERVICES:
|
||||||
|
return <SSLProviderEditFormGoogleTrustServicesConfig />;
|
||||||
|
}
|
||||||
|
}, [providerType]);
|
||||||
|
|
||||||
|
const updateContextSettings = async (settings: MaybeModelRecordWithId<SettingsModel<SSLProviderSettingsContent>>) => {
|
||||||
|
setFormPending(true);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const resp = await saveSettings(settings);
|
||||||
|
setSettings(resp);
|
||||||
|
setFormProviderType(resp.content?.provider);
|
||||||
|
|
||||||
|
messageApi.success(t("common.text.operation_succeeded"));
|
||||||
|
} catch (err) {
|
||||||
|
notificationApi.error({ message: t("common.text.request_error"), description: getErrMsg(err) });
|
||||||
|
} finally {
|
||||||
|
setFormPending(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SSLProviderContext.Provider
|
||||||
|
value={{
|
||||||
|
pending: formPending,
|
||||||
|
settings: settings!,
|
||||||
|
updateSettings: updateContextSettings,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{MessageContextHolder}
|
||||||
|
{NotificationContextHolder}
|
||||||
|
|
||||||
|
{loading ? (
|
||||||
|
<Skeleton active />
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<Form form={form} disabled={formPending} layout="vertical" initialValues={{ provider: providerType }}>
|
||||||
|
<Form.Item className="mb-2" name="provider" label={t("settings.sslprovider.form.provider.label")} initialValue={SSLPROVIDERS.LETS_ENCRYPT}>
|
||||||
|
<CheckCard.Group className="w-full" onChange={(value) => setFormProviderType(value as SSLProviders)}>
|
||||||
|
<CheckCard
|
||||||
|
avatar={<img src={"/imgs/acme/letsencrypt.svg"} className="size-8" />}
|
||||||
|
size="small"
|
||||||
|
title="Let's Encrypt"
|
||||||
|
value={SSLPROVIDERS.LETS_ENCRYPT}
|
||||||
|
/>
|
||||||
|
<CheckCard avatar={<img src={"/imgs/acme/zerossl.svg"} className="size-8" />} size="small" title="ZeroSSL" value={SSLPROVIDERS.ZERO_SSL} />
|
||||||
|
<CheckCard
|
||||||
|
avatar={<img src={"/imgs/acme/google.svg"} className="size-8" />}
|
||||||
|
size="small"
|
||||||
|
title="Google Trust Services"
|
||||||
|
value={SSLPROVIDERS.GOOGLE_TRUST_SERVICES}
|
||||||
|
/>
|
||||||
|
</CheckCard.Group>
|
||||||
|
</Form.Item>
|
||||||
|
</Form>
|
||||||
|
|
||||||
|
<div className="md:max-w-[40rem]">{providerFormComponent}</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</SSLProviderContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SettingsSSLProvider;
|
@ -1,9 +1,9 @@
|
|||||||
import { ClientResponseError } from "pocketbase";
|
import { ClientResponseError } from "pocketbase";
|
||||||
|
|
||||||
import { SETTINGS_NAMES, type SettingsModel } from "@/domain/settings";
|
import { type SettingsModel, type SettingsNames } from "@/domain/settings";
|
||||||
import { getPocketBase } from "./pocketbase";
|
import { getPocketBase } from "./pocketbase";
|
||||||
|
|
||||||
export const get = async <T>(name: (typeof SETTINGS_NAMES)[keyof typeof SETTINGS_NAMES]) => {
|
export const get = async <T extends NonNullable<unknown>>(name: SettingsNames) => {
|
||||||
try {
|
try {
|
||||||
const resp = await getPocketBase().collection("settings").getFirstListItem<SettingsModel<T>>(`name='${name}'`, {
|
const resp = await getPocketBase().collection("settings").getFirstListItem<SettingsModel<T>>(`name='${name}'`, {
|
||||||
requestKey: null,
|
requestKey: null,
|
||||||
@ -21,7 +21,7 @@ export const get = async <T>(name: (typeof SETTINGS_NAMES)[keyof typeof SETTINGS
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const save = async <T>(record: MaybeModelRecordWithId<SettingsModel<T>>) => {
|
export const save = async <T extends NonNullable<unknown>>(record: MaybeModelRecordWithId<SettingsModel<T>>) => {
|
||||||
if (record.id) {
|
if (record.id) {
|
||||||
return await getPocketBase().collection("settings").update<SettingsModel<T>>(record.id, record);
|
return await getPocketBase().collection("settings").update<SettingsModel<T>>(record.id, record);
|
||||||
}
|
}
|
||||||
|
@ -12,7 +12,7 @@ import Settings from "./pages/settings/Settings";
|
|||||||
import SettingsAccount from "./pages/settings/SettingsAccount";
|
import SettingsAccount from "./pages/settings/SettingsAccount";
|
||||||
import SettingsPassword from "./pages/settings/SettingsPassword";
|
import SettingsPassword from "./pages/settings/SettingsPassword";
|
||||||
import SettingsNotification from "./pages/settings/SettingsNotification";
|
import SettingsNotification from "./pages/settings/SettingsNotification";
|
||||||
import SSLProvider from "./pages/settings/SSLProvider";
|
import SettingsSSLProvider from "./pages/settings/SettingsSSLProvider";
|
||||||
|
|
||||||
export const router = createHashRouter([
|
export const router = createHashRouter([
|
||||||
{
|
{
|
||||||
@ -57,7 +57,7 @@ export const router = createHashRouter([
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/settings/ssl-provider",
|
path: "/settings/ssl-provider",
|
||||||
element: <SSLProvider />,
|
element: <SettingsSSLProvider />,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
@ -22,10 +22,12 @@ export const useNotifyChannelStore = create<NotifyChannelState>((set, get) => {
|
|||||||
|
|
||||||
setChannel: async (channel, config) => {
|
setChannel: async (channel, config) => {
|
||||||
settings ??= await getSettings<NotifyChannelsSettingsContent>(SETTINGS_NAMES.NOTIFY_CHANNELS);
|
settings ??= await getSettings<NotifyChannelsSettingsContent>(SETTINGS_NAMES.NOTIFY_CHANNELS);
|
||||||
return get().setChannels({
|
return get().setChannels(
|
||||||
...settings.content,
|
produce(settings, (draft) => {
|
||||||
[channel]: { ...settings.content[channel], ...config },
|
draft.content ??= {};
|
||||||
});
|
draft.content[channel] = { ...draft.content[channel], ...config };
|
||||||
|
})
|
||||||
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
setChannels: async (channels) => {
|
setChannels: async (channels) => {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user