mirror of
https://git.mirrors.martin98.com/https://github.com/SigNoz/signoz
synced 2025-08-12 06:29:02 +08:00
feat: [SIG-526]: UI Integrations V0 (#4595)
* feat: integrations v0 base setup routes and components * chore: typecheck fix * feat: integrations landing page changes * feat: initial header setup * feat: integrations list page setup * feat: integrations details content root setup * feat: integration detail content setup * feat: added overview tab * feat: added data tab * feat: handle configuration tab * feat: add min height for the container * feat: generate apis and hooks for usage * feat: added remove integration modal * feat: added remove integration modal * feat: added remove integration modal * feat: added test connection bars * chore: add bottom margins * feat: added test connection modal * feat: add all types of test connection * feat: add all types of test connection * fix: address review comments * fix: address review comments * feat: added get all integrations API and search bar implemnetation * feat: navigate to overview section in case of row click and configure in btn * feat: integrate get integration details api * feat: handle integration details page gracefully * feat: integrate uninstall API and the connection states * feat: add install integration API call * feat: added api error handling * feat: handle error states for list and details api * feat: handle the logs and metrics columns * feat: add TODOs for pending tasks * feat: comment from side nav * feat: added support for custom tags in react markdown * chore: revert the temporary change for merge * feat: integrate the status api calls and polling logic * chore: add markdown components and correct the polling issue * chore: handle light mode * chore: remove integrations from sideNav * fix: address review comments * fix: address review comments
This commit is contained in:
parent
7136ecc2fe
commit
0c4149225f
@ -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)": [
|
||||
|
1
frontend/public/Icons/redis-logo.svg
Normal file
1
frontend/public/Icons/redis-logo.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg width="24" height="24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M23.06 17.526c-1.281.668-7.916 3.396-9.328 4.132-1.413.736-2.198.73-3.314.196C9.303 21.32 2.242 18.468.97 17.86c-.636-.303-.97-.56-.97-.802v-2.426s9.192-2.001 10.676-2.534c1.484-.532 1.999-.551 3.262-.089 1.263.463 8.814 1.826 10.062 2.283v2.391c0 .24-.288.503-.94.843z" fill="#912626"/><path d="M23.06 15.114c-1.281.668-7.916 3.396-9.329 4.132-1.412.737-2.197.73-3.313.196C9.302 18.91 2.242 16.056.97 15.45c-1.272-.608-1.298-1.027-.049-1.516 1.25-.49 8.271-3.244 9.755-3.776 1.484-.533 1.999-.552 3.262-.09 1.263.463 7.858 3.088 9.106 3.546 1.248.457 1.296.834.015 1.501z" fill="#C6302B"/><path d="M23.06 13.6c-1.281.668-7.916 3.396-9.328 4.133-1.413.736-2.198.73-3.314.196S2.242 14.543.97 13.935c-.636-.304-.97-.56-.97-.802v-2.426s9.192-2.001 10.676-2.534c1.484-.532 1.999-.551 3.262-.089C15.2 8.547 22.752 9.91 24 10.366v2.392c0 .24-.288.503-.94.843z" fill="#912626"/><path d="M23.06 11.19c-1.281.667-7.916 3.395-9.329 4.131-1.412.737-2.197.73-3.313.196-1.116-.533-8.176-3.386-9.448-3.993-1.272-.608-1.298-1.027-.049-1.516 1.25-.49 8.271-3.244 9.755-3.776 1.484-.533 1.999-.552 3.262-.09 1.263.463 7.858 3.088 9.106 3.545 1.248.458 1.296.835.015 1.502z" fill="#C6302B"/><path d="M23.06 9.53c-1.281.668-7.916 3.396-9.328 4.132-1.413.737-2.198.73-3.314.196-1.116-.533-8.176-3.386-9.448-3.993C.334 9.56 0 9.305 0 9.062V6.636s9.192-2 10.676-2.533c1.484-.533 1.999-.552 3.262-.09C15.2 4.477 22.752 5.84 24 6.297v2.392c0 .24-.288.502-.94.842z" fill="#912626"/><path d="M23.06 7.118c-1.281.668-7.916 3.396-9.329 4.132-1.412.737-2.197.73-3.313.196C9.303 10.913 2.242 8.061.97 7.453-.302 6.845-.328 6.427.921 5.937c1.25-.489 8.271-3.244 9.755-3.776 1.484-.532 1.999-.552 3.262-.089 1.263.463 7.858 3.088 9.106 3.545 1.248.457 1.296.834.015 1.501z" fill="#C6302B"/><path d="M14.933 4.758l-2.064.215-.462 1.111-.746-1.24L9.28 4.63l1.778-.641-.534-.985 1.665.651 1.569-.513-.424 1.017 1.6.6zm-2.649 5.393l-3.85-1.597 5.517-.847-1.667 2.444zM6.945 5.376c1.63 0 2.95.512 2.95 1.143 0 .632-1.32 1.144-2.95 1.144-1.629 0-2.95-.512-2.95-1.144 0-.63 1.321-1.143 2.95-1.143z" fill="#fff"/><path d="M17.371 5.062l3.266 1.29-3.263 1.29-.003-2.58z" fill="#621B1C"/><path d="M13.758 6.492l3.613-1.43.003 2.58-.354.139-3.262-1.29z" fill="#9A2928"/></svg>
|
After Width: | Height: | Size: 2.3 KiB |
@ -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"
|
||||
}
|
||||
|
@ -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'
|
||||
),
|
||||
);
|
||||
|
@ -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 = {
|
||||
|
7
frontend/src/api/Integrations/getAllIntegrations.ts
Normal file
7
frontend/src/api/Integrations/getAllIntegrations.ts
Normal file
@ -0,0 +1,7 @@
|
||||
import axios from 'api';
|
||||
import { AxiosResponse } from 'axios';
|
||||
import { AllIntegrationsProps } from 'types/api/integrations/types';
|
||||
|
||||
export const getAllIntegrations = (): Promise<
|
||||
AxiosResponse<AllIntegrationsProps>
|
||||
> => axios.get(`/integrations`);
|
11
frontend/src/api/Integrations/getIntegration.ts
Normal file
11
frontend/src/api/Integrations/getIntegration.ts
Normal file
@ -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<AxiosResponse<GetIntegrationProps>> =>
|
||||
axios.get(`/integrations/${props.integrationId}`);
|
11
frontend/src/api/Integrations/getIntegrationStatus.ts
Normal file
11
frontend/src/api/Integrations/getIntegrationStatus.ts
Normal file
@ -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<AxiosResponse<GetIntegrationStatusProps>> =>
|
||||
axios.get(`/integrations/${props.integrationId}/connection_status`);
|
31
frontend/src/api/Integrations/installIntegration.ts
Normal file
31
frontend/src/api/Integrations/installIntegration.ts
Normal file
@ -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<InstalledIntegrationsSuccessResponse> | 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;
|
31
frontend/src/api/Integrations/uninstallIntegration.ts
Normal file
31
frontend/src/api/Integrations/uninstallIntegration.ts
Normal file
@ -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<UninstallIntegrationSuccessResponse> | 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;
|
@ -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 <h1 style={{ color }}>This is custom element</h1>;
|
||||
}
|
||||
|
||||
function MarkdownRenderer({
|
||||
markdownContent,
|
||||
variables,
|
||||
@ -85,12 +91,14 @@ function MarkdownRenderer({
|
||||
|
||||
return (
|
||||
<ReactMarkdown
|
||||
rehypePlugins={[rehypeRaw as any]}
|
||||
components={{
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
a: Link,
|
||||
pre: Pre,
|
||||
code: Code,
|
||||
customtag: CustomTag,
|
||||
}}
|
||||
>
|
||||
{interpolatedMarkdown}
|
||||
|
@ -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;
|
||||
|
@ -16,6 +16,7 @@ import {
|
||||
ScrollText,
|
||||
Settings,
|
||||
Slack,
|
||||
// Unplug,
|
||||
UserPlus,
|
||||
} from 'lucide-react';
|
||||
|
||||
@ -89,6 +90,11 @@ const menuItems: SidebarItem[] = [
|
||||
label: 'Alerts',
|
||||
icon: <BellDot size={16} />,
|
||||
},
|
||||
// {
|
||||
// key: ROUTES.INTEGRATIONS_INSTALLED,
|
||||
// label: 'Integrations',
|
||||
// icon: <Unplug size={16} />,
|
||||
// },
|
||||
{
|
||||
key: ROUTES.ALL_ERROR,
|
||||
label: 'Exceptions',
|
||||
@ -121,6 +127,7 @@ export const NEW_ROUTES_MENU_ITEM_KEY_MAP: Record<string, string> = {
|
||||
[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;
|
||||
|
@ -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];
|
||||
|
13
frontend/src/hooks/Integrations/useGetAllIntegrations.ts
Normal file
13
frontend/src/hooks/Integrations/useGetAllIntegrations.ts
Normal file
@ -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<AllIntegrationsProps>,
|
||||
AxiosError
|
||||
> =>
|
||||
useQuery<AxiosResponse<AllIntegrationsProps>, AxiosError>({
|
||||
queryKey: ['Integrations'],
|
||||
queryFn: () => getAllIntegrations(),
|
||||
});
|
18
frontend/src/hooks/Integrations/useGetIntegration.ts
Normal file
18
frontend/src/hooks/Integrations/useGetIntegration.ts
Normal file
@ -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<GetIntegrationProps>,
|
||||
AxiosError
|
||||
> =>
|
||||
useQuery<AxiosResponse<GetIntegrationProps>, AxiosError>({
|
||||
queryKey: ['Integration', integrationId],
|
||||
queryFn: () => getIntegration({ integrationId }),
|
||||
});
|
20
frontend/src/hooks/Integrations/useGetIntegrationStatus.ts
Normal file
20
frontend/src/hooks/Integrations/useGetIntegrationStatus.ts
Normal file
@ -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<GetIntegrationStatusProps>,
|
||||
AxiosError
|
||||
> =>
|
||||
useQuery<AxiosResponse<GetIntegrationStatusProps>, AxiosError>({
|
||||
queryKey: ['Integration', integrationId, Date.now()],
|
||||
queryFn: () => getIntegrationStatus({ integrationId }),
|
||||
enabled,
|
||||
});
|
37
frontend/src/pages/Integrations/Header.tsx
Normal file
37
frontend/src/pages/Integrations/Header.tsx
Normal file
@ -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<SetStateAction<string>>;
|
||||
}
|
||||
|
||||
function Header(props: HeaderProps): JSX.Element {
|
||||
const { searchTerm, setSearchTerm } = props;
|
||||
|
||||
const handleSearch = (e: React.ChangeEvent<HTMLInputElement>): void => {
|
||||
setSearchTerm(e.target.value);
|
||||
};
|
||||
return (
|
||||
<div className="integrations-header">
|
||||
<Typography.Title className="title">Integrations</Typography.Title>
|
||||
<Typography.Text className="subtitle">
|
||||
Manage Integrations for this workspace
|
||||
</Typography.Text>
|
||||
|
||||
<Input
|
||||
placeholder="Search for an integration..."
|
||||
prefix={<Search size={12} color={Color.BG_VANILLA_400} />}
|
||||
value={searchTerm}
|
||||
onChange={handleSearch}
|
||||
className="integrations-search-input"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default Header;
|
@ -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: (
|
||||
<Button
|
||||
type="text"
|
||||
className="integration-tab-btns"
|
||||
icon={<Drum size={14} />}
|
||||
>
|
||||
<Typography.Text className="typography">Overview</Typography.Text>
|
||||
</Button>
|
||||
),
|
||||
children: (
|
||||
<Overview
|
||||
categories={integrationData.categories}
|
||||
assets={integrationData.assets}
|
||||
overviewContent={integrationData.overview}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'configuration',
|
||||
label: (
|
||||
<Button
|
||||
type="text"
|
||||
className="integration-tab-btns"
|
||||
icon={<Hammer size={14} />}
|
||||
>
|
||||
<Typography.Text className="typography">Configure</Typography.Text>
|
||||
</Button>
|
||||
),
|
||||
children: <Configure configuration={integrationData.configuration} />,
|
||||
},
|
||||
{
|
||||
key: 'dataCollected',
|
||||
label: (
|
||||
<Button
|
||||
type="text"
|
||||
className="integration-tab-btns"
|
||||
icon={<Table2 size={14} />}
|
||||
>
|
||||
<Typography.Text className="typography">Data Collected</Typography.Text>
|
||||
</Button>
|
||||
),
|
||||
children: (
|
||||
<DataCollected
|
||||
logsData={integrationData.data_collected.logs}
|
||||
metricsData={integrationData.data_collected.metrics}
|
||||
/>
|
||||
),
|
||||
},
|
||||
];
|
||||
return (
|
||||
<div className="integration-detail-container">
|
||||
<Tabs defaultActiveKey={activeDetailTab} items={items} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default IntegrationDetailContent;
|
@ -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 (
|
||||
<div className="integration-detail-configure">
|
||||
<div className="configure-menu">
|
||||
{configuration.map((config, index) => (
|
||||
<Tooltip title={config.title} key={config.title} placement="left">
|
||||
<Button
|
||||
key={config.title}
|
||||
type="text"
|
||||
className={cx('configure-menu-item', {
|
||||
active: selectedConfigStep === index,
|
||||
})}
|
||||
onClick={(): void => handleMenuClick(index)}
|
||||
>
|
||||
<Typography.Text ellipsis>{config.title}</Typography.Text>
|
||||
</Button>
|
||||
</Tooltip>
|
||||
))}
|
||||
</div>
|
||||
<div className="markdown-container">
|
||||
<MarkdownRenderer
|
||||
variables={{}}
|
||||
markdownContent={configuration[selectedConfigStep].instructions}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default Configure;
|
@ -0,0 +1,85 @@
|
||||
import './IntegrationDetailContentTabs.styles.scss';
|
||||
|
||||
import { Table, Typography } from 'antd';
|
||||
import { BarChart2, ScrollText } from 'lucide-react';
|
||||
|
||||
interface DataCollectedProps {
|
||||
logsData: Array<any>;
|
||||
metricsData: Array<any>;
|
||||
}
|
||||
|
||||
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 (
|
||||
<div className="integration-data-collected">
|
||||
<div className="logs-section">
|
||||
<div className="logs-heading">
|
||||
<ScrollText size={14} />
|
||||
<Typography.Text>Logs</Typography.Text>
|
||||
</div>
|
||||
<Table
|
||||
columns={logsColumns}
|
||||
rowClassName={(_, index): string =>
|
||||
index % 2 === 0 ? 'table-row-dark' : ''
|
||||
}
|
||||
dataSource={logsData}
|
||||
pagination={{ pageSize: 3 }}
|
||||
className="logs-section-table"
|
||||
/>
|
||||
</div>
|
||||
<div className="metrics-section">
|
||||
<div className="metrics-heading">
|
||||
<BarChart2 size={14} />
|
||||
<Typography.Text>Metrics</Typography.Text>
|
||||
</div>
|
||||
<Table
|
||||
columns={metricsColumns}
|
||||
rowClassName={(_, index): string =>
|
||||
index % 2 === 0 ? 'table-row-dark' : ''
|
||||
}
|
||||
dataSource={metricsData}
|
||||
pagination={{ pageSize: 3 }}
|
||||
className="metrics-section-table"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default DataCollected;
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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<any>;
|
||||
};
|
||||
dashboards: Array<any>;
|
||||
alerts: Array<any>;
|
||||
};
|
||||
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 (
|
||||
<div className="integration-detail-overview">
|
||||
<div className="integration-detail-overview-left-container">
|
||||
<div className="integration-detail-overview-category">
|
||||
<Typography.Text className="heading">Category</Typography.Text>
|
||||
<div className="category-tabs">
|
||||
{categories.map((category) => (
|
||||
<div key={category} className="category-tab">
|
||||
{category}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div className="integration-detail-overview-assets">
|
||||
<Typography.Text className="heading">Assets</Typography.Text>
|
||||
<ul className="assets-list">
|
||||
{assetsCount.map((count, index) => {
|
||||
if (count === 0) {
|
||||
return undefined;
|
||||
}
|
||||
return (
|
||||
<li key={assetLabelMap[index]}>
|
||||
{count} {assetLabelMap[index]}
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div className="integration-detail-overview-right-container">
|
||||
<MarkdownRenderer variables={{}} markdownContent={overviewContent} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default Overview;
|
@ -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 (
|
||||
<div className="integration-connection-header">
|
||||
<div className="integration-detail-header" key={id}>
|
||||
<div style={{ display: 'flex', gap: '10px' }}>
|
||||
<div className="image-container">
|
||||
<img src={icon} alt={title} className="image" />
|
||||
</div>
|
||||
<div className="details">
|
||||
<Typography.Text className="heading">{title}</Typography.Text>
|
||||
<Typography.Text className="description">{description}</Typography.Text>
|
||||
</div>
|
||||
</div>
|
||||
<Button
|
||||
className="configure-btn"
|
||||
icon={<ArrowLeftRight size={14} />}
|
||||
disabled={isInstallLoading}
|
||||
onClick={(): void => {
|
||||
if (connectionState === ConnectionStates.NotInstalled) {
|
||||
mutate({ integration_id: id, config: {} });
|
||||
} else {
|
||||
showModal();
|
||||
}
|
||||
}}
|
||||
>
|
||||
{connectionState === ConnectionStates.NotInstalled
|
||||
? `Connect ${title}`
|
||||
: `Test Connection`}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{connectionState !== ConnectionStates.NotInstalled && (
|
||||
<TestConnection connectionState={connectionState} />
|
||||
)}
|
||||
|
||||
<Modal
|
||||
className="test-connection-modal"
|
||||
open={isModalOpen}
|
||||
title="Test Connection"
|
||||
onOk={handleOk}
|
||||
onCancel={handleCancel}
|
||||
okText="I understand"
|
||||
okButtonProps={{ className: 'understandBtn', icon: <Check size={14} /> }}
|
||||
cancelButtonProps={{ style: { display: 'none' } }}
|
||||
>
|
||||
<div className="connection-content">
|
||||
<TestConnection connectionState={connectionState} />
|
||||
{connectionState === ConnectionStates.Connected ||
|
||||
connectionState === ConnectionStates.NoDataSinceLong ? (
|
||||
<>
|
||||
<div className="data-info">
|
||||
<Typography.Text className="last-data">
|
||||
Last recieved from
|
||||
</Typography.Text>
|
||||
<Typography.Text className="last-value">
|
||||
{latestData.last_received_from}
|
||||
</Typography.Text>
|
||||
</div>
|
||||
<div className="data-info">
|
||||
<Typography.Text className="last-data">
|
||||
Last recieved at
|
||||
</Typography.Text>
|
||||
<Typography.Text className="last-value">
|
||||
{latestData.last_received_ts_ms
|
||||
? dayjs(latestData.last_received_ts_ms).format('DD MMM YYYY HH:mm')
|
||||
: ''}
|
||||
</Typography.Text>
|
||||
</div>
|
||||
</>
|
||||
) : connectionState === ConnectionStates.TestingConnection ? (
|
||||
<div className="data-test-connection">
|
||||
<div className="last-data">
|
||||
After adding the {title} integration, you need to manually configure
|
||||
your Redis data source to start sending data to SigNoz.
|
||||
</div>
|
||||
<div className="last-data">
|
||||
The status bar above would turn green if we are successfully receiving
|
||||
the data.
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</Modal>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default IntegrationDetailHeader;
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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 (
|
||||
<div className="integration-detail-content">
|
||||
<Button
|
||||
type="text"
|
||||
icon={<ArrowLeft size={14} />}
|
||||
className="all-integrations-btn"
|
||||
onClick={(): void => {
|
||||
setSelectedIntegration(null);
|
||||
}}
|
||||
>
|
||||
All Integrations
|
||||
</Button>
|
||||
|
||||
{loading ? (
|
||||
<div className="loading-integration-details">
|
||||
Please wait.. While we load the integration details
|
||||
</div>
|
||||
) : isError ? (
|
||||
<div className="error-container">
|
||||
<div className="error-content">
|
||||
<img
|
||||
src="/Icons/awwSnap.svg"
|
||||
alt="error-emoji"
|
||||
className="error-state-svg"
|
||||
/>
|
||||
<Typography.Text>
|
||||
Something went wrong :/ Please retry or contact support.
|
||||
</Typography.Text>
|
||||
<div className="error-btns">
|
||||
<Button
|
||||
type="primary"
|
||||
className="retry-btn"
|
||||
onClick={(): Promise<any> => refetch()}
|
||||
icon={<RotateCw size={14} />}
|
||||
>
|
||||
Retry
|
||||
</Button>
|
||||
<div
|
||||
className="contact-support"
|
||||
onClick={(): void => handleContactSupport(isCloudUser())}
|
||||
>
|
||||
<Typography.Link className="text">Contact Support </Typography.Link>
|
||||
|
||||
<MoveUpRight size={14} color={Color.BG_ROBIN_400} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
integrationData && (
|
||||
<>
|
||||
<IntegrationDetailHeader
|
||||
id={selectedIntegration}
|
||||
title={defaultTo(integrationData?.title, '')}
|
||||
description={defaultTo(integrationData?.description, '')}
|
||||
icon={defaultTo(integrationData?.icon, '')}
|
||||
connectionState={connectionStatus}
|
||||
connectionData={defaultTo(
|
||||
integrationStatus?.data.data.connection_status,
|
||||
{ logs: null, metrics: null },
|
||||
)}
|
||||
refetchIntegrationDetails={refetch}
|
||||
/>
|
||||
<IntegrationDetailContent
|
||||
activeDetailTab={activeDetailTab}
|
||||
integrationData={integrationData}
|
||||
/>
|
||||
|
||||
{connectionStatus !== ConnectionStates.NotInstalled && (
|
||||
<IntergrationsUninstallBar
|
||||
integrationTitle={defaultTo(integrationData?.title, '')}
|
||||
integrationId={selectedIntegration}
|
||||
refetchIntegrationDetails={refetch}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default IntegrationDetailPage;
|
@ -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 (
|
||||
<div className="uninstall-integration-bar">
|
||||
<div className="unintall-integration-bar-text">
|
||||
<Typography.Text className="heading">Remove Integration</Typography.Text>
|
||||
<Typography.Text className="subtitle">
|
||||
Removing the {integrationTitle} integration would make your workspace stop
|
||||
listening for data from {integrationTitle} instances.
|
||||
</Typography.Text>
|
||||
</div>
|
||||
<Button
|
||||
className="uninstall-integration-btn"
|
||||
icon={<X size={14} />}
|
||||
onClick={(): void => showModal()}
|
||||
>
|
||||
Remove from SigNoz
|
||||
</Button>
|
||||
<Modal
|
||||
className="remove-integration-modal"
|
||||
open={isModalOpen}
|
||||
title="Remove integration"
|
||||
onOk={handleOk}
|
||||
onCancel={handleCancel}
|
||||
okText="Remove Integration"
|
||||
okButtonProps={{
|
||||
danger: true,
|
||||
disabled: isUninstallLoading,
|
||||
}}
|
||||
>
|
||||
<Typography.Text className="remove-integration-text">
|
||||
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.
|
||||
</Typography.Text>
|
||||
</Modal>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default IntergrationsUninstallBar;
|
@ -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 (
|
||||
<div className={cx('connection-container', connectionState)}>
|
||||
<ul className="connection-text">
|
||||
<li>{ConnectionStatesLabelMap[connectionState]}</li>
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default TestConnection;
|
@ -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;
|
||||
}
|
228
frontend/src/pages/Integrations/Integrations.styles.scss
Normal file
228
frontend/src/pages/Integrations/Integrations.styles.scss
Normal file
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
41
frontend/src/pages/Integrations/Integrations.tsx
Normal file
41
frontend/src/pages/Integrations/Integrations.tsx
Normal file
@ -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<string | null>(
|
||||
null,
|
||||
);
|
||||
|
||||
const [activeDetailTab, setActiveDetailTab] = useState<string | null>(null);
|
||||
|
||||
const [searchTerm, setSearchTerm] = useState<string>('');
|
||||
return (
|
||||
<div className="integrations-container">
|
||||
<div className="integrations-content">
|
||||
{selectedIntegration && activeDetailTab ? (
|
||||
<IntegrationDetailPage
|
||||
selectedIntegration={selectedIntegration}
|
||||
setSelectedIntegration={setSelectedIntegration}
|
||||
activeDetailTab={activeDetailTab}
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
<Header setSearchTerm={setSearchTerm} searchTerm={searchTerm} />
|
||||
<IntegrationsList
|
||||
setSelectedIntegration={setSelectedIntegration}
|
||||
searchTerm={searchTerm}
|
||||
setActiveDetailTab={setActiveDetailTab}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default Integrations;
|
120
frontend/src/pages/Integrations/IntegrationsList.tsx
Normal file
120
frontend/src/pages/Integrations/IntegrationsList.tsx
Normal file
@ -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<SetStateAction<string | null>>;
|
||||
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 (
|
||||
<div className="integrations-list">
|
||||
{!loading && isError && (
|
||||
<div className="error-container">
|
||||
<div className="error-content">
|
||||
<img
|
||||
src="/Icons/awwSnap.svg"
|
||||
alt="error-emoji"
|
||||
className="error-state-svg"
|
||||
/>
|
||||
<Typography.Text>
|
||||
Something went wrong :/ Please retry or contact support.
|
||||
</Typography.Text>
|
||||
<div className="error-btns">
|
||||
<Button
|
||||
type="primary"
|
||||
className="retry-btn"
|
||||
onClick={(): Promise<any> => refetch()}
|
||||
icon={<RotateCw size={14} />}
|
||||
>
|
||||
Retry
|
||||
</Button>
|
||||
<div
|
||||
className="contact-support"
|
||||
onClick={(): void => handleContactSupport(isCloudUser())}
|
||||
>
|
||||
<Typography.Link className="text">Contact Support </Typography.Link>
|
||||
|
||||
<MoveUpRight size={14} color={Color.BG_ROBIN_400} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{!isError && (
|
||||
<List
|
||||
dataSource={filteredDataList}
|
||||
loading={loading}
|
||||
itemLayout="horizontal"
|
||||
renderItem={(item): JSX.Element => (
|
||||
<List.Item
|
||||
key={item.id}
|
||||
className="integrations-list-item"
|
||||
onClick={(): void => {
|
||||
setSelectedIntegration(item.id);
|
||||
setActiveDetailTab('overview');
|
||||
}}
|
||||
>
|
||||
<div style={{ display: 'flex', gap: '10px' }}>
|
||||
<div className="list-item-image-container">
|
||||
<img src={item.icon} alt={item.title} className="list-item-image" />
|
||||
</div>
|
||||
<div className="list-item-details">
|
||||
<Typography.Text className="heading">{item.title}</Typography.Text>
|
||||
<Typography.Text className="description">
|
||||
{item.description}
|
||||
</Typography.Text>
|
||||
</div>
|
||||
</div>
|
||||
<Button
|
||||
className="configure-btn"
|
||||
onClick={(event): void => {
|
||||
event.stopPropagation();
|
||||
setSelectedIntegration(item.id);
|
||||
setActiveDetailTab('configuration');
|
||||
}}
|
||||
>
|
||||
Configure
|
||||
</Button>
|
||||
</List.Item>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default IntegrationsList;
|
3
frontend/src/pages/Integrations/index.ts
Normal file
3
frontend/src/pages/Integrations/index.ts
Normal file
@ -0,0 +1,3 @@
|
||||
import Integrations from './Integrations';
|
||||
|
||||
export default Integrations;
|
9
frontend/src/pages/Integrations/utils.ts
Normal file
9
frontend/src/pages/Integrations/utils.ts
Normal file
@ -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');
|
||||
}
|
||||
};
|
@ -0,0 +1,9 @@
|
||||
function IntegrationsMarketPlace(): JSX.Element {
|
||||
return (
|
||||
<div>
|
||||
<h1>IntegrationsMarketPlace</h1>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default IntegrationsMarketPlace;
|
3
frontend/src/pages/IntegrationsMarketPlace/index.ts
Normal file
3
frontend/src/pages/IntegrationsMarketPlace/index.ts
Normal file
@ -0,0 +1,3 @@
|
||||
import IntegrationsMarketPlace from './IntegrationsMarketPlace';
|
||||
|
||||
export default IntegrationsMarketPlace;
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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 (
|
||||
<div className="integrations-module-container">
|
||||
<RouteTab routes={routes} activeKey={pathname} history={history} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default IntegrationsModulePage;
|
15
frontend/src/pages/IntegrationsModulePage/constants.tsx
Normal file
15
frontend/src/pages/IntegrationsModulePage/constants.tsx
Normal file
@ -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: (
|
||||
<div className="tab-item">
|
||||
<Compass size={16} /> Integrations
|
||||
</div>
|
||||
),
|
||||
route: ROUTES.INTEGRATIONS_INSTALLED,
|
||||
key: ROUTES.INTEGRATIONS_INSTALLED,
|
||||
};
|
3
frontend/src/pages/IntegrationsModulePage/index.ts
Normal file
3
frontend/src/pages/IntegrationsModulePage/index.ts
Normal file
@ -0,0 +1,3 @@
|
||||
import IntegrationsModulePage from './IntegrationsModulePage';
|
||||
|
||||
export default IntegrationsModulePage;
|
105
frontend/src/types/api/integrations/types.ts
Normal file
105
frontend/src/types/api/integrations/types.ts
Normal file
@ -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;
|
||||
}
|
@ -92,4 +92,7 @@ export const routePermission: Record<keyof typeof ROUTES, ROLES[]> = {
|
||||
LOGS_BASE: [],
|
||||
OLD_LOGS_EXPLORER: [],
|
||||
SHORTCUTS: ['ADMIN', 'EDITOR', 'VIEWER'],
|
||||
INTEGRATIONS_BASE: ['ADMIN', 'EDITOR', 'VIEWER'],
|
||||
INTEGRATIONS_INSTALLED: ['ADMIN', 'EDITOR', 'VIEWER'],
|
||||
INTEGRATIONS_MARKETPLACE: ['ADMIN', 'EDITOR', 'VIEWER'],
|
||||
};
|
||||
|
@ -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"
|
||||
|
Loading…
x
Reference in New Issue
Block a user