diff --git a/.gitignore b/.gitignore index 0d065668..f5943cce 100644 --- a/.gitignore +++ b/.gitignore @@ -48,3 +48,4 @@ next-env.d.ts *.njsproj *.sln *.sw? +*_copy* \ No newline at end of file diff --git a/app/components/home/header/index.tsx b/app/components/home/header/index.tsx index 86eb5ac4..b9947189 100644 --- a/app/components/home/header/index.tsx +++ b/app/components/home/header/index.tsx @@ -11,9 +11,11 @@ import HomeMonitorHeaderMenu from './menu'; import useContextStore from '../../../context'; import Role from '../../../../shared/permissions/role'; import { PermissionsBits } from '../../../../shared/permissions/bitFlags'; +import type { MonitorType } from '../../../types/monitor'; const typeToText = { docker: 'Docker Container', + email: 'Email (SMTP)', http: 'HTTP/S', json: 'JSON Query', ping: 'Ping', @@ -28,6 +30,31 @@ interface HomeMonitorHeaderProps { isMobile?: boolean; } +const HeaderSubtitle = ({ type, url }: { type: MonitorType; url: string }) => { + const { t } = useTranslation(); + + if (!type || !url) return null; + + if (type === 'push') { + return ( +
+ Passive push + monitor +
+ ); + } + + return ( +
+ {typeToText[type]} + {t('home.header.subtitle') + ' '} + + {url} + +
+ ); +}; + const HomeMonitorHeader = ({ isInfoOpen, setIsInfoOpen, @@ -67,15 +94,7 @@ const HomeMonitorHeader = ({
{t('common.monitor')} - {activeMonitor.name}
- {activeMonitor?.url ? ( -
- {typeToText[activeMonitor.type]} - {t('home.header.subtitle') + ' '} - - {activeMonitor.url} - -
- ) : null} +
{isEditor ? : null} diff --git a/app/components/modal/notification/dropdown/platform.tsx b/app/components/modal/notification/dropdown/platform.tsx index 6d47a187..d385df62 100644 --- a/app/components/modal/notification/dropdown/platform.tsx +++ b/app/components/modal/notification/dropdown/platform.tsx @@ -3,7 +3,10 @@ import NotificationIcon from './icon'; import Dropdown from '../../../ui/dropdown'; import useDropdown from '../../../../hooks/useDropdown'; import notificationsIcons from '../../../../constant/notifications.json'; -import type { NotificationProps } from '../../../../types/notifications'; +import type { + NotificationPlatforms, + NotificationProps, +} from '../../../../types/notifications'; interface NotificationModalPlatformProps { isEdit?: boolean; @@ -11,13 +14,7 @@ interface NotificationModalPlatformProps { key: keyof NotificationProps; value: string; }) => void; - platform: - | 'Discord' - | 'HomeAssistant' - | 'Pushover' - | 'Slack' - | 'Telegram' - | 'Webhook'; + platform: NotificationPlatforms; } const NotificationModalPlatform = ({ @@ -60,11 +57,11 @@ const NotificationModalPlatform = ({ {Object.values(notificationsIcons).map((notification) => ( { - setPlatform({ key: 'platform', value: notification.name }); + setPlatform({ key: 'platform', value: notification.id }); toggleDropdown(); }} - key={notification.name} - id={`notification-type-${notification.name}`} + key={notification.id} + id={`notification-type-${notification.id}`} > ; - const testNotification = async () => { try { await createPostRequest('/api/notifications/test', inputs); @@ -115,8 +112,9 @@ const NotificationModal = ({ platform={inputs.platform} /> - @@ -127,7 +125,14 @@ const NotificationModal = ({ /> - + + {inputs.platform !== 'Email' && ( + + )} + + {inputs.platform === 'Email' && ( + + )} ); }; diff --git a/app/components/modal/notification/platform/discord.tsx b/app/components/modal/notification/platform/discord.tsx deleted file mode 100644 index c6777624..00000000 --- a/app/components/modal/notification/platform/discord.tsx +++ /dev/null @@ -1,96 +0,0 @@ -// import dependencies -import { Input } from '@lunalytics/ui'; - -interface NotificationModalDiscordInputProps { - values: { - friendlyName: string; - token: string; - data: { - username: string; - textMessage: string; - }; - }; - errors: { - friendlyName?: string; - token?: string; - username?: string; - textMessage?: string; - }; - handleInput: (input: { key: string; value: any }) => void; -} - -const NotificationModalDiscordInput = ({ - values, - errors, - handleInput, -}: NotificationModalDiscordInputProps) => { - return ( - <> - ) => { - handleInput({ key: 'friendlyName', value: e.target.value }); - }} - /> - - For more information about how to create a Discord webhoook checkout - this guide:{' '} - - https://lunalytics.xyz/guides/discord/create-webhook - - - } - id="webhook-url" - isRequired - error={errors?.token} - value={values.token} - onChange={(e: React.ChangeEvent) => { - handleInput({ key: 'token', value: e.target.value }); - }} - /> - ) => { - handleInput({ - key: 'data', - value: { ...values.data, username: e.target.value }, - }); - }} - /> - ) => { - handleInput({ - key: 'data', - value: { ...values.data, textMessage: e.target.value }, - }); - }} - /> - - ); -}; - -NotificationModalDiscordInput.displayName = 'NotificationModalDiscordInput'; - -export default NotificationModalDiscordInput; diff --git a/app/components/modal/notification/platform/homeAssistant.tsx b/app/components/modal/notification/platform/homeAssistant.tsx deleted file mode 100644 index 0c63ee48..00000000 --- a/app/components/modal/notification/platform/homeAssistant.tsx +++ /dev/null @@ -1,98 +0,0 @@ -// import local files -import { Input } from '@lunalytics/ui'; - -interface NotificationModalHomeAssistantInputProps { - values: { - friendlyName: string; - token: string; - data: { - homeAssistantUrl: string; - homeAssistantNotificationService: string; - }; - }; - errors: { - friendlyName?: string; - token?: string; - homeAssistantUrl?: string; - homeAssistantNotificationService?: string; - }; - handleInput: (input: { key: string; value: any }) => void; -} - -const NotificationModalHomeAssistantInput = ({ - values, - errors, - handleInput, -}: NotificationModalHomeAssistantInputProps) => { - return ( - <> - ) => { - handleInput({ key: 'friendlyName', value: e.target.value }); - }} - /> - - URL of your HomeAssistant instance.} - id="home-assistant-url" - isRequired - error={errors?.homeAssistantUrl} - defaultValue={values.data?.homeAssistantUrl} - onChange={(e: React.ChangeEvent) => { - handleInput({ - key: 'data', - value: { ...values.data, homeAssistantUrl: e.target.value }, - }); - }} - /> - - Notification service to use.} - id="home-assistant-notification-service" - isRequired - error={errors?.homeAssistantNotificationService} - defaultValue={values.data?.homeAssistantNotificationService} - onChange={(e: React.ChangeEvent) => { - handleInput({ - key: 'data', - value: { - ...values.data, - homeAssistantNotificationService: e.target.value, - }, - }); - }} - /> - - - Long-lived access tokens can be created using the "Long-Lived - Access Tokens" section at the bottom of a user's Home - Assistant profile page. - - } - id="home-assistant-access-token" - error={errors?.token} - defaultValue={values.token} - onChange={(e: React.ChangeEvent) => { - handleInput({ key: 'token', value: e.target.value }); - }} - /> - - ); -}; - -NotificationModalHomeAssistantInput.displayName = - 'NotificationModalHomeAssistantInput'; - -export default NotificationModalHomeAssistantInput; diff --git a/app/components/modal/notification/platform/index.tsx b/app/components/modal/notification/platform/index.tsx deleted file mode 100644 index cbb6ea9a..00000000 --- a/app/components/modal/notification/platform/index.tsx +++ /dev/null @@ -1,6 +0,0 @@ -export { default as Discord } from './discord'; -export { default as HomeAssistant } from './homeAssistant'; -export { default as Pushover } from './pushover'; -export { default as Slack } from './slack'; -export { default as Telegram } from './telegram'; -export { default as Webhook } from './webhook'; diff --git a/app/components/modal/notification/platform/pushover.tsx b/app/components/modal/notification/platform/pushover.tsx deleted file mode 100644 index 5fdcf027..00000000 --- a/app/components/modal/notification/platform/pushover.tsx +++ /dev/null @@ -1,148 +0,0 @@ -// import dependencies -import { Dropdown, Input, PasswordInput } from '@lunalytics/ui'; - -interface NotificationModalPushoverInputProps { - values: { - friendlyName: string; - token: string; - data: { - userKey: string; - applicationToken: string; - device: string; - priority: string; - ttl: string; - }; - }; - errors: { - friendlyName?: string; - token?: string; - userKey?: string; - applicationToken?: string; - device?: string; - priority?: string; - messageTtl?: string; - }; - handleInput: (input: { key: string; value: any }) => void; -} - -const NotificationModalPushoverInput = ({ - values, - errors, - handleInput, -}: NotificationModalPushoverInputProps) => { - const handlePriorityChange = (value: string) => { - handleInput({ key: 'data', value: { ...values.data, priority: value } }); - }; - - return ( - <> - ) => { - handleInput({ key: 'friendlyName', value: e.target.value }); - }} - /> - ) => { - handleInput({ - key: 'data', - value: { ...values.data, userKey: e.target.value }, - }); - }} - /> - ) => { - handleInput({ key: 'token', value: e.target.value }); - }} - /> - ) => { - handleInput({ - key: 'data', - value: { ...values.data, device: e.target.value }, - }); - }} - /> - -
- - handlePriorityChange('-2'), - }, - { - id: '2', - text: '-1', - type: 'item', - onClick: () => handlePriorityChange('-1'), - }, - { - id: '3', - text: '0', - type: 'item', - onClick: () => handlePriorityChange('0'), - }, - { - id: '4', - text: '1', - type: 'item', - onClick: () => handlePriorityChange('1'), - }, - { - id: '5', - text: '2', - type: 'item', - onClick: () => handlePriorityChange('2'), - }, - ]} - > -
- {values.data?.priority || '0'} -
-
-
- - ) => { - handleInput({ - key: 'data', - value: { ...values.data, ttl: e.target.value }, - }); - }} - /> - - ); -}; - -NotificationModalPushoverInput.displayName = 'NotificationModalPushoverInput'; - -export default NotificationModalPushoverInput; diff --git a/app/components/modal/notification/platform/slack.tsx b/app/components/modal/notification/platform/slack.tsx deleted file mode 100644 index 87b6f1a2..00000000 --- a/app/components/modal/notification/platform/slack.tsx +++ /dev/null @@ -1,115 +0,0 @@ -// import dependencies -import { Input } from '@lunalytics/ui'; - -interface NotificationModalSlackInputProps { - values: { - friendlyName: string; - token: string; - data: { - username: string; - textMessage: string; - channel: string; - }; - }; - errors: { - friendlyName?: string; - token?: string; - username?: string; - textMessage?: string; - channel?: string; - }; - handleInput: (input: { key: string; value: any }) => void; -} - -const NotificationModalSlackInput = ({ - values, - errors, - handleInput, -}: NotificationModalSlackInputProps) => { - return ( - <> - ) => { - handleInput({ key: 'friendlyName', value: e.target.value }); - }} - /> - - - For more information about how to create a webhook checkout this - guide:{' '} - - https://lunalytics.xyz/guides/slack/create-webhook - - - } - id="webhook-url" - isRequired - error={errors?.token} - defaultValue={values.token} - onChange={(e: React.ChangeEvent) => { - handleInput({ key: 'token', value: e.target.value }); - }} - /> - - ) => { - handleInput({ - key: 'data', - value: { ...values.data, username: e.target.value }, - }); - }} - /> - - ) => { - handleInput({ - key: 'data', - value: { ...values.data, textMessage: e.target.value }, - }); - }} - /> - - ) => { - handleInput({ - key: 'data', - value: { ...values.data, channel: e.target.value }, - }); - }} - /> - - ); -}; - -NotificationModalSlackInput.displayName = 'NotificationModalSlackInput'; - -export default NotificationModalSlackInput; diff --git a/app/components/modal/notification/platform/telegram.tsx b/app/components/modal/notification/platform/telegram.tsx deleted file mode 100644 index b83f80da..00000000 --- a/app/components/modal/notification/platform/telegram.tsx +++ /dev/null @@ -1,137 +0,0 @@ -// import dependencies -import { Input } from '@lunalytics/ui'; - -// import local files -import Switch from '../../../ui/switch'; - -interface NotificationModalTelegramInputProps { - values: { - friendlyName: string; - token: string; - data: { - chatId: string; - disableNotification: boolean; - protectContent: boolean; - }; - }; - errors: { - friendlyName?: string; - token?: string; - chatId?: string; - disableNotification?: string; - protectContent?: string; - }; - handleInput: (input: { key: string; value: any }) => void; -} - -const NotificationModalTelegramInput = ({ - values, - errors, - handleInput, -}: NotificationModalTelegramInputProps) => { - return ( - <> - ) => { - handleInput({ key: 'friendlyName', value: e.target.value }); - }} - /> - - - For more information about how to create a bot token checkout this - guide:{' '} - - https://lunalytics.xyz/guides/telegram/create-bot - - - } - id="bot-token" - isRequired - error={errors?.token} - defaultValue={values.token} - onChange={(e: React.ChangeEvent) => { - handleInput({ key: 'token', value: e.target.value }); - }} - /> - - - For more information about how to find the chat ID for a channel - checkout this guide:{' '} - - https://lunalytics.xyz/guides/telegram/find-chat-id - - - } - id="chat-id" - isRequired - error={errors?.chatId} - defaultValue={values.data?.chatId} - onChange={(e: React.ChangeEvent) => { - handleInput({ - key: 'data', - value: { ...values.data, chatId: e.target.value }, - }); - }} - /> - -
- ) => { - handleInput({ - key: 'data', - value: { ...values.data, disableNotification: e.target.checked }, - }); - }} - /> -
- -
- ) => { - handleInput({ - key: 'data', - value: { ...values.data, protectContent: e.target.checked }, - }); - }} - /> -
- - ); -}; - -NotificationModalTelegramInput.displayName = 'NotificationModalTelegramInput'; - -export default NotificationModalTelegramInput; diff --git a/app/components/modal/notification/platform/webhook.tsx b/app/components/modal/notification/platform/webhook.tsx deleted file mode 100644 index 042e0d1d..00000000 --- a/app/components/modal/notification/platform/webhook.tsx +++ /dev/null @@ -1,158 +0,0 @@ -// import dependencies -import { Textarea, Input } from '@lunalytics/ui'; - -// import local files -import Dropdown from '../../../ui/dropdown'; -import useDropdown from '../../../../hooks/useDropdown'; -import Switch from '../../../ui/switch'; - -interface NotificationModalWebhookInputProps { - values: { - showAdditionalHeaders: boolean; - friendlyName: string; - token: string; - data: { - requestType: string; - additionalHeaders: string; - }; - }; - errors: { - friendlyName?: string; - token?: string; - requestType?: string; - additionalHeaders?: string; - }; - handleInput: (input: { key: string; value: any }) => void; -} - -const NotificationModalWebhookInput = ({ - values, - errors, - handleInput, -}: NotificationModalWebhookInputProps) => { - const { dropdownIsOpen, toggleDropdown } = useDropdown(); - - const showAdditionalHeaders = - typeof values.showAdditionalHeaders !== 'boolean' - ? values.data?.additionalHeaders - : values.showAdditionalHeaders; - - return ( - <> - ) => { - handleInput({ key: 'friendlyName', value: e.target.value }); - }} - /> - - For more information about how to create a custom webhoook checkout - this guide:{' '} - - https://lunalytics.xyz/guides/webhook/services - - - } - id="webhook-url" - isRequired - error={errors?.token} - defaultValue={values.token} - onChange={(e: React.ChangeEvent) => { - handleInput({ key: 'token', value: e.target.value }); - }} - /> - - - - {values.data?.requestType || 'application/json'} - - - {[ - 'application/json', - 'form-data', - // 'custom body' - ].map((method) => ( - { - handleInput({ - key: 'data', - value: { ...values.data, requestType: method }, - }); - toggleDropdown(); - }} - > - {method} - - ))} - - - -
- ) => - handleInput({ - key: 'showAdditionalHeaders', - value: e.target.checked, - }) - } - description={ - showAdditionalHeaders && ( - <> -
- Add additional headers to be sent with the webhook request. - Make sure to follow JSON key/value format. -
- - - ) - } - /> -
- - ); -}; - -NotificationModalWebhookInput.displayName = 'NotificationModalWebhookInput'; - -export default NotificationModalWebhookInput; diff --git a/app/components/notifications/content/index.tsx b/app/components/notifications/content/index.tsx index 5ba733fb..c601249b 100644 --- a/app/components/notifications/content/index.tsx +++ b/app/components/notifications/content/index.tsx @@ -1,40 +1,110 @@ -// import dependencies +import { useMemo } from 'react'; +import { Button } from '@lunalytics/ui'; import { observer } from 'mobx-react-lite'; import { useTranslation } from 'react-i18next'; -// import local files +import ActionBar from '../../ui/actionBar'; +import SwitchWithText from '../../ui/switch'; import useContextStore from '../../../context'; import useNotificationForm from '../../../hooks/useNotificationForm'; import NotificationsTemplates from '../../../../shared/notifications'; import NotificationModalPayload from '../../modal/notification/payload'; import NotificationModalType from '../../modal/notification/dropdown/type'; -import * as NotificationPlatformContent from './platform'; -import ActionBar from '../../ui/actionBar'; -import { useMemo } from 'react'; -import { Button } from '@lunalytics/ui'; +import * as NotificationPlatformContent from '../../../constant/notifications/layout'; +import NotificationRenderer from './renderer'; +import { + NotificationBasicEmailTemplate, + NotificationNerdyEmailTemplate, + NotificationPrettyEmailTemplate, +} from '../emails'; + +export const EmailComponent = ({ type }: { type: string }) => { + if (type === 'basic') { + return ( + + ); + } + + if (type === 'pretty') { + return ( + + ); + } + if (type === 'nerdy') { + return ( + + ); + } -const NotificationContent = () => { + return null; +}; + +const NotificationRender = ({ isEdit = false }: { isEdit: boolean }) => { const { notificationStore: { addNotification, activeNotification: notification }, } = useContextStore(); + const { t } = useTranslation(); + const { inputs, errors, handleInput, handleSubmit } = useNotificationForm( notification, true ); - const { t } = useTranslation(); const showSaveActionBar = useMemo(() => { return JSON.stringify(notification) !== JSON.stringify(inputs); }, [JSON.stringify(notification), JSON.stringify(inputs)]); + const components = + notification && NotificationPlatformContent[notification?.platform]; + + if (!components) { + return null; + } + const message = NotificationsTemplates[notification.platform][inputs.messageType]; - const Content = NotificationPlatformContent[notification.platform]; return ( -
- +
+ + +
+ ) => { + handleInput({ key: 'isEnabled', value: e.target.checked }); + }} + /> +
{ /> - + {notification.platform !== 'Email' && ( + + )} + + {notification.platform === 'Email' && ( + + )}
@@ -77,4 +153,4 @@ const NotificationContent = () => { ); }; -export default observer(NotificationContent); +export default observer(NotificationRender); diff --git a/app/components/notifications/content/platform/discord.tsx b/app/components/notifications/content/platform/discord.tsx deleted file mode 100644 index ea9240fb..00000000 --- a/app/components/notifications/content/platform/discord.tsx +++ /dev/null @@ -1,105 +0,0 @@ -// import dependencies -import { useState } from 'react'; -import { Input } from '@lunalytics/ui'; -import { IoMdEye, IoMdEyeOff } from 'react-icons/io'; -import { useTranslation } from 'react-i18next'; - -// import local files -import SwitchWithText from '../../../ui/switch'; -import type { NotificationDiscord } from '../../../../types/notifications'; - -const NotificationDiscordContent = ({ - inputs, - errors, - handleInput, -}: { - inputs: NotificationDiscord; - errors: Partial>; - handleInput: ( - input: Partial> - ) => void; -}) => { - const [showPassword, setShowPassword] = useState(false); - const { t } = useTranslation(); - - return ( - <> -
- ) => { - handleInput({ key: 'friendlyName', value: e.target.value }); - }} - /> - - ) => - handleInput({ key: 'token', value: event.target.value }) - } - iconRight={ -
setShowPassword(!showPassword)} - className="notification-content-icon" - > - {showPassword ? ( - - ) : ( - - )} -
- } - /> - - ) => { - handleInput({ - key: 'data', - value: { ...inputs.data, username: e.target.value }, - }); - }} - /> - - ) => { - handleInput({ - key: 'data', - value: { ...inputs.data, textMessage: e.target.value }, - }); - }} - /> -
- -
- ) => { - handleInput({ key: 'isEnabled', value: e.target.checked }); - }} - /> -
- - ); -}; - -export default NotificationDiscordContent; diff --git a/app/components/notifications/content/platform/homeAssistant.tsx b/app/components/notifications/content/platform/homeAssistant.tsx deleted file mode 100644 index 2a82e183..00000000 --- a/app/components/notifications/content/platform/homeAssistant.tsx +++ /dev/null @@ -1,107 +0,0 @@ -import { useState } from 'react'; -import { Input } from '@lunalytics/ui'; -import { useTranslation } from 'react-i18next'; -import { IoMdEye, IoMdEyeOff } from 'react-icons/io'; - -import SwitchWithText from '../../../ui/switch'; -import type { NotificationHomeAssistant } from '../../../../types/notifications'; - -const NotificationHomeAssistantContent = ({ - inputs, - errors, - handleInput, -}: { - inputs: NotificationHomeAssistant; - errors: Partial>; - handleInput: ( - input: Partial> - ) => void; -}) => { - const [showPassword, setShowPassword] = useState(false); - const { t } = useTranslation(); - - return ( - <> -
- ) => { - handleInput({ key: 'friendlyName', value: e.target.value }); - }} - /> - - ) => { - handleInput({ - key: 'data', - value: { ...inputs.data, homeAssistantUrl: e.target.value }, - }); - }} - /> - - ) => { - handleInput({ - key: 'data', - value: { - ...inputs.data, - homeAssistantNotificationService: e.target.value, - }, - }); - }} - /> - - setShowPassword(!showPassword)} - className="notification-content-icon" - > - {showPassword ? ( - - ) : ( - - )} -
- } - onChange={(e: React.ChangeEvent) => { - handleInput({ key: 'token', value: e.target.value }); - }} - /> -
- -
- ) => { - handleInput({ key: 'isEnabled', value: e.target.checked }); - }} - /> -
- - ); -}; - -export default NotificationHomeAssistantContent; diff --git a/app/components/notifications/content/platform/index.tsx b/app/components/notifications/content/platform/index.tsx deleted file mode 100644 index cbb6ea9a..00000000 --- a/app/components/notifications/content/platform/index.tsx +++ /dev/null @@ -1,6 +0,0 @@ -export { default as Discord } from './discord'; -export { default as HomeAssistant } from './homeAssistant'; -export { default as Pushover } from './pushover'; -export { default as Slack } from './slack'; -export { default as Telegram } from './telegram'; -export { default as Webhook } from './webhook'; diff --git a/app/components/notifications/content/platform/pushover.tsx b/app/components/notifications/content/platform/pushover.tsx deleted file mode 100644 index 6b657e2e..00000000 --- a/app/components/notifications/content/platform/pushover.tsx +++ /dev/null @@ -1,158 +0,0 @@ -// import dependencies -import { Dropdown, Input, PasswordInput } from '@lunalytics/ui'; -import { useTranslation } from 'react-i18next'; -// import local files -import SwitchWithText from '../../../ui/switch'; -import type { - NotificationPushover, - NotificationPushoverErrors, -} from '../../../../types/notifications'; - -const NotificationPushoverContent = ({ - inputs, - errors, - handleInput, -}: { - inputs: NotificationPushover; - errors: Partial>; - handleInput: (input: { - key: keyof NotificationPushover | 'data'; - value: Partial | string | number | boolean; - }) => void; -}) => { - const handlePriorityChange = (value: string) => { - handleInput({ - key: 'data', - value: { ...inputs.data, priority: value }, - }); - }; - - const { t } = useTranslation(); - - return ( - <> -
- ) => { - handleInput({ key: 'friendlyName', value: e.target.value }); - }} - /> - - ) => { - handleInput({ - key: 'data', - value: { ...inputs.data, userKey: e.target.value }, - }); - }} - /> - - ) => { - handleInput({ key: 'token', value: e.target.value }); - }} - /> - - ) => { - handleInput({ - key: 'data', - value: { ...inputs.data, device: e.target.value }, - }); - }} - /> -
- -
- - handlePriorityChange('-2'), - }, - { - id: '2', - text: '-1', - type: 'item', - onClick: () => handlePriorityChange('-1'), - }, - { - id: '3', - text: '0', - type: 'item', - onClick: () => handlePriorityChange('0'), - }, - { - id: '4', - text: '1', - type: 'item', - onClick: () => handlePriorityChange('1'), - }, - { - id: '5', - text: '2', - type: 'item', - onClick: () => handlePriorityChange('2'), - }, - ]} - > -
- {inputs.data?.priority || '0'} -
-
-
- - ) => { - handleInput({ - key: 'data', - value: { ...inputs.data, ttl: e.target.value }, - }); - }} - /> - -
- ) => { - handleInput({ key: 'isEnabled', value: e.target.checked }); - }} - /> -
- - ); -}; - -export default NotificationPushoverContent; diff --git a/app/components/notifications/content/platform/slack.tsx b/app/components/notifications/content/platform/slack.tsx deleted file mode 100644 index 86929859..00000000 --- a/app/components/notifications/content/platform/slack.tsx +++ /dev/null @@ -1,120 +0,0 @@ -// import dependencies -import { useState } from 'react'; -import { Input } from '@lunalytics/ui'; -import { useTranslation } from 'react-i18next'; -import { IoMdEye, IoMdEyeOff } from 'react-icons/io'; - -// import local files -import SwitchWithText from '../../../ui/switch'; -import type { NotificationSlack } from '../../../../types/notifications'; - -const NotificationSlackContent = ({ - inputs, - errors, - handleInput, -}: { - inputs: NotificationSlack; - errors: Partial>; - handleInput: ( - input: Partial> - ) => void; -}) => { - const [showPassword, setShowPassword] = useState(false); - const { t } = useTranslation(); - - return ( - <> -
- ) => { - handleInput({ key: 'friendlyName', value: e.target.value }); - }} - /> - - ) => { - handleInput({ key: 'token', value: e.target.value }); - }} - iconRight={ -
setShowPassword(!showPassword)} - className="notification-content-icon" - > - {showPassword ? ( - - ) : ( - - )} -
- } - /> - - ) => { - handleInput({ - key: 'data', - value: { ...inputs.data, username: e.target.value }, - }); - }} - /> - - ) => { - handleInput({ - key: 'data', - value: { ...inputs.data, textMessage: e.target.value }, - }); - }} - /> -
- - ) => { - handleInput({ - key: 'data', - value: { ...inputs.data, channel: e.target.value }, - }); - }} - /> - -
- ) => { - handleInput({ key: 'isEnabled', value: e.target.checked }); - }} - /> -
- - ); -}; - -export default NotificationSlackContent; diff --git a/app/components/notifications/content/platform/telegram.tsx b/app/components/notifications/content/platform/telegram.tsx deleted file mode 100644 index 84a0cd09..00000000 --- a/app/components/notifications/content/platform/telegram.tsx +++ /dev/null @@ -1,125 +0,0 @@ -import { useState } from 'react'; -import { Input } from '@lunalytics/ui'; -import { useTranslation } from 'react-i18next'; -import { IoMdEye, IoMdEyeOff } from 'react-icons/io'; - -import SwitchWithText from '../../../ui/switch'; -import type { NotificationTelegram } from '../../../../types/notifications'; - -const NotificationDiscordContent = ({ - inputs, - errors, - handleInput, -}: { - inputs: NotificationTelegram; - errors: Partial>; - handleInput: ( - input: Partial> - ) => void; -}) => { - const [showPassword, setShowPassword] = useState(false); - const { t } = useTranslation(); - - return ( - <> -
- ) => { - handleInput({ key: 'friendlyName', value: e.target.value }); - }} - /> - - ) => - handleInput({ key: 'token', value: event.target.value }) - } - iconRight={ -
setShowPassword(!showPassword)} - className="notification-content-icon" - > - {showPassword ? ( - - ) : ( - - )} -
- } - /> - - ) => { - handleInput({ - key: 'data', - value: { ...inputs.data, chatId: e.target.value }, - }); - }} - /> -
- -
- ) => { - handleInput({ - key: 'data', - value: { ...inputs.data, disableNotification: e.target.checked }, - }); - }} - /> - - ) => { - handleInput({ - key: 'data', - value: { ...inputs.data, protectContent: e.target.checked }, - }); - }} - /> - - ) => { - handleInput({ key: 'isEnabled', value: e.target.checked }); - }} - /> -
- - ); -}; - -export default NotificationDiscordContent; diff --git a/app/components/notifications/content/platform/webhook.tsx b/app/components/notifications/content/platform/webhook.tsx deleted file mode 100644 index 85620e75..00000000 --- a/app/components/notifications/content/platform/webhook.tsx +++ /dev/null @@ -1,148 +0,0 @@ -// import dependencies -import { useState } from 'react'; -import { useTranslation } from 'react-i18next'; -import { Input, Textarea } from '@lunalytics/ui'; -import { IoMdEye, IoMdEyeOff } from 'react-icons/io'; - -// import local files -import SwitchWithText from '../../../ui/switch'; -import type { NotificationWebhook } from '../../../../types/notifications'; - -const NotificationDiscordContent = ({ - inputs, - errors, - handleInput, -}: { - inputs: NotificationWebhook; - errors: Partial>; - handleInput: (input: { key: keyof NotificationWebhook; value: any }) => void; -}) => { - const [showPassword, setShowPassword] = useState(false); - const { t } = useTranslation(); - - return ( - <> -
- ) => { - handleInput({ key: 'friendlyName', value: e.target.value }); - }} - /> - - ) => - handleInput({ key: 'token', value: event.target.value }) - } - iconRight={ -
setShowPassword(!showPassword)} - className="notification-content-icon" - > - {showPassword ? ( - - ) : ( - - )} -
- } - /> - - ) => { - handleInput({ - key: 'data', - value: { ...inputs.data, username: e.target.value }, - }); - }} - /> - - ) => { - handleInput({ - key: 'data', - value: { ...inputs.data, chatId: e.target.value }, - }); - }} - /> -
- -
- ) => { - handleInput({ key: 'isEnabled', value: e.target.checked }); - }} - /> - - ) => { - handleInput({ - key: 'data', - value: { - ...inputs.data, - showAdditionalHeaders: e.target.checked, - }, - }); - }} - description={ - inputs.data.showAdditionalHeaders && ( - - ) - } - /> -
- - ); -}; - -export default NotificationDiscordContent; diff --git a/app/components/notifications/content/renderer/components.tsx b/app/components/notifications/content/renderer/components.tsx new file mode 100644 index 00000000..4ef165ac --- /dev/null +++ b/app/components/notifications/content/renderer/components.tsx @@ -0,0 +1,184 @@ +import { Trans } from 'react-i18next'; +import { Dropdown, Input, PasswordInput, Textarea } from '@lunalytics/ui'; + +import SwitchWithText from '../../../ui/switch'; +import type { NotificationInputInputType } from '../../../../types/constant/notifications'; + +export const getNotificationComponent = ( + component: NotificationInputInputType, + value: string | number | boolean, + error: string | undefined, + onChange: ( + value: string | number | boolean, + key: string, + isDataField: boolean + ) => void, + isEdit: boolean +) => { + const { type, isDataField, key, options, subtitle, title, ...props } = + component; + + if (type === 'input' || type === 'number') { + return ( + {title}} + subtitle={ + subtitle && !isEdit ? ( + <> + {subtitle.text} + {subtitle.link && ( + + {subtitle.link} + + )} + + ) : null + } + onChange={(event: React.ChangeEvent) => { + onChange(event.target.value, key, isDataField); + }} + /> + ); + } + + if (type === 'password') { + return ( + {title}} + subtitle={ + subtitle && !isEdit ? ( + <> + {subtitle.text} + {subtitle.link && ( + + {subtitle.link} + + )} + + ) : null + } + onChange={(event: React.ChangeEvent) => + onChange(event.target.value, key, isDataField) + } + /> + ); + } + + if (type === 'switch') { + return ( +
+ + {subtitle.text} + {subtitle.link && ( + + {subtitle.link} + + )} + + ) : null + } + checked={value} + onChange={(event: React.ChangeEvent) => + onChange(event.target.checked, key, isDataField) + } + /> +
+ ); + } + + if (type === 'dropdown') { + return ( +
+ + { + return { + id: item.id, + text: item.id, + type: 'item', + onClick: () => onChange(item.id, key, isDataField), + }; + })} + > +
+ {value || props.defaultValue} +
+
+
+ ); + } + + if (type === 'textarea') { + return ( + + ); + } + + if (type === 'empty') { + return
; + } + + return null; +}; diff --git a/app/components/notifications/content/renderer/index.tsx b/app/components/notifications/content/renderer/index.tsx new file mode 100644 index 00000000..f4241436 --- /dev/null +++ b/app/components/notifications/content/renderer/index.tsx @@ -0,0 +1,75 @@ +import { getNotificationComponent } from './components'; +import * as NotificationPlatformContent from '../../../../constant/notifications/layout'; +import type { + NotificationErrorProps, + NotificationProps, +} from '../../../../types/notifications'; +import type { NotificationInputLayoutType } from '../../../../types/constant/notifications'; + +const NotificationRenderer = ({ + inputs, + errors, + handleInput, + isEdit = false, +}: { + inputs: NotificationProps; + errors: NotificationErrorProps; + handleInput: (value: any) => void; + isEdit: boolean; +}) => { + const componentOnclick = (value: any, key: string, isDataField: boolean) => { + if (isDataField) { + handleInput({ key: 'data', value: { ...inputs.data, [key]: value } }); + } else { + handleInput({ key, value }); + } + }; + + const components = inputs && NotificationPlatformContent[inputs?.platform]; + + if (!components) { + return null; + } + + const Content = components.map( + (component: NotificationInputLayoutType, index: number) => { + if (component.type === 'group') { + return ( +
+ {component.items.map((component) => { + const value = component.isDataField + ? inputs.data?.[component.key] + : inputs[component.key]; + return getNotificationComponent( + component, + value, + errors[component.key], + componentOnclick, + isEdit + ); + })} +
+ ); + } + + const value = component.isDataField + ? inputs.data?.[component.key] + : inputs[component.key]; + + return getNotificationComponent( + component, + value, + errors[component.key], + componentOnclick, + isEdit + ); + } + ); + + return Content; +}; + +export default NotificationRenderer; diff --git a/app/components/notifications/emails/basic.tsx b/app/components/notifications/emails/basic.tsx new file mode 100644 index 00000000..27b50e5c --- /dev/null +++ b/app/components/notifications/emails/basic.tsx @@ -0,0 +1,161 @@ +const NotificationBasicEmailTemplate = ({ + serviceName, + dashboardUrl, + timestamp, +}: { + serviceName: string; + dashboardUrl: string; + timestamp: string; +}) => { + return ( +
+ + + + + + + + + + + + + + + + + + +
+ + Lunalytics - Outage Detected +
+

+ 🚨 Service Currently Unreachable +

+

+ Our monitoring system has detected that{' '} + {serviceName} is + currently{' '} + down/unreachable. +

+ +

+ Detected at: {timestamp} +

+ + + +

+ You’ll be notified again when the service is back online. +

+
+ + Lunalytics + {' '} + - Made with ❤️ by KSJaay +
+
+ ); +}; + +export default NotificationBasicEmailTemplate; diff --git a/app/components/notifications/emails/index.tsx b/app/components/notifications/emails/index.tsx new file mode 100644 index 00000000..8cdc7422 --- /dev/null +++ b/app/components/notifications/emails/index.tsx @@ -0,0 +1,4 @@ +export { default as NotificationBasicEmailTemplate } from './basic'; +export { default as NotificationPrettyEmailTemplate } from './pretty'; +export { default as NotificationNerdyEmailTemplate } from './nerdy'; +export { default as NotificationRecoveredEmailTemplate } from './recovered'; diff --git a/app/components/notifications/emails/nerdy.tsx b/app/components/notifications/emails/nerdy.tsx new file mode 100644 index 00000000..5cc13c9e --- /dev/null +++ b/app/components/notifications/emails/nerdy.tsx @@ -0,0 +1,251 @@ +const NotificationNerdyEmailTemplate = ({ + id, + serviceName, + dashboardUrl, + address, + type, + timestamp, + status, + latency, + statusMessage, +}: { + id: string; + serviceName: string; + dashboardUrl: string; + address?: string; + type?: string; + timestamp: string; + status?: number; + latency?: number; + statusMessage?: string; +}) => { + return ( +
+ + + + + + + + + + + + + + + + + +
+ + Lunalytics - Outage Detected +
+

+ 🚨 Service Currently Unreachable +

+

+ Our monitoring system has detected that{' '} + {serviceName} is + currently{' '} + down/unreachable. +

+ +

+ 📊 Monitor Information +

+ +
+
+
ID:
+
{id}
+
+ +
+
Name:
+
{serviceName}
+
+ +
+
Address:
+
{address}
+
+ +
+
Type:
+
{type}
+
+ +
+
+
+
+
+ +

+ 🌍 Status Breakdown +

+
+
+
Status:
+
{status}
+
+ +
+
Latency:
+
{latency} ms
+
+ +
+
Message:
+
{statusMessage}
+
+ +
+
Timestamp:
+
{timestamp}
+
+ +
+
+
+
+
+ + + +

+ You can manage notification preferences or review uptime logs in + your dashboard. +

+
+ + Lunalytics + {' '} + - Made with ❤️ by KSJaay +
+
+ ); +}; + +export default NotificationNerdyEmailTemplate; diff --git a/app/components/notifications/emails/pretty.tsx b/app/components/notifications/emails/pretty.tsx new file mode 100644 index 00000000..60bfd8ad --- /dev/null +++ b/app/components/notifications/emails/pretty.tsx @@ -0,0 +1,193 @@ +const NotificationPrettyEmailTemplate = ({ + serviceName, + dashboardUrl, + timestamp, + latency, + status, + message, +}: { + serviceName: string; + dashboardUrl: string; + timestamp: string; + latency: number; + status: string; + message: string; +}) => { + return ( +
+ + + + + + + + + + + + + + + + + + +
+ + Lunalytics - Outage Detected +
+

+ 🚨 Service Currently Unreachable +

+

+ Our monitoring system has detected that{' '} + {serviceName} is + currently{' '} + down/unreachable. +

+ +

+ Detected at {timestamp} +

+ +

+ 📊 Monitor Information +

+ +
+
+
Name:
+
{serviceName}
+
+
+
Latency:
+
{latency}ms
+
+
+
Status:
+
{status}
+
+
+
Message:
+
{message}
+
+
+
Timestamp:
+
{timestamp}
+
+
+ + +
+ + Lunalytics + {' '} + - Made with ❤️ by KSJaay +
+
+ ); +}; + +export default NotificationPrettyEmailTemplate; diff --git a/app/components/notifications/emails/recovered.tsx b/app/components/notifications/emails/recovered.tsx new file mode 100644 index 00000000..43987386 --- /dev/null +++ b/app/components/notifications/emails/recovered.tsx @@ -0,0 +1,182 @@ +const NotificationRecoveredEmailTemplate = ({ + serviceName, + dashboardUrl, + timestamp, +}: { + serviceName: string; + dashboardUrl: string; + timestamp: string; +}) => { + return ( +
+ + + + + + + + + + + + + + + + + + +
+ + Lunalytics - Service Restored +
+

+ ✅ Your monitored service is back online +

+ +

+ Lunalytics detected that{' '} + {serviceName} has + successfully recovered and is now{' '} + operational. +

+ +

+ Resolved at: {timestamp} +

+ +
+

+ Monitoring will continue to ensure your service remains + stable. You’ll be alerted again if any further issues occur. +

+
+ + + +

+ You can manage notification preferences or review uptime logs in + your dashboard. +

+
+ + Lunalytics + {' '} + - Made with ❤️ by KSJaay +
+
+ ); +}; + +export default NotificationRecoveredEmailTemplate; diff --git a/app/components/notifications/list.tsx b/app/components/notifications/list.tsx index bf27ea0a..75050fc3 100644 --- a/app/components/notifications/list.tsx +++ b/app/components/notifications/list.tsx @@ -2,15 +2,7 @@ import classNames from 'classnames'; import { observer } from 'mobx-react-lite'; import useContextStore from '../../context'; import type { NotificationProps } from '../../types/notifications'; - -const NotificationIcons = { - Discord: '/notifications/discord.svg', - HomeAssistant: '/notifications/homeAssistant.svg', - Pushover: '/notifications/pushover.svg', - Slack: '/notifications/slack.svg', - Telegram: '/notifications/telegram.svg', - Webhook: '/notifications/webhook.svg', -}; +import notificationsIcons from '../../constant/notifications.json'; const NotificationList = ({ notifications, @@ -40,7 +32,9 @@ const NotificationList = ({ >
diff --git a/app/components/ui/actionBar.tsx b/app/components/ui/actionBar.tsx index 0c2c1d8c..1afd13d4 100644 --- a/app/components/ui/actionBar.tsx +++ b/app/components/ui/actionBar.tsx @@ -5,7 +5,7 @@ import classNames from 'classnames'; interface ActionBarProps { show: boolean; - variant?: 'full' | 'compact'; + variant?: 'full' | 'compact' | 'floating'; position?: 'top' | 'bottom'; slideDirection?: 'up' | 'down' | 'left' | 'right'; className?: string; diff --git a/app/components/ui/switch.tsx b/app/components/ui/switch.tsx index 56040ddc..8206912a 100644 --- a/app/components/ui/switch.tsx +++ b/app/components/ui/switch.tsx @@ -19,7 +19,7 @@ const SwitchWithText = ({ return ( <>
-
+
{shortDescription && (
{shortDescription}
diff --git a/app/constant/notifications.json b/app/constant/notifications.json index 22158782..bd0b84b6 100644 --- a/app/constant/notifications.json +++ b/app/constant/notifications.json @@ -1,8 +1,37 @@ { - "Discord": { "name": "Discord", "icon": "discord.svg" }, - "HomeAssistant": { "name": "HomeAssistant", "icon": "homeAssistant.svg" }, - "Pushover": { "name": "Pushover", "icon": "pushover.svg" }, - "Slack": { "name": "Slack", "icon": "slack.svg" }, - "Telegram": { "name": "Telegram", "icon": "telegram.svg" }, - "Webhook": { "name": "Webhook", "icon": "webhook.svg" } + "Discord": { + "id": "Discord", + "name": "Discord", + "icon": "discord.svg" + }, + "Email": { + "id": "Email", + "name": "Email (SMTP)", + "icon": "mail.svg" + }, + "HomeAssistant": { + "id": "HomeAssistant", + "name": "HomeAssistant", + "icon": "homeAssistant.svg" + }, + "Pushover": { + "id": "Pushover", + "name": "Pushover", + "icon": "pushover.svg" + }, + "Slack": { + "id": "Slack", + "name": "Slack", + "icon": "slack.svg" + }, + "Telegram": { + "id": "Telegram", + "name": "Telegram", + "icon": "telegram.svg" + }, + "Webhook": { + "id": "Webhook", + "name": "Webhook", + "icon": "webhook.svg" + } } diff --git a/app/constant/notifications/layout/discord.ts b/app/constant/notifications/layout/discord.ts new file mode 100644 index 00000000..8fae24ca --- /dev/null +++ b/app/constant/notifications/layout/discord.ts @@ -0,0 +1,41 @@ +import type { NotificationInputLayoutType } from '../../../types/constant/notifications'; + +export const DiscordLayout: NotificationInputLayoutType[] = [ + { + type: 'group', + items: [ + { + type: 'input', + isDataField: false, + key: 'friendlyName', + title: 'notification.input.friendly_name', + placeholder: 'Lunalytics', + }, + { + type: 'password', + isDataField: false, + key: 'token', + title: 'notification.input.webhook_url', + placeholder: 'https://discord.com/api/webhooks', + subtitle: { + text: 'notification.discord.token_description', + link: 'http://support.discord.com/hc/en-us/articles/228383668-Intro-to-Webhooks', + }, + }, + { + type: 'input', + isDataField: true, + key: 'username', + title: 'notification.input.webhook_username', + placeholder: 'Lunalytics', + }, + { + type: 'input', + isDataField: true, + key: 'textMessage', + title: 'notification.input.text_message', + placeholder: 'Alert @everyone', + }, + ], + }, +]; diff --git a/app/constant/notifications/layout/email.ts b/app/constant/notifications/layout/email.ts new file mode 100644 index 00000000..4d9922d6 --- /dev/null +++ b/app/constant/notifications/layout/email.ts @@ -0,0 +1,88 @@ +import type { NotificationInputLayoutType } from '../../../types/constant/notifications'; + +export const EmailLayout: NotificationInputLayoutType[] = [ + { + type: 'input', + isDataField: false, + key: 'friendlyName', + title: 'Friendly Name', + placeholder: 'Lunalytics', + }, + { + type: 'group', + items: [ + { + type: 'input', + isDataField: false, + key: 'token', + title: 'Hostname', + placeholder: 'smtp.lunalytics.xyz', + subtitle: { + text: 'For more information please read nodemailer documentation: ', + link: 'https://nodemailer.com', + }, + }, + { + type: 'input', + isDataField: true, + key: 'port', + title: 'Port', + placeholder: '587', + }, + ], + }, + { + type: 'switch', + isDataField: true, + key: 'security', + title: 'Security', + subtitle: { + text: 'Whether TLS (465) should be used to connect to the SMTP server', + }, + }, + { + type: 'group', + items: [ + { + type: 'input', + isDataField: true, + key: 'username', + title: 'Username', + placeholder: 'Lunalytics', + }, + { + type: 'password', + isDataField: true, + key: 'password', + title: 'Password', + placeholder: 'xxxxxxxxxxxx', + }, + { + type: 'input', + isDataField: true, + key: 'fromEmail', + title: 'From Email', + placeholder: 'ksjaay@lunalytics.xyz', + }, + { + type: 'input', + isDataField: true, + key: 'toEmail', + title: 'To Email(s)', + placeholder: 'ksjaay@lunalytics.xyz, example@lunalytics.xyz', + }, + { + type: 'input', + isDataField: true, + key: 'ccEmail', + title: 'CC Email(s)', + }, + { + type: 'input', + isDataField: true, + key: 'bccEmail', + title: 'BCC Email(s)', + }, + ], + }, +]; diff --git a/app/constant/notifications/layout/homeAssistant.ts b/app/constant/notifications/layout/homeAssistant.ts new file mode 100644 index 00000000..8e013d6e --- /dev/null +++ b/app/constant/notifications/layout/homeAssistant.ts @@ -0,0 +1,45 @@ +import type { NotificationInputLayoutType } from '../../../types/constant/notifications'; + +export const HomeAssistantLayout: NotificationInputLayoutType[] = [ + { + type: 'group', + items: [ + { + type: 'input', + isDataField: false, + key: 'friendlyName', + title: 'Friendly Name', + placeholder: 'Lunalytics', + }, + { + type: 'input', + isDataField: true, + key: 'homeAssistantUrl', + title: 'HomeAssistant URL', + placeholder: 'https://www.home-assistant.io', + subtitle: { + text: 'The URL of your Home Assistant instance.', + }, + }, + { + type: 'input', + isDataField: true, + key: 'homeAssistantNotificationService', + title: 'Notification Service', + placeholder: 'mobile_app_', + subtitle: { + text: 'Notification service to use.', + }, + }, + { + type: 'password', + isDataField: false, + key: 'token', + title: 'Long Lived Access Token', + subtitle: { + text: 'Long-lived access tokens can be created using the "Long-Lived Access Tokens" section at the bottom of a user\'s Home Assistant profile page.', + }, + }, + ], + }, +]; diff --git a/app/constant/notifications/layout/index.ts b/app/constant/notifications/layout/index.ts new file mode 100644 index 00000000..582dd2f2 --- /dev/null +++ b/app/constant/notifications/layout/index.ts @@ -0,0 +1,7 @@ +export { DiscordLayout as Discord } from './discord'; +export { EmailLayout as Email } from './email'; +export { HomeAssistantLayout as HomeAssistant } from './homeAssistant'; +export { PushoverLayout as Pushover } from './pushover'; +export { SlackLayout as Slack } from './slack'; +export { TelegramLayout as Telegram } from './telegram'; +export { WebhookLayout as Webhook } from './webhook'; diff --git a/app/constant/notifications/layout/pushover.ts b/app/constant/notifications/layout/pushover.ts new file mode 100644 index 00000000..29f555ac --- /dev/null +++ b/app/constant/notifications/layout/pushover.ts @@ -0,0 +1,54 @@ +import type { NotificationInputLayoutType } from '../../../types/constant/notifications'; + +export const PushoverLayout: NotificationInputLayoutType[] = [ + { + type: 'group', + items: [ + { + type: 'input', + key: 'friendlyName', + title: 'Friendly Name', + placeholder: 'Lunalytics', + }, + { + type: 'password', + isDataField: true, + key: 'userKey', + title: 'User Key', + placeholder: 'xxxxxxxxxxxxxxxx', + }, + { + type: 'password', + key: 'token', + title: 'Application Token', + placeholder: 'xxxxxxxxxxxxxxxx', + }, + { + type: 'input', + isDataField: true, + key: 'device', + title: 'Device', + }, + ], + }, + { + type: 'dropdown', + isDataField: true, + key: 'priority', + title: 'Priority', + defaultValue: '0', + options: [ + { id: '-2' }, + { id: '-1' }, + { id: '0' }, + { id: '1' }, + { id: '2' }, + ], + }, + { + type: 'number', + isDataField: true, + key: 'ttl', + title: 'Message TTL (Seconds)', + }, +]; diff --git a/app/constant/notifications/layout/slack.ts b/app/constant/notifications/layout/slack.ts new file mode 100644 index 00000000..246a3734 --- /dev/null +++ b/app/constant/notifications/layout/slack.ts @@ -0,0 +1,48 @@ +import type { NotificationInputLayoutType } from '../../../types/constant/notifications'; + +export const SlackLayout: NotificationInputLayoutType[] = [ + { + type: 'group', + items: [ + { + type: 'input', + isDataField: false, + key: 'friendlyName', + title: 'Friendly Name', + placeholder: 'Lunalytics', + }, + { + type: 'password', + isDataField: false, + key: 'token', + title: 'Webhook URL', + placeholder: 'https://hooks.slack.com/services/...', + subtitle: { + text: 'For more information about how to create a webhook checkout this guide: ', + link: 'https://docs.slack.dev/messaging/sending-messages-using-incoming-webhooks/', + }, + }, + { + type: 'input', + isDataField: true, + key: 'username', + title: 'Webhook Username', + placeholder: 'Lunalytics', + }, + { + type: 'input', + isDataField: true, + key: 'textMessage', + title: 'Text Message', + placeholder: 'Alert @here', + }, + ], + }, + { + type: 'input', + isDataField: true, + key: 'channel', + title: 'Channel name', + placeholder: '#lunalytics-alerts', + }, +]; diff --git a/app/constant/notifications/layout/telegram.ts b/app/constant/notifications/layout/telegram.ts new file mode 100644 index 00000000..ccd7c110 --- /dev/null +++ b/app/constant/notifications/layout/telegram.ts @@ -0,0 +1,57 @@ +import type { NotificationInputLayoutType } from '../../../types/constant/notifications'; + +export const TelegramLayout: NotificationInputLayoutType[] = [ + { + type: 'group', + items: [ + { + type: 'input', + isDataField: false, + key: 'friendlyName', + title: 'Friendly Name', + placeholder: 'Lunalytics', + }, + { + type: 'password', + isDataField: false, + key: 'token', + title: 'Bot Token', + placeholder: 'Bot Token', + subtitle: { + text: 'For more information about how to create a bot token checkout this guide: ', + link: 'https://core.telegram.org/bots/tutorial', + }, + }, + { + type: 'input', + isDataField: true, + key: 'chatId', + title: 'Chat ID', + placeholder: '12389741289', + subtitle: { + text: 'For more information about how to find the chat ID for a channel checkout this guide: ', + link: 'https://gist.github.com/nafiesl/4ad622f344cd1dc3bb1ecbe468ff9f8a', + }, + }, + ], + }, + { type: 'empty' }, + { + type: 'switch', + isDataField: true, + key: 'disableNotification', + title: 'Disable Notification', + subtitle: { + text: 'If enabled, users will receive a notification without any sound.', + }, + }, + { + type: 'switch', + isDataField: true, + key: 'protectContent', + title: 'Protect Content', + subtitle: { + text: 'If enabled, message content will be protected from forwarding and saving.', + }, + }, +]; diff --git a/app/constant/notifications/layout/webhook.ts b/app/constant/notifications/layout/webhook.ts new file mode 100644 index 00000000..f8807407 --- /dev/null +++ b/app/constant/notifications/layout/webhook.ts @@ -0,0 +1,41 @@ +import type { NotificationInputLayoutType } from '../../../types/constant/notifications'; + +export const WebhookLayout: NotificationInputLayoutType[] = [ + { + type: 'group', + items: [ + { + type: 'input', + isDataField: false, + key: 'friendlyName', + title: 'Friendly Name', + placeholder: 'Lunalytics', + }, + { + type: 'password', + isDataField: false, + key: 'token', + title: 'Webhook URL', + placeholder: 'https://lunalytics.xyz/webhooks/example', + }, + ], + }, + { + type: 'dropdown', + isDataField: true, + key: 'requestType', + title: 'Request Type', + defaultValue: 'application/json', + options: [{ id: 'application/json' }, { id: 'form-data' }], + }, + { + type: 'textarea', + isDataField: true, + key: 'additionalHeaders', + rows: 8, + title: 'Additional Headers', + subtitle: { + text: 'Add additional headers to be sent with the webhook request. Make sure to follow JSON key/value format.', + }, + }, +]; diff --git a/app/pages/notifications.tsx b/app/pages/notifications.tsx index 7418c010..894c3ce1 100644 --- a/app/pages/notifications.tsx +++ b/app/pages/notifications.tsx @@ -3,19 +3,19 @@ import '../styles/pages/notifications.scss'; // import dependencies import { Button } from '@lunalytics/ui'; -import { useEffect, useMemo, useState } from 'react'; import { observer } from 'mobx-react-lite'; +import { useTranslation } from 'react-i18next'; +import { useEffect, useMemo, useState } from 'react'; // import local files import useContextStore from '../context'; import Navigation from '../components/navigation'; +import useScreenSize from '../hooks/useScreenSize'; +import { filterData } from '../../shared/utils/search'; import NotificationList from '../components/notifications/list'; import NotificationModal from '../components/modal/notification'; -import NotificationContent from '../components/notifications/content'; import HomeNotificationHeader from '../components/notifications/header'; -import { filterData } from '../../shared/utils/search'; -import { useTranslation } from 'react-i18next'; -import useScreenSize from '../hooks/useScreenSize'; +import NotificationRender from '../components/notifications/content'; const Notifications = () => { const { @@ -54,12 +54,12 @@ const Notifications = () => { return (
- +
); } - const content = isDesktop ? : null; + const content = isDesktop ? : null; return ( ; valid_status_codes: string[]; email: string; - type: Type; + type: MonitorType; notificationId: string; notificationType: string; uptimePercentage: number; diff --git a/app/types/notifications.d.ts b/app/types/notifications.d.ts index 9c4ddb61..1c6cb91d 100644 --- a/app/types/notifications.d.ts +++ b/app/types/notifications.d.ts @@ -1,6 +1,7 @@ export type NotificationMessageType = 'basic' | 'pretty' | 'nerdy'; export type NotificationPlatforms = | 'Discord' + | 'Email' | 'HomeAssistant' | 'Pushover' | 'Slack' diff --git a/babel.config.json b/babel.config.json new file mode 100644 index 00000000..d5603011 --- /dev/null +++ b/babel.config.json @@ -0,0 +1,13 @@ +{ + "presets": [ + [ + "@babel/preset-env", + { + "targets": { "node": "current" }, + "modules": false + } + ], + "@babel/preset-react", + "@babel/preset-typescript" + ] +} diff --git a/package-lock.json b/package-lock.json index e16f4cee..e7f53f87 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "lunalytics", - "version": "0.10.8", + "version": "0.10.9", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "lunalytics", - "version": "0.10.8", + "version": "0.10.9", "license": "SEE LICENSE IN LICENSE", "dependencies": { "@dnd-kit/core": "6.3.1", @@ -37,6 +37,7 @@ "mobx": "6.13.7", "mobx-react-lite": "4.1.0", "nanoid": "5.1.5", + "nodemailer": "^7.0.10", "pg": "8.16.0", "ping": "0.4.4", "react": "19.1.1", @@ -46,7 +47,7 @@ "react-icons": "5.5.0", "react-router-dom": "7.6.2", "react-toastify": "11.0.5", - "react-window": "1.8.11", + "react-window": "^1.8.11", "recharts": "3.2.1", "swapy": "1.0.5", "ua-parser-js": "2.0.3", @@ -55,6 +56,10 @@ "winston-daily-rotate-file": "5.0.0" }, "devDependencies": { + "@babel/core": "^7.28.5", + "@babel/preset-env": "^7.28.5", + "@babel/preset-react": "^7.28.5", + "@babel/preset-typescript": "^7.28.5", "@eslint/compat": "1.3.1", "@eslint/eslintrc": "3.3.1", "@eslint/js": "9.33.0", @@ -78,7 +83,7 @@ "oxlint": "1.5.0", "rollup-plugin-visualizer": "6.0.3", "sass": "1.89.2", - "typescript": "5.9.2", + "typescript": "^5.9.2", "typescript-eslint": "8.39.1", "vite": "7.1.12", "vite-plugin-compression2": "2.2.0", @@ -88,40 +93,1710 @@ "node": ">= 22.x" } }, + "node_modules/@babel/code-frame": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", + "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.27.1", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/code-frame/node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@babel/compat-data": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.5.tgz", + "integrity": "sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.5.tgz", + "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.5", + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-module-transforms": "^7.28.3", + "@babel/helpers": "^7.28.4", + "@babel/parser": "^7.28.5", + "@babel/template": "^7.27.2", + "@babel/traverse": "^7.28.5", + "@babel/types": "^7.28.5", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/core/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/generator": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.5.tgz", + "integrity": "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.28.5", + "@babel/types": "^7.28.5", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-annotate-as-pure": { + "version": "7.27.3", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.3.tgz", + "integrity": "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.27.3" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", + "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.27.2", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-create-class-features-plugin": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.28.5.tgz", + "integrity": "sha512-q3WC4JfdODypvxArsJQROfupPBq9+lMwjKq7C33GhbFYJsufD0yd/ziwD+hJucLeWsnFPWZjsU2DNFqBPE7jwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-member-expression-to-functions": "^7.28.5", + "@babel/helper-optimise-call-expression": "^7.27.1", + "@babel/helper-replace-supers": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", + "@babel/traverse": "^7.28.5", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-create-class-features-plugin/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-create-regexp-features-plugin": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.28.5.tgz", + "integrity": "sha512-N1EhvLtHzOvj7QQOUCCS3NrPJP8c5W6ZXCHDn7Yialuy1iu4r5EmIYkXlKNqT99Ciw+W0mDqWoR6HWMZlFP3hw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.3", + "regexpu-core": "^6.3.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-create-regexp-features-plugin/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-define-polyfill-provider": { + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.5.tgz", + "integrity": "sha512-uJnGFcPsWQK8fvjgGP5LZUZZsYGIoPeRjSF5PGwrelYgq7Q15/Ft9NGFp1zglwgIv//W0uG4BevRuSJRyylZPg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-plugin-utils": "^7.27.1", + "debug": "^4.4.1", + "lodash.debounce": "^4.0.8", + "resolve": "^1.22.10" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/@babel/helper-define-polyfill-provider/node_modules/resolve": { + "version": "1.22.11", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", + "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-member-expression-to-functions": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.28.5.tgz", + "integrity": "sha512-cwM7SBRZcPCLgl8a7cY0soT1SptSzAlMH39vwiRpOQkJlh53r5hdHwLSCZpQdVLT39sZt+CRpNwYG4Y2v77atg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.28.5", + "@babel/types": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", + "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz", + "integrity": "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1", + "@babel/traverse": "^7.28.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-optimise-call-expression": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.27.1.tgz", + "integrity": "sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz", + "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-remap-async-to-generator": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.27.1.tgz", + "integrity": "sha512-7fiA521aVw8lSPeI4ZOD3vRFkoqkJcS+z4hFo82bFSH/2tNd6eJ5qCVMS5OzDmZh/kaHQeBaeyxK6wljcPtveA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.1", + "@babel/helper-wrap-function": "^7.27.1", + "@babel/traverse": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-replace-supers": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.27.1.tgz", + "integrity": "sha512-7EHz6qDZc8RYS5ElPoShMheWvEgERonFCs7IAonWLLUTXW59DP14bCZt89/GKyreYn8g3S83m21FelHKbeDCKA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-member-expression-to-functions": "^7.27.1", + "@babel/helper-optimise-call-expression": "^7.27.1", + "@babel/traverse": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.27.1.tgz", + "integrity": "sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/helper-string-parser": { "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", "dev": true, "license": "MIT", - "engines": { - "node": ">=6.9.0" + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-wrap-function": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.28.3.tgz", + "integrity": "sha512-zdf983tNfLZFletc0RRXYrHrucBEg95NIFMkn6K9dbeMYnsgHaSBGcQqdsCSStG2PYwRre0Qc2NNSCXbG+xc6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.27.2", + "@babel/traverse": "^7.28.3", + "@babel/types": "^7.28.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.4.tgz", + "integrity": "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.4" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.5.tgz", + "integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.5" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-firefox-class-in-computed-class-key": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.28.5.tgz", + "integrity": "sha512-87GDMS3tsmMSi/3bWOte1UblL+YUTFMV8SZPZ2eSEL17s74Cw/l63rR6NmGVKMYW2GYi85nE+/d6Hw5N0bEk2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/traverse": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-safari-class-field-initializer-scope": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-class-field-initializer-scope/-/plugin-bugfix-safari-class-field-initializer-scope-7.27.1.tgz", + "integrity": "sha512-qNeq3bCKnGgLkEXUuFry6dPlGfCdQNZbn7yUAPCInwAJHMU7THJfrBSozkcWq5sNM6RcF3S8XyQL2A52KNR9IA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.27.1.tgz", + "integrity": "sha512-g4L7OYun04N1WyqMNjldFwlfPCLVkgB54A/YCXICZYBsvJJE3kByKv9c9+R/nAfmIfjl2rKYLNyMHboYbZaWaA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.27.1.tgz", + "integrity": "sha512-oO02gcONcD5O1iTLi/6frMJBIwWEHceWGSGqrpCmEL8nogiS6J9PBlE48CaK20/Jx1LuRml9aDftLgdjXT8+Cw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", + "@babel/plugin-transform-optional-chaining": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.13.0" + } + }, + "node_modules/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.28.3.tgz", + "integrity": "sha512-b6YTX108evsvE4YgWyQ921ZAFFQm3Bn+CA3+ZXlNVnPhx+UfsVURoPjfGAPCjBgrqo30yX/C2nZGX96DxvR9Iw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/traverse": "^7.28.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-proposal-private-property-in-object": { + "version": "7.21.0-placeholder-for-preset-env.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz", + "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-assertions": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.27.1.tgz", + "integrity": "sha512-UT/Jrhw57xg4ILHLFnzFpPDlMbcdEicaAtjPQpbj9wa8T4r5KVWCimHcL/460g8Ht0DMxDyjsLgiWSkVjnwPFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-attributes": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.27.1.tgz", + "integrity": "sha512-oFT0FrKHgF53f4vOsZGi2Hh3I35PfSmVs4IBFLFj4dnafP+hIWDLg3VyKmUHfLoLHlyxY4C7DGtmHuJgn+IGww==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.27.1.tgz", + "integrity": "sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.27.1.tgz", + "integrity": "sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-unicode-sets-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz", + "integrity": "sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-arrow-functions": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.27.1.tgz", + "integrity": "sha512-8Z4TGic6xW70FKThA5HYEKKyBpOOsucTOD1DjU3fZxDg+K3zBJcXMFnt/4yQiZnf5+MiOMSXQ9PaEK/Ilh1DeA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-async-generator-functions": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.28.0.tgz", + "integrity": "sha512-BEOdvX4+M765icNPZeidyADIvQ1m1gmunXufXxvRESy/jNNyfovIqUyE7MVgGBjWktCoJlzvFA1To2O4ymIO3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-remap-async-to-generator": "^7.27.1", + "@babel/traverse": "^7.28.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-async-to-generator": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.27.1.tgz", + "integrity": "sha512-NREkZsZVJS4xmTr8qzE5y8AfIPqsdQfRuUiLRTEzb7Qii8iFWCyDKaUV2c0rCuh4ljDZ98ALHP/PetiBV2nddA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-remap-async-to-generator": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoped-functions": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.27.1.tgz", + "integrity": "sha512-cnqkuOtZLapWYZUYM5rVIdv1nXYuFVIltZ6ZJ7nIj585QsjKM5dhL2Fu/lICXZ1OyIAFc7Qy+bvDAtTXqGrlhg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoping": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.28.5.tgz", + "integrity": "sha512-45DmULpySVvmq9Pj3X9B+62Xe+DJGov27QravQJU1LLcapR6/10i+gYVAucGGJpHBp5mYxIMK4nDAT/QDLr47g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-class-properties": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.27.1.tgz", + "integrity": "sha512-D0VcalChDMtuRvJIu3U/fwWjf8ZMykz5iZsg77Nuj821vCKI3zCyRLwRdWbsuJ/uRwZhZ002QtCqIkwC/ZkvbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-class-static-block": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.28.3.tgz", + "integrity": "sha512-LtPXlBbRoc4Njl/oh1CeD/3jC+atytbnf/UqLoqTDcEYGUPj022+rvfkbDYieUrSj3CaV4yHDByPE+T2HwfsJg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.28.3", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.12.0" + } + }, + "node_modules/@babel/plugin-transform-classes": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.28.4.tgz", + "integrity": "sha512-cFOlhIYPBv/iBoc+KS3M6et2XPtbT2HiCRfBXWtfpc9OAyostldxIf9YAYB6ypURBBbx+Qv6nyrLzASfJe+hBA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-globals": "^7.28.0", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-replace-supers": "^7.27.1", + "@babel/traverse": "^7.28.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-computed-properties": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.27.1.tgz", + "integrity": "sha512-lj9PGWvMTVksbWiDT2tW68zGS/cyo4AkZ/QTp0sQT0mjPopCmrSkzxeXkznjqBxzDI6TclZhOJbBmbBLjuOZUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/template": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-destructuring": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.28.5.tgz", + "integrity": "sha512-Kl9Bc6D0zTUcFUvkNuQh4eGXPKKNDOJQXVyyM4ZAQPMveniJdxi8XMJwLo+xSoW3MIq81bD33lcUe9kZpl0MCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/traverse": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-dotall-regex": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.27.1.tgz", + "integrity": "sha512-gEbkDVGRvjj7+T1ivxrfgygpT7GUd4vmODtYpbs0gZATdkX8/iSnOtZSxiZnsgm1YjTgjI6VKBGSJJevkrclzw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-duplicate-keys": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.27.1.tgz", + "integrity": "sha512-MTyJk98sHvSs+cvZ4nOauwTTG1JeonDjSGvGGUNHreGQns+Mpt6WX/dVzWBHgg+dYZhkC4X+zTDfkTU+Vy9y7Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-duplicate-named-capturing-groups-regex": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.27.1.tgz", + "integrity": "sha512-hkGcueTEzuhB30B3eJCbCYeCaaEQOmQR0AdvzpD4LoN0GXMWzzGSuRrxR2xTnCrvNbVwK9N6/jQ92GSLfiZWoQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-dynamic-import": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.27.1.tgz", + "integrity": "sha512-MHzkWQcEmjzzVW9j2q8LGjwGWpG2mjwaaB0BNQwst3FIjqsg8Ct/mIZlvSPJvfi9y2AC8mi/ktxbFVL9pZ1I4A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-explicit-resource-management": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-explicit-resource-management/-/plugin-transform-explicit-resource-management-7.28.0.tgz", + "integrity": "sha512-K8nhUcn3f6iB+P3gwCv/no7OdzOZQcKchW6N389V6PD8NUWKZHzndOd9sPDVbMoBsbmjMqlB4L9fm+fEFNVlwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/plugin-transform-destructuring": "^7.28.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-exponentiation-operator": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.28.5.tgz", + "integrity": "sha512-D4WIMaFtwa2NizOp+dnoFjRez/ClKiC2BqqImwKd1X28nqBtZEyCYJ2ozQrrzlxAFrcrjxo39S6khe9RNDlGzw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-export-namespace-from": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.27.1.tgz", + "integrity": "sha512-tQvHWSZ3/jH2xuq/vZDy0jNn+ZdXJeM8gHvX4lnJmsc3+50yPlWdZXIc5ay+umX+2/tJIqHqiEqcJvxlmIvRvQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-for-of": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.27.1.tgz", + "integrity": "sha512-BfbWFFEJFQzLCQ5N8VocnCtA8J1CLkNTe2Ms2wocj75dd6VpiqS5Z5quTYcUoo4Yq+DN0rtikODccuv7RU81sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-function-name": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.27.1.tgz", + "integrity": "sha512-1bQeydJF9Nr1eBCMMbC+hdwmRlsv5XYOMu03YSWFwNs0HsAmtSxxF1fyuYPqemVldVyFmlCU7w8UE14LupUSZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-compilation-targets": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/traverse": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-json-strings": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.27.1.tgz", + "integrity": "sha512-6WVLVJiTjqcQauBhn1LkICsR2H+zm62I3h9faTDKt1qP4jn2o72tSvqMwtGFKGTpojce0gJs+76eZ2uCHRZh0Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-literals": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.27.1.tgz", + "integrity": "sha512-0HCFSepIpLTkLcsi86GG3mTUzxV5jpmbv97hTETW3yzrAij8aqlD36toB1D0daVFJM8NK6GvKO0gslVQmm+zZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-logical-assignment-operators": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.28.5.tgz", + "integrity": "sha512-axUuqnUTBuXyHGcJEVVh9pORaN6wC5bYfE7FGzPiaWa3syib9m7g+/IT/4VgCOe2Upef43PHzeAvcrVek6QuuA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-member-expression-literals": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.27.1.tgz", + "integrity": "sha512-hqoBX4dcZ1I33jCSWcXrP+1Ku7kdqXf1oeah7ooKOIiAdKQ+uqftgCFNOSzA5AMS2XIHEYeGFg4cKRCdpxzVOQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-amd": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.27.1.tgz", + "integrity": "sha512-iCsytMg/N9/oFq6n+gFTvUYDZQOMK5kEdeYxmxt91fcJGycfxVP9CnrxoliM0oumFERba2i8ZtwRUCMhvP1LnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-transforms": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-commonjs": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.27.1.tgz", + "integrity": "sha512-OJguuwlTYlN0gBZFRPqwOGNWssZjfIUdS7HMYtN8c1KmwpwHFBwTeFZrg9XZa+DFTitWOW5iTAG7tyCUPsCCyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-transforms": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-systemjs": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.28.5.tgz", + "integrity": "sha512-vn5Jma98LCOeBy/KpeQhXcV2WZgaRUtjwQmjoBuLNlOmkg0fB5pdvYVeWRYI69wWKwK2cD1QbMiUQnoujWvrew==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-transforms": "^7.28.3", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-umd": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.27.1.tgz", + "integrity": "sha512-iQBE/xC5BV1OxJbp6WG7jq9IWiD+xxlZhLrdwpPkTX3ydmXdvoCpyfJN7acaIBZaOqTfr76pgzqBJflNbeRK+w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-transforms": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.27.1.tgz", + "integrity": "sha512-SstR5JYy8ddZvD6MhV0tM/j16Qds4mIpJTOd1Yu9J9pJjH93bxHECF7pgtc28XvkzTD6Pxcm/0Z73Hvk7kb3Ng==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-new-target": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.27.1.tgz", + "integrity": "sha512-f6PiYeqXQ05lYq3TIfIDu/MtliKUbNwkGApPUvyo6+tc7uaR4cPjPe7DFPr15Uyycg2lZU6btZ575CuQoYh7MQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-nullish-coalescing-operator": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.27.1.tgz", + "integrity": "sha512-aGZh6xMo6q9vq1JGcw58lZ1Z0+i0xB2x0XaauNIUXd6O1xXc3RwoWEBlsTQrY4KQ9Jf0s5rgD6SiNkaUdJegTA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-numeric-separator": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.27.1.tgz", + "integrity": "sha512-fdPKAcujuvEChxDBJ5c+0BTaS6revLV7CJL08e4m3de8qJfNIuCc2nc7XJYOjBoTMJeqSmwXJ0ypE14RCjLwaw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-object-rest-spread": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.28.4.tgz", + "integrity": "sha512-373KA2HQzKhQCYiRVIRr+3MjpCObqzDlyrM6u4I201wL8Mp2wHf7uB8GhDwis03k2ti8Zr65Zyyqs1xOxUF/Ew==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/plugin-transform-destructuring": "^7.28.0", + "@babel/plugin-transform-parameters": "^7.27.7", + "@babel/traverse": "^7.28.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-object-super": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.27.1.tgz", + "integrity": "sha512-SFy8S9plRPbIcxlJ8A6mT/CxFdJx/c04JEctz4jf8YZaVS2px34j7NXRrlGlHkN/M2gnpL37ZpGRGVFLd3l8Ng==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-replace-supers": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-optional-catch-binding": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.27.1.tgz", + "integrity": "sha512-txEAEKzYrHEX4xSZN4kJ+OfKXFVSWKB2ZxM9dpcE3wT7smwkNmXo5ORRlVzMVdJbD+Q8ILTgSD7959uj+3Dm3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-optional-chaining": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.28.5.tgz", + "integrity": "sha512-N6fut9IZlPnjPwgiQkXNhb+cT8wQKFlJNqcZkWlcTqkcqx6/kU4ynGmLFoa4LViBSirn05YAwk+sQBbPfxtYzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-parameters": { + "version": "7.27.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.27.7.tgz", + "integrity": "sha512-qBkYTYCb76RRxUM6CcZA5KRu8K4SM8ajzVeUgVdMVO9NN9uI/GaVmBg/WKJJGnNokV9SY8FxNOVWGXzqzUidBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-private-methods": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.27.1.tgz", + "integrity": "sha512-10FVt+X55AjRAYI9BrdISN9/AQWHqldOeZDUoLyif1Kn05a56xVBXb8ZouL8pZ9jem8QpXaOt8TS7RHUIS+GPA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-private-property-in-object": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.27.1.tgz", + "integrity": "sha512-5J+IhqTi1XPa0DXF83jYOaARrX+41gOewWbkPyjMNRDqgOCqdffGh8L3f/Ek5utaEBZExjSAzcyjmV9SSAWObQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.1", + "@babel/helper-create-class-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-property-literals": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.27.1.tgz", + "integrity": "sha512-oThy3BCuCha8kDZ8ZkgOg2exvPYUlprMukKQXI1r1pJ47NCvxfkEy8vK+r/hT9nF0Aa4H1WUPZZjHTFtAhGfmQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-display-name": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.28.0.tgz", + "integrity": "sha512-D6Eujc2zMxKjfa4Zxl4GHMsmhKKZ9VpcqIchJLvwTxad9zWIYulwYItBovpDOoNLISpcZSXoDJ5gaGbQUDqViA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.27.1.tgz", + "integrity": "sha512-2KH4LWGSrJIkVf5tSiBFYuXDAoWRq2MMwgivCf+93dd0GQi8RXLjKA/0EvRnVV5G0hrHczsquXuD01L8s6dmBw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.1", + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/plugin-syntax-jsx": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-development": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.27.1.tgz", + "integrity": "sha512-ykDdF5yI4f1WrAolLqeF3hmYU12j9ntLQl/AOG1HAS21jxyg1Q0/J/tpREuYLfatGdGmXp/3yS0ZA76kOlVq9Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/plugin-transform-react-jsx": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-pure-annotations": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.27.1.tgz", + "integrity": "sha512-JfuinvDOsD9FVMTHpzA/pBLisxpv1aSf+OIV8lgH3MuWrks19R27e6a6DipIg4aX1Zm9Wpb04p8wljfKrVSnPA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-regenerator": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.28.4.tgz", + "integrity": "sha512-+ZEdQlBoRg9m2NnzvEeLgtvBMO4tkFBw5SQIUgLICgTrumLoU7lr+Oghi6km2PFj+dbUt2u1oby2w3BDO9YQnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-regexp-modifiers": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regexp-modifiers/-/plugin-transform-regexp-modifiers-7.27.1.tgz", + "integrity": "sha512-TtEciroaiODtXvLZv4rmfMhkCv8jx3wgKpL68PuiPh2M4fvz5jhsA7697N1gMvkvr/JTF13DrFYyEbY9U7cVPA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-reserved-words": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.27.1.tgz", + "integrity": "sha512-V2ABPHIJX4kC7HegLkYoDpfg9PVmuWy/i6vUM5eGK22bx4YVFD3M5F0QQnWQoDs6AGsUWTVOopBiMFQgHaSkVw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-shorthand-properties": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.27.1.tgz", + "integrity": "sha512-N/wH1vcn4oYawbJ13Y/FxcQrWk63jhfNa7jef0ih7PHSIHX2LB7GWE1rkPrOnka9kwMxb6hMl19p7lidA+EHmQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-spread": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.27.1.tgz", + "integrity": "sha512-kpb3HUqaILBJcRFVhFUs6Trdd4mkrzcGXss+6/mxUd273PfbWqSDHRzMT2234gIg2QYfAjvXLSquP1xECSg09Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-sticky-regex": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.27.1.tgz", + "integrity": "sha512-lhInBO5bi/Kowe2/aLdBAawijx+q1pQzicSgnkB6dUPc1+RC8QmJHKf2OjvU+NZWitguJHEaEmbV6VWEouT58g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-template-literals": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.27.1.tgz", + "integrity": "sha512-fBJKiV7F2DxZUkg5EtHKXQdbsbURW3DZKQUWphDum0uRP6eHGGa/He9mc0mypL680pb+e/lDIthRohlv8NCHkg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-typeof-symbol": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.27.1.tgz", + "integrity": "sha512-RiSILC+nRJM7FY5srIyc4/fGIwUhyDuuBSdWn4y6yT6gm652DpCHZjIipgn6B7MQ1ITOUnAKWixEUjQRIBIcLw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-typescript": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.28.5.tgz", + "integrity": "sha512-x2Qa+v/CuEoX7Dr31iAfr0IhInrVOWZU/2vJMJ00FOR/2nM0BcBEclpaf9sWCDc+v5e9dMrhSH8/atq/kX7+bA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-create-class-features-plugin": "^7.28.5", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", + "@babel/plugin-syntax-typescript": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-escapes": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.27.1.tgz", + "integrity": "sha512-Ysg4v6AmF26k9vpfFuTZg8HRfVWzsh1kVfowA23y9j/Gu6dOuahdUVhkLqpObp3JIv27MLSii6noRnuKN8H0Mg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-property-regex": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.27.1.tgz", + "integrity": "sha512-uW20S39PnaTImxp39O5qFlHLS9LJEmANjMG7SxIhap8rCHqu0Ik+tLEPX5DKmHn6CsWQ7j3lix2tFOa5YtL12Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-regex": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.27.1.tgz", + "integrity": "sha512-xvINq24TRojDuyt6JGtHmkVkrfVV3FPT16uytxImLeBZqW3/H52yN+kM1MGuyPkIQxrzKwPHs5U/MP3qKyzkGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-sets-regex": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.27.1.tgz", + "integrity": "sha512-EtkOujbc4cgvb0mlpQefi4NTPBzhSIevblFevACNLUspmrALgmEBdL/XfnyyITfd8fKBZrZys92zOWcik7j9Tw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/preset-env": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.28.5.tgz", + "integrity": "sha512-S36mOoi1Sb6Fz98fBfE+UZSpYw5mJm0NUHtIKrOuNcqeFauy1J6dIvXm2KRVKobOSaGq4t/hBXdN4HGU3wL9Wg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.28.5", + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-validator-option": "^7.27.1", + "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.28.5", + "@babel/plugin-bugfix-safari-class-field-initializer-scope": "^7.27.1", + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.27.1", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.27.1", + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.28.3", + "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", + "@babel/plugin-syntax-import-assertions": "^7.27.1", + "@babel/plugin-syntax-import-attributes": "^7.27.1", + "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", + "@babel/plugin-transform-arrow-functions": "^7.27.1", + "@babel/plugin-transform-async-generator-functions": "^7.28.0", + "@babel/plugin-transform-async-to-generator": "^7.27.1", + "@babel/plugin-transform-block-scoped-functions": "^7.27.1", + "@babel/plugin-transform-block-scoping": "^7.28.5", + "@babel/plugin-transform-class-properties": "^7.27.1", + "@babel/plugin-transform-class-static-block": "^7.28.3", + "@babel/plugin-transform-classes": "^7.28.4", + "@babel/plugin-transform-computed-properties": "^7.27.1", + "@babel/plugin-transform-destructuring": "^7.28.5", + "@babel/plugin-transform-dotall-regex": "^7.27.1", + "@babel/plugin-transform-duplicate-keys": "^7.27.1", + "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.27.1", + "@babel/plugin-transform-dynamic-import": "^7.27.1", + "@babel/plugin-transform-explicit-resource-management": "^7.28.0", + "@babel/plugin-transform-exponentiation-operator": "^7.28.5", + "@babel/plugin-transform-export-namespace-from": "^7.27.1", + "@babel/plugin-transform-for-of": "^7.27.1", + "@babel/plugin-transform-function-name": "^7.27.1", + "@babel/plugin-transform-json-strings": "^7.27.1", + "@babel/plugin-transform-literals": "^7.27.1", + "@babel/plugin-transform-logical-assignment-operators": "^7.28.5", + "@babel/plugin-transform-member-expression-literals": "^7.27.1", + "@babel/plugin-transform-modules-amd": "^7.27.1", + "@babel/plugin-transform-modules-commonjs": "^7.27.1", + "@babel/plugin-transform-modules-systemjs": "^7.28.5", + "@babel/plugin-transform-modules-umd": "^7.27.1", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.27.1", + "@babel/plugin-transform-new-target": "^7.27.1", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.27.1", + "@babel/plugin-transform-numeric-separator": "^7.27.1", + "@babel/plugin-transform-object-rest-spread": "^7.28.4", + "@babel/plugin-transform-object-super": "^7.27.1", + "@babel/plugin-transform-optional-catch-binding": "^7.27.1", + "@babel/plugin-transform-optional-chaining": "^7.28.5", + "@babel/plugin-transform-parameters": "^7.27.7", + "@babel/plugin-transform-private-methods": "^7.27.1", + "@babel/plugin-transform-private-property-in-object": "^7.27.1", + "@babel/plugin-transform-property-literals": "^7.27.1", + "@babel/plugin-transform-regenerator": "^7.28.4", + "@babel/plugin-transform-regexp-modifiers": "^7.27.1", + "@babel/plugin-transform-reserved-words": "^7.27.1", + "@babel/plugin-transform-shorthand-properties": "^7.27.1", + "@babel/plugin-transform-spread": "^7.27.1", + "@babel/plugin-transform-sticky-regex": "^7.27.1", + "@babel/plugin-transform-template-literals": "^7.27.1", + "@babel/plugin-transform-typeof-symbol": "^7.27.1", + "@babel/plugin-transform-unicode-escapes": "^7.27.1", + "@babel/plugin-transform-unicode-property-regex": "^7.27.1", + "@babel/plugin-transform-unicode-regex": "^7.27.1", + "@babel/plugin-transform-unicode-sets-regex": "^7.27.1", + "@babel/preset-modules": "0.1.6-no-external-plugins", + "babel-plugin-polyfill-corejs2": "^0.4.14", + "babel-plugin-polyfill-corejs3": "^0.13.0", + "babel-plugin-polyfill-regenerator": "^0.6.5", + "core-js-compat": "^3.43.0", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/preset-env/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/preset-modules": { + "version": "0.1.6-no-external-plugins", + "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz", + "integrity": "sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/types": "^7.4.4", + "esutils": "^2.0.2" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0 || ^8.0.0-0 <8.0.0" } }, - "node_modules/@babel/helper-validator-identifier": { + "node_modules/@babel/preset-react": { "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", - "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.28.5.tgz", + "integrity": "sha512-Z3J8vhRq7CeLjdC58jLv4lnZ5RKFUJWqH5emvxmv9Hv3BD1T9R/Im713R4MTKwvFaV74ejZ3sM01LyEKk4ugNQ==", "dev": true, "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-validator-option": "^7.27.1", + "@babel/plugin-transform-react-display-name": "^7.28.0", + "@babel/plugin-transform-react-jsx": "^7.27.1", + "@babel/plugin-transform-react-jsx-development": "^7.27.1", + "@babel/plugin-transform-react-pure-annotations": "^7.27.1" + }, "engines": { "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/parser": { + "node_modules/@babel/preset-typescript": { "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.5.tgz", - "integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==", + "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.28.5.tgz", + "integrity": "sha512-+bQy5WOI2V6LJZpPVxY+yp66XdZ2yifu0Mc1aP5CQKgjn4QM5IN2i5fAZ4xKop47pr8rpVhiAeu+nDQa12C8+g==", "dev": true, "license": "MIT", "dependencies": { - "@babel/types": "^7.28.5" - }, - "bin": { - "parser": "bin/babel-parser.js" + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-validator-option": "^7.27.1", + "@babel/plugin-syntax-jsx": "^7.27.1", + "@babel/plugin-transform-modules-commonjs": "^7.27.1", + "@babel/plugin-transform-typescript": "^7.28.5" }, "engines": { - "node": ">=6.0.0" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, "node_modules/@babel/runtime": { @@ -133,6 +1808,40 @@ "node": ">=6.9.0" } }, + "node_modules/@babel/template": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", + "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/parser": "^7.27.2", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.5.tgz", + "integrity": "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.5", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.28.5", + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.5", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/types": { "version": "7.28.5", "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz", @@ -1369,6 +3078,28 @@ } } }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, "node_modules/@jridgewell/resolve-uri": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", @@ -3551,6 +5282,58 @@ "proxy-from-env": "^1.1.0" } }, + "node_modules/babel-plugin-polyfill-corejs2": { + "version": "0.4.14", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.14.tgz", + "integrity": "sha512-Co2Y9wX854ts6U8gAAPXfn0GmAyctHuK8n0Yhfjd6t30g7yvKjspvvOo9yG+z52PZRgFErt7Ka2pYnXCjLKEpg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.27.7", + "@babel/helper-define-polyfill-provider": "^0.6.5", + "semver": "^6.3.1" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-plugin-polyfill-corejs2/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/babel-plugin-polyfill-corejs3": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.13.0.tgz", + "integrity": "sha512-U+GNwMdSFgzVmfhNm8GJUX88AadB3uo9KpJqS3FaqNIPKgySuvMb+bHPsOmmuWyIcuqZj/pzt1RUIUZns4y2+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.6.5", + "core-js-compat": "^3.43.0" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-plugin-polyfill-regenerator": { + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.5.tgz", + "integrity": "sha512-ISqQ2frbiNU9vIJkzg7dlPpznPZ4jOiUQ1uSmB0fEHeowtN3COYRsXr/xexn64NpU13P06jc/L5TgiJXOgrbEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.6.5" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -3577,6 +5360,16 @@ ], "license": "MIT" }, + "node_modules/baseline-browser-mapping": { + "version": "2.8.28", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.28.tgz", + "integrity": "sha512-gYjt7OIqdM0PcttNYP2aVrr2G0bMALkBaoehD4BuRGjAOtipg0b6wHg1yNL+s5zSnLZZrGHOw4IrND8CD+3oIQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.js" + } + }, "node_modules/bcrypt-pbkdf": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", @@ -3731,6 +5524,40 @@ "node": ">=8" } }, + "node_modules/browserslist": { + "version": "4.28.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.0.tgz", + "integrity": "sha512-tbydkR/CxfMwelN0vwdP/pLkDwyAASZ+VfWm4EOwlB6SWhx1sYnWLqo8N5j0rAzPfzfRaxt0mM/4wPU/Su84RQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.8.25", + "caniuse-lite": "^1.0.30001754", + "electron-to-chromium": "^1.5.249", + "node-releases": "^2.0.27", + "update-browserslist-db": "^1.1.4" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, "node_modules/buffer": { "version": "5.7.1", "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", @@ -3848,6 +5675,27 @@ "node": ">=6" } }, + "node_modules/caniuse-lite": { + "version": "1.0.30001754", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001754.tgz", + "integrity": "sha512-x6OeBXueoAceOmotzx3PO4Zpt4rzpeIFsSr6AAePTZxSkXiYDUmpypEl7e2+8NCd9bD7bXjqyef8CJYPC1jfxg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, "node_modules/caseless": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", @@ -4309,6 +6157,13 @@ "node": ">= 0.6" } }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, "node_modules/cookie": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz", @@ -4346,6 +6201,20 @@ "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", "license": "MIT" }, + "node_modules/core-js-compat": { + "version": "3.46.0", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.46.0.tgz", + "integrity": "sha512-p9hObIIEENxSV8xIu+V68JjSeARg6UVMG5mR+JEUguG3sI6MsiS1njz2jHmyJDvA+8jX/sytkBHup6kxhM9law==", + "dev": true, + "license": "MIT", + "dependencies": { + "browserslist": "^4.26.3" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, "node_modules/core-util-is": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", @@ -4915,6 +6784,13 @@ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", "license": "MIT" }, + "node_modules/electron-to-chromium": { + "version": "1.5.250", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.250.tgz", + "integrity": "sha512-/5UMj9IiGDMOFBnN4i7/Ry5onJrAGSbOGo3s9FEKmwobGq6xw832ccET0CE3CkkMBZ8GJSlUIesZofpyurqDXw==", + "dev": true, + "license": "ISC" + }, "node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", @@ -6060,6 +7936,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/get-caller-file": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", @@ -7383,6 +9269,19 @@ "dev": true, "license": "ISC" }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/jsonata": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/jsonata/-/jsonata-2.0.6.tgz", @@ -8032,6 +9931,13 @@ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", "license": "MIT" }, + "node_modules/lodash.debounce": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", + "dev": true, + "license": "MIT" + }, "node_modules/lodash.includes": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", @@ -8178,6 +10084,16 @@ "integrity": "sha512-HusN80C0ohtT9kOHQH7EuUaqzRQsnekpa+2ot8OzvW0iC08dq/YtM/7uKwwajldQsCrHyC8q9fz3t3L+TmDltA==", "license": "MIT" }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, "node_modules/luxon": { "version": "3.6.1", "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.6.1.tgz", @@ -8632,6 +10548,22 @@ "node": ">= 0.6" } }, + "node_modules/node-releases": { + "version": "2.0.27", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", + "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/nodemailer": { + "version": "7.0.10", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-7.0.10.tgz", + "integrity": "sha512-Us/Se1WtT0ylXgNFfyFSx4LElllVLJXQjWi2Xz17xWw7amDKO2MLtFnVp1WACy7GkVGs+oBlRopVNUzlrGSw1w==", + "license": "MIT-0", + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/nodemon": { "version": "3.1.10", "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.10.tgz", @@ -9835,6 +11767,26 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/regenerate": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", + "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==", + "dev": true, + "license": "MIT" + }, + "node_modules/regenerate-unicode-properties": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.2.2.tgz", + "integrity": "sha512-m03P+zhBeQd1RGnYxrGyDAPpWX/epKirLrp8e3qevZdVkKtnCrjjWczIbYc8+xd6vcTStVlqfycTx1KR4LOr0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "regenerate": "^1.4.2" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/regexp.prototype.flags": { "version": "1.5.4", "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", @@ -9856,6 +11808,44 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/regexpu-core": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-6.4.0.tgz", + "integrity": "sha512-0ghuzq67LI9bLXpOX/ISfve/Mq33a4aFRzoQYhnnok1JOFpmE/A2TBGkNVenOGEeSBCjIiWcc6MVOG5HEQv0sA==", + "dev": true, + "license": "MIT", + "dependencies": { + "regenerate": "^1.4.2", + "regenerate-unicode-properties": "^10.2.2", + "regjsgen": "^0.8.0", + "regjsparser": "^0.13.0", + "unicode-match-property-ecmascript": "^2.0.0", + "unicode-match-property-value-ecmascript": "^2.2.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/regjsgen": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.8.0.tgz", + "integrity": "sha512-RvwtGe3d7LvWiDQXeQw8p5asZUmfU1G/l6WbUXeHta7Y2PEIvBTwH6E2EfmYUK8pxcxEdEmaomqyp0vZZ7C+3Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/regjsparser": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.13.0.tgz", + "integrity": "sha512-NZQZdC5wOE/H3UT28fVGL+ikOZcEzfMGk/c3iN9UGxzWHMa1op7274oyiUVrAG4B2EuFhus8SvkaYnhvW92p9Q==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "jsesc": "~3.1.0" + }, + "bin": { + "regjsparser": "bin/parser" + } + }, "node_modules/request-progress": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/request-progress/-/request-progress-3.0.0.tgz", @@ -11444,6 +13434,50 @@ "integrity": "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw==", "license": "MIT" }, + "node_modules/unicode-canonical-property-names-ecmascript": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.1.tgz", + "integrity": "sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-match-property-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", + "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "unicode-canonical-property-names-ecmascript": "^2.0.0", + "unicode-property-aliases-ecmascript": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-match-property-value-ecmascript": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.2.1.tgz", + "integrity": "sha512-JQ84qTuMg4nVkx8ga4A16a1epI9H6uTXAknqxkGF/aFfRLw1xC/Bp24HNLaZhHSkWd3+84t8iXnp1J0kYcZHhg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-property-aliases-ecmascript": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.2.0.tgz", + "integrity": "sha512-hpbDzxUY9BFwX+UeBnxv3Sh1q7HFxj48DTmXchNgRa46lO8uj3/1iEn3MiNUYTg1g9ctIqXCCERn8gYZhHC5lQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/universalify": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", @@ -11473,6 +13507,37 @@ "node": ">=8" } }, + "node_modules/update-browserslist-db": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.4.tgz", + "integrity": "sha512-q0SPT4xyU84saUX+tomz1WLkxUbuaJnR1xWt17M7fJtEJigJeWUNGUqrauFXsHnqev9y9JTRGwk13tFBuKby4A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, "node_modules/uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", @@ -12012,6 +14077,13 @@ "node": ">=10" } }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, "node_modules/yaml": { "version": "2.8.0", "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.0.tgz", diff --git a/package.json b/package.json index be6680bc..3b10d872 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "lunalytics", - "version": "0.10.8", + "version": "0.10.9", "description": "Open source Node.js server/website monitoring tool", "private": true, "author": "KSJaay ", @@ -17,6 +17,8 @@ "dev": "concurrently \"npm run watch\" \"npm run server:watch\"", "watch": "vite --port 3000", "build": "vite build --config ./vite.config.js", + "build:emails": "node ./scripts/transpile-emails.cjs", + "create:notification": "node ./scripts/notification.js", "docker": "node ./scripts/setup.js && npm run start", "docker:release": "./docker/release.bash", "lint": "npm run lint:app && npm run lint:server", @@ -66,6 +68,7 @@ "mobx": "6.13.7", "mobx-react-lite": "4.1.0", "nanoid": "5.1.5", + "nodemailer": "7.0.10", "pg": "8.16.0", "ping": "0.4.4", "react": "19.1.1", @@ -84,6 +87,10 @@ "winston-daily-rotate-file": "5.0.0" }, "devDependencies": { + "@babel/core": "7.28.5", + "@babel/preset-env": "7.28.5", + "@babel/preset-react": "7.28.5", + "@babel/preset-typescript": "7.28.5", "@eslint/compat": "1.3.1", "@eslint/eslintrc": "3.3.1", "@eslint/js": "9.33.0", diff --git a/public/locales/en.json b/public/locales/en.json index 909d6602..740ed167 100644 --- a/public/locales/en.json +++ b/public/locales/en.json @@ -117,6 +117,9 @@ "request_type": "Request Type", "additional_headers": "Additional Headers", "payload": "Payload" + }, + "discord": { + "token_description": "For more information about how to create a Discord webhook checkout this guide: " } }, "status": { diff --git a/public/notifications/mail.svg b/public/notifications/mail.svg new file mode 100644 index 00000000..2a3d9b8f --- /dev/null +++ b/public/notifications/mail.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/scripts/transpile-emails.cjs b/scripts/transpile-emails.cjs new file mode 100644 index 00000000..27ba7f5d --- /dev/null +++ b/scripts/transpile-emails.cjs @@ -0,0 +1,59 @@ +const path = require('path'); +const fs = require('fs'); +const babel = require('@babel/core'); + +const srcDir = path.join(__dirname, '../app/components/notifications/emails'); +const outDir = path.join(__dirname, '../shared/notifications/emails'); + +if (!fs.existsSync(outDir)) fs.mkdirSync(outDir, { recursive: true }); + +fs.readdirSync(srcDir).forEach((file) => { + if (file.endsWith('.jsx') || file.endsWith('.tsx') || file.endsWith('.ts')) { + const srcPath = path.join(srcDir, file); + const outPath = path.join(outDir, file.replace(/\.(jsx|tsx|ts)$/, '.js')); + const code = fs.readFileSync(srcPath, 'utf8'); + const { code: transpiled } = babel.transformSync(code, { + presets: [ + [ + require.resolve('@babel/preset-env'), + { targets: { node: 'current' } }, + ], + require.resolve('@babel/preset-react'), + require.resolve('@babel/preset-typescript'), + ], + filename: file, + }); + + // Definitely not created by AI cause this was way too confusing for my little brain :) + + let esmCode = transpiled + .replace(/exports\.default\s*=\s*void 0;?\n?/g, '') + .replace(/var _default = exports\.default = ([\w$]+);?\n?/g, '') + .replace( + /Object\.defineProperty\(exports, "__esModule"[\s\S]*?\);?\n/, + '' + ) + .replace(/"use strict";\n?/, ''); + + let match = + esmCode.match(/const ([A-Z][\w$]*)\s*=/) || + esmCode.match(/function ([A-Z][\w$]*)\s*\(/) || + esmCode.match(/class ([A-Z][\w$]*)\s/); + let componentName = match ? match[1] : null; + + if (!/^import React from ['"]react['"];/.test(esmCode)) { + esmCode = 'import React from "react";\n' + esmCode; + } + + if ( + componentName && + !new RegExp(`export default ${componentName};\\s*$`).test(esmCode) + ) { + esmCode = + esmCode.replace(/\n*$/, '') + `\n\nexport default ${componentName};\n`; + } + + fs.writeFileSync(outPath, esmCode, 'utf8'); + console.log(`Transpiled & patched: ${file} -> ${outPath}`); + } +}); diff --git a/server/notifications/email.js b/server/notifications/email.js new file mode 100644 index 00000000..b67f8b28 --- /dev/null +++ b/server/notifications/email.js @@ -0,0 +1,128 @@ +import nodemailer from 'nodemailer'; +import NotificationBase from './base.js'; +import { + EmailTemplateMessages, + EmailTemplateObjects, +} from '../../shared/notifications/email.js'; +import NotificationReplacers from '../../shared/notifications/replacers/notification.js'; + +class Email extends NotificationBase { + name = 'Email'; + + async send(notification, monitor, heartbeat) { + try { + if ( + !EmailTemplateMessages[notification.messageType] || + !EmailTemplateObjects[notification.messageType] + ) + return null; + + const content = NotificationReplacers( + EmailTemplateObjects[notification.messageType], + monitor, + heartbeat + ); + + const template = + await EmailTemplateMessages[notification.messageType](content); + + const transporter = nodemailer.createTransport({ + host: notification.token, + port: notification.data?.port || 587, + secure: notification.data?.security, + auth: { + user: notification.data?.username, + pass: notification.data?.password, + }, + }); + + const emailOptions = { + from: notification.data?.fromEmail || notification.data?.username, + to: notification.data?.toEmail, + cc: notification.data?.ccEmail, + bcc: notification.data?.bccEmail, + subject: `Lunalytics - ${monitor.name} is down!`, + html: template, + }; + + await transporter.sendMail(emailOptions); + return this.success; + } catch (error) { + this.handleError(error); + } + } + + async test(notification) { + try { + const transporter = nodemailer.createTransport({ + host: notification.token, + port: notification.data?.port || 587, + secure: notification.data?.security, + auth: { + user: notification.data?.username, + pass: notification.data?.password, + }, + }); + + const emailComponent = EmailTemplateMessages.basic({ + serviceName: 'Lunalytics', + dashboardUrl: 'https://lunalytics.xyz', + timestamp: new Date().toISOString(), + }); + + const emailOptions = { + from: notification.data?.fromEmail || notification.data?.username, + to: notification.data?.toEmail, + cc: notification.data?.ccEmail, + bcc: notification.data?.bccEmail, + subject: `Lunalytics - Test SMTP Email!`, + html: emailComponent, + }; + + await transporter.sendMail(emailOptions); + + return this.success; + } catch (error) { + this.handleError(error); + } + } + + async sendRecovery(notification, monitor, heartbeat) { + try { + const content = NotificationReplacers( + EmailTemplateObjects.recovery, + monitor, + heartbeat + ); + + const template = EmailTemplateMessages.recovery(content); + + const transporter = nodemailer.createTransport({ + host: notification.token, + port: notification.data?.port || 587, + secure: notification.data?.security, + auth: { + user: notification.data?.username, + pass: notification.data?.password, + }, + }); + + const emailOptions = { + from: notification.data?.fromEmail || notification.data?.username, + to: notification.data?.toEmail, + cc: notification.data?.ccEmail, + bcc: notification.data?.bccEmail, + subject: `Lunalytics - ${monitor.name} has recovered!`, + html: template, + }; + + await transporter.sendMail(emailOptions); + + return this.success; + } catch (error) { + this.handleError(error); + } + } +} + +export default Email; diff --git a/server/notifications/index.js b/server/notifications/index.js index 051140ec..9ca7c650 100644 --- a/server/notifications/index.js +++ b/server/notifications/index.js @@ -1,4 +1,5 @@ import Discord from './discord.js'; +import Email from './email.js'; import HomeAssistant from './homeAssistant.js'; import Pushover from './pushover.js'; import Telegram from './telegram.js'; @@ -7,6 +8,7 @@ import Webhook from './webhook.js'; const NotificationServices = { Discord, + Email, HomeAssistant, Pushover, Telegram, diff --git a/shared/notifications/email.js b/shared/notifications/email.js new file mode 100644 index 00000000..ff295af9 --- /dev/null +++ b/shared/notifications/email.js @@ -0,0 +1,50 @@ +import { renderToStaticMarkup } from 'react-dom/server'; +import NotificationBasicEmailTemplate from './emails/basic.js'; +import NotificationPrettyEmailTemplate from './emails/pretty.js'; +import NotificationNerdyEmailTemplate from './emails/nerdy.js'; +import NotificationRecoveryEmailTemplate from './emails/recovered.js'; + +const EmailSchema = {}; + +const EmailTemplateObjects = { + basic: JSON.stringify({ + serviceName: '{{service_name}}', + dashboardUrl: '{{service_address}}', + timestamp: '{{date[YYYY-MM-DDTHH:mm:ssZ[Z]]}}', + }), + pretty: JSON.stringify({ + serviceName: '{{service_name}}', + dashboardUrl: '{{service_address}}', + timestamp: '{{date[YYYY-MM-DDTHH:mm:ssZ[Z]]}}', + latency: '{{heartbeat_latency}}', + status: '{{heartbeat_status}}', + message: '{{heartbeat_message}}', + }), + nerdy: JSON.stringify({ + id: '{{service_monitorId}}', + serviceName: '{{service_name}}', + dashboardUrl: '{{service_address}}', + address: '{{service_address}}', + type: '{{service_type}}', + timestamp: '{{date[YYYY-MM-DDTHH:mm:ssZ[Z]]}}', + status: '{{heartbeat_status}}', + latency: '{{heartbeat_latency}}', + statusMessage: '{{heartbeat_message}}', + }), + recovery: JSON.stringify({ + serviceName: '{{service_name}}', + dashboardUrl: '{{service_address}}', + timestamp: '{{date[YYYY-MM-DDTHH:mm:ssZ[Z]]}}', + }), +}; + +const EmailTemplateMessages = { + basic: (props) => renderToStaticMarkup(NotificationBasicEmailTemplate(props)), + pretty: (props) => + renderToStaticMarkup(NotificationPrettyEmailTemplate(props)), + nerdy: (props) => renderToStaticMarkup(NotificationNerdyEmailTemplate(props)), + recovery: (props) => + renderToStaticMarkup(NotificationRecoveryEmailTemplate(props)), +}; + +export { EmailTemplateObjects, EmailTemplateMessages, EmailSchema }; diff --git a/shared/notifications/emails/basic.js b/shared/notifications/emails/basic.js new file mode 100644 index 00000000..89a13e5c --- /dev/null +++ b/shared/notifications/emails/basic.js @@ -0,0 +1,118 @@ +import React from "react"; + +const NotificationBasicEmailTemplate = ({ + serviceName, + dashboardUrl, + timestamp +}) => { + return /*#__PURE__*/React.createElement("div", { + style: { + fontFamily: "'Inter', 'Segoe UI', Helvetica, Arial, sans-serif", + backgroundColor: '#171a1c', + padding: '24px', + margin: '0', + color: '#f3f6fb' + } + }, /*#__PURE__*/React.createElement("table", { + width: "100%", + cellPadding: "0", + cellSpacing: "0", + style: { + maxWidth: '600px', + margin: '0 auto', + backgroundColor: '#22272a', + borderRadius: '10px', + overflow: 'hidden', + boxShadow: '0 2px 8px rgba(0,0,0,0.1)' + } + }, /*#__PURE__*/React.createElement("thead", null, /*#__PURE__*/React.createElement("tr", null, /*#__PURE__*/React.createElement("th", { + style: { + backgroundColor: '#b80a47', + padding: '20px 0', + fontSize: '22px', + fontWeight: '600', + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + gap: '10px', + color: '#f3f6fb' + } + }, /*#__PURE__*/React.createElement("img", { + src: "https://raw.githubusercontent.com/KSJaay/Lunalytics/refs/heads/main/public/icon-192x192.png", + width: '36px', + height: '36px' + }), "Lunalytics - Outage Detected"))), /*#__PURE__*/React.createElement("tbody", null, /*#__PURE__*/React.createElement("tr", null, /*#__PURE__*/React.createElement("td", { + style: { + padding: '24px', + backgroundColor: '#22272a' + } + }, /*#__PURE__*/React.createElement("h2", { + style: { + margin: '0 0 12px 0', + color: '#9fa9af', + fontSize: '20px' + } + }, "\uD83D\uDEA8 Service Currently Unreachable"), /*#__PURE__*/React.createElement("p", { + style: { + margin: '0 0 12px 0', + fontSize: '16px', + lineHeight: '1.5', + color: '#f3f6fb' + } + }, "Our monitoring system has detected that", ' ', /*#__PURE__*/React.createElement("strong", { + style: { + color: '#9fa9af' + } + }, serviceName), " is currently", ' ', /*#__PURE__*/React.createElement("strong", { + style: { + color: '#f31260' + } + }, "down/unreachable"), "."), /*#__PURE__*/React.createElement("p", { + style: { + margin: '0 0 16px 0', + fontSize: '14px', + color: '#9fa9af' + } + }, "Detected at: ", timestamp), /*#__PURE__*/React.createElement("div", { + style: { + textAlign: 'center', + margin: '28px 0' + } + }, /*#__PURE__*/React.createElement("a", { + href: dashboardUrl, + target: "_blank", + rel: "noopener noreferrer", + style: { + display: 'inline-block', + backgroundColor: '#0072f5', + padding: '12px 24px', + borderRadius: '8px', + fontSize: '15px', + textDecoration: 'none', + fontWeight: '600', + color: '#f3f6fb' + } + }, "View Dashboard")), /*#__PURE__*/React.createElement("p", { + style: { + fontSize: '14px', + color: '#9fa9af' + } + }, "You\u2019ll be notified again when the service is back online.")))), /*#__PURE__*/React.createElement("tfoot", null, /*#__PURE__*/React.createElement("tr", null, /*#__PURE__*/React.createElement("td", { + style: { + backgroundColor: '#292e31', + padding: '16px', + textAlign: 'center', + fontSize: '12px', + color: '#9fa9af' + } + }, /*#__PURE__*/React.createElement("a", { + href: "https://github.com/KSJaay/lunalytics", + style: { + color: '#13a452' + }, + target: "_blank", + rel: "noopener noreferrer" + }, "Lunalytics"), ' ', "- Made with \u2764\uFE0F by KSJaay"))))); +}; + +export default NotificationBasicEmailTemplate; diff --git a/shared/notifications/emails/index.js b/shared/notifications/emails/index.js new file mode 100644 index 00000000..71b5900d --- /dev/null +++ b/shared/notifications/emails/index.js @@ -0,0 +1,31 @@ +import React from "react"; + +Object.defineProperty(exports, "NotificationBasicEmailTemplate", { + enumerable: true, + get: function () { + return _basic.default; + } +}); +Object.defineProperty(exports, "NotificationNerdyEmailTemplate", { + enumerable: true, + get: function () { + return _nerdy.default; + } +}); +Object.defineProperty(exports, "NotificationPrettyEmailTemplate", { + enumerable: true, + get: function () { + return _pretty.default; + } +}); +Object.defineProperty(exports, "NotificationRecoveredEmailTemplate", { + enumerable: true, + get: function () { + return _recovered.default; + } +}); +var _basic = _interopRequireDefault(require("./basic")); +var _pretty = _interopRequireDefault(require("./pretty")); +var _nerdy = _interopRequireDefault(require("./nerdy")); +var _recovered = _interopRequireDefault(require("./recovered")); +function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; } \ No newline at end of file diff --git a/shared/notifications/emails/nerdy.js b/shared/notifications/emails/nerdy.js new file mode 100644 index 00000000..6b506fa1 --- /dev/null +++ b/shared/notifications/emails/nerdy.js @@ -0,0 +1,239 @@ +import React from "react"; + +const NotificationNerdyEmailTemplate = ({ + id, + serviceName, + dashboardUrl, + address, + type, + timestamp, + status, + latency, + statusMessage +}) => { + return /*#__PURE__*/React.createElement("div", { + style: { + fontFamily: "'Inter', 'Segoe UI', Helvetica, Arial, sans-serif", + backgroundColor: '#171a1c', + color: '#f3f6fb', + padding: '40px 0' + } + }, /*#__PURE__*/React.createElement("table", { + width: "100%", + cellPadding: "0", + cellSpacing: "0", + style: { + maxWidth: '700px', + margin: '0 auto', + backgroundColor: '#22272a', + borderRadius: '14px', + boxShadow: '0 4px 16px rgba(0,0,0,0.6)' + } + }, /*#__PURE__*/React.createElement("thead", null, /*#__PURE__*/React.createElement("tr", null, /*#__PURE__*/React.createElement("th", { + style: { + backgroundColor: '#b80a47', + padding: '20px 0', + fontSize: '22px', + fontWeight: '600', + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + gap: '10px', + borderRadius: '16px 16px 0 0', + color: '#f3f6fb' + } + }, /*#__PURE__*/React.createElement("img", { + src: "https://raw.githubusercontent.com/KSJaay/Lunalytics/refs/heads/main/public/icon-192x192.png", + width: '36px', + height: '36px' + }), "Lunalytics - Outage Detected"))), /*#__PURE__*/React.createElement("tbody", null, /*#__PURE__*/React.createElement("tr", null, /*#__PURE__*/React.createElement("td", { + style: { + padding: '28px 32px' + } + }, /*#__PURE__*/React.createElement("h2", { + style: { + margin: '0 0 12px 0', + color: '#9fa9af', + fontSize: '20px' + } + }, "\uD83D\uDEA8 Service Currently Unreachable"), /*#__PURE__*/React.createElement("p", { + style: { + margin: '0 0 12px 0', + fontSize: '16px', + lineHeight: '1.5', + color: '#f3f6fb' + } + }, "Our monitoring system has detected that", ' ', /*#__PURE__*/React.createElement("strong", { + style: { + color: '#9fa9af' + } + }, serviceName), " is currently", ' ', /*#__PURE__*/React.createElement("strong", { + style: { + color: '#f31260' + } + }, "down/unreachable"), "."), /*#__PURE__*/React.createElement("h3", { + style: { + color: '#96c1f2', + fontSize: '16px', + marginBottom: '12px' + } + }, "\uD83D\uDCCA Monitor Information"), /*#__PURE__*/React.createElement("div", { + style: { + fontFamily: "'Fira Code', monospace", + backgroundColor: '#171a1c', + borderRadius: '8px', + padding: '16px', + color: '#f3f6fb', + marginBottom: '24px' + } + }, /*#__PURE__*/React.createElement("div", { + style: { + display: 'flex', + gap: '8px' + } + }, /*#__PURE__*/React.createElement("div", { + style: { + color: '#9fa9af' + } + }, "ID:"), /*#__PURE__*/React.createElement("div", null, id)), /*#__PURE__*/React.createElement("div", { + style: { + display: 'flex', + gap: '8px' + } + }, /*#__PURE__*/React.createElement("div", { + style: { + color: '#9fa9af' + } + }, "Name:"), /*#__PURE__*/React.createElement("div", null, serviceName)), /*#__PURE__*/React.createElement("div", { + style: { + display: 'flex', + gap: '8px' + } + }, /*#__PURE__*/React.createElement("div", { + style: { + color: '#9fa9af' + } + }, "Address:"), /*#__PURE__*/React.createElement("div", null, address)), /*#__PURE__*/React.createElement("div", { + style: { + display: 'flex', + gap: '8px' + } + }, /*#__PURE__*/React.createElement("div", { + style: { + color: '#9fa9af' + } + }, "Type:"), /*#__PURE__*/React.createElement("div", null, type)), /*#__PURE__*/React.createElement("div", { + style: { + display: 'flex', + gap: '8px' + } + }, /*#__PURE__*/React.createElement("div", { + style: { + color: '#9fa9af' + } + }), /*#__PURE__*/React.createElement("div", null))), /*#__PURE__*/React.createElement("h3", { + style: { + color: '#96c1f2', + fontSize: '16px', + marginBottom: '12px' + } + }, "\uD83C\uDF0D Status Breakdown"), /*#__PURE__*/React.createElement("div", { + style: { + fontFamily: "'Fira Code', monospace", + backgroundColor: '#171a1c', + borderRadius: '8px', + padding: '16px', + color: '#f3f6fb', + marginBottom: '24px' + } + }, /*#__PURE__*/React.createElement("div", { + style: { + display: 'flex', + gap: '8px' + } + }, /*#__PURE__*/React.createElement("div", { + style: { + color: '#9fa9af' + } + }, "Status:"), /*#__PURE__*/React.createElement("div", null, status)), /*#__PURE__*/React.createElement("div", { + style: { + display: 'flex', + gap: '8px' + } + }, /*#__PURE__*/React.createElement("div", { + style: { + color: '#9fa9af' + } + }, "Latency:"), /*#__PURE__*/React.createElement("div", null, latency, " ms")), /*#__PURE__*/React.createElement("div", { + style: { + display: 'flex', + gap: '8px' + } + }, /*#__PURE__*/React.createElement("div", { + style: { + color: '#9fa9af' + } + }, "Message:"), /*#__PURE__*/React.createElement("div", null, statusMessage)), /*#__PURE__*/React.createElement("div", { + style: { + display: 'flex', + gap: '8px' + } + }, /*#__PURE__*/React.createElement("div", { + style: { + color: '#9fa9af' + } + }, "Timestamp:"), /*#__PURE__*/React.createElement("div", null, timestamp)), /*#__PURE__*/React.createElement("div", { + style: { + display: 'flex', + gap: '8px' + } + }, /*#__PURE__*/React.createElement("div", { + style: { + color: '#9fa9af' + } + }), /*#__PURE__*/React.createElement("div", null))), /*#__PURE__*/React.createElement("div", { + style: { + textAlign: 'center', + margin: '36px 0 12px' + } + }, /*#__PURE__*/React.createElement("a", { + href: dashboardUrl, + target: "_blank", + rel: "noopener noreferrer", + style: { + display: 'inline-block', + backgroundColor: '#0072f5', + color: '#f3f6fb', + padding: '12px 24px', + borderRadius: '8px', + fontSize: '15px', + textDecoration: 'none', + fontWeight: '600' + } + }, "View Dashboard")), /*#__PURE__*/React.createElement("p", { + style: { + margin: '0', + fontSize: '13px', + color: '#9fa9af', + textAlign: 'center' + } + }, "You can manage notification preferences or review uptime logs in your dashboard.")))), /*#__PURE__*/React.createElement("tfoot", null, /*#__PURE__*/React.createElement("tr", null, /*#__PURE__*/React.createElement("td", { + style: { + backgroundColor: '#292e31', + padding: '16px', + textAlign: 'center', + fontSize: '12px', + color: '#9fa9af', + borderRadius: '0 0 16px 16px' + } + }, /*#__PURE__*/React.createElement("a", { + href: "https://github.com/KSJaay/lunalytics", + style: { + color: '#13a452' + }, + target: "_blank", + rel: "noopener noreferrer" + }, "Lunalytics"), ' ', "- Made with \u2764\uFE0F by KSJaay"))))); +}; + +export default NotificationNerdyEmailTemplate; diff --git a/shared/notifications/emails/pretty.js b/shared/notifications/emails/pretty.js new file mode 100644 index 00000000..b505ff94 --- /dev/null +++ b/shared/notifications/emails/pretty.js @@ -0,0 +1,173 @@ +import React from "react"; + +const NotificationPrettyEmailTemplate = ({ + serviceName, + dashboardUrl, + timestamp, + latency, + status, + message +}) => { + return /*#__PURE__*/React.createElement("div", { + style: { + fontFamily: "'Inter', 'Segoe UI', Helvetica, Arial, sans-serif", + backgroundColor: '#171a1c', + padding: '32px 0', + color: '#f3f6fb' + } + }, /*#__PURE__*/React.createElement("table", { + width: "100%", + cellPadding: "0", + cellSpacing: "0", + style: { + maxWidth: '650px', + margin: '0 auto', + backgroundColor: '#22272a', + borderRadius: '16px', + overflow: 'hidden', + boxShadow: '0 6px 20px rgba(0,0,0,0.4)' + } + }, /*#__PURE__*/React.createElement("thead", null, /*#__PURE__*/React.createElement("tr", null, /*#__PURE__*/React.createElement("th", { + style: { + backgroundColor: '#b80a47', + padding: '20px 0', + fontSize: '22px', + fontWeight: '600', + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + gap: '10px', + color: '#f3f6fb' + } + }, /*#__PURE__*/React.createElement("img", { + src: "https://raw.githubusercontent.com/KSJaay/Lunalytics/refs/heads/main/public/icon-192x192.png", + width: '36px', + height: '36px' + }), "Lunalytics - Outage Detected"))), /*#__PURE__*/React.createElement("tbody", null, /*#__PURE__*/React.createElement("tr", null, /*#__PURE__*/React.createElement("td", { + style: { + padding: '32px' + } + }, /*#__PURE__*/React.createElement("h2", { + style: { + margin: '0 0 12px 0', + color: '#9fa9af', + fontSize: '20px' + } + }, "\uD83D\uDEA8 Service Currently Unreachable"), /*#__PURE__*/React.createElement("p", { + style: { + margin: '0 0 12px 0', + fontSize: '16px', + lineHeight: '1.5', + color: '#f3f6fb' + } + }, "Our monitoring system has detected that", ' ', /*#__PURE__*/React.createElement("strong", { + style: { + color: '#9fa9af' + } + }, serviceName), " is currently", ' ', /*#__PURE__*/React.createElement("strong", { + style: { + color: '#f31260' + } + }, "down/unreachable"), "."), /*#__PURE__*/React.createElement("p", { + style: { + margin: '0 0 20px 0', + fontSize: '14px', + color: '#9fa9af' + } + }, "Detected at ", timestamp), /*#__PURE__*/React.createElement("h3", { + style: { + color: '#96c1f2', + fontSize: '16px', + marginBottom: '12px' + } + }, "\uD83D\uDCCA Monitor Information"), /*#__PURE__*/React.createElement("div", { + style: { + backgroundColor: '#171a1c', + borderRadius: '10px', + marginBottom: '24px', + padding: '12px', + width: '100%', + fontFamily: "'Fira Code', monospace", + color: '#f3f6fb' + } + }, /*#__PURE__*/React.createElement("div", { + style: { + display: 'flex', + gap: '8px' + } + }, /*#__PURE__*/React.createElement("div", { + style: { + color: '#9fa9af' + } + }, "Name:"), /*#__PURE__*/React.createElement("div", null, serviceName)), /*#__PURE__*/React.createElement("div", { + style: { + display: 'flex', + gap: '8px' + } + }, /*#__PURE__*/React.createElement("div", { + style: { + color: '#9fa9af' + } + }, "Latency:"), /*#__PURE__*/React.createElement("div", null, latency, "ms")), /*#__PURE__*/React.createElement("div", { + style: { + display: 'flex', + gap: '8px' + } + }, /*#__PURE__*/React.createElement("div", { + style: { + color: '#9fa9af' + } + }, "Status:"), /*#__PURE__*/React.createElement("div", null, status)), /*#__PURE__*/React.createElement("div", { + style: { + display: 'flex', + gap: '8px' + } + }, /*#__PURE__*/React.createElement("div", { + style: { + color: '#9fa9af' + } + }, "Message:"), /*#__PURE__*/React.createElement("div", null, message)), /*#__PURE__*/React.createElement("div", { + style: { + display: 'flex', + gap: '8px' + } + }, /*#__PURE__*/React.createElement("div", { + style: { + color: '#9fa9af' + } + }, "Timestamp:"), /*#__PURE__*/React.createElement("div", null, timestamp))), /*#__PURE__*/React.createElement("div", { + style: { + textAlign: 'center', + margin: '28px 0' + } + }, /*#__PURE__*/React.createElement("a", { + href: dashboardUrl, + style: { + display: 'inline-block', + backgroundColor: '#0072f5', + padding: '12px 24px', + borderRadius: '8px', + fontSize: '15px', + textDecoration: 'none', + fontWeight: '600', + color: '#f3f6fb' + } + }, "View Dashboard"))))), /*#__PURE__*/React.createElement("tfoot", null, /*#__PURE__*/React.createElement("tr", null, /*#__PURE__*/React.createElement("td", { + style: { + backgroundColor: '#292e31', + padding: '16px', + textAlign: 'center', + fontSize: '12px', + color: '#9fa9af' + } + }, /*#__PURE__*/React.createElement("a", { + href: "https://github.com/KSJaay/lunalytics", + style: { + color: '#13a452' + }, + target: "_blank", + rel: "noopener noreferrer" + }, "Lunalytics"), ' ', "- Made with \u2764\uFE0F by KSJaay"))))); +}; + +export default NotificationPrettyEmailTemplate; diff --git a/shared/notifications/emails/recovered.js b/shared/notifications/emails/recovered.js new file mode 100644 index 00000000..3cdf7cd7 --- /dev/null +++ b/shared/notifications/emails/recovered.js @@ -0,0 +1,134 @@ +import React from "react"; + +const NotificationRecoveredEmailTemplate = ({ + serviceName, + dashboardUrl, + timestamp +}) => { + return /*#__PURE__*/React.createElement("div", { + style: { + fontFamily: "'Inter', 'Segoe UI', Helvetica, Arial, sans-serif", + backgroundColor: '#171a1c', + padding: '32px 0', + color: '#f3f6fb' + } + }, /*#__PURE__*/React.createElement("table", { + width: "100%", + cellPadding: "0", + cellSpacing: "0", + style: { + maxWidth: '600px', + margin: '0 auto', + borderRadius: '16px', + overflow: 'hidden', + backgroundColor: '#22272a', + boxShadow: '0 4px 16px rgba(0,0,0,0.4)' + } + }, /*#__PURE__*/React.createElement("thead", null, /*#__PURE__*/React.createElement("tr", null, /*#__PURE__*/React.createElement("th", { + style: { + backgroundColor: '#13a452', + color: '#f3f6fb', + padding: '20px 0', + fontSize: '22px', + fontWeight: '600', + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + gap: '10px', + borderRadius: '16px 16px 0 0' + } + }, /*#__PURE__*/React.createElement("img", { + src: "https://raw.githubusercontent.com/KSJaay/Lunalytics/refs/heads/main/public/icon-192x192.png", + width: '36px', + height: '36px' + }), "Lunalytics - Service Restored"))), /*#__PURE__*/React.createElement("tbody", null, /*#__PURE__*/React.createElement("tr", null, /*#__PURE__*/React.createElement("td", { + style: { + padding: '32px' + } + }, /*#__PURE__*/React.createElement("h2", { + style: { + margin: '0 0 12px 0', + fontSize: '20px', + fontWeight: '600', + color: '#17c964' + } + }, "\u2705 Your monitored service is back online"), /*#__PURE__*/React.createElement("p", { + style: { + margin: '0 0 16px 0', + fontSize: '16px', + lineHeight: '1.6', + color: '#f3f6fb' + } + }, "Lunalytics detected that", ' ', /*#__PURE__*/React.createElement("strong", { + style: { + color: '#9fa9af' + } + }, serviceName), " has successfully recovered and is now", ' ', /*#__PURE__*/React.createElement("strong", { + style: { + color: '#13a452' + } + }, "operational"), "."), /*#__PURE__*/React.createElement("p", { + style: { + margin: '0 0 24px 0', + fontSize: '14px', + color: '#9fa9af' + } + }, "Resolved at: ", timestamp), /*#__PURE__*/React.createElement("div", { + style: { + backgroundColor: '#171a1c', + border: '1px solid #22272a', + borderRadius: '10px', + padding: '16px', + marginBottom: '28px' + } + }, /*#__PURE__*/React.createElement("p", { + style: { + margin: 0, + fontSize: '15px', + color: '#9fa9af', + lineHeight: '1.6' + } + }, "Monitoring will continue to ensure your service remains stable. You\u2019ll be alerted again if any further issues occur.")), /*#__PURE__*/React.createElement("div", { + style: { + textAlign: 'center', + margin: '28px 0' + } + }, /*#__PURE__*/React.createElement("a", { + href: dashboardUrl, + style: { + display: 'inline-block', + backgroundColor: '#0072f5', + color: '#f3f6fb', + padding: '12px 24px', + borderRadius: '8px', + fontSize: '15px', + textDecoration: 'none', + fontWeight: '600' + } + }, "View Dashboard")), /*#__PURE__*/React.createElement("p", { + style: { + margin: '0', + fontSize: '13px', + color: '#9fa9af', + textAlign: 'center' + } + }, "You can manage notification preferences or review uptime logs in your dashboard.")))), /*#__PURE__*/React.createElement("tfoot", null, /*#__PURE__*/React.createElement("tr", null, /*#__PURE__*/React.createElement("td", { + style: { + backgroundColor: '#292e31', + padding: '16px', + textAlign: 'center', + fontSize: '12px', + color: '#9fa9af', + borderRadius: '0 0 16px 16px' + } + }, /*#__PURE__*/React.createElement("a", { + href: "https://github.com/KSJaay/lunalytics", + style: { + color: '#13a452' + }, + target: "_blank", + rel: "noopener noreferrer" + }, "Lunalytics"), ' ', "- Made with \u2764\uFE0F by KSJaay"))))); +}; + +export default NotificationRecoveredEmailTemplate; diff --git a/shared/notifications/index.js b/shared/notifications/index.js index 58349152..35125225 100644 --- a/shared/notifications/index.js +++ b/shared/notifications/index.js @@ -1,4 +1,5 @@ import { DiscordTemplateMessages } from './discord'; +import { EmailTemplateMessages } from './email'; import { HomeAssistantTemplateMessages } from './homeAssistant'; import { PushoverTemplateMessages } from './pushover'; import { SlackTemplateMessages } from './slack'; @@ -7,6 +8,7 @@ import { WebhookTemplateMessages } from './webhook'; const NotificationsTemplates = { Discord: DiscordTemplateMessages, + Email: EmailTemplateMessages, HomeAssistant: HomeAssistantTemplateMessages, Pushover: PushoverTemplateMessages, Slack: SlackTemplateMessages, diff --git a/shared/validators/notifications/email.js b/shared/validators/notifications/email.js new file mode 100644 index 00000000..19728ac4 --- /dev/null +++ b/shared/validators/notifications/email.js @@ -0,0 +1,75 @@ +import { NotificationValidatorError } from '../../utils/errors.js'; + +const friendlyNameRegex = /^[a-zA-Z0-9_-]+$/; +const messageTypes = ['basic', 'pretty', 'nerdy']; +const tokenRegex = + /^https:\/\/(?:discord\.com|discordapp\.com)\/api\/webhooks\/[0-9]+\/[0-9a-zA-Z_.-]+$/; + +const Email = ({ + friendlyName, + token, + messageType, + data: { + port = 587, + security = true, + username, + password, + fromEmail, + toEmail, + ccEmail, + bccEmail, + }, +}) => { + if (friendlyNameRegex && !friendlyNameRegex.test(friendlyName)) { + throw new NotificationValidatorError( + 'friendlyName', + 'Invalid Friendly Name. Must be alphanumeric, dashes, and underscores only.' + ); + } + + if (!token) { + throw new NotificationValidatorError('token', 'Invalid Email Webhook URL'); + } + + if (!messageTypes.includes(messageType)) { + throw new NotificationValidatorError('messageType', 'Invalid Message Type'); + } + + if (!port) { + throw new NotificationValidatorError('port', 'Invalid Port'); + } + + if (security === undefined) { + throw new NotificationValidatorError( + 'security', + 'Invalid Security Setting' + ); + } + + if (!username) { + throw new NotificationValidatorError('username', 'Invalid Username'); + } + + if (!password) { + throw new NotificationValidatorError('password', 'Invalid Password'); + } + + return { + platform: 'Email', + messageType, + token, + friendlyName, + data: { + port, + security, + username, + password, + fromEmail, + toEmail, + ccEmail, + bccEmail, + }, + }; +}; + +export default Email; diff --git a/shared/validators/notifications/index.js b/shared/validators/notifications/index.js index 89a68a41..fd1ee47e 100644 --- a/shared/validators/notifications/index.js +++ b/shared/validators/notifications/index.js @@ -1,4 +1,5 @@ import Discord from './discord.js'; +import Email from './email.js'; import HomeAssistant from './homeAssistant.js'; import Pushover from './pushover.js'; import Slack from './slack.js'; @@ -7,6 +8,7 @@ import Webhook from './webhook.js'; const NotificationValidators = { Discord, + Email, HomeAssistant, Pushover, Slack,