diff --git a/frontend/package.json b/frontend/package.json index 293b4903fb..e7d1861cd4 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -107,6 +107,7 @@ "react-virtuoso": "4.0.3", "redux": "^4.0.5", "redux-thunk": "^2.3.0", + "rehype-raw": "7.0.0", "stream": "^0.0.2", "style-loader": "1.3.0", "styled-components": "^5.3.11", @@ -203,6 +204,7 @@ "jest-styled-components": "^7.0.8", "lint-staged": "^12.5.0", "msw": "1.3.2", + "npm-run-all": "latest", "portfinder-sync": "^0.0.2", "prettier": "2.2.1", "raw-loader": "4.0.2", @@ -216,8 +218,7 @@ "ts-node": "^10.2.1", "typescript-plugin-css-modules": "5.0.1", "webpack-bundle-analyzer": "^4.5.0", - "webpack-cli": "^4.9.2", - "npm-run-all": "latest" + "webpack-cli": "^4.9.2" }, "lint-staged": { "*.(js|jsx|ts|tsx)": [ diff --git a/frontend/public/Icons/redis-logo.svg b/frontend/public/Icons/redis-logo.svg new file mode 100644 index 0000000000..424f1e575f --- /dev/null +++ b/frontend/public/Icons/redis-logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/public/locales/en/titles.json b/frontend/public/locales/en/titles.json index 204ce0c479..b8cc27821d 100644 --- a/frontend/public/locales/en/titles.json +++ b/frontend/public/locales/en/titles.json @@ -43,5 +43,6 @@ "LOGS_SAVE_VIEWS": "SigNoz | Logs Saved Views", "TRACES_SAVE_VIEWS": "SigNoz | Traces Saved Views", "DEFAULT": "Open source Observability Platform | SigNoz", - "SHORTCUTS": "SigNoz | Shortcuts" + "SHORTCUTS": "SigNoz | Shortcuts", + "INTEGRATIONS_INSTALLED": "SigNoz | Integrations" } diff --git a/frontend/src/AppRoutes/pageComponents.ts b/frontend/src/AppRoutes/pageComponents.ts index 6d26f9b55a..bea07a7e51 100644 --- a/frontend/src/AppRoutes/pageComponents.ts +++ b/frontend/src/AppRoutes/pageComponents.ts @@ -190,3 +190,18 @@ export const WorkspaceBlocked = Loadable( export const ShortcutsPage = Loadable( () => import(/* webpackChunkName: "ShortcutsPage" */ 'pages/Shortcuts'), ); + +export const InstalledIntegrations = Loadable( + () => + import( + /* webpackChunkName: "InstalledIntegrations" */ 'pages/IntegrationsModulePage' + ), +); + +export const IntegrationsMarketPlace = Loadable( + // eslint-disable-next-line sonarjs/no-identical-functions + () => + import( + /* webpackChunkName: "IntegrationsMarketPlace" */ 'pages/IntegrationsModulePage' + ), +); diff --git a/frontend/src/AppRoutes/routes.ts b/frontend/src/AppRoutes/routes.ts index 1115360883..d9921d94d1 100644 --- a/frontend/src/AppRoutes/routes.ts +++ b/frontend/src/AppRoutes/routes.ts @@ -1,6 +1,4 @@ import ROUTES from 'constants/routes'; -import Shortcuts from 'pages/Shortcuts/Shortcuts'; -import WorkspaceBlocked from 'pages/WorkspaceLocked'; import { RouteProps } from 'react-router-dom'; import { @@ -16,6 +14,8 @@ import { EditRulesPage, ErrorDetails, IngestionSettings, + InstalledIntegrations, + IntegrationsMarketPlace, LicensePage, ListAllALertsPage, LiveLogs, @@ -35,6 +35,7 @@ import { ServiceMetricsPage, ServicesTablePage, SettingsPage, + ShortcutsPage, SignupPage, SomethingWentWrong, StatusPage, @@ -45,6 +46,7 @@ import { TracesSaveViews, UnAuthorized, UsageExplorerPage, + WorkspaceBlocked, } from './pageComponents'; const routes: AppRoutes[] = [ @@ -331,10 +333,24 @@ const routes: AppRoutes[] = [ { path: ROUTES.SHORTCUTS, exact: true, - component: Shortcuts, + component: ShortcutsPage, isPrivate: true, key: 'SHORTCUTS', }, + { + path: ROUTES.INTEGRATIONS_INSTALLED, + exact: true, + component: InstalledIntegrations, + isPrivate: true, + key: 'INTEGRATIONS_INSTALLED', + }, + { + path: ROUTES.INTEGRATIONS_MARKETPLACE, + exact: true, + component: IntegrationsMarketPlace, + isPrivate: true, + key: 'INTEGRATIONS_MARKETPLACE', + }, ]; export const SUPPORT_ROUTE: AppRoutes = { diff --git a/frontend/src/api/Integrations/getAllIntegrations.ts b/frontend/src/api/Integrations/getAllIntegrations.ts new file mode 100644 index 0000000000..8aec6ef9cc --- /dev/null +++ b/frontend/src/api/Integrations/getAllIntegrations.ts @@ -0,0 +1,7 @@ +import axios from 'api'; +import { AxiosResponse } from 'axios'; +import { AllIntegrationsProps } from 'types/api/integrations/types'; + +export const getAllIntegrations = (): Promise< + AxiosResponse +> => axios.get(`/integrations`); diff --git a/frontend/src/api/Integrations/getIntegration.ts b/frontend/src/api/Integrations/getIntegration.ts new file mode 100644 index 0000000000..84fb696343 --- /dev/null +++ b/frontend/src/api/Integrations/getIntegration.ts @@ -0,0 +1,11 @@ +import axios from 'api'; +import { AxiosResponse } from 'axios'; +import { + GetIntegrationPayloadProps, + GetIntegrationProps, +} from 'types/api/integrations/types'; + +export const getIntegration = ( + props: GetIntegrationPayloadProps, +): Promise> => + axios.get(`/integrations/${props.integrationId}`); diff --git a/frontend/src/api/Integrations/getIntegrationStatus.ts b/frontend/src/api/Integrations/getIntegrationStatus.ts new file mode 100644 index 0000000000..fbfbca2782 --- /dev/null +++ b/frontend/src/api/Integrations/getIntegrationStatus.ts @@ -0,0 +1,11 @@ +import axios from 'api'; +import { AxiosResponse } from 'axios'; +import { + GetIntegrationPayloadProps, + GetIntegrationStatusProps, +} from 'types/api/integrations/types'; + +export const getIntegrationStatus = ( + props: GetIntegrationPayloadProps, +): Promise> => + axios.get(`/integrations/${props.integrationId}/connection_status`); diff --git a/frontend/src/api/Integrations/installIntegration.ts b/frontend/src/api/Integrations/installIntegration.ts new file mode 100644 index 0000000000..609ec00545 --- /dev/null +++ b/frontend/src/api/Integrations/installIntegration.ts @@ -0,0 +1,31 @@ +import axios from 'api'; +import { ErrorResponseHandler } from 'api/ErrorResponseHandler'; +import { AxiosError } from 'axios'; +import { ErrorResponse, SuccessResponse } from 'types/api'; +import { + InstalledIntegrationsSuccessResponse, + InstallIntegrationKeyProps, +} from 'types/api/integrations/types'; + +const installIntegration = async ( + props: InstallIntegrationKeyProps, +): Promise< + SuccessResponse | ErrorResponse +> => { + try { + const response = await axios.post('/integrations/install', { + ...props, + }); + + return { + statusCode: 200, + error: null, + message: response.data.status, + payload: response.data.data, + }; + } catch (error) { + return ErrorResponseHandler(error as AxiosError); + } +}; + +export default installIntegration; diff --git a/frontend/src/api/Integrations/uninstallIntegration.ts b/frontend/src/api/Integrations/uninstallIntegration.ts new file mode 100644 index 0000000000..f2a9760bfc --- /dev/null +++ b/frontend/src/api/Integrations/uninstallIntegration.ts @@ -0,0 +1,31 @@ +import axios from 'api'; +import { ErrorResponseHandler } from 'api/ErrorResponseHandler'; +import { AxiosError } from 'axios'; +import { ErrorResponse, SuccessResponse } from 'types/api'; +import { + UninstallIntegrationProps, + UninstallIntegrationSuccessResponse, +} from 'types/api/integrations/types'; + +const unInstallIntegration = async ( + props: UninstallIntegrationProps, +): Promise< + SuccessResponse | ErrorResponse +> => { + try { + const response = await axios.post('/integrations/uninstall', { + ...props, + }); + + return { + statusCode: 200, + error: null, + message: response.data.status, + payload: response.data.data, + }; + } catch (error) { + return ErrorResponseHandler(error as AxiosError); + } +}; + +export default unInstallIntegration; diff --git a/frontend/src/components/MarkdownRenderer/MarkdownRenderer.tsx b/frontend/src/components/MarkdownRenderer/MarkdownRenderer.tsx index cd6a5fdc33..20be0677bd 100644 --- a/frontend/src/components/MarkdownRenderer/MarkdownRenderer.tsx +++ b/frontend/src/components/MarkdownRenderer/MarkdownRenderer.tsx @@ -1,10 +1,12 @@ /* eslint-disable no-restricted-syntax */ /* eslint-disable react/jsx-props-no-spreading */ /* eslint-disable @typescript-eslint/explicit-function-return-type */ + import ReactMarkdown from 'react-markdown'; import { CodeProps } from 'react-markdown/lib/ast-to-react'; import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter'; import { a11yDark } from 'react-syntax-highlighter/dist/cjs/styles/prism'; +import rehypeRaw from 'rehype-raw'; import CodeCopyBtn from './CodeCopyBtn/CodeCopyBtn'; @@ -74,6 +76,10 @@ const interpolateMarkdown = ( return interpolatedContent; }; +function CustomTag({ color }: { color: string }): JSX.Element { + return

This is custom element

; +} + function MarkdownRenderer({ markdownContent, variables, @@ -85,12 +91,14 @@ function MarkdownRenderer({ return ( {interpolatedMarkdown} diff --git a/frontend/src/constants/routes.ts b/frontend/src/constants/routes.ts index afa2b60dd2..d579d7791a 100644 --- a/frontend/src/constants/routes.ts +++ b/frontend/src/constants/routes.ts @@ -46,6 +46,9 @@ const ROUTES = { TRACES_SAVE_VIEWS: '/traces/saved-views', WORKSPACE_LOCKED: '/workspace-locked', SHORTCUTS: '/shortcuts', + INTEGRATIONS_BASE: '/integrations', + INTEGRATIONS_INSTALLED: '/integrations/installed', + INTEGRATIONS_MARKETPLACE: '/integrations/marketplace', } as const; export default ROUTES; diff --git a/frontend/src/container/SideNav/menuItems.tsx b/frontend/src/container/SideNav/menuItems.tsx index 9b897d2a9a..ed6f10b10a 100644 --- a/frontend/src/container/SideNav/menuItems.tsx +++ b/frontend/src/container/SideNav/menuItems.tsx @@ -16,6 +16,7 @@ import { ScrollText, Settings, Slack, + // Unplug, UserPlus, } from 'lucide-react'; @@ -89,6 +90,11 @@ const menuItems: SidebarItem[] = [ label: 'Alerts', icon: , }, + // { + // key: ROUTES.INTEGRATIONS_INSTALLED, + // label: 'Integrations', + // icon: , + // }, { key: ROUTES.ALL_ERROR, label: 'Exceptions', @@ -121,6 +127,7 @@ export const NEW_ROUTES_MENU_ITEM_KEY_MAP: Record = { [ROUTES.TRACES_EXPLORER]: ROUTES.TRACE, [ROUTES.TRACE_EXPLORER]: ROUTES.TRACE, [ROUTES.LOGS_BASE]: ROUTES.LOGS_EXPLORER, + [ROUTES.INTEGRATIONS_BASE]: ROUTES.INTEGRATIONS_INSTALLED, }; export default menuItems; diff --git a/frontend/src/container/TopNav/DateTimeSelectionV2/config.ts b/frontend/src/container/TopNav/DateTimeSelectionV2/config.ts index 383b9eef2c..7999845550 100644 --- a/frontend/src/container/TopNav/DateTimeSelectionV2/config.ts +++ b/frontend/src/container/TopNav/DateTimeSelectionV2/config.ts @@ -135,6 +135,9 @@ export const routesToSkip = [ ROUTES.TRACES_EXPLORER, ROUTES.TRACES_SAVE_VIEWS, ROUTES.SHORTCUTS, + ROUTES.INTEGRATIONS_BASE, + ROUTES.INTEGRATIONS_INSTALLED, + ROUTES.INTEGRATIONS_MARKETPLACE, ]; export const routesToDisable = [ROUTES.LOGS_EXPLORER, ROUTES.LIVE_LOGS]; diff --git a/frontend/src/hooks/Integrations/useGetAllIntegrations.ts b/frontend/src/hooks/Integrations/useGetAllIntegrations.ts new file mode 100644 index 0000000000..c32bbd19e7 --- /dev/null +++ b/frontend/src/hooks/Integrations/useGetAllIntegrations.ts @@ -0,0 +1,13 @@ +import { getAllIntegrations } from 'api/Integrations/getAllIntegrations'; +import { AxiosError, AxiosResponse } from 'axios'; +import { useQuery, UseQueryResult } from 'react-query'; +import { AllIntegrationsProps } from 'types/api/integrations/types'; + +export const useGetAllIntegrations = (): UseQueryResult< + AxiosResponse, + AxiosError +> => + useQuery, AxiosError>({ + queryKey: ['Integrations'], + queryFn: () => getAllIntegrations(), + }); diff --git a/frontend/src/hooks/Integrations/useGetIntegration.ts b/frontend/src/hooks/Integrations/useGetIntegration.ts new file mode 100644 index 0000000000..05cad6c40d --- /dev/null +++ b/frontend/src/hooks/Integrations/useGetIntegration.ts @@ -0,0 +1,18 @@ +import { getIntegration } from 'api/Integrations/getIntegration'; +import { AxiosError, AxiosResponse } from 'axios'; +import { useQuery, UseQueryResult } from 'react-query'; +import { + GetIntegrationPayloadProps, + GetIntegrationProps, +} from 'types/api/integrations/types'; + +export const useGetIntegration = ({ + integrationId, +}: GetIntegrationPayloadProps): UseQueryResult< + AxiosResponse, + AxiosError +> => + useQuery, AxiosError>({ + queryKey: ['Integration', integrationId], + queryFn: () => getIntegration({ integrationId }), + }); diff --git a/frontend/src/hooks/Integrations/useGetIntegrationStatus.ts b/frontend/src/hooks/Integrations/useGetIntegrationStatus.ts new file mode 100644 index 0000000000..af58f63996 --- /dev/null +++ b/frontend/src/hooks/Integrations/useGetIntegrationStatus.ts @@ -0,0 +1,20 @@ +import { getIntegrationStatus } from 'api/Integrations/getIntegrationStatus'; +import { AxiosError, AxiosResponse } from 'axios'; +import { useQuery, UseQueryResult } from 'react-query'; +import { + GetIntegrationPayloadProps, + GetIntegrationStatusProps, +} from 'types/api/integrations/types'; + +export const useGetIntegrationStatus = ({ + integrationId, + enabled, +}: GetIntegrationPayloadProps): UseQueryResult< + AxiosResponse, + AxiosError +> => + useQuery, AxiosError>({ + queryKey: ['Integration', integrationId, Date.now()], + queryFn: () => getIntegrationStatus({ integrationId }), + enabled, + }); diff --git a/frontend/src/pages/Integrations/Header.tsx b/frontend/src/pages/Integrations/Header.tsx new file mode 100644 index 0000000000..f6b8592762 --- /dev/null +++ b/frontend/src/pages/Integrations/Header.tsx @@ -0,0 +1,37 @@ +import './Integrations.styles.scss'; + +import { Color } from '@signozhq/design-tokens'; +import { Input, Typography } from 'antd'; +import { Search } from 'lucide-react'; +import { Dispatch, SetStateAction } from 'react'; + +interface HeaderProps { + searchTerm: string; + setSearchTerm: Dispatch>; +} + +function Header(props: HeaderProps): JSX.Element { + const { searchTerm, setSearchTerm } = props; + + const handleSearch = (e: React.ChangeEvent): void => { + setSearchTerm(e.target.value); + }; + return ( +
+ Integrations + + Manage Integrations for this workspace + + + } + value={searchTerm} + onChange={handleSearch} + className="integrations-search-input" + /> +
+ ); +} + +export default Header; diff --git a/frontend/src/pages/Integrations/IntegrationDetailPage/IntegrationDetailContent.tsx b/frontend/src/pages/Integrations/IntegrationDetailPage/IntegrationDetailContent.tsx new file mode 100644 index 0000000000..6083489b58 --- /dev/null +++ b/frontend/src/pages/Integrations/IntegrationDetailPage/IntegrationDetailContent.tsx @@ -0,0 +1,79 @@ +import './IntegrationDetailPage.styles.scss'; + +import { Button, Tabs, TabsProps, Typography } from 'antd'; +import { Drum, Hammer, Table2 } from 'lucide-react'; +import { IntegrationDetailedProps } from 'types/api/integrations/types'; + +import Configure from './IntegrationDetailContentTabs/Configure'; +import DataCollected from './IntegrationDetailContentTabs/DataCollected'; +import Overview from './IntegrationDetailContentTabs/Overview'; + +interface IntegrationDetailContentProps { + activeDetailTab: string; + integrationData: IntegrationDetailedProps; +} + +function IntegrationDetailContent( + props: IntegrationDetailContentProps, +): JSX.Element { + const { activeDetailTab, integrationData } = props; + const items: TabsProps['items'] = [ + { + key: 'overview', + label: ( + + ), + children: ( + + ), + }, + { + key: 'configuration', + label: ( + + ), + children: , + }, + { + key: 'dataCollected', + label: ( + + ), + children: ( + + ), + }, + ]; + return ( +
+ +
+ ); +} + +export default IntegrationDetailContent; diff --git a/frontend/src/pages/Integrations/IntegrationDetailPage/IntegrationDetailContentTabs/Configure.tsx b/frontend/src/pages/Integrations/IntegrationDetailPage/IntegrationDetailContentTabs/Configure.tsx new file mode 100644 index 0000000000..ede3b41137 --- /dev/null +++ b/frontend/src/pages/Integrations/IntegrationDetailPage/IntegrationDetailContentTabs/Configure.tsx @@ -0,0 +1,48 @@ +import './IntegrationDetailContentTabs.styles.scss'; + +import { Button, Tooltip, Typography } from 'antd'; +import cx from 'classnames'; +import { MarkdownRenderer } from 'components/MarkdownRenderer/MarkdownRenderer'; +import { useState } from 'react'; + +interface ConfigurationProps { + configuration: Array<{ title: string; instructions: string }>; +} + +function Configure(props: ConfigurationProps): JSX.Element { + // TODO Mardown renderer support once instructions are ready + const { configuration } = props; + const [selectedConfigStep, setSelectedConfigStep] = useState(0); + + const handleMenuClick = (index: number): void => { + setSelectedConfigStep(index); + }; + return ( +
+
+ {configuration.map((config, index) => ( + + + + ))} +
+
+ +
+
+ ); +} + +export default Configure; diff --git a/frontend/src/pages/Integrations/IntegrationDetailPage/IntegrationDetailContentTabs/DataCollected.tsx b/frontend/src/pages/Integrations/IntegrationDetailPage/IntegrationDetailContentTabs/DataCollected.tsx new file mode 100644 index 0000000000..a3c387dc3a --- /dev/null +++ b/frontend/src/pages/Integrations/IntegrationDetailPage/IntegrationDetailContentTabs/DataCollected.tsx @@ -0,0 +1,85 @@ +import './IntegrationDetailContentTabs.styles.scss'; + +import { Table, Typography } from 'antd'; +import { BarChart2, ScrollText } from 'lucide-react'; + +interface DataCollectedProps { + logsData: Array; + metricsData: Array; +} + +function DataCollected(props: DataCollectedProps): JSX.Element { + const { logsData, metricsData } = props; + const logsColumns = [ + { + title: 'Name', + dataIndex: 'name', + key: 'name', + }, + { + title: 'Path', + dataIndex: 'path', + key: 'path', + }, + { + title: 'Type', + dataIndex: 'type', + key: 'type', + }, + ]; + + const metricsColumns = [ + { + title: 'Name', + dataIndex: 'name', + key: 'name', + }, + { + title: 'Type', + dataIndex: 'type', + key: 'type', + }, + { + title: 'Unit', + dataIndex: 'unit', + key: 'unit', + }, + ]; + + return ( +
+
+
+ + Logs +
+ + index % 2 === 0 ? 'table-row-dark' : '' + } + dataSource={logsData} + pagination={{ pageSize: 3 }} + className="logs-section-table" + /> + +
+
+ + Metrics +
+
+ index % 2 === 0 ? 'table-row-dark' : '' + } + dataSource={metricsData} + pagination={{ pageSize: 3 }} + className="metrics-section-table" + /> + + + ); +} + +export default DataCollected; diff --git a/frontend/src/pages/Integrations/IntegrationDetailPage/IntegrationDetailContentTabs/IntegrationDetailContentTabs.styles.scss b/frontend/src/pages/Integrations/IntegrationDetailPage/IntegrationDetailContentTabs/IntegrationDetailContentTabs.styles.scss new file mode 100644 index 0000000000..8932c4f99b --- /dev/null +++ b/frontend/src/pages/Integrations/IntegrationDetailPage/IntegrationDetailContentTabs/IntegrationDetailContentTabs.styles.scss @@ -0,0 +1,296 @@ +.integration-detail-overview { + display: flex; + + .integration-detail-overview-left-container { + display: flex; + flex-direction: column; + width: 25%; + gap: 26px; + border-right: 1px solid var(--bg-slate-500); + padding: 16px 0; + color: var(--bg-vanilla-400); + font-family: Inter; + font-size: 11px; + font-style: normal; + font-weight: 400; + line-height: 16px; /* 145.455% */ + letter-spacing: 0.44px; + text-transform: uppercase; + + .integration-detail-overview-category { + display: flex; + flex-direction: column; + + .heading { + color: var(--bg-vanilla-400); + font-family: Inter; + font-size: 11px; + font-style: normal; + font-weight: 500; + line-height: 16px; /* 145.455% */ + letter-spacing: 0.44px; + text-transform: uppercase; + } + + .category-tabs { + display: flex; + gap: 6px; + flex-flow: wrap; + margin-top: 12px; + + .category-tab { + padding: 2px 8px; + border-radius: 4px; + border: 1px solid rgba(173, 127, 88, 0.2); + background: rgba(173, 127, 88, 0.1); + color: var(--bg-sienna-400); + font-family: Inter; + font-size: 14px; + font-style: normal; + font-weight: 400; + line-height: 20px; /* 142.857% */ + letter-spacing: -0.07px; + text-transform: none; + } + } + } + + .integration-detail-overview-assets { + display: flex; + flex-direction: column; + + .heading { + color: var(--bg-vanilla-400); + font-family: Inter; + font-size: 11px; + font-style: normal; + font-weight: 500; + line-height: 16px; /* 145.455% */ + letter-spacing: 0.44px; + text-transform: uppercase; + } + + .assets-list { + margin-left: 5px; + margin-top: 12px; + color: var(--bg-vanilla-400); + font-family: Inter; + font-size: 14px; + font-style: normal; + font-weight: 400; + line-height: 22px; /* 157.143% */ + letter-spacing: -0.07px; + padding-inline-start: 16px !important; + text-transform: none; + } + } + } + + .integration-detail-overview-right-container { + width: 75%; + padding: 16px 0 0 16px; + max-height: 600px; + overflow-y: auto; + } +} + +.integration-data-collected { + display: flex; + flex-direction: column; + gap: 32px; + margin-top: 8px; + color: var(--bg-vanilla-400); + font-family: Inter; + font-size: 14px; + font-style: normal; + font-weight: 400; + line-height: 20px; /* 142.857% */ + letter-spacing: -0.07px; + + .logs-section { + display: flex; + flex-direction: column; + gap: 8px; + + .table-row-dark { + background: rgba(255, 255, 255, 0.01); + } + + .logs-section-table { + border-radius: 6px; + border: 1px solid var(--bg-slate-400); + background: var(--bg-ink-400); + + .ant-table-thead { + text-transform: uppercase; + } + .ant-table-cell { + background: unset !important; + border-bottom: none !important; + } + + .ant-table-cell::before { + background-color: unset !important; + } + } + + .logs-heading { + display: flex; + align-items: center; + gap: 8px; + padding: 4px 6px; + } + } + + .metrics-section { + display: flex; + flex-direction: column; + gap: 8px; + + .table-row-dark { + background: rgba(255, 255, 255, 0.01); + } + + .metrics-section-table { + border-radius: 6px; + border: 1px solid var(--bg-slate-400); + background: var(--bg-ink-400); + + .ant-table-thead { + text-transform: uppercase; + } + + .ant-table-cell { + background: unset !important; + border-bottom: none !important; + } + + .ant-table-cell::before { + background-color: unset !important; + } + } + + .metrics-heading { + display: flex; + align-items: center; + gap: 8px; + padding: 4px 6px; + } + } +} + +.integration-detail-configure { + display: flex; + + .configure-menu { + display: flex; + flex-direction: column; + width: 25%; + padding: 16px 16px 0px 0px; + border-right: 1px solid var(--bg-slate-500); + gap: 8px; + + .configure-menu-item { + padding: 4px 8px; + text-align: start; + color: var(--bg-vanilla-100); + font-family: Inter; + font-size: 14px; + font-style: normal; + font-weight: 400; + line-height: 18px; /* 128.571% */ + } + + .configure-menu-item:hover { + background-color: rgba(255, 255, 255, 0.08); + } + + .active { + color: rgba(255, 255, 255, 0.85); + background-color: rgba(255, 255, 255, 0.08); + } + } + + .markdown-container { + width: 75%; + padding: 16px 0px 0px 16px; + max-height: 600px; + overflow-y: auto; + } +} + +.lightMode { + .integration-detail-overview { + .integration-detail-overview-left-container { + border-right: 1px solid var(--bg-vanilla-400); + + color: var(--bg-slate-100); + + .integration-detail-overview-category { + .heading { + color: var(--bg-slate-100); + } + .category-tabs { + .category-tab { + border: 1px solid rgba(173, 127, 88, 0.2); + background: rgba(173, 127, 88, 0.1); + color: var(--bg-sienna-500); + } + } + } + + .integration-detail-overview-assets { + .heading { + color: var(--bg-slate-100); + } + .assets-list { + color: var(--bg-slate-100); + } + } + } + } + + .integration-data-collected { + color: var(--bg-vanilla-400); + + .logs-section { + .table-row-dark { + background: rgba(255, 255, 255, 0.01); + } + + .logs-section-table { + border: 1px solid var(--bg-vanilla-400); + background: var(--bg-vanilla-300); + } + } + + .metrics-section { + .table-row-dark { + background: rgba(255, 255, 255, 0.01); + } + + .metrics-section-table { + border: 1px solid var(--bg-vanilla-400); + background: var(--bg-vanilla-300); + } + } + } + + .integration-detail-configure { + .configure-menu { + border-right: 1px solid var(--bg-vanilla-400); + + .configure-menu-item { + color: var(--bg-vanilla-100); + } + .configure-menu-item:hover { + background-color: var(--bg-vanilla-200); + } + + .active { + color: rgba(255, 255, 255, 0.85); + background-color: var(--bg-vanilla-200); + } + } + } +} diff --git a/frontend/src/pages/Integrations/IntegrationDetailPage/IntegrationDetailContentTabs/Overview.tsx b/frontend/src/pages/Integrations/IntegrationDetailPage/IntegrationDetailContentTabs/Overview.tsx new file mode 100644 index 0000000000..84183369bd --- /dev/null +++ b/frontend/src/pages/Integrations/IntegrationDetailPage/IntegrationDetailContentTabs/Overview.tsx @@ -0,0 +1,63 @@ +import './IntegrationDetailContentTabs.styles.scss'; + +import { Typography } from 'antd'; +import { MarkdownRenderer } from 'components/MarkdownRenderer/MarkdownRenderer'; + +interface OverviewProps { + categories: string[]; + assets: { + logs: { + pipelines: Array; + }; + dashboards: Array; + alerts: Array; + }; + overviewContent: string; +} + +function Overview(props: OverviewProps): JSX.Element { + const { categories, assets, overviewContent } = props; + const assetsCount = [ + assets.logs.pipelines.length, + assets.dashboards.length, + assets.alerts.length, + ]; + + const assetLabelMap = ['Pipelines', 'Dashboards', 'Alerts']; + return ( +
+
+
+ Category +
+ {categories.map((category) => ( +
+ {category} +
+ ))} +
+
+
+ Assets +
    + {assetsCount.map((count, index) => { + if (count === 0) { + return undefined; + } + return ( +
  • + {count} {assetLabelMap[index]} +
  • + ); + })} +
+
+
+
+ +
+
+ ); +} + +export default Overview; diff --git a/frontend/src/pages/Integrations/IntegrationDetailPage/IntegrationDetailHeader.tsx b/frontend/src/pages/Integrations/IntegrationDetailPage/IntegrationDetailHeader.tsx new file mode 100644 index 0000000000..34f5e612bf --- /dev/null +++ b/frontend/src/pages/Integrations/IntegrationDetailPage/IntegrationDetailHeader.tsx @@ -0,0 +1,190 @@ +/* eslint-disable no-nested-ternary */ +import './IntegrationDetailPage.styles.scss'; + +import { Button, Modal, Typography } from 'antd'; +import installIntegration from 'api/Integrations/installIntegration'; +import { SOMETHING_WENT_WRONG } from 'constants/api'; +import dayjs from 'dayjs'; +import { useNotifications } from 'hooks/useNotifications'; +import { ArrowLeftRight, Check } from 'lucide-react'; +import { useState } from 'react'; +import { useMutation } from 'react-query'; +import { IntegrationStatusProps } from 'types/api/integrations/types'; + +import TestConnection, { ConnectionStates } from './TestConnection'; + +interface IntegrationDetailHeaderProps { + id: string; + title: string; + description: string; + icon: string; + refetchIntegrationDetails: () => void; + connectionState: ConnectionStates; + connectionData: IntegrationStatusProps['connection_status']; +} +function IntegrationDetailHeader( + props: IntegrationDetailHeaderProps, +): JSX.Element { + const { + id, + title, + icon, + description, + connectionState, + connectionData, + refetchIntegrationDetails, + } = props; + const [isModalOpen, setIsModalOpen] = useState(false); + + const { notifications } = useNotifications(); + + const showModal = (): void => { + setIsModalOpen(true); + }; + + const handleOk = (): void => { + setIsModalOpen(false); + }; + + const handleCancel = (): void => { + setIsModalOpen(false); + }; + + const { mutate, isLoading: isInstallLoading } = useMutation( + installIntegration, + { + onSuccess: () => { + refetchIntegrationDetails(); + }, + onError: () => { + notifications.error({ + message: SOMETHING_WENT_WRONG, + }); + }, + }, + ); + + let latestData: { + last_received_ts_ms: number | null; + last_received_from: string | null; + } = { + last_received_ts_ms: null, + last_received_from: null, + }; + + if ( + connectionData.logs?.last_received_ts_ms && + connectionData.metrics?.last_received_ts_ms + ) { + if ( + connectionData.logs.last_received_ts_ms > + connectionData.metrics.last_received_ts_ms + ) { + latestData = { + last_received_ts_ms: connectionData.logs.last_received_ts_ms, + last_received_from: connectionData.logs.last_received_from, + }; + } else { + latestData = { + last_received_ts_ms: connectionData.metrics.last_received_ts_ms, + last_received_from: connectionData.metrics.last_received_from, + }; + } + } else if (connectionData.logs?.last_received_ts_ms) { + latestData = { + last_received_ts_ms: connectionData.logs.last_received_ts_ms, + last_received_from: connectionData.logs.last_received_from, + }; + } else if (connectionData.metrics?.last_received_ts_ms) { + latestData = { + last_received_ts_ms: connectionData.metrics.last_received_ts_ms, + last_received_from: connectionData.metrics.last_received_from, + }; + } + return ( +
+
+
+
+ {title} +
+
+ {title} + {description} +
+
+ +
+ + {connectionState !== ConnectionStates.NotInstalled && ( + + )} + + }} + cancelButtonProps={{ style: { display: 'none' } }} + > +
+ + {connectionState === ConnectionStates.Connected || + connectionState === ConnectionStates.NoDataSinceLong ? ( + <> +
+ + Last recieved from + + + {latestData.last_received_from} + +
+
+ + Last recieved at + + + {latestData.last_received_ts_ms + ? dayjs(latestData.last_received_ts_ms).format('DD MMM YYYY HH:mm') + : ''} + +
+ + ) : connectionState === ConnectionStates.TestingConnection ? ( +
+
+ After adding the {title} integration, you need to manually configure + your Redis data source to start sending data to SigNoz. +
+
+ The status bar above would turn green if we are successfully receiving + the data. +
+
+ ) : null} +
+
+
+ ); +} + +export default IntegrationDetailHeader; diff --git a/frontend/src/pages/Integrations/IntegrationDetailPage/IntegrationDetailPage.styles.scss b/frontend/src/pages/Integrations/IntegrationDetailPage/IntegrationDetailPage.styles.scss new file mode 100644 index 0000000000..125cff56f9 --- /dev/null +++ b/frontend/src/pages/Integrations/IntegrationDetailPage/IntegrationDetailPage.styles.scss @@ -0,0 +1,665 @@ +.integration-detail-content { + display: flex; + flex-direction: column; + gap: 16px; + margin: 12px 0px 20px 0px; + + .error-container { + display: flex; + border-radius: 6px; + border: 1px solid var(--bg-slate-500); + background: var(--bg-ink-400); + align-items: center; + justify-content: center; + flex-direction: column; + + .error-content { + display: flex; + flex-direction: column; + justify-content: center; + height: 300px; + gap: 15px; + + .error-btns { + display: flex; + flex-direction: row; + gap: 16px; + align-items: center; + + .retry-btn { + display: flex; + align-items: center; + } + + .contact-support { + display: flex; + align-items: center; + gap: 4px; + cursor: pointer; + + .text { + color: var(--text-robin-400); + font-weight: 500; + } + } + } + + .error-state-svg { + height: 40px; + width: 40px; + } + } + } + + .loading-integration-details { + display: flex; + height: 400px; + justify-content: center; + align-items: center; + } + + .all-integrations-btn { + width: fit-content; + display: flex; + justify-content: center; + align-items: center; + height: 24px; + padding-left: 0px; + color: #c0c1c3; + font-family: Inter; + font-size: 14px; + font-style: normal; + font-weight: 400; + line-height: 18px; /* 128.571% */ + } + + .all-integrations-btn:hover { + &.ant-btn-text { + background-color: unset !important; + } + } + + .integration-connection-header { + display: flex; + flex-direction: column; + padding: 16px; + gap: 12px; + border-radius: 6px; + border: 1px solid var(--bg-slate-500); + background: var(--bg-ink-400); + + .integration-detail-header { + display: flex; + gap: 10px; + justify-content: space-between; + + .image-container { + height: 40px; + width: 40px; + flex-shrink: 0; + border-radius: 2px; + border: 1px solid var(--bg-ink-50); + background: var(--bg-ink-300); + display: flex; + align-items: center; + justify-content: center; + + .image { + height: 24px; + width: 24px; + } + } + .details { + display: flex; + flex-direction: column; + .heading { + color: var(--bg-vanilla-100); + font-family: Inter; + font-size: 14px; + font-style: normal; + font-weight: 500; + line-height: 20px; /* 142.857% */ + letter-spacing: -0.07px; + } + + .description { + color: var(--bg-vanilla-400); + font-family: Inter; + font-size: 12px; + font-style: normal; + font-weight: 400; + line-height: 18px; /* 150% */ + } + } + + .configure-btn { + display: flex; + justify-content: center; + align-items: center; + align-self: flex-start; + gap: 2px; + flex-shrink: 0; + min-width: 143px; + height: 30px; + padding: 6px; + border-radius: 2px; + border: 1px solid var(--bg-ink-50); + background: var(--bg-robin-500); + color: var(--bg-vanilla-100); + font-family: Inter; + font-size: 12px; + font-style: normal; + font-weight: 500; + line-height: 10px; /* 83.333% */ + letter-spacing: 0.12px; + } + } + + .connection-container { + padding: 0 18px; + height: 37px; + display: flex; + align-items: center; + + .connection-text { + margin: 0px; + padding: 0px 0px 0px 10px; + font-family: Inter; + font-size: 14px; + font-style: normal; + font-weight: 400; + line-height: 22px; /* 157.143% */ + letter-spacing: -0.07px; + } + } + + .testingConnection { + border-radius: 4px; + border: 1px solid rgba(255, 205, 86, 0.1); + background: rgba(255, 205, 86, 0.1); + color: var(--bg-amber-400); + } + + .connected { + border-radius: 4px; + border: 1px solid rgba(37, 225, 146, 0.1); + background: rgba(37, 225, 146, 0.1); + color: var(--bg-forest-400); + } + + .connectionFailed { + border-radius: 4px; + border: 1px solid rgba(218, 85, 101, 0.2); + background: rgba(218, 85, 101, 0.06); + color: var(--bg-cherry-500); + } + + .noDataSinceLong { + border-radius: 4px; + border: 1px solid rgba(78, 116, 248, 0.1); + background: rgba(78, 116, 248, 0.1); + color: var(--bg-robin-400); + } + } + + .integration-detail-container { + border-radius: 6px; + padding: 10px 16px; + border: 1px solid var(--bg-slate-500); + background: var(--bg-ink-400, #121317); + min-height: 300px; + + .integration-tab-btns { + display: flex; + align-items: center; + justify-content: center; + padding: 8px 8px 18px 8px !important; + + .typography { + color: var(--bg-vanilla-100); + font-family: Inter; + font-size: 14px; + font-style: normal; + font-weight: 400; + line-height: 20px; /* 142.857% */ + letter-spacing: -0.07px; + } + } + + .integration-tab-btns:hover { + &.ant-btn-text { + background-color: unset !important; + } + } + + .ant-tabs-nav-list { + gap: 24px; + } + + .ant-tabs-nav { + padding: 0px !important; + } + + .ant-tabs-tab { + padding: 0 !important; + } + + .ant-tabs-tab + .ant-tabs-tab { + margin: 0px !important; + } + } + + .uninstall-integration-bar { + display: flex; + padding: 16px; + border-radius: 4px; + border: 1px solid rgba(218, 85, 101, 0.2); + background: rgba(218, 85, 101, 0.06); + + .unintall-integration-bar-text { + display: flex; + flex-direction: column; + gap: 6px; + + .heading { + color: var(--bg-cherry-500); + font-family: Inter; + font-size: 14px; + font-style: normal; + font-weight: 400; + line-height: normal; + letter-spacing: -0.07px; + } + + .subtitle { + color: var(--bg-cherry-300); + font-family: Inter; + font-size: 14px; + font-style: normal; + font-weight: 400; + line-height: 22px; /* 157.143% */ + letter-spacing: -0.07px; + } + } + + .uninstall-integration-btn { + border-radius: 2px; + background: var(--Accent---Secondary-Cherry, #da5565); + border-color: unset !important; + padding: 9px 13px; + display: flex; + align-items: center; + justify-content: center; + color: var(--bg-ink-300); + text-align: center; + font-family: Inter; + font-size: 12px; + font-style: normal; + font-weight: 500; + line-height: 13.3px; /* 110.833% */ + } + + .uninstall-integration-btn:hover { + &.ant-btn-default { + color: var(--bg-ink-300) !important; + } + } + } +} + +.remove-integration-modal { + .ant-modal-content { + width: 384px; + min-height: 200px; + flex-shrink: 0; + border-radius: 4px; + border: 1px solid var(--bg-slate-500); + box-shadow: 0px -4px 16px 2px rgba(0, 0, 0, 0.2); + background: var(--bg-ink-400); + } + + .ant-modal-footer { + margin-top: 28px; + } + + .ant-modal-header { + background: unset; + margin-bottom: 8px; + } + + .ant-modal-title { + color: var(--bg-vanilla-100); + font-family: Inter; + font-size: 14px; + font-style: normal; + font-weight: 400; + line-height: 20px; /* 142.857% */ + letter-spacing: -0.07px; + } + + .remove-integration-text { + color: var(--bg-vanilla-400); + font-family: Inter; + font-size: 14px; + font-style: normal; + font-weight: 400; + line-height: 20px; /* 142.857% */ + letter-spacing: -0.07px; + } +} + +.test-connection-modal { + .ant-modal-content { + width: 512px; + min-height: 170px; + flex-shrink: 0; + border-radius: 4px; + border: 1px solid var(--bg-slate-500); + background: var(--bg-ink-400); + box-shadow: 0px -4px 16px 2px rgba(0, 0, 0, 0.2); + + .ant-modal-header { + margin-bottom: 16px; + } + + .ant-modal-body { + border-top: 1px solid var(--bg-slate-500); + padding-top: 16px; + } + + .ant-modal-footer { + margin-top: 25px; + display: flex; + flex-direction: row-reverse; + + .understandBtn { + border-radius: 2px; + border: 1px solid var(--bg-slate-400); + background: var(--bg-ink-300); + box-shadow: none; + color: var(--bg-vanilla-400); + font-family: Inter; + font-size: 12px; + font-style: normal; + font-weight: 400; + line-height: 10px; /* 83.333% */ + letter-spacing: 0.12px; + display: flex; + justify-content: center; + align-items: center; + width: 131px; + height: 30px; + padding: 6px; + flex-shrink: 0; + } + } + } + + .ant-modal-header { + background: unset; + } + + .connection-content { + display: flex; + flex-direction: column; + gap: 16px; + + .connection-container { + padding: 0 10px; + height: 37px; + display: flex; + align-items: center; + + .connection-text { + margin: 0px; + padding: 0px 0px 0px 10px; + font-family: Inter; + font-size: 14px; + font-style: normal; + font-weight: 400; + line-height: 22px; /* 157.143% */ + letter-spacing: -0.07px; + } + } + + .data-test-connection { + display: flex; + flex-direction: column; + gap: 16px; + } + .data-info { + display: flex; + justify-content: space-between; + + .last-data { + color: var(--bg-vanilla-400); + font-family: Inter; + font-size: 14px; + font-style: normal; + font-weight: 400; + line-height: 22px; /* 157.143% */ + letter-spacing: -0.07px; + } + + .last-value { + color: var(--bg-vanilla-100); + font-family: 'Space Mono'; + font-size: 12px; + font-style: normal; + font-weight: 400; + line-height: 18px; /* 150% */ + } + } + .testingConnection { + border-radius: 4px; + border: 1px solid rgba(255, 205, 86, 0.1); + background: rgba(255, 205, 86, 0.1); + color: var(--bg-amber-400); + } + + .connected { + border-radius: 4px; + border: 1px solid rgba(37, 225, 146, 0.1); + background: rgba(37, 225, 146, 0.1); + color: var(--bg-forest-400); + } + + .connectionFailed { + border-radius: 4px; + border: 1px solid rgba(218, 85, 101, 0.2); + background: rgba(218, 85, 101, 0.06); + color: var(--bg-cherry-500); + } + + .noDataSinceLong { + border-radius: 4px; + border: 1px solid rgba(78, 116, 248, 0.1); + background: rgba(78, 116, 248, 0.1); + color: var(--bg-robin-400); + } + } +} + +.lightMode { + .integration-detail-content { + .error-container { + border: 1px solid var(--bg-slate-500); + background: var(--bg-ink-400); + + .error-content { + .error-btns { + .contact-support { + .text { + color: var(--text-robin-400); + font-weight: 500; + } + } + } + } + } + + .all-integrations-btn { + color: var(--bg-slate-400); + } + + .all-integrations-btn:hover { + &.ant-btn-text { + background-color: unset !important; + } + } + + .integration-connection-header { + border: 1px solid var(--bg-vanilla-400); + background: var(--bg-vanilla-300); + + .integration-detail-header { + .image-container { + border: 1px solid var(--bg-ink-50); + background: var(--bg-vanilla-200); + } + .details { + .heading { + color: var(--bg-slate-400); + } + + .description { + color: var(--bg-slate-100); + } + } + } + + .testingConnection { + border: 1px solid rgba(255, 205, 86, 0.1); + background: rgba(255, 205, 86, 0.1); + color: var(--bg-amber-600); + } + + .connected { + border: 1px solid rgba(37, 225, 146, 0.1); + background: rgba(37, 225, 146, 0.1); + color: var(--bg-forest-600); + } + + .connectionFailed { + border: 1px solid rgba(218, 85, 101, 0.2); + background: rgba(218, 85, 101, 0.06); + color: var(--bg-cherry-500); + } + + .noDataSinceLong { + border: 1px solid rgba(78, 116, 248, 0.1); + background: rgba(78, 116, 248, 0.1); + color: var(--bg-robin-400); + } + } + + .integration-detail-container { + border: 1px solid var(--bg-vanilla-400); + background: var(--bg-vanilla-300); + + .integration-tab-btns { + .typography { + color: var(--bg-slate-100); + } + } + } + + .uninstall-integration-bar { + border: 1px solid rgba(218, 85, 101, 0.2); + background: rgba(218, 85, 101, 0.06); + + .unintall-integration-bar-text { + .heading { + color: var(--bg-cherry-500); + } + + .subtitle { + color: var(--bg-cherry-400); + } + } + + .uninstall-integration-btn { + background: var(--Accent---Secondary-Cherry, #da5565); + border-color: var(--bg-cherry-300) !important; + color: var(--bg-vanilla-100); + } + + .uninstall-integration-btn:hover { + &.ant-btn-default { + color: var(--bg-vanilla-300) !important; + } + } + } + } + + .remove-integration-modal { + .ant-modal-content { + border: 1px solid var(--bg-vanilla-400); + box-shadow: 0px -4px 16px 2px rgba(0, 0, 0, 0.2); + background: var(--bg-vanilla-300); + } + + .ant-modal-title { + color: var(--bg-slate-100); + } + + .remove-integration-text { + color: var(--bg-slate-400); + } + } + + .test-connection-modal { + .ant-modal-content { + border: 1px solid var(--bg-vanilla-400); + background: var(--bg-vanilla-300); + box-shadow: 0px -4px 16px 2px rgba(0, 0, 0, 0.2); + + .ant-modal-body { + border-top: 1px solid var(--bg-vanilla-400); + } + + .ant-modal-footer { + .understandBtn { + border: 1px solid var(--bg-vanilla-400); + background: var(--bg-vanilla-400); + color: var(--bg-slate-400); + } + } + } + + .connection-content { + .data-info { + .last-data { + color: var(--bg-slate-400); + } + + .last-value { + color: var(--bg-slate-100); + } + } + .testingConnection { + border: 1px solid rgba(255, 205, 86, 0.1); + background: rgba(255, 205, 86, 0.1); + color: var(--bg-amber-600); + } + + .connected { + border: 1px solid rgba(37, 225, 146, 0.1); + background: rgba(37, 225, 146, 0.1); + color: var(--bg-forest-600); + } + + .connectionFailed { + border: 1px solid rgba(218, 85, 101, 0.2); + background: rgba(218, 85, 101, 0.06); + color: var(--bg-cherry-500); + } + + .noDataSinceLong { + border: 1px solid rgba(78, 116, 248, 0.1); + background: rgba(78, 116, 248, 0.1); + color: var(--bg-robin-400); + } + } + } +} diff --git a/frontend/src/pages/Integrations/IntegrationDetailPage/IntegrationDetailPage.tsx b/frontend/src/pages/Integrations/IntegrationDetailPage/IntegrationDetailPage.tsx new file mode 100644 index 0000000000..3d498a07d8 --- /dev/null +++ b/frontend/src/pages/Integrations/IntegrationDetailPage/IntegrationDetailPage.tsx @@ -0,0 +1,156 @@ +/* eslint-disable jsx-a11y/no-static-element-interactions */ +/* eslint-disable jsx-a11y/click-events-have-key-events */ +/* eslint-disable no-nested-ternary */ +import './IntegrationDetailPage.styles.scss'; + +import { Color } from '@signozhq/design-tokens'; +import { Button, Typography } from 'antd'; +import { useGetIntegration } from 'hooks/Integrations/useGetIntegration'; +import { useGetIntegrationStatus } from 'hooks/Integrations/useGetIntegrationStatus'; +import { defaultTo } from 'lodash-es'; +import { ArrowLeft, MoveUpRight, RotateCw } from 'lucide-react'; +import { useEffect } from 'react'; +import { isCloudUser } from 'utils/app'; + +import { handleContactSupport } from '../utils'; +import IntegrationDetailContent from './IntegrationDetailContent'; +import IntegrationDetailHeader from './IntegrationDetailHeader'; +import IntergrationsUninstallBar from './IntegrationsUninstallBar'; +import { ConnectionStates } from './TestConnection'; +import { getConnectionStatesFromConnectionStatus } from './utils'; + +interface IntegrationDetailPageProps { + selectedIntegration: string; + setSelectedIntegration: (id: string | null) => void; + activeDetailTab: string; +} + +function IntegrationDetailPage(props: IntegrationDetailPageProps): JSX.Element { + const { selectedIntegration, setSelectedIntegration, activeDetailTab } = props; + + const { + data, + isLoading, + isFetching, + refetch, + isRefetching, + isError, + } = useGetIntegration({ + integrationId: selectedIntegration, + }); + + const { + data: integrationStatus, + refetch: refetchStatus, + isLoading: isStatusLoading, + } = useGetIntegrationStatus({ + integrationId: selectedIntegration, + enabled: false, + }); + + const loading = isLoading || isFetching || isRefetching || isStatusLoading; + const integrationData = data?.data.data; + + const connectionStatus = getConnectionStatesFromConnectionStatus( + integrationData?.installation, + defaultTo( + integrationStatus?.data.data.connection_status, + defaultTo(integrationData?.connection_status, { logs: null, metrics: null }), + ), + ); + + useEffect(() => { + // we should once get data on load and then keep polling every 5 seconds + refetchStatus(); + const timer = setInterval(() => { + refetchStatus(); + }, 5000); + + return (): void => { + clearInterval(timer); + }; + }, [refetchStatus]); + + return ( +
+ + + {loading ? ( +
+ Please wait.. While we load the integration details +
+ ) : isError ? ( +
+
+ error-emoji + + Something went wrong :/ Please retry or contact support. + +
+ +
handleContactSupport(isCloudUser())} + > + Contact Support + + +
+
+
+
+ ) : ( + integrationData && ( + <> + + + + {connectionStatus !== ConnectionStates.NotInstalled && ( + + )} + + ) + )} +
+ ); +} + +export default IntegrationDetailPage; diff --git a/frontend/src/pages/Integrations/IntegrationDetailPage/IntegrationsUninstallBar.tsx b/frontend/src/pages/Integrations/IntegrationDetailPage/IntegrationsUninstallBar.tsx new file mode 100644 index 0000000000..7f64c9bb1d --- /dev/null +++ b/frontend/src/pages/Integrations/IntegrationDetailPage/IntegrationsUninstallBar.tsx @@ -0,0 +1,89 @@ +import './IntegrationDetailPage.styles.scss'; + +import { Button, Modal, Typography } from 'antd'; +import unInstallIntegration from 'api/Integrations/uninstallIntegration'; +import { SOMETHING_WENT_WRONG } from 'constants/api'; +import { useNotifications } from 'hooks/useNotifications'; +import { X } from 'lucide-react'; +import { useState } from 'react'; +import { useMutation } from 'react-query'; + +interface IntergrationsUninstallBarProps { + integrationTitle: string; + integrationId: string; + refetchIntegrationDetails: () => void; +} +function IntergrationsUninstallBar( + props: IntergrationsUninstallBarProps, +): JSX.Element { + const { integrationTitle, integrationId, refetchIntegrationDetails } = props; + const { notifications } = useNotifications(); + const [isModalOpen, setIsModalOpen] = useState(false); + + const { + mutate: uninstallIntegration, + isLoading: isUninstallLoading, + } = useMutation(unInstallIntegration, { + onSuccess: () => { + refetchIntegrationDetails(); + setIsModalOpen(false); + }, + onError: () => { + notifications.error({ + message: SOMETHING_WENT_WRONG, + }); + }, + }); + + const showModal = (): void => { + setIsModalOpen(true); + }; + + const handleOk = (): void => { + uninstallIntegration({ + integrationId, + }); + }; + + const handleCancel = (): void => { + setIsModalOpen(false); + }; + return ( +
+
+ Remove Integration + + Removing the {integrationTitle} integration would make your workspace stop + listening for data from {integrationTitle} instances. + +
+ + + + Removing this integration makes SigNoz stop listening for data from{' '} + {integrationTitle} instances. You would still have to manually remove the + configuration in your code to stop sending data. + + +
+ ); +} + +export default IntergrationsUninstallBar; diff --git a/frontend/src/pages/Integrations/IntegrationDetailPage/TestConnection.tsx b/frontend/src/pages/Integrations/IntegrationDetailPage/TestConnection.tsx new file mode 100644 index 0000000000..e593e121e1 --- /dev/null +++ b/frontend/src/pages/Integrations/IntegrationDetailPage/TestConnection.tsx @@ -0,0 +1,35 @@ +import './IntegrationDetailPage.styles.scss'; + +import cx from 'classnames'; + +export enum ConnectionStates { + Connected = 'connected', + TestingConnection = 'testingConnection', + NoDataSinceLong = 'noDataSinceLong', + NotInstalled = 'notInstalled', +} + +const ConnectionStatesLabelMap = { + [ConnectionStates.Connected]: 'This integration is working properly', + [ConnectionStates.TestingConnection]: 'Listening for data...', + [ConnectionStates.NoDataSinceLong]: + 'This integration has not received data in a while :/', + [ConnectionStates.NotInstalled]: '', +}; + +interface TestConnectionProps { + connectionState: ConnectionStates; +} + +function TestConnection(props: TestConnectionProps): JSX.Element { + const { connectionState } = props; + return ( +
+
    +
  • {ConnectionStatesLabelMap[connectionState]}
  • +
+
+ ); +} + +export default TestConnection; diff --git a/frontend/src/pages/Integrations/IntegrationDetailPage/utils.ts b/frontend/src/pages/Integrations/IntegrationDetailPage/utils.ts new file mode 100644 index 0000000000..43a4f76a5e --- /dev/null +++ b/frontend/src/pages/Integrations/IntegrationDetailPage/utils.ts @@ -0,0 +1,55 @@ +import dayjs from 'dayjs'; +import { isNull, isUndefined } from 'lodash-es'; + +import { ConnectionStates } from './TestConnection'; + +export function getConnectionStatesFromConnectionStatus( + installation: + | { + installed_at: string; + } + | null + | undefined, + connection_status: { + logs: + | { + last_received_ts_ms: number; + last_received_from: string; + } + | null + | undefined; + metrics: + | { + last_received_ts_ms: number; + last_received_from: string; + } + | null + | undefined; + }, +): ConnectionStates { + if (isNull(installation) || isUndefined(installation)) { + return ConnectionStates.NotInstalled; + } + if ( + (isNull(connection_status.logs) || isUndefined(connection_status.logs)) && + (isNull(connection_status.metrics) || isUndefined(connection_status.metrics)) + ) { + const installationDate = dayjs(installation.installed_at); + if (installationDate.isBefore(dayjs().subtract(7, 'days'))) { + return ConnectionStates.NoDataSinceLong; + } + return ConnectionStates.TestingConnection; + } + + const logsDate = dayjs(connection_status.logs?.last_received_ts_ms); + const metricsDate = dayjs(connection_status.metrics?.last_received_ts_ms); + + if ( + logsDate.isBefore(dayjs().subtract(7, 'days')) && + metricsDate.isBefore(dayjs().subtract(7, 'days')) + ) { + return ConnectionStates.NoDataSinceLong; + } + + return ConnectionStates.Connected; +} diff --git a/frontend/src/pages/Integrations/Integrations.styles.scss b/frontend/src/pages/Integrations/Integrations.styles.scss new file mode 100644 index 0000000000..f3e8492257 --- /dev/null +++ b/frontend/src/pages/Integrations/Integrations.styles.scss @@ -0,0 +1,228 @@ +.integrations-container { + margin-top: 24px; + display: flex; + justify-content: center; + width: 100%; + + .integrations-content { + width: calc(100% - 30px); + max-width: 736px; + + .integrations-header { + .title { + color: var(--bg-vanilla-100); + font-size: var(--font-size-lg); + font-style: normal; + line-height: 28px; /* 155.556% */ + letter-spacing: -0.09px; + font-family: Inter; + font-weight: 500; + } + + .subtitle { + color: var(--bg-vanilla-400); + font-size: var(--font-size-sm); + font-style: normal; + line-height: 20px; /* 142.857% */ + letter-spacing: -0.07px; + font-family: Inter; + font-weight: 400; + } + + .integrations-search-input { + margin-top: 1rem; + border-radius: 2px; + border: 1px solid var(--bg-slate-400); + background: var(--bg-ink-300); + + .ant-input { + background-color: unset; + } + } + } + + .integrations-list { + margin-top: 16px; + + .error-container { + display: flex; + border-radius: 6px; + border: 1px solid var(--bg-slate-500); + background: var(--bg-ink-400); + align-items: center; + justify-content: center; + flex-direction: column; + + .error-content { + display: flex; + flex-direction: column; + justify-content: center; + height: 300px; + gap: 15px; + + .error-btns { + display: flex; + flex-direction: row; + gap: 16px; + align-items: center; + + .retry-btn { + display: flex; + align-items: center; + } + + .contact-support { + display: flex; + align-items: center; + gap: 4px; + cursor: pointer; + + .text { + color: var(--text-robin-400); + font-weight: 500; + } + } + } + + .error-state-svg { + height: 40px; + width: 40px; + } + } + } + + .ant-list-items { + gap: 16px; + display: flex; + flex-direction: column; + } + + .integrations-list-item { + display: flex; + gap: 10px; + padding: 16px; + border-radius: 6px; + border: 1px solid var(--bg-slate-500); + background: var(--bg-ink-400); + cursor: pointer; + + .list-item-image-container { + height: 40px; + width: 40px; + flex-shrink: 0; + border-radius: 2px; + border: 1px solid var(--bg-ink-50); + background: var(--bg-ink-300); + display: flex; + align-items: center; + justify-content: center; + + .list-item-image { + height: 24px; + width: 24px; + } + } + + .list-item-details { + display: flex; + flex-direction: column; + + .heading { + color: var(--bg-vanilla-100); + font-family: Inter; + font-size: 14px; + font-style: normal; + font-weight: 500; + line-height: 20px; /* 142.857% */ + letter-spacing: -0.07px; + margin-bottom: 8px; + } + + .description { + color: var(--bg-vanilla-400); + font-family: Inter; + font-size: 12px; + font-style: normal; + font-weight: 400; + line-height: 18px; /* 150% */ + } + } + + .configure-btn { + display: flex; + justify-content: center; + align-items: center; + align-self: flex-start; + gap: 2px; + flex-shrink: 0; + width: 78px; + height: 24px; + padding: 6px 1px; + border-radius: 2px; + border: 1px solid #303540; + background: var(--bg-ink-200); + box-shadow: none; + color: var(--bg-vanilla-400); + font-family: Inter; + font-size: 12px; + font-style: normal; + font-weight: 400; + line-height: 10px; /* 83.333% */ + letter-spacing: 0.12px; + } + } + } + } +} + +.lightMode { + .integrations-container { + .integrations-content { + .integrations-header { + .title { + color: var(--bg-slate-400); + } + .subtitle { + color: var(--bg-slate-100); + } + .integrations-search-input { + border: 1px solid var(--bg-vanilla-400); + background: var(--bg-vanilla-100); + } + } + + .integrations-list { + .error-container { + border: 1px solid var(--bg-vanilla-400); + background: var(--bg-vanilla-300); + } + + .integrations-list-item { + border: 1px solid var(--bg-vanilla-400); + background: var(--bg-vanilla-300); + + .list-item-image-container { + border: 1px solid var(--bg-ink-50); + background: var(--bg-vanilla-200); + } + + .list-item-details { + .heading { + color: var(--bg-slate-400); + } + + .description { + color: var(--bg-slate-100); + } + } + + .configure-btn { + border: 1px solid var(--bg-vanilla-400); + background: var(--bg-vanilla-200); + color: var(--bg-slate-400); + } + } + } + } + } +} diff --git a/frontend/src/pages/Integrations/Integrations.tsx b/frontend/src/pages/Integrations/Integrations.tsx new file mode 100644 index 0000000000..6d25a20a6f --- /dev/null +++ b/frontend/src/pages/Integrations/Integrations.tsx @@ -0,0 +1,41 @@ +import './Integrations.styles.scss'; + +import { useState } from 'react'; + +import Header from './Header'; +import IntegrationDetailPage from './IntegrationDetailPage/IntegrationDetailPage'; +import IntegrationsList from './IntegrationsList'; + +function Integrations(): JSX.Element { + const [selectedIntegration, setSelectedIntegration] = useState( + null, + ); + + const [activeDetailTab, setActiveDetailTab] = useState(null); + + const [searchTerm, setSearchTerm] = useState(''); + return ( +
+
+ {selectedIntegration && activeDetailTab ? ( + + ) : ( + <> +
+ + + )} +
+
+ ); +} + +export default Integrations; diff --git a/frontend/src/pages/Integrations/IntegrationsList.tsx b/frontend/src/pages/Integrations/IntegrationsList.tsx new file mode 100644 index 0000000000..47cd76bb68 --- /dev/null +++ b/frontend/src/pages/Integrations/IntegrationsList.tsx @@ -0,0 +1,120 @@ +/* eslint-disable jsx-a11y/no-static-element-interactions */ +/* eslint-disable jsx-a11y/click-events-have-key-events */ +import './Integrations.styles.scss'; + +import { Color } from '@signozhq/design-tokens'; +import { Button, List, Typography } from 'antd'; +import { useGetAllIntegrations } from 'hooks/Integrations/useGetAllIntegrations'; +import { MoveUpRight, RotateCw } from 'lucide-react'; +import { Dispatch, SetStateAction, useMemo } from 'react'; +import { isCloudUser } from 'utils/app'; + +import { handleContactSupport } from './utils'; + +interface IntegrationsListProps { + setSelectedIntegration: (id: string) => void; + setActiveDetailTab: Dispatch>; + searchTerm: string; +} + +function IntegrationsList(props: IntegrationsListProps): JSX.Element { + const { setSelectedIntegration, searchTerm, setActiveDetailTab } = props; + + const { + data, + isFetching, + isLoading, + isRefetching, + isError, + refetch, + } = useGetAllIntegrations(); + + const filteredDataList = useMemo(() => { + if (data?.data.data.integrations) { + return data?.data.data.integrations.filter((item) => + item.title.toLowerCase().includes(searchTerm.toLowerCase()), + ); + } + return []; + }, [data?.data.data.integrations, searchTerm]); + + const loading = isLoading || isFetching || isRefetching; + + return ( +
+ {!loading && isError && ( +
+
+ error-emoji + + Something went wrong :/ Please retry or contact support. + +
+ +
handleContactSupport(isCloudUser())} + > + Contact Support + + +
+
+
+
+ )} + {!isError && ( + ( + { + setSelectedIntegration(item.id); + setActiveDetailTab('overview'); + }} + > +
+
+ {item.title} +
+
+ {item.title} + + {item.description} + +
+
+ +
+ )} + /> + )} +
+ ); +} + +export default IntegrationsList; diff --git a/frontend/src/pages/Integrations/index.ts b/frontend/src/pages/Integrations/index.ts new file mode 100644 index 0000000000..806360c344 --- /dev/null +++ b/frontend/src/pages/Integrations/index.ts @@ -0,0 +1,3 @@ +import Integrations from './Integrations'; + +export default Integrations; diff --git a/frontend/src/pages/Integrations/utils.ts b/frontend/src/pages/Integrations/utils.ts new file mode 100644 index 0000000000..81c70b6091 --- /dev/null +++ b/frontend/src/pages/Integrations/utils.ts @@ -0,0 +1,9 @@ +import history from 'lib/history'; + +export const handleContactSupport = (isCloudUser: boolean): void => { + if (isCloudUser) { + history.push('/support'); + } else { + window.open('https://signoz.io/slack', '_blank'); + } +}; diff --git a/frontend/src/pages/IntegrationsMarketPlace/IntegrationsMarketPlace.tsx b/frontend/src/pages/IntegrationsMarketPlace/IntegrationsMarketPlace.tsx new file mode 100644 index 0000000000..c63f8a659a --- /dev/null +++ b/frontend/src/pages/IntegrationsMarketPlace/IntegrationsMarketPlace.tsx @@ -0,0 +1,9 @@ +function IntegrationsMarketPlace(): JSX.Element { + return ( +
+

IntegrationsMarketPlace

+
+ ); +} + +export default IntegrationsMarketPlace; diff --git a/frontend/src/pages/IntegrationsMarketPlace/index.ts b/frontend/src/pages/IntegrationsMarketPlace/index.ts new file mode 100644 index 0000000000..6c088880e7 --- /dev/null +++ b/frontend/src/pages/IntegrationsMarketPlace/index.ts @@ -0,0 +1,3 @@ +import IntegrationsMarketPlace from './IntegrationsMarketPlace'; + +export default IntegrationsMarketPlace; diff --git a/frontend/src/pages/IntegrationsModulePage/IntegrationsModulePage.styles.scss b/frontend/src/pages/IntegrationsModulePage/IntegrationsModulePage.styles.scss new file mode 100644 index 0000000000..4ff58bea40 --- /dev/null +++ b/frontend/src/pages/IntegrationsModulePage/IntegrationsModulePage.styles.scss @@ -0,0 +1,27 @@ +.integrations-module-container { + .ant-tabs-nav { + padding: 0 16px; + margin-bottom: 0px; + + &::before { + border-bottom: 1px solid var(--bg-slate-400) !important; + } + } + + .tab-item { + display: flex; + justify-content: center; + align-items: center; + gap: 8px; + } +} + +.lightMode { + .integrations-module-container { + .ant-tabs-nav { + &::before { + border-bottom: 1px solid var(--bg-vanilla-400) !important; + } + } + } +} diff --git a/frontend/src/pages/IntegrationsModulePage/IntegrationsModulePage.tsx b/frontend/src/pages/IntegrationsModulePage/IntegrationsModulePage.tsx new file mode 100644 index 0000000000..bdcf05b2de --- /dev/null +++ b/frontend/src/pages/IntegrationsModulePage/IntegrationsModulePage.tsx @@ -0,0 +1,21 @@ +import './IntegrationsModulePage.styles.scss'; + +import RouteTab from 'components/RouteTab'; +import { TabRoutes } from 'components/RouteTab/types'; +import history from 'lib/history'; +import { useLocation } from 'react-use'; + +import { installedIntegrations } from './constants'; + +function IntegrationsModulePage(): JSX.Element { + const { pathname } = useLocation(); + + const routes: TabRoutes[] = [installedIntegrations]; + return ( +
+ +
+ ); +} + +export default IntegrationsModulePage; diff --git a/frontend/src/pages/IntegrationsModulePage/constants.tsx b/frontend/src/pages/IntegrationsModulePage/constants.tsx new file mode 100644 index 0000000000..d0100798a8 --- /dev/null +++ b/frontend/src/pages/IntegrationsModulePage/constants.tsx @@ -0,0 +1,15 @@ +import { TabRoutes } from 'components/RouteTab/types'; +import ROUTES from 'constants/routes'; +import { Compass } from 'lucide-react'; +import Integrations from 'pages/Integrations'; + +export const installedIntegrations: TabRoutes = { + Component: Integrations, + name: ( +
+ Integrations +
+ ), + route: ROUTES.INTEGRATIONS_INSTALLED, + key: ROUTES.INTEGRATIONS_INSTALLED, +}; diff --git a/frontend/src/pages/IntegrationsModulePage/index.ts b/frontend/src/pages/IntegrationsModulePage/index.ts new file mode 100644 index 0000000000..690904079a --- /dev/null +++ b/frontend/src/pages/IntegrationsModulePage/index.ts @@ -0,0 +1,3 @@ +import IntegrationsModulePage from './IntegrationsModulePage'; + +export default IntegrationsModulePage; diff --git a/frontend/src/types/api/integrations/types.ts b/frontend/src/types/api/integrations/types.ts new file mode 100644 index 0000000000..87bc21fa56 --- /dev/null +++ b/frontend/src/types/api/integrations/types.ts @@ -0,0 +1,105 @@ +interface IntegrationsProps { + author: { + email: string; + homepage: string; + name: string; + }; + description: string; + id: string; + icon: string; + is_installed: boolean; + title: string; +} + +export interface AllIntegrationsProps { + status: string; + data: { + integrations: IntegrationsProps[]; + }; +} + +export interface IntegrationDetailedProps { + description: string; + id: string; + installation: { + installed_at: string; + } | null; + title: string; + author: { + email: string; + homepage: string; + name: string; + }; + icon: string; + connection_status: { + logs: { + last_received_ts_ms: number; + last_received_from: string; + } | null; + metrics: { + last_received_ts_ms: number; + last_received_from: string; + } | null; + }; + categories: string[]; + assets: { + logs: { + pipelines: []; + }; + dashboards: []; + alerts: []; + }; + overview: string; + configuration: [ + { + title: string; + instructions: string; + }, + ]; + data_collected: { + logs: string[]; + metrics: string[]; + }; +} +export interface GetIntegrationProps { + data: IntegrationDetailedProps; +} + +export interface IntegrationStatusProps { + connection_status: { + logs: { + last_received_ts_ms: number; + last_received_from: string; + } | null; + metrics: { + last_received_ts_ms: number; + last_received_from: string; + } | null; + }; +} + +export interface GetIntegrationStatusProps { + data: IntegrationStatusProps; +} + +export interface GetIntegrationPayloadProps { + integrationId: string; + enabled?: boolean; +} + +export interface InstallIntegrationKeyProps { + integration_id: string; + config: any; +} + +export interface InstalledIntegrationsSuccessResponse { + data: IntegrationsProps; +} + +export interface UninstallIntegrationProps { + integrationId: string; +} + +export interface UninstallIntegrationSuccessResponse { + data: any; +} diff --git a/frontend/src/utils/permission/index.ts b/frontend/src/utils/permission/index.ts index 265ffe02a5..5667b93ce0 100644 --- a/frontend/src/utils/permission/index.ts +++ b/frontend/src/utils/permission/index.ts @@ -92,4 +92,7 @@ export const routePermission: Record = { LOGS_BASE: [], OLD_LOGS_EXPLORER: [], SHORTCUTS: ['ADMIN', 'EDITOR', 'VIEWER'], + INTEGRATIONS_BASE: ['ADMIN', 'EDITOR', 'VIEWER'], + INTEGRATIONS_INSTALLED: ['ADMIN', 'EDITOR', 'VIEWER'], + INTEGRATIONS_MARKETPLACE: ['ADMIN', 'EDITOR', 'VIEWER'], }; diff --git a/frontend/yarn.lock b/frontend/yarn.lock index 6474b180c1..df275f43e4 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -4198,6 +4198,13 @@ dependencies: "@types/unist" "^2" +"@types/hast@^3.0.0": + version "3.0.4" + resolved "https://registry.yarnpkg.com/@types/hast/-/hast-3.0.4.tgz#1d6b39993b82cea6ad783945b0508c25903e15aa" + integrity sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ== + dependencies: + "@types/unist" "*" + "@types/history@^4.7.11": version "4.7.11" resolved "https://registry.npmjs.org/@types/history/-/history-4.7.11.tgz" @@ -4297,6 +4304,13 @@ dependencies: "@types/unist" "^2" +"@types/mdast@^4.0.0": + version "4.0.3" + resolved "https://registry.yarnpkg.com/@types/mdast/-/mdast-4.0.3.tgz#1e011ff013566e919a4232d1701ad30d70cab333" + integrity sha512-LsjtqsyF+d2/yFOYaN22dHZI1Cpwkrj+g06G8+qtUKlhovPW89YhqSnfKtMbkgmEtYpH2gydRNULd6y8mciAFg== + dependencies: + "@types/unist" "*" + "@types/mdx@^2.0.0": version "2.0.7" resolved "https://registry.yarnpkg.com/@types/mdx/-/mdx-2.0.7.tgz#c7482e995673e01b83f8e96df83b3843ea76401f" @@ -4577,6 +4591,11 @@ resolved "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.3.tgz" integrity sha512-NfQ4gyz38SL8sDNrSixxU2Os1a5xcdFxipAFxYEuLUlvU2uDwS4NUpsImcf1//SlWItCVMMLiylsxbmNMToV/g== +"@types/unist@*", "@types/unist@^3.0.0": + version "3.0.2" + resolved "https://registry.yarnpkg.com/@types/unist/-/unist-3.0.2.tgz#6dd61e43ef60b34086287f83683a5c1b2dc53d20" + integrity sha512-dqId9J8K/vGi5Zr7oo212BGii5m3q5Hxlkwy3WpYuKPklmBEvsbMYYyLxAQpSffdLl/gdW0XUpKWFvYmyoWCoQ== + "@types/unist@^2", "@types/unist@^2.0.0": version "2.0.8" resolved "https://registry.yarnpkg.com/@types/unist/-/unist-2.0.8.tgz#bb197b9639aa1a04cf464a617fe800cccd92ad5c" @@ -4799,6 +4818,11 @@ resolved "https://registry.npmjs.org/@ungap/custom-elements/-/custom-elements-1.2.0.tgz" integrity sha512-zdSuu79stAwVUtzkQU9B5jhGh2LavtkeX4kxd2jtMJmZt7QqRJ1KJW5bukt/vUOaUs3z674GHd+nqYm0bu0Gyg== +"@ungap/structured-clone@^1.0.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.2.0.tgz#756641adb587851b5ccb3e095daf27ae581c8406" + integrity sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ== + "@volar/language-core@1.11.1", "@volar/language-core@~1.11.1": version "1.11.1" resolved "https://registry.yarnpkg.com/@volar/language-core/-/language-core-1.11.1.tgz#ecdf12ea8dc35fb8549e517991abcbf449a5ad4f" @@ -7620,6 +7644,13 @@ detect-node@^2.0.4, detect-node@^2.1.0: resolved "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz" integrity sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g== +devlop@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/devlop/-/devlop-1.1.0.tgz#4db7c2ca4dc6e0e834c30be70c94bbc976dc7018" + integrity sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA== + dependencies: + dequal "^2.0.0" + diff-sequences@^27.5.1: version "27.5.1" resolved "https://registry.npmjs.org/diff-sequences/-/diff-sequences-27.5.1.tgz" @@ -9376,6 +9407,20 @@ hast-util-from-parse5@^7.0.0: vfile-location "^4.0.0" web-namespaces "^2.0.0" +hast-util-from-parse5@^8.0.0: + version "8.0.1" + resolved "https://registry.yarnpkg.com/hast-util-from-parse5/-/hast-util-from-parse5-8.0.1.tgz#654a5676a41211e14ee80d1b1758c399a0327651" + integrity sha512-Er/Iixbc7IEa7r/XLtuG52zoqn/b3Xng/w6aZQ0xGVxzhw5xUFxcRqdPzP6yFi/4HBYRaifaI5fQ1RH8n0ZeOQ== + dependencies: + "@types/hast" "^3.0.0" + "@types/unist" "^3.0.0" + devlop "^1.0.0" + hastscript "^8.0.0" + property-information "^6.0.0" + vfile "^6.0.0" + vfile-location "^5.0.0" + web-namespaces "^2.0.0" + hast-util-has-property@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/hast-util-has-property/-/hast-util-has-property-2.0.1.tgz#8ec99c3e8f02626304ee438cdb9f0528b017e083" @@ -9408,6 +9453,13 @@ hast-util-parse-selector@^3.0.0: dependencies: "@types/hast" "^2.0.0" +hast-util-parse-selector@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/hast-util-parse-selector/-/hast-util-parse-selector-4.0.0.tgz#352879fa86e25616036037dd8931fb5f34cb4a27" + integrity sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A== + dependencies: + "@types/hast" "^3.0.0" + hast-util-raw@^7.0.0, hast-util-raw@^7.2.0: version "7.2.3" resolved "https://registry.yarnpkg.com/hast-util-raw/-/hast-util-raw-7.2.3.tgz#dcb5b22a22073436dbdc4aa09660a644f4991d99" @@ -9425,6 +9477,25 @@ hast-util-raw@^7.0.0, hast-util-raw@^7.2.0: web-namespaces "^2.0.0" zwitch "^2.0.0" +hast-util-raw@^9.0.0: + version "9.0.2" + resolved "https://registry.yarnpkg.com/hast-util-raw/-/hast-util-raw-9.0.2.tgz#39b4a4886bd9f0a5dd42e86d02c966c2c152884c" + integrity sha512-PldBy71wO9Uq1kyaMch9AHIghtQvIwxBUkv823pKmkTM3oV1JxtsTNYdevMxvUHqcnOAuO65JKU2+0NOxc2ksA== + dependencies: + "@types/hast" "^3.0.0" + "@types/unist" "^3.0.0" + "@ungap/structured-clone" "^1.0.0" + hast-util-from-parse5 "^8.0.0" + hast-util-to-parse5 "^8.0.0" + html-void-elements "^3.0.0" + mdast-util-to-hast "^13.0.0" + parse5 "^7.0.0" + unist-util-position "^5.0.0" + unist-util-visit "^5.0.0" + vfile "^6.0.0" + web-namespaces "^2.0.0" + zwitch "^2.0.0" + hast-util-select@^5.0.5, hast-util-select@~5.0.1: version "5.0.5" resolved "https://registry.yarnpkg.com/hast-util-select/-/hast-util-select-5.0.5.tgz#be9ccb71d2278681ca024727f12abd4f93b3e9bc" @@ -9496,6 +9567,19 @@ hast-util-to-parse5@^7.0.0: web-namespaces "^2.0.0" zwitch "^2.0.0" +hast-util-to-parse5@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/hast-util-to-parse5/-/hast-util-to-parse5-8.0.0.tgz#477cd42d278d4f036bc2ea58586130f6f39ee6ed" + integrity sha512-3KKrV5ZVI8if87DVSi1vDeByYrkGzg4mEfeu4alwgmmIeARiBLKCZS2uw5Gb6nU9x9Yufyj3iudm6i7nl52PFw== + dependencies: + "@types/hast" "^3.0.0" + comma-separated-tokens "^2.0.0" + devlop "^1.0.0" + property-information "^6.0.0" + space-separated-tokens "^2.0.0" + web-namespaces "^2.0.0" + zwitch "^2.0.0" + hast-util-to-string@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/hast-util-to-string/-/hast-util-to-string-2.0.0.tgz#b008b0a4ea472bf34dd390b7eea1018726ae152a" @@ -9530,6 +9614,17 @@ hastscript@^7.0.0: property-information "^6.0.0" space-separated-tokens "^2.0.0" +hastscript@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/hastscript/-/hastscript-8.0.0.tgz#4ef795ec8dee867101b9f23cc830d4baf4fd781a" + integrity sha512-dMOtzCEd3ABUeSIISmrETiKuyydk1w0pa+gE/uormcTpSYuaNJPbX1NU3JLyscSLjwAQM8bWMhhIlnCqnRvDTw== + dependencies: + "@types/hast" "^3.0.0" + comma-separated-tokens "^2.0.0" + hast-util-parse-selector "^4.0.0" + property-information "^6.0.0" + space-separated-tokens "^2.0.0" + he@^1.2.0: version "1.2.0" resolved "https://registry.npmjs.org/he/-/he-1.2.0.tgz" @@ -9643,6 +9738,11 @@ html-void-elements@^2.0.0: resolved "https://registry.yarnpkg.com/html-void-elements/-/html-void-elements-2.0.1.tgz#29459b8b05c200b6c5ee98743c41b979d577549f" integrity sha512-0quDb7s97CfemeJAnW9wC0hw78MtW7NU3hqtCD75g2vFlDLt36llsYD7uB7SUzojLMP24N5IatXf7ylGXiGG9A== +html-void-elements@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/html-void-elements/-/html-void-elements-3.0.0.tgz#fc9dbd84af9e747249034d4d62602def6517f1d7" + integrity sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg== + html-webpack-plugin@5.5.0: version "5.5.0" resolved "https://registry.yarnpkg.com/html-webpack-plugin/-/html-webpack-plugin-5.5.0.tgz#c3911936f57681c1f9f4d8b68c158cd9dfe52f50" @@ -11880,6 +11980,21 @@ mdast-util-to-hast@^12.1.0: unist-util-position "^4.0.0" unist-util-visit "^4.0.0" +mdast-util-to-hast@^13.0.0: + version "13.1.0" + resolved "https://registry.yarnpkg.com/mdast-util-to-hast/-/mdast-util-to-hast-13.1.0.tgz#1ae54d903150a10fe04d59f03b2b95fd210b2124" + integrity sha512-/e2l/6+OdGp/FB+ctrJ9Avz71AN/GRH3oi/3KAx/kMnoUsD6q0woXlDT8lLEeViVKE7oZxE7RXzvO3T8kF2/sA== + dependencies: + "@types/hast" "^3.0.0" + "@types/mdast" "^4.0.0" + "@ungap/structured-clone" "^1.0.0" + devlop "^1.0.0" + micromark-util-sanitize-uri "^2.0.0" + trim-lines "^3.0.0" + unist-util-position "^5.0.0" + unist-util-visit "^5.0.0" + vfile "^6.0.0" + mdast-util-to-markdown@^1.0.0, mdast-util-to-markdown@^1.3.0: version "1.5.0" resolved "https://registry.yarnpkg.com/mdast-util-to-markdown/-/mdast-util-to-markdown-1.5.0.tgz#c13343cb3fc98621911d33b5cd42e7d0731171c6" @@ -12216,6 +12331,14 @@ micromark-util-character@^1.0.0: micromark-util-symbol "^1.0.0" micromark-util-types "^1.0.0" +micromark-util-character@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/micromark-util-character/-/micromark-util-character-2.1.0.tgz#31320ace16b4644316f6bf057531689c71e2aee1" + integrity sha512-KvOVV+X1yLBfs9dCBSopq/+G1PcgT3lAK07mC4BzXi5E7ahzMAF8oIupDDJ6mievI6F+lAATkbQQlQixJfT3aQ== + dependencies: + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + micromark-util-chunked@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/micromark-util-chunked/-/micromark-util-chunked-1.1.0.tgz#37a24d33333c8c69a74ba12a14651fd9ea8a368b" @@ -12262,6 +12385,11 @@ micromark-util-encode@^1.0.0: resolved "https://registry.yarnpkg.com/micromark-util-encode/-/micromark-util-encode-1.1.0.tgz#92e4f565fd4ccb19e0dcae1afab9a173bbeb19a5" integrity sha512-EuEzTWSTAj9PA5GOAs992GzNh2dGQO52UvAbtSOMvXTxv3Criqb6IOzJUBCmEqrrXSblJIJBbFFv6zPxpreiJw== +micromark-util-encode@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/micromark-util-encode/-/micromark-util-encode-2.0.0.tgz#0921ac7953dc3f1fd281e3d1932decfdb9382ab1" + integrity sha512-pS+ROfCXAGLWCOc8egcBvT0kf27GoWMqtdarNfDcjb6YLuV5cM3ioG45Ys2qOVqeqSbjaKg72vU+Wby3eddPsA== + micromark-util-events-to-acorn@^1.0.0: version "1.2.3" resolved "https://registry.yarnpkg.com/micromark-util-events-to-acorn/-/micromark-util-events-to-acorn-1.2.3.tgz#a4ab157f57a380e646670e49ddee97a72b58b557" @@ -12304,6 +12432,15 @@ micromark-util-sanitize-uri@^1.0.0, micromark-util-sanitize-uri@^1.1.0: micromark-util-encode "^1.0.0" micromark-util-symbol "^1.0.0" +micromark-util-sanitize-uri@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.0.tgz#ec8fbf0258e9e6d8f13d9e4770f9be64342673de" + integrity sha512-WhYv5UEcZrbAtlsnPuChHUAsu/iBPOVaEVsntLBIdpibO0ddy8OzavZz3iL2xVvBZOpolujSliP65Kq0/7KIYw== + dependencies: + micromark-util-character "^2.0.0" + micromark-util-encode "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-subtokenize@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/micromark-util-subtokenize/-/micromark-util-subtokenize-1.1.0.tgz#941c74f93a93eaf687b9054aeb94642b0e92edb1" @@ -12319,11 +12456,21 @@ micromark-util-symbol@^1.0.0: resolved "https://registry.yarnpkg.com/micromark-util-symbol/-/micromark-util-symbol-1.1.0.tgz#813cd17837bdb912d069a12ebe3a44b6f7063142" integrity sha512-uEjpEYY6KMs1g7QfJ2eX1SQEV+ZT4rUD3UcF6l57acZvLNK7PBZL+ty82Z1qhK1/yXIY4bdx04FKMgR0g4IAag== +micromark-util-symbol@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/micromark-util-symbol/-/micromark-util-symbol-2.0.0.tgz#12225c8f95edf8b17254e47080ce0862d5db8044" + integrity sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw== + micromark-util-types@^1.0.0, micromark-util-types@^1.0.1: version "1.1.0" resolved "https://registry.yarnpkg.com/micromark-util-types/-/micromark-util-types-1.1.0.tgz#e6676a8cae0bb86a2171c498167971886cb7e283" integrity sha512-ukRBgie8TIAcacscVHSiddHjO4k/q3pnedmzMQ4iwDcK0FtFCohKOlFbaOL/mPgfnPsL3C1ZyxJa4sbWrBl3jg== +micromark-util-types@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/micromark-util-types/-/micromark-util-types-2.0.0.tgz#63b4b7ffeb35d3ecf50d1ca20e68fc7caa36d95e" + integrity sha512-oNh6S2WMHWRZrmutsRmDDfkzKtxF+bc2VxLC9dvtrDIRFln627VsFP6fLMgTryGDljgLPjkrzQSDcPrjPyDJ5w== + micromark@^3.0.0: version "3.2.0" resolved "https://registry.yarnpkg.com/micromark/-/micromark-3.2.0.tgz#1af9fef3f995ea1ea4ac9c7e2f19c48fd5c006e9" @@ -13265,6 +13412,13 @@ parse5@6.0.1, parse5@^6.0.0, parse5@^6.0.1: resolved "https://registry.yarnpkg.com/parse5/-/parse5-6.0.1.tgz#e1a1c085c569b3dc08321184f19a39cc27f7c30b" integrity sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw== +parse5@^7.0.0: + version "7.1.2" + resolved "https://registry.yarnpkg.com/parse5/-/parse5-7.1.2.tgz#0736bebbfd77793823240a23b7fc5e010b7f8e32" + integrity sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw== + dependencies: + entities "^4.4.0" + parseurl@~1.3.2, parseurl@~1.3.3: version "1.3.3" resolved "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz" @@ -15013,6 +15167,15 @@ rehype-prism-plus@~1.6.1: unist-util-filter "^4.0.0" unist-util-visit "^4.0.0" +rehype-raw@7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/rehype-raw/-/rehype-raw-7.0.0.tgz#59d7348fd5dbef3807bbaa1d443efd2dd85ecee4" + integrity sha512-/aE8hCfKlQeA8LmyeyQvQF3eBiLRGNlfBJEvWH7ivp9sBqs7TNqBL5X3v157rM4IFETqDnIOO+z5M/biZbo9Ww== + dependencies: + "@types/hast" "^3.0.0" + hast-util-raw "^9.0.0" + vfile "^6.0.0" + rehype-raw@^6.1.1: version "6.1.1" resolved "https://registry.yarnpkg.com/rehype-raw/-/rehype-raw-6.1.1.tgz#81bbef3793bd7abacc6bf8335879d1b6c868c9d4" @@ -16844,6 +17007,13 @@ unist-util-is@^5.0.0: dependencies: "@types/unist" "^2.0.0" +unist-util-is@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/unist-util-is/-/unist-util-is-6.0.0.tgz#b775956486aff107a9ded971d996c173374be424" + integrity sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw== + dependencies: + "@types/unist" "^3.0.0" + unist-util-position-from-estree@^1.0.0, unist-util-position-from-estree@^1.1.0: version "1.1.2" resolved "https://registry.yarnpkg.com/unist-util-position-from-estree/-/unist-util-position-from-estree-1.1.2.tgz#8ac2480027229de76512079e377afbcabcfcce22" @@ -16858,6 +17028,13 @@ unist-util-position@^4.0.0: dependencies: "@types/unist" "^2.0.0" +unist-util-position@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/unist-util-position/-/unist-util-position-5.0.0.tgz#678f20ab5ca1207a97d7ea8a388373c9cf896be4" + integrity sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA== + dependencies: + "@types/unist" "^3.0.0" + unist-util-remove-position@^4.0.0: version "4.0.2" resolved "https://registry.yarnpkg.com/unist-util-remove-position/-/unist-util-remove-position-4.0.2.tgz#a89be6ea72e23b1a402350832b02a91f6a9afe51" @@ -16873,6 +17050,13 @@ unist-util-stringify-position@^3.0.0: dependencies: "@types/unist" "^2.0.0" +unist-util-stringify-position@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz#449c6e21a880e0855bf5aabadeb3a740314abac2" + integrity sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ== + dependencies: + "@types/unist" "^3.0.0" + unist-util-visit-parents@^5.0.0, unist-util-visit-parents@^5.1.1: version "5.1.3" resolved "https://registry.yarnpkg.com/unist-util-visit-parents/-/unist-util-visit-parents-5.1.3.tgz#b4520811b0ca34285633785045df7a8d6776cfeb" @@ -16881,6 +17065,14 @@ unist-util-visit-parents@^5.0.0, unist-util-visit-parents@^5.1.1: "@types/unist" "^2.0.0" unist-util-is "^5.0.0" +unist-util-visit-parents@^6.0.0: + version "6.0.1" + resolved "https://registry.yarnpkg.com/unist-util-visit-parents/-/unist-util-visit-parents-6.0.1.tgz#4d5f85755c3b8f0dc69e21eca5d6d82d22162815" + integrity sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw== + dependencies: + "@types/unist" "^3.0.0" + unist-util-is "^6.0.0" + unist-util-visit@^4.0.0, unist-util-visit@^4.1.0, unist-util-visit@^4.1.2, unist-util-visit@~4.1.0: version "4.1.2" resolved "https://registry.yarnpkg.com/unist-util-visit/-/unist-util-visit-4.1.2.tgz#125a42d1eb876283715a3cb5cceaa531828c72e2" @@ -16890,6 +17082,15 @@ unist-util-visit@^4.0.0, unist-util-visit@^4.1.0, unist-util-visit@^4.1.2, unist unist-util-is "^5.0.0" unist-util-visit-parents "^5.1.1" +unist-util-visit@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/unist-util-visit/-/unist-util-visit-5.0.0.tgz#a7de1f31f72ffd3519ea71814cccf5fd6a9217d6" + integrity sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg== + dependencies: + "@types/unist" "^3.0.0" + unist-util-is "^6.0.0" + unist-util-visit-parents "^6.0.0" + universalify@^0.1.0: version "0.1.2" resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" @@ -17126,6 +17327,14 @@ vfile-location@^4.0.0: "@types/unist" "^2.0.0" vfile "^5.0.0" +vfile-location@^5.0.0: + version "5.0.2" + resolved "https://registry.yarnpkg.com/vfile-location/-/vfile-location-5.0.2.tgz#220d9ca1ab6f8b2504a4db398f7ebc149f9cb464" + integrity sha512-NXPYyxyBSH7zB5U6+3uDdd6Nybz6o6/od9rk8bp9H8GR3L+cm/fC0uUTbqBmUTnMCUDslAGBOIKNfvvb+gGlDg== + dependencies: + "@types/unist" "^3.0.0" + vfile "^6.0.0" + vfile-message@^3.0.0: version "3.1.4" resolved "https://registry.yarnpkg.com/vfile-message/-/vfile-message-3.1.4.tgz#15a50816ae7d7c2d1fa87090a7f9f96612b59dea" @@ -17134,6 +17343,14 @@ vfile-message@^3.0.0: "@types/unist" "^2.0.0" unist-util-stringify-position "^3.0.0" +vfile-message@^4.0.0: + version "4.0.2" + resolved "https://registry.yarnpkg.com/vfile-message/-/vfile-message-4.0.2.tgz#c883c9f677c72c166362fd635f21fc165a7d1181" + integrity sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw== + dependencies: + "@types/unist" "^3.0.0" + unist-util-stringify-position "^4.0.0" + vfile@^5.0.0: version "5.3.7" resolved "https://registry.yarnpkg.com/vfile/-/vfile-5.3.7.tgz#de0677e6683e3380fafc46544cfe603118826ab7" @@ -17144,6 +17361,15 @@ vfile@^5.0.0: unist-util-stringify-position "^3.0.0" vfile-message "^3.0.0" +vfile@^6.0.0: + version "6.0.1" + resolved "https://registry.yarnpkg.com/vfile/-/vfile-6.0.1.tgz#1e8327f41eac91947d4fe9d237a2dd9209762536" + integrity sha512-1bYqc7pt6NIADBJ98UiG0Bn/CHIVOoZ/IyEkqIruLg0mE1BKzkOXY2D6CSqQIcKqgadppE5lrxgWXJmXd7zZJw== + dependencies: + "@types/unist" "^3.0.0" + unist-util-stringify-position "^4.0.0" + vfile-message "^4.0.0" + vite-plugin-dts@^3.6.4: version "3.7.0" resolved "https://registry.yarnpkg.com/vite-plugin-dts/-/vite-plugin-dts-3.7.0.tgz#654ee7c38c0cdd4589b9bc198a264f34172bd870"