mirror of https://github.com/usual2970/certimate
				
				
				
			
							parent
							
								
									332c5c5127
								
							
						
					
					
						commit
						ffdd61b5ee
					
				|  | @ -5,6 +5,7 @@ const ( | |||
| 	NotifyChannelWebhook  = "webhook" | ||||
| 	NotifyChannelTelegram = "telegram" | ||||
| 	NotifyChannelLark     = "lark" | ||||
| 	NotifyChannelServerChan = "serverchan" | ||||
| ) | ||||
| 
 | ||||
| type NotifyTestPushReq struct { | ||||
|  |  | |||
|  | @ -5,6 +5,8 @@ import ( | |||
| 	"fmt" | ||||
| 	"strconv" | ||||
| 
 | ||||
| 	stdhttp "net/http" | ||||
| 
 | ||||
| 	"github.com/usual2970/certimate/internal/domain" | ||||
| 	"github.com/usual2970/certimate/internal/utils/app" | ||||
| 
 | ||||
|  | @ -102,6 +104,8 @@ func getNotifier(channel string, conf map[string]any) (notifyPackage.Notifier, e | |||
| 		return getLarkNotifier(conf), nil | ||||
| 	case domain.NotifyChannelWebhook: | ||||
| 		return getWebhookNotifier(conf), nil | ||||
| 	case domain.NotifyChannelServerChan: | ||||
| 		return getServerChanNotifier(conf), nil | ||||
| 	} | ||||
| 
 | ||||
| 	return nil, fmt.Errorf("notifier not found") | ||||
|  | @ -132,6 +136,25 @@ func getTelegramNotifier(conf map[string]any) notifyPackage.Notifier { | |||
| 	return rs | ||||
| } | ||||
| 
 | ||||
| func getServerChanNotifier(conf map[string]any) notifyPackage.Notifier { | ||||
| 	rs := http.New() | ||||
| 
 | ||||
| 	rs.AddReceivers(&http.Webhook{ | ||||
| 		URL:         getString(conf, "url"), | ||||
| 		Header:      stdhttp.Header{}, | ||||
| 		ContentType: "application/json", | ||||
| 		Method:      stdhttp.MethodPost, | ||||
| 		BuildPayload: func(subject, message string) (payload any) { | ||||
| 			return map[string]string{ | ||||
| 				"text": subject, | ||||
| 				"desp": message, | ||||
| 			} | ||||
| 		}, | ||||
| 	}) | ||||
| 
 | ||||
| 	return rs | ||||
| } | ||||
| 
 | ||||
| func getDingTalkNotifier(conf map[string]any) notifyPackage.Notifier { | ||||
| 	return dingding.New(&dingding.Config{ | ||||
| 		Token:  getString(conf, "accessToken"), | ||||
|  |  | |||
|  | @ -0,0 +1,236 @@ | |||
| import { useEffect, useState } from "react"; | ||||
| import { useTranslation } from "react-i18next"; | ||||
| 
 | ||||
| import { Button } from "@/components/ui/button"; | ||||
| import { Input } from "@/components/ui/input"; | ||||
| import { Label } from "@/components/ui/label"; | ||||
| import { Switch } from "@/components/ui/switch"; | ||||
| import { useToast } from "@/components/ui/use-toast"; | ||||
| import { getErrMessage } from "@/lib/error"; | ||||
| import { isValidURL } from "@/lib/url"; | ||||
| import { NotifyChannels, NotifyChannelServerChan } from "@/domain/settings"; | ||||
| import { update } from "@/repository/settings"; | ||||
| import { useNotifyContext } from "@/providers/notify"; | ||||
| import { notifyTest } from "@/api/notify"; | ||||
| import Show from "@/components/Show"; | ||||
| 
 | ||||
| type ServerChanSetting = { | ||||
|   id: string; | ||||
|   name: string; | ||||
|   data: NotifyChannelServerChan; | ||||
| }; | ||||
| 
 | ||||
| const ServerChan = () => { | ||||
|   const { config, setChannels } = useNotifyContext(); | ||||
|   const { t } = useTranslation(); | ||||
|   const [changed, setChanged] = useState<boolean>(false); | ||||
| 
 | ||||
|   const [serverchan, setServerChan] = useState<ServerChanSetting>({ | ||||
|     id: config.id ?? "", | ||||
|     name: "notifyChannels", | ||||
|     data: { | ||||
|       url: "", | ||||
|       enabled: false, | ||||
|     }, | ||||
|   }); | ||||
| 
 | ||||
|   const [originServerChan, setOriginServerChan] = useState<ServerChanSetting>({ | ||||
|     id: config.id ?? "", | ||||
|     name: "notifyChannels", | ||||
|     data: { | ||||
|       url: "", | ||||
|       enabled: false, | ||||
|     }, | ||||
|   }); | ||||
| 
 | ||||
|   useEffect(() => { | ||||
|     setChanged(false); | ||||
|   }, [config]); | ||||
| 
 | ||||
|   useEffect(() => { | ||||
|     const data = getDetailServerChan(); | ||||
|     setOriginServerChan({ | ||||
|       id: config.id ?? "", | ||||
|       name: "serverchan", | ||||
|       data, | ||||
|     }); | ||||
|   }, [config]); | ||||
| 
 | ||||
|   useEffect(() => { | ||||
|     const data = getDetailServerChan(); | ||||
|     setServerChan({ | ||||
|       id: config.id ?? "", | ||||
|       name: "serverchan", | ||||
|       data, | ||||
|     }); | ||||
|   }, [config]); | ||||
| 
 | ||||
|   const { toast } = useToast(); | ||||
| 
 | ||||
|   const checkChanged = (data: NotifyChannelServerChan) => { | ||||
|     if (data.url !== originServerChan.data.url) { | ||||
|       setChanged(true); | ||||
|     } else { | ||||
|       setChanged(false); | ||||
|     } | ||||
|   }; | ||||
| 
 | ||||
|   const getDetailServerChan = () => { | ||||
|     const df: NotifyChannelServerChan = { | ||||
|       url: "", | ||||
|       enabled: false, | ||||
|     }; | ||||
|     if (!config.content) { | ||||
|       return df; | ||||
|     } | ||||
|     const chanels = config.content as NotifyChannels; | ||||
|     if (!chanels.serverchan) { | ||||
|       return df; | ||||
|     } | ||||
| 
 | ||||
|     return chanels.serverchan as NotifyChannelServerChan; | ||||
|   }; | ||||
| 
 | ||||
|   const handleSaveClick = async () => { | ||||
|     try { | ||||
|       serverchan.data.url = serverchan.data.url.trim(); | ||||
|       if (!isValidURL(serverchan.data.url)) { | ||||
|         toast({ | ||||
|           title: t("common.save.failed.message"), | ||||
|           description: t("settings.notification.url.errmsg.invalid"), | ||||
|           variant: "destructive", | ||||
|         }); | ||||
|         return; | ||||
|       } | ||||
| 
 | ||||
|       const resp = await update({ | ||||
|         ...config, | ||||
|         name: "notifyChannels", | ||||
|         content: { | ||||
|           ...config.content, | ||||
|           serverchan: { | ||||
|             ...serverchan.data, | ||||
|           }, | ||||
|         }, | ||||
|       }); | ||||
| 
 | ||||
|       setChannels(resp); | ||||
|       toast({ | ||||
|         title: t("common.save.succeeded.message"), | ||||
|         description: t("settings.notification.config.saved.message"), | ||||
|       }); | ||||
|     } catch (e) { | ||||
|       const msg = getErrMessage(e); | ||||
| 
 | ||||
|       toast({ | ||||
|         title: t("common.save.failed.message"), | ||||
|         description: `${t("settings.notification.config.failed.message")}: ${msg}`, | ||||
|         variant: "destructive", | ||||
|       }); | ||||
|     } | ||||
|   }; | ||||
| 
 | ||||
|   const handlePushTestClick = async () => { | ||||
|     try { | ||||
|       await notifyTest("serverchan"); | ||||
| 
 | ||||
|       toast({ | ||||
|         title: t("settings.notification.config.push.test.message.success.message"), | ||||
|         description: t("settings.notification.config.push.test.message.success.message"), | ||||
|       }); | ||||
|     } catch (e) { | ||||
|       const msg = getErrMessage(e); | ||||
| 
 | ||||
|       toast({ | ||||
|         title: t("settings.notification.config.push.test.message.failed.message"), | ||||
|         description: `${t("settings.notification.config.push.test.message.failed.message")}: ${msg}`, | ||||
|         variant: "destructive", | ||||
|       }); | ||||
|     } | ||||
|   }; | ||||
| 
 | ||||
|   const handleSwitchChange = async () => { | ||||
|     const newData = { | ||||
|       ...serverchan, | ||||
|       data: { | ||||
|         ...serverchan.data, | ||||
|         enabled: !serverchan.data.enabled, | ||||
|       }, | ||||
|     }; | ||||
|     setServerChan(newData); | ||||
| 
 | ||||
|     try { | ||||
|       const resp = await update({ | ||||
|         ...config, | ||||
|         name: "notifyChannels", | ||||
|         content: { | ||||
|           ...config.content, | ||||
|           serverchan: { | ||||
|             ...newData.data, | ||||
|           }, | ||||
|         }, | ||||
|       }); | ||||
| 
 | ||||
|       setChannels(resp); | ||||
|     } catch (e) { | ||||
|       const msg = getErrMessage(e); | ||||
| 
 | ||||
|       toast({ | ||||
|         title: t("common.save.failed.message"), | ||||
|         description: `${t("settings.notification.config.failed.message")}: ${msg}`, | ||||
|         variant: "destructive", | ||||
|       }); | ||||
|     } | ||||
|   }; | ||||
| 
 | ||||
|   return ( | ||||
|     <div> | ||||
|       <Input | ||||
|         placeholder={t("settings.notification.serverchan.url.placeholder")} | ||||
|         value={serverchan.data.url} | ||||
|         onChange={(e) => { | ||||
|           const newData = { | ||||
|             ...serverchan, | ||||
|             data: { | ||||
|               ...serverchan.data, | ||||
|               url: e.target.value, | ||||
|             }, | ||||
|           }; | ||||
| 
 | ||||
|           checkChanged(newData.data); | ||||
|           setServerChan(newData); | ||||
|         }} | ||||
|       /> | ||||
| 
 | ||||
|       <div className="flex items-center space-x-1 mt-2"> | ||||
|         <Switch id="airplane-mode" checked={serverchan.data.enabled} onCheckedChange={handleSwitchChange} /> | ||||
|         <Label htmlFor="airplane-mode">{t("settings.notification.config.enable")}</Label> | ||||
|       </div> | ||||
| 
 | ||||
|       <div className="flex justify-end mt-2"> | ||||
|         <Show when={changed}> | ||||
|           <Button | ||||
|             onClick={() => { | ||||
|               handleSaveClick(); | ||||
|             }} | ||||
|           > | ||||
|             {t("common.save")} | ||||
|           </Button> | ||||
|         </Show> | ||||
| 
 | ||||
|         <Show when={!changed && serverchan.id != ""}> | ||||
|           <Button | ||||
|             variant="secondary" | ||||
|             onClick={() => { | ||||
|               handlePushTestClick(); | ||||
|             }} | ||||
|           > | ||||
|             {t("settings.notification.config.push.test.message")} | ||||
|           </Button> | ||||
|         </Show> | ||||
|       </div> | ||||
|     </div> | ||||
|   ); | ||||
| }; | ||||
| 
 | ||||
| export default ServerChan; | ||||
|  | @ -22,9 +22,10 @@ export type NotifyChannels = { | |||
|   lark?: NotifyChannel; | ||||
|   telegram?: NotifyChannel; | ||||
|   webhook?: NotifyChannel; | ||||
|   serverchan?: NotifyChannel; | ||||
| }; | ||||
| 
 | ||||
| export type NotifyChannel = NotifyChannelDingTalk | NotifyChannelLark | NotifyChannelTelegram | NotifyChannelWebhook; | ||||
| export type NotifyChannel = NotifyChannelDingTalk | NotifyChannelLark | NotifyChannelTelegram | NotifyChannelWebhook | NotifyChannelServerChan; | ||||
| 
 | ||||
| export type NotifyChannelDingTalk = { | ||||
|   accessToken: string; | ||||
|  | @ -48,6 +49,11 @@ export type NotifyChannelWebhook = { | |||
|   enabled: boolean; | ||||
| }; | ||||
| 
 | ||||
| export type NotifyChannelServerChan = { | ||||
|   url: string; | ||||
|   enabled: boolean; | ||||
| }; | ||||
| 
 | ||||
| export const defaultNotifyTemplate: NotifyTemplate = { | ||||
|   title: "您有 {COUNT} 张证书即将过期", | ||||
|   content: "有 {COUNT} 张证书即将过期,域名分别为 {DOMAINS},请保持关注!", | ||||
|  |  | |||
|  | @ -74,6 +74,7 @@ | |||
|   "common.provider.local": "Local Deployment", | ||||
|   "common.provider.ssh": "SSH Deployment", | ||||
|   "common.provider.webhook": "Webhook", | ||||
|   "common.provider.serverchan": "ServerChan", | ||||
|   "common.provider.kubernetes": "Kubernetes", | ||||
|   "common.provider.kubernetes.secret": "Kubernetes - Secret", | ||||
|   "common.provider.dingtalk": "DingTalk", | ||||
|  |  | |||
|  | @ -35,6 +35,7 @@ | |||
|   "settings.notification.config.push.test.message.success.message": "Send test notification successfully", | ||||
|   "settings.notification.dingtalk.secret.placeholder": "Signature for signed addition", | ||||
|   "settings.notification.url.errmsg.invalid": "Invalid Url format", | ||||
|   "settings.notification.serverchan.url.placeholder": "Url, e.g. https://sctapi.ftqq.com/****************.send", | ||||
| 
 | ||||
|   "settings.ca.tab": "Certificate Authority", | ||||
|   "settings.ca.provider.errmsg.empty": "Please select a Certificate Authority", | ||||
|  |  | |||
|  | @ -73,6 +73,7 @@ | |||
|   "common.provider.local": "本地部署", | ||||
|   "common.provider.ssh": "SSH 部署", | ||||
|   "common.provider.webhook": "Webhook", | ||||
|   "common.provider.serverchan": "Server酱", | ||||
|   "common.provider.kubernetes": "Kubernetes", | ||||
|   "common.provider.kubernetes.secret": "Kubernetes - Secret", | ||||
|   "common.provider.dingtalk": "钉钉", | ||||
|  |  | |||
|  | @ -35,6 +35,7 @@ | |||
|   "settings.notification.config.push.test.message.success.message": "推送测试消息成功", | ||||
|   "settings.notification.dingtalk.secret.placeholder": "加签的签名", | ||||
|   "settings.notification.url.errmsg.invalid": "URL 格式不正确", | ||||
|   "settings.notification.serverchan.url.placeholder": "Url, 形如: https://sctapi.ftqq.com/****************.send", | ||||
| 
 | ||||
|   "settings.ca.tab": "证书颁发机构(CA)", | ||||
|   "settings.ca.provider.errmsg.empty": "请选择证书分发机构", | ||||
|  |  | |||
|  | @ -6,6 +6,7 @@ import Lark from "@/components/notify/Lark"; | |||
| import NotifyTemplate from "@/components/notify/NotifyTemplate"; | ||||
| import Telegram from "@/components/notify/Telegram"; | ||||
| import Webhook from "@/components/notify/Webhook"; | ||||
| import ServerChan from "@/components/notify/ServerChan"; | ||||
| import { NotifyProvider } from "@/providers/notify"; | ||||
| 
 | ||||
| const Notify = () => { | ||||
|  | @ -53,6 +54,13 @@ const Notify = () => { | |||
|                 <Webhook /> | ||||
|               </AccordionContent> | ||||
|             </AccordionItem> | ||||
| 
 | ||||
|             <AccordionItem value="item-6" className="dark:border-stone-200"> | ||||
|               <AccordionTrigger>{t("common.provider.serverchan")}</AccordionTrigger> | ||||
|               <AccordionContent> | ||||
|                 <ServerChan /> | ||||
|               </AccordionContent> | ||||
|             </AccordionItem> | ||||
|           </Accordion> | ||||
|         </div> | ||||
|       </NotifyProvider> | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue
	
	 Leo Chen
						Leo Chen