mirror of
https://git.mirrors.martin98.com/https://github.com/SigNoz/signoz
synced 2025-08-12 15:19:00 +08:00
AWS Integration changes (#7025)
* fix: update AWS accounts API response to return accounts list * feat: display skeleton UI for account actions and refactored rendering logic * chore: update AWS service naming from "AWS Web Services" to "Amazon Web Services" * feat: aws integration success modal changes * feat: auto-select first service when no service is active * feat: display 'enable service' if service hasn't been configured and 'Configure (x/2)' if configured * fix: display no data yet if status is not available * feat: properly handle remove integration account flow * fix: rename accountId param to cloudAccountId * fix: update the aws service list and details api parameter from account_id to cloud_account_id * fix: fix the issue of stale service config modal enabled/disabled state * chore: improve the UI of configure button * feat: add connection parameters support for AWS cloud integration * feat: add optional link support for cloud service dashboards * fix: get the correct supported signals count + a minor refactoring * fix: remove cloudAccountId on success of account remove * chore: update the remove integration copy * refactor: add react query key for AWS connection parameters * fix: correct typo in integration loading state variable name * refactor: move skeleton inline styles to style file and do overall refactoring * chore: address the requested changes
This commit is contained in:
parent
b215c6a0ce
commit
c3164912e6
19
frontend/src/api/Integrations/removeAwsIntegrationAccount.ts
Normal file
19
frontend/src/api/Integrations/removeAwsIntegrationAccount.ts
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import axios from 'api';
|
||||||
|
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||||
|
|
||||||
|
const removeAwsIntegrationAccount = async (
|
||||||
|
accountId: string,
|
||||||
|
): Promise<SuccessResponse<Record<string, never>> | ErrorResponse> => {
|
||||||
|
const response = await axios.post(
|
||||||
|
`/cloud-integrations/aws/accounts/${accountId}/disconnect`,
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
statusCode: 200,
|
||||||
|
error: null,
|
||||||
|
message: response.data.status,
|
||||||
|
payload: response.data.data,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export default removeAwsIntegrationAccount;
|
@ -9,19 +9,22 @@ import {
|
|||||||
import {
|
import {
|
||||||
AccountConfigPayload,
|
AccountConfigPayload,
|
||||||
AccountConfigResponse,
|
AccountConfigResponse,
|
||||||
|
ConnectionParams,
|
||||||
ConnectionUrlResponse,
|
ConnectionUrlResponse,
|
||||||
} from 'types/api/integrations/aws';
|
} from 'types/api/integrations/aws';
|
||||||
|
|
||||||
export const getAwsAccounts = async (): Promise<CloudAccount[]> => {
|
export const getAwsAccounts = async (): Promise<CloudAccount[]> => {
|
||||||
const response = await axios.get('/cloud-integrations/aws/accounts');
|
const response = await axios.get('/cloud-integrations/aws/accounts');
|
||||||
|
|
||||||
return response.data.data;
|
return response.data.data.accounts;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getAwsServices = async (
|
export const getAwsServices = async (
|
||||||
accountId?: string,
|
cloudAccountId?: string,
|
||||||
): Promise<Service[]> => {
|
): Promise<Service[]> => {
|
||||||
const params = accountId ? { account_id: accountId } : undefined;
|
const params = cloudAccountId
|
||||||
|
? { cloud_account_id: cloudAccountId }
|
||||||
|
: undefined;
|
||||||
const response = await axios.get('/cloud-integrations/aws/services', {
|
const response = await axios.get('/cloud-integrations/aws/services', {
|
||||||
params,
|
params,
|
||||||
});
|
});
|
||||||
@ -31,9 +34,11 @@ export const getAwsServices = async (
|
|||||||
|
|
||||||
export const getServiceDetails = async (
|
export const getServiceDetails = async (
|
||||||
serviceId: string,
|
serviceId: string,
|
||||||
accountId?: string,
|
cloudAccountId?: string,
|
||||||
): Promise<ServiceData> => {
|
): Promise<ServiceData> => {
|
||||||
const params = accountId ? { account_id: accountId } : undefined;
|
const params = cloudAccountId
|
||||||
|
? { cloud_account_id: cloudAccountId }
|
||||||
|
: undefined;
|
||||||
const response = await axios.get(
|
const response = await axios.get(
|
||||||
`/cloud-integrations/aws/services/${serviceId}`,
|
`/cloud-integrations/aws/services/${serviceId}`,
|
||||||
{ params },
|
{ params },
|
||||||
@ -74,3 +79,10 @@ export const updateServiceConfig = async (
|
|||||||
);
|
);
|
||||||
return response.data;
|
return response.data;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const getConnectionParams = async (): Promise<ConnectionParams> => {
|
||||||
|
const response = await axios.get(
|
||||||
|
'/cloud-integrations/aws/accounts/generate-connection-params',
|
||||||
|
);
|
||||||
|
return response.data.data;
|
||||||
|
};
|
||||||
|
@ -41,5 +41,6 @@ export const REACT_QUERY_KEY = {
|
|||||||
AWS_UPDATE_ACCOUNT_CONFIG: 'AWS_UPDATE_ACCOUNT_CONFIG',
|
AWS_UPDATE_ACCOUNT_CONFIG: 'AWS_UPDATE_ACCOUNT_CONFIG',
|
||||||
AWS_UPDATE_SERVICE_CONFIG: 'AWS_UPDATE_SERVICE_CONFIG',
|
AWS_UPDATE_SERVICE_CONFIG: 'AWS_UPDATE_SERVICE_CONFIG',
|
||||||
AWS_GENERATE_CONNECTION_URL: 'AWS_GENERATE_CONNECTION_URL',
|
AWS_GENERATE_CONNECTION_URL: 'AWS_GENERATE_CONNECTION_URL',
|
||||||
|
AWS_GET_CONNECTION_PARAMS: 'AWS_GET_CONNECTION_PARAMS',
|
||||||
GET_ATTRIBUTE_VALUES: 'GET_ATTRIBUTE_VALUES',
|
GET_ATTRIBUTE_VALUES: 'GET_ATTRIBUTE_VALUES',
|
||||||
};
|
};
|
||||||
|
@ -24,7 +24,9 @@ function Header(): JSX.Element {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: (
|
title: (
|
||||||
<div className="cloud-header__breadcrumb-title">AWS web services</div>
|
<div className="cloud-header__breadcrumb-title">
|
||||||
|
Amazon Web Services
|
||||||
|
</div>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
|
@ -21,7 +21,7 @@ function HeroSection(): JSX.Element {
|
|||||||
<img src="/Logos/aws-dark.svg" alt="aws-logo" />
|
<img src="/Logos/aws-dark.svg" alt="aws-logo" />
|
||||||
</div>
|
</div>
|
||||||
<div className="hero-section__details">
|
<div className="hero-section__details">
|
||||||
<div className="title">AWS Web Services</div>
|
<div className="title">Amazon Web Services</div>
|
||||||
<div className="description">
|
<div className="description">
|
||||||
One-click setup for AWS monitoring with SigNoz
|
One-click setup for AWS monitoring with SigNoz
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,41 +1,56 @@
|
|||||||
.hero-section__actions {
|
.hero-section {
|
||||||
margin-top: 12px;
|
&__actions {
|
||||||
|
margin-top: 12px;
|
||||||
|
|
||||||
&-with-account {
|
&-with-account {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
.hero-section__action-buttons {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hero-section__action-button {
|
|
||||||
font-family: 'Inter';
|
|
||||||
border-radius: 2px;
|
|
||||||
cursor: pointer;
|
|
||||||
font-size: 12px;
|
|
||||||
font-weight: 500;
|
|
||||||
line-height: 16px;
|
|
||||||
padding: 8px 17px;
|
|
||||||
|
|
||||||
&.primary {
|
|
||||||
background: var(--bg-robin-500);
|
|
||||||
border: none;
|
|
||||||
color: var(--bg-vanilla-100);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&.secondary {
|
&__input-skeleton {
|
||||||
|
width: 300px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__new-account-button-skeleton {
|
||||||
|
width: 180px;
|
||||||
|
margin-right: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__account-settings-button-skeleton {
|
||||||
|
width: 140px;
|
||||||
|
}
|
||||||
|
&__action-buttons {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
border: 1px solid var(--bg-ink-300);
|
gap: 8px;
|
||||||
color: var(--bg-vanilla-100);
|
}
|
||||||
|
&__action-button {
|
||||||
|
font-family: 'Inter';
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
background: var(--bg-slate-400);
|
cursor: pointer;
|
||||||
box-shadow: none;
|
font-size: 12px;
|
||||||
|
font-weight: 500;
|
||||||
|
line-height: 16px;
|
||||||
|
padding: 8px 17px;
|
||||||
|
|
||||||
|
&.primary {
|
||||||
|
background: var(--bg-robin-500);
|
||||||
|
border: none;
|
||||||
|
color: var(--bg-vanilla-100);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.secondary {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
border: 1px solid var(--bg-ink-300);
|
||||||
|
color: var(--bg-vanilla-100);
|
||||||
|
border-radius: 2px;
|
||||||
|
background: var(--bg-slate-400);
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import './AccountActions.style.scss';
|
import './AccountActions.style.scss';
|
||||||
|
|
||||||
import { Color } from '@signozhq/design-tokens';
|
import { Color } from '@signozhq/design-tokens';
|
||||||
import { Button, Select } from 'antd';
|
import { Button, Select, Skeleton } from 'antd';
|
||||||
import { SelectProps } from 'antd/lib';
|
import { SelectProps } from 'antd/lib';
|
||||||
import { useAwsAccounts } from 'hooks/integrations/aws/useAwsAccounts';
|
import { useAwsAccounts } from 'hooks/integrations/aws/useAwsAccounts';
|
||||||
import useUrlQuery from 'hooks/useUrlQuery';
|
import useUrlQuery from 'hooks/useUrlQuery';
|
||||||
@ -53,15 +53,100 @@ const getAccountById = (
|
|||||||
): CloudAccount | null =>
|
): CloudAccount | null =>
|
||||||
accounts.find((account) => account.cloud_account_id === accountId) || null;
|
accounts.find((account) => account.cloud_account_id === accountId) || null;
|
||||||
|
|
||||||
|
function AccountActionsRenderer({
|
||||||
|
accounts,
|
||||||
|
isLoading,
|
||||||
|
activeAccount,
|
||||||
|
selectOptions,
|
||||||
|
onAccountChange,
|
||||||
|
onIntegrationModalOpen,
|
||||||
|
onAccountSettingsModalOpen,
|
||||||
|
}: {
|
||||||
|
accounts: CloudAccount[] | undefined;
|
||||||
|
isLoading: boolean;
|
||||||
|
activeAccount: CloudAccount | null;
|
||||||
|
selectOptions: SelectProps['options'];
|
||||||
|
onAccountChange: (value: string) => void;
|
||||||
|
onIntegrationModalOpen: () => void;
|
||||||
|
onAccountSettingsModalOpen: () => void;
|
||||||
|
}): JSX.Element {
|
||||||
|
if (isLoading) {
|
||||||
|
return (
|
||||||
|
<div className="hero-section__actions-with-account">
|
||||||
|
<Skeleton.Input
|
||||||
|
active
|
||||||
|
size="large"
|
||||||
|
block
|
||||||
|
className="hero-section__input-skeleton"
|
||||||
|
/>
|
||||||
|
<div className="hero-section__action-buttons">
|
||||||
|
<Skeleton.Button
|
||||||
|
active
|
||||||
|
size="large"
|
||||||
|
className="hero-section__new-account-button-skeleton"
|
||||||
|
/>
|
||||||
|
<Skeleton.Button
|
||||||
|
active
|
||||||
|
size="large"
|
||||||
|
className="hero-section__account-settings-button-skeleton"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (accounts?.length) {
|
||||||
|
return (
|
||||||
|
<div className="hero-section__actions-with-account">
|
||||||
|
<Select
|
||||||
|
value={`Account: ${activeAccount?.cloud_account_id}`}
|
||||||
|
options={selectOptions}
|
||||||
|
rootClassName="cloud-account-selector"
|
||||||
|
placeholder="Select AWS Account"
|
||||||
|
suffixIcon={<ChevronDown size={16} color={Color.BG_VANILLA_400} />}
|
||||||
|
optionRender={(option): JSX.Element =>
|
||||||
|
renderOption(option, activeAccount?.cloud_account_id)
|
||||||
|
}
|
||||||
|
onChange={onAccountChange}
|
||||||
|
/>
|
||||||
|
<div className="hero-section__action-buttons">
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
className="hero-section__action-button primary"
|
||||||
|
onClick={onIntegrationModalOpen}
|
||||||
|
>
|
||||||
|
Add New AWS Account
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
type="default"
|
||||||
|
className="hero-section__action-button secondary"
|
||||||
|
onClick={onAccountSettingsModalOpen}
|
||||||
|
>
|
||||||
|
Account Settings
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
className="hero-section__action-button primary"
|
||||||
|
onClick={onIntegrationModalOpen}
|
||||||
|
>
|
||||||
|
Integrate Now
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function AccountActions(): JSX.Element {
|
function AccountActions(): JSX.Element {
|
||||||
const urlQuery = useUrlQuery();
|
const urlQuery = useUrlQuery();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { data: accounts } = useAwsAccounts();
|
const { data: accounts, isLoading } = useAwsAccounts();
|
||||||
|
|
||||||
const initialAccount = useMemo(
|
const initialAccount = useMemo(
|
||||||
() =>
|
() =>
|
||||||
accounts?.length
|
accounts?.length
|
||||||
? getAccountById(accounts, urlQuery.get('accountId') || '') || accounts[0]
|
? getAccountById(accounts, urlQuery.get('cloudAccountId') || '') ||
|
||||||
|
accounts[0]
|
||||||
: null,
|
: null,
|
||||||
[accounts, urlQuery],
|
[accounts, urlQuery],
|
||||||
);
|
);
|
||||||
@ -74,7 +159,7 @@ function AccountActions(): JSX.Element {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (initialAccount !== null) {
|
if (initialAccount !== null) {
|
||||||
setActiveAccount(initialAccount);
|
setActiveAccount(initialAccount);
|
||||||
urlQuery.set('accountId', initialAccount.cloud_account_id);
|
urlQuery.set('cloudAccountId', initialAccount.cloud_account_id);
|
||||||
navigate({ search: urlQuery.toString() });
|
navigate({ search: urlQuery.toString() });
|
||||||
}
|
}
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
@ -98,60 +183,35 @@ function AccountActions(): JSX.Element {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="hero-section__actions">
|
<div className="hero-section__actions">
|
||||||
{accounts?.length ? (
|
<AccountActionsRenderer
|
||||||
<div className="hero-section__actions-with-account">
|
accounts={accounts}
|
||||||
<Select
|
isLoading={isLoading}
|
||||||
value={`Account: ${activeAccount?.cloud_account_id}`}
|
activeAccount={activeAccount}
|
||||||
options={selectOptions}
|
selectOptions={selectOptions}
|
||||||
rootClassName="cloud-account-selector"
|
onAccountChange={(value): void => {
|
||||||
placeholder="Select AWS Account"
|
if (accounts) {
|
||||||
suffixIcon={<ChevronDown size={16} color={Color.BG_VANILLA_400} />}
|
setActiveAccount(getAccountById(accounts, value));
|
||||||
optionRender={(option): JSX.Element =>
|
urlQuery.set('cloudAccountId', value);
|
||||||
renderOption(option, activeAccount?.cloud_account_id)
|
navigate({ search: urlQuery.toString() });
|
||||||
}
|
}
|
||||||
onChange={(value): void => {
|
}}
|
||||||
setActiveAccount(getAccountById(accounts, value));
|
onIntegrationModalOpen={(): void => setIsIntegrationModalOpen(true)}
|
||||||
urlQuery.set('accountId', value);
|
onAccountSettingsModalOpen={(): void => setIsAccountSettingsModalOpen(true)}
|
||||||
navigate({ search: urlQuery.toString() });
|
/>
|
||||||
}}
|
|
||||||
/>
|
{isIntegrationModalOpen && (
|
||||||
<div className="hero-section__action-buttons">
|
<CloudAccountSetupModal
|
||||||
<Button
|
onClose={(): void => setIsIntegrationModalOpen(false)}
|
||||||
type="primary"
|
/>
|
||||||
className="hero-section__action-button primary"
|
|
||||||
onClick={(): void => setIsIntegrationModalOpen(true)}
|
|
||||||
>
|
|
||||||
Add New AWS Account
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
type="default"
|
|
||||||
className="hero-section__action-button secondary"
|
|
||||||
onClick={(): void => setIsAccountSettingsModalOpen(true)}
|
|
||||||
>
|
|
||||||
Account Settings
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<Button
|
|
||||||
className="hero-section__action-button primary"
|
|
||||||
onClick={(): void => setIsIntegrationModalOpen(true)}
|
|
||||||
>
|
|
||||||
Integrate Now
|
|
||||||
</Button>
|
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<CloudAccountSetupModal
|
{isAccountSettingsModalOpen && (
|
||||||
isOpen={isIntegrationModalOpen}
|
<AccountSettingsModal
|
||||||
onClose={(): void => setIsIntegrationModalOpen(false)}
|
onClose={(): void => setIsAccountSettingsModalOpen(false)}
|
||||||
/>
|
account={activeAccount as CloudAccount}
|
||||||
|
setActiveAccount={setActiveAccount}
|
||||||
<AccountSettingsModal
|
/>
|
||||||
isOpen={isAccountSettingsModalOpen}
|
)}
|
||||||
onClose={(): void => setIsAccountSettingsModalOpen(false)}
|
|
||||||
account={activeAccount as CloudAccount}
|
|
||||||
setActiveAccount={setActiveAccount}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -2,27 +2,27 @@ import './AccountSettingsModal.style.scss';
|
|||||||
|
|
||||||
import { Form, Select, Switch } from 'antd';
|
import { Form, Select, Switch } from 'antd';
|
||||||
import SignozModal from 'components/SignozModal/SignozModal';
|
import SignozModal from 'components/SignozModal/SignozModal';
|
||||||
|
import { REACT_QUERY_KEY } from 'constants/reactQueryKeys';
|
||||||
import {
|
import {
|
||||||
getRegionPreviewText,
|
getRegionPreviewText,
|
||||||
useAccountSettingsModal,
|
useAccountSettingsModal,
|
||||||
} from 'hooks/integrations/aws/useAccountSettingsModal';
|
} from 'hooks/integrations/aws/useAccountSettingsModal';
|
||||||
import IntergrationsUninstallBar from 'pages/Integrations/IntegrationDetailPage/IntegrationsUninstallBar';
|
import useUrlQuery from 'hooks/useUrlQuery';
|
||||||
import { ConnectionStates } from 'pages/Integrations/IntegrationDetailPage/TestConnection';
|
import history from 'lib/history';
|
||||||
import { AWS_INTEGRATION } from 'pages/Integrations/IntegrationsList';
|
|
||||||
import { Dispatch, SetStateAction, useCallback } from 'react';
|
import { Dispatch, SetStateAction, useCallback } from 'react';
|
||||||
|
import { useQueryClient } from 'react-query';
|
||||||
|
|
||||||
import { CloudAccount } from '../../ServicesSection/types';
|
import { CloudAccount } from '../../ServicesSection/types';
|
||||||
import { RegionSelector } from './RegionSelector';
|
import { RegionSelector } from './RegionSelector';
|
||||||
|
import RemoveIntegrationAccount from './RemoveIntegrationAccount';
|
||||||
|
|
||||||
interface AccountSettingsModalProps {
|
interface AccountSettingsModalProps {
|
||||||
isOpen: boolean;
|
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
account: CloudAccount;
|
account: CloudAccount;
|
||||||
setActiveAccount: Dispatch<SetStateAction<CloudAccount | null>>;
|
setActiveAccount: Dispatch<SetStateAction<CloudAccount | null>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
function AccountSettingsModal({
|
function AccountSettingsModal({
|
||||||
isOpen,
|
|
||||||
onClose,
|
onClose,
|
||||||
account,
|
account,
|
||||||
setActiveAccount,
|
setActiveAccount,
|
||||||
@ -42,6 +42,16 @@ function AccountSettingsModal({
|
|||||||
handleClose,
|
handleClose,
|
||||||
} = useAccountSettingsModal({ onClose, account, setActiveAccount });
|
} = useAccountSettingsModal({ onClose, account, setActiveAccount });
|
||||||
|
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
const urlQuery = useUrlQuery();
|
||||||
|
|
||||||
|
const handleRemoveIntegrationAccountSuccess = (): void => {
|
||||||
|
queryClient.invalidateQueries([REACT_QUERY_KEY.AWS_ACCOUNTS]);
|
||||||
|
urlQuery.delete('cloudAccountId');
|
||||||
|
handleClose();
|
||||||
|
history.replace({ search: urlQuery.toString() });
|
||||||
|
};
|
||||||
|
|
||||||
const renderRegionSelector = useCallback(() => {
|
const renderRegionSelector = useCallback(() => {
|
||||||
if (isRegionSelectOpen) {
|
if (isRegionSelectOpen) {
|
||||||
return (
|
return (
|
||||||
@ -120,7 +130,7 @@ function AccountSettingsModal({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<SignozModal
|
<SignozModal
|
||||||
open={isOpen}
|
open
|
||||||
title={modalTitle}
|
title={modalTitle}
|
||||||
onCancel={handleClose}
|
onCancel={handleClose}
|
||||||
onOk={handleSubmit}
|
onOk={handleSubmit}
|
||||||
@ -164,12 +174,9 @@ function AccountSettingsModal({
|
|||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
<div className="integration-detail-content">
|
<div className="integration-detail-content">
|
||||||
<IntergrationsUninstallBar
|
<RemoveIntegrationAccount
|
||||||
integrationTitle={AWS_INTEGRATION.title}
|
accountId={account?.id}
|
||||||
integrationId={AWS_INTEGRATION.id}
|
onRemoveIntegrationAccountSuccess={handleRemoveIntegrationAccountSuccess}
|
||||||
onUnInstallSuccess={handleClose}
|
|
||||||
removeIntegrationTitle="Remove"
|
|
||||||
connectionStatus={ConnectionStates.Connected}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -2,11 +2,9 @@ import './CloudAccountSetupModal.style.scss';
|
|||||||
|
|
||||||
import { Color } from '@signozhq/design-tokens';
|
import { Color } from '@signozhq/design-tokens';
|
||||||
import SignozModal from 'components/SignozModal/SignozModal';
|
import SignozModal from 'components/SignozModal/SignozModal';
|
||||||
import ROUTES from 'constants/routes';
|
|
||||||
import { useIntegrationModal } from 'hooks/integrations/aws/useIntegrationModal';
|
import { useIntegrationModal } from 'hooks/integrations/aws/useIntegrationModal';
|
||||||
import { SquareArrowOutUpRight } from 'lucide-react';
|
import { SquareArrowOutUpRight } from 'lucide-react';
|
||||||
import { useCallback } from 'react';
|
import { useCallback } from 'react';
|
||||||
import { useNavigate } from 'react-router-dom-v5-compat';
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
ActiveViewEnum,
|
ActiveViewEnum,
|
||||||
@ -18,7 +16,6 @@ import { RegionSelector } from './RegionSelector';
|
|||||||
import { SuccessView } from './SuccessView';
|
import { SuccessView } from './SuccessView';
|
||||||
|
|
||||||
function CloudAccountSetupModal({
|
function CloudAccountSetupModal({
|
||||||
isOpen,
|
|
||||||
onClose,
|
onClose,
|
||||||
}: IntegrationModalProps): JSX.Element {
|
}: IntegrationModalProps): JSX.Element {
|
||||||
const {
|
const {
|
||||||
@ -41,6 +38,8 @@ function CloudAccountSetupModal({
|
|||||||
accountId,
|
accountId,
|
||||||
selectedDeploymentRegion,
|
selectedDeploymentRegion,
|
||||||
handleRegionChange,
|
handleRegionChange,
|
||||||
|
connectionParams,
|
||||||
|
isConnectionParamsLoading,
|
||||||
} = useIntegrationModal({ onClose });
|
} = useIntegrationModal({ onClose });
|
||||||
|
|
||||||
const renderContent = useCallback(() => {
|
const renderContent = useCallback(() => {
|
||||||
@ -71,6 +70,8 @@ function CloudAccountSetupModal({
|
|||||||
accountId={accountId}
|
accountId={accountId}
|
||||||
selectedDeploymentRegion={selectedDeploymentRegion}
|
selectedDeploymentRegion={selectedDeploymentRegion}
|
||||||
handleRegionChange={handleRegionChange}
|
handleRegionChange={handleRegionChange}
|
||||||
|
connectionParams={connectionParams}
|
||||||
|
isConnectionParamsLoading={isConnectionParamsLoading}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}, [
|
}, [
|
||||||
@ -86,6 +87,8 @@ function CloudAccountSetupModal({
|
|||||||
accountId,
|
accountId,
|
||||||
selectedDeploymentRegion,
|
selectedDeploymentRegion,
|
||||||
handleRegionChange,
|
handleRegionChange,
|
||||||
|
connectionParams,
|
||||||
|
isConnectionParamsLoading,
|
||||||
setSelectedRegions,
|
setSelectedRegions,
|
||||||
setIncludeAllRegions,
|
setIncludeAllRegions,
|
||||||
]);
|
]);
|
||||||
@ -96,11 +99,6 @@ function CloudAccountSetupModal({
|
|||||||
[selectedRegions, allRegions],
|
[selectedRegions, allRegions],
|
||||||
);
|
);
|
||||||
|
|
||||||
const navigate = useNavigate();
|
|
||||||
const handleGoToDashboards = useCallback((): void => {
|
|
||||||
navigate(ROUTES.ALL_DASHBOARD);
|
|
||||||
}, [navigate]);
|
|
||||||
|
|
||||||
const getModalConfig = useCallback(() => {
|
const getModalConfig = useCallback(() => {
|
||||||
// Handle success state first
|
// Handle success state first
|
||||||
if (modalState === ModalStateEnum.SUCCESS) {
|
if (modalState === ModalStateEnum.SUCCESS) {
|
||||||
@ -108,11 +106,11 @@ function CloudAccountSetupModal({
|
|||||||
title: 'AWS Webservice Integration',
|
title: 'AWS Webservice Integration',
|
||||||
okText: (
|
okText: (
|
||||||
<div className="cloud-account-setup-success-view__footer-button">
|
<div className="cloud-account-setup-success-view__footer-button">
|
||||||
Go to Dashboards
|
Continue
|
||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
block: true,
|
block: true,
|
||||||
onOk: handleGoToDashboards,
|
onOk: handleClose,
|
||||||
cancelButtonProps: { style: { display: 'none' } },
|
cancelButtonProps: { style: { display: 'none' } },
|
||||||
disabled: false,
|
disabled: false,
|
||||||
};
|
};
|
||||||
@ -151,7 +149,7 @@ function CloudAccountSetupModal({
|
|||||||
isLoading,
|
isLoading,
|
||||||
isGeneratingUrl,
|
isGeneratingUrl,
|
||||||
activeView,
|
activeView,
|
||||||
handleGoToDashboards,
|
handleClose,
|
||||||
setActiveView,
|
setActiveView,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
@ -159,7 +157,7 @@ function CloudAccountSetupModal({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<SignozModal
|
<SignozModal
|
||||||
open={isOpen}
|
open
|
||||||
className="cloud-account-setup-modal"
|
className="cloud-account-setup-modal"
|
||||||
title={modalConfig.title}
|
title={modalConfig.title}
|
||||||
onCancel={handleClose}
|
onCancel={handleClose}
|
||||||
|
@ -12,6 +12,7 @@ import {
|
|||||||
MonitoringRegionsSection,
|
MonitoringRegionsSection,
|
||||||
RegionDeploymentSection,
|
RegionDeploymentSection,
|
||||||
} from './IntegrateNowFormSections';
|
} from './IntegrateNowFormSections';
|
||||||
|
import RenderConnectionFields from './RenderConnectionParams';
|
||||||
|
|
||||||
const allRegions = (): string[] =>
|
const allRegions = (): string[] =>
|
||||||
regions.flatMap((r) => r.subRegions.map((sr) => sr.name));
|
regions.flatMap((r) => r.subRegions.map((sr) => sr.name));
|
||||||
@ -35,6 +36,8 @@ export function RegionForm({
|
|||||||
accountId,
|
accountId,
|
||||||
selectedDeploymentRegion,
|
selectedDeploymentRegion,
|
||||||
handleRegionChange,
|
handleRegionChange,
|
||||||
|
connectionParams,
|
||||||
|
isConnectionParamsLoading,
|
||||||
}: RegionFormProps): JSX.Element {
|
}: RegionFormProps): JSX.Element {
|
||||||
const startTimeRef = useRef(Date.now());
|
const startTimeRef = useRef(Date.now());
|
||||||
const refetchInterval = 10 * 1000;
|
const refetchInterval = 10 * 1000;
|
||||||
@ -88,6 +91,11 @@ export function RegionForm({
|
|||||||
isFormDisabled={isFormDisabled}
|
isFormDisabled={isFormDisabled}
|
||||||
/>
|
/>
|
||||||
<ComplianceNote />
|
<ComplianceNote />
|
||||||
|
<RenderConnectionFields
|
||||||
|
isConnectionParamsLoading={isConnectionParamsLoading}
|
||||||
|
connectionParams={connectionParams}
|
||||||
|
isFormDisabled={isFormDisabled}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</Form>
|
</Form>
|
||||||
);
|
);
|
||||||
|
@ -0,0 +1,48 @@
|
|||||||
|
.remove-integration-account {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 16px;
|
||||||
|
border-radius: 4px;
|
||||||
|
border: 1px solid rgba(218, 85, 101, 0.2);
|
||||||
|
background: rgba(218, 85, 101, 0.06);
|
||||||
|
&__header {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__title {
|
||||||
|
color: var(--bg-cherry-500);
|
||||||
|
font-size: 14px;
|
||||||
|
letter-spacing: -0.07px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__subtitle {
|
||||||
|
color: var(--bg-cherry-300);
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 22px;
|
||||||
|
letter-spacing: -0.07px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__button {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
background: var(--bg-cherry-500);
|
||||||
|
border: none;
|
||||||
|
color: var(--bg-vanilla-100);
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 500;
|
||||||
|
line-height: 13.3px; /* 110.833% */
|
||||||
|
padding: 9px 13px;
|
||||||
|
.ant-btn-icon {
|
||||||
|
margin-inline-end: 4px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
&.ant-btn-default {
|
||||||
|
color: var(--bg-vanilla-300) !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,94 @@
|
|||||||
|
import './RemoveIntegrationAccount.scss';
|
||||||
|
|
||||||
|
import { Button, Modal } from 'antd';
|
||||||
|
import logEvent from 'api/common/logEvent';
|
||||||
|
import removeAwsIntegrationAccount from 'api/Integrations/removeAwsIntegrationAccount';
|
||||||
|
import { SOMETHING_WENT_WRONG } from 'constants/api';
|
||||||
|
import { useNotifications } from 'hooks/useNotifications';
|
||||||
|
import { X } from 'lucide-react';
|
||||||
|
import { INTEGRATION_TELEMETRY_EVENTS } from 'pages/Integrations/utils';
|
||||||
|
import { useState } from 'react';
|
||||||
|
import { useMutation } from 'react-query';
|
||||||
|
|
||||||
|
function RemoveIntegrationAccount({
|
||||||
|
accountId,
|
||||||
|
onRemoveIntegrationAccountSuccess,
|
||||||
|
}: {
|
||||||
|
accountId: string;
|
||||||
|
onRemoveIntegrationAccountSuccess: () => void;
|
||||||
|
}): JSX.Element {
|
||||||
|
const { notifications } = useNotifications();
|
||||||
|
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||||
|
|
||||||
|
const showModal = (): void => {
|
||||||
|
setIsModalOpen(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const {
|
||||||
|
mutate: removeIntegration,
|
||||||
|
isLoading: isRemoveIntegrationLoading,
|
||||||
|
} = useMutation(removeAwsIntegrationAccount, {
|
||||||
|
onSuccess: () => {
|
||||||
|
onRemoveIntegrationAccountSuccess?.();
|
||||||
|
setIsModalOpen(false);
|
||||||
|
},
|
||||||
|
onError: () => {
|
||||||
|
notifications.error({
|
||||||
|
message: SOMETHING_WENT_WRONG,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const handleOk = (): void => {
|
||||||
|
logEvent(INTEGRATION_TELEMETRY_EVENTS.AWS_INTEGRATION_ACCOUNT_REMOVED, {
|
||||||
|
accountId,
|
||||||
|
});
|
||||||
|
removeIntegration(accountId);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCancel = (): void => {
|
||||||
|
setIsModalOpen(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="remove-integration-account">
|
||||||
|
<div className="remove-integration-account__header">
|
||||||
|
<div className="remove-integration-account__title">Remove Integration</div>
|
||||||
|
<div className="remove-integration-account__subtitle">
|
||||||
|
Removing this integration won't delete any existing data but will stop
|
||||||
|
collecting new data from AWS.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Button
|
||||||
|
className="remove-integration-account__button"
|
||||||
|
icon={<X size={14} />}
|
||||||
|
onClick={(): void => showModal()}
|
||||||
|
>
|
||||||
|
Remove
|
||||||
|
</Button>
|
||||||
|
<Modal
|
||||||
|
className="remove-integration-modal"
|
||||||
|
open={isModalOpen}
|
||||||
|
title="Remove integration"
|
||||||
|
onOk={handleOk}
|
||||||
|
onCancel={handleCancel}
|
||||||
|
okText="Remove Integration"
|
||||||
|
okButtonProps={{
|
||||||
|
danger: true,
|
||||||
|
disabled: isRemoveIntegrationLoading,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className="remove-integration-modal__text">
|
||||||
|
Removing this account will remove all components created for sending
|
||||||
|
telemetry to SigNoz in your AWS account within the next ~15 minutes
|
||||||
|
(cloudformation stacks named signoz-integration-telemetry-collection in
|
||||||
|
enabled regions). <br />
|
||||||
|
<br />
|
||||||
|
After that, you can delete the cloudformation stack that was created
|
||||||
|
manually when connecting this account.
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default RemoveIntegrationAccount;
|
@ -0,0 +1,61 @@
|
|||||||
|
import { Form, Input } from 'antd';
|
||||||
|
import { ConnectionParams } from 'types/api/integrations/aws';
|
||||||
|
|
||||||
|
function RenderConnectionFields({
|
||||||
|
isConnectionParamsLoading,
|
||||||
|
connectionParams,
|
||||||
|
isFormDisabled,
|
||||||
|
}: {
|
||||||
|
isConnectionParamsLoading?: boolean;
|
||||||
|
connectionParams?: ConnectionParams | null;
|
||||||
|
isFormDisabled?: boolean;
|
||||||
|
}): JSX.Element | null {
|
||||||
|
if (
|
||||||
|
isConnectionParamsLoading ||
|
||||||
|
(!!connectionParams?.ingestion_url &&
|
||||||
|
!!connectionParams?.ingestion_key &&
|
||||||
|
!!connectionParams?.signoz_api_url)
|
||||||
|
) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Form.Item name="connection_params">
|
||||||
|
{!connectionParams?.ingestion_url && (
|
||||||
|
<Form.Item
|
||||||
|
name="ingestion_url"
|
||||||
|
label="Ingestion URL"
|
||||||
|
rules={[{ required: true, message: 'Please enter ingestion URL' }]}
|
||||||
|
>
|
||||||
|
<Input placeholder="Enter ingestion URL" disabled={isFormDisabled} />
|
||||||
|
</Form.Item>
|
||||||
|
)}
|
||||||
|
{!connectionParams?.ingestion_key && (
|
||||||
|
<Form.Item
|
||||||
|
name="ingestion_key"
|
||||||
|
label="Ingestion Key"
|
||||||
|
rules={[{ required: true, message: 'Please enter ingestion key' }]}
|
||||||
|
>
|
||||||
|
<Input placeholder="Enter ingestion key" disabled={isFormDisabled} />
|
||||||
|
</Form.Item>
|
||||||
|
)}
|
||||||
|
{!connectionParams?.signoz_api_url && (
|
||||||
|
<Form.Item
|
||||||
|
name="signoz_api_url"
|
||||||
|
label="SigNoz API URL"
|
||||||
|
rules={[{ required: true, message: 'Please enter SigNoz API URL' }]}
|
||||||
|
>
|
||||||
|
<Input placeholder="Enter SigNoz API URL" disabled={isFormDisabled} />
|
||||||
|
</Form.Item>
|
||||||
|
)}
|
||||||
|
</Form.Item>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
RenderConnectionFields.defaultProps = {
|
||||||
|
connectionParams: null,
|
||||||
|
isFormDisabled: false,
|
||||||
|
isConnectionParamsLoading: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default RenderConnectionFields;
|
@ -53,23 +53,18 @@ export function SuccessView(): JSX.Element {
|
|||||||
WHAT NEXT
|
WHAT NEXT
|
||||||
</h4>
|
</h4>
|
||||||
<div className="what-next-items-wrapper">
|
<div className="what-next-items-wrapper">
|
||||||
{[
|
<Alert
|
||||||
'Understand your AWS services with SigNoz’s out-of-the-box dashboards',
|
message={
|
||||||
'Set up alerts for real-time monitoring.',
|
<div className="what-next-items-wrapper__item">
|
||||||
'Track logs and traces.',
|
<div className="what-next-item-bullet-icon">•</div>
|
||||||
].map((item) => (
|
<div className="what-next-item-text">
|
||||||
<Alert
|
Set up your AWS services effortlessly under your enabled account.
|
||||||
key={item}
|
|
||||||
message={
|
|
||||||
<div className="what-next-items-wrapper__item">
|
|
||||||
<div className="what-next-item-bullet-icon">•</div>
|
|
||||||
<div className="what-next-item-text">{item}</div>
|
|
||||||
</div>
|
</div>
|
||||||
}
|
</div>
|
||||||
type="info"
|
}
|
||||||
className="what-next-items-wrapper__item"
|
type="info"
|
||||||
/>
|
className="what-next-items-wrapper__item"
|
||||||
))}
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { FormInstance } from 'antd';
|
import { FormInstance } from 'antd';
|
||||||
import { Dispatch, SetStateAction } from 'react';
|
import { Dispatch, SetStateAction } from 'react';
|
||||||
|
import { ConnectionParams } from 'types/api/integrations/aws';
|
||||||
|
|
||||||
export enum ActiveViewEnum {
|
export enum ActiveViewEnum {
|
||||||
SELECT_REGIONS = 'select-regions',
|
SELECT_REGIONS = 'select-regions',
|
||||||
@ -25,9 +26,10 @@ export interface RegionFormProps {
|
|||||||
accountId?: string;
|
accountId?: string;
|
||||||
selectedDeploymentRegion: string | undefined;
|
selectedDeploymentRegion: string | undefined;
|
||||||
handleRegionChange: (value: string) => void;
|
handleRegionChange: (value: string) => void;
|
||||||
|
connectionParams?: ConnectionParams;
|
||||||
|
isConnectionParamsLoading?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IntegrationModalProps {
|
export interface IntegrationModalProps {
|
||||||
isOpen: boolean;
|
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
import { Link } from 'react-router-dom';
|
||||||
|
|
||||||
import { ServiceData } from './types';
|
import { ServiceData } from './types';
|
||||||
|
|
||||||
function DashboardItem({
|
function DashboardItem({
|
||||||
@ -5,8 +7,8 @@ function DashboardItem({
|
|||||||
}: {
|
}: {
|
||||||
dashboard: ServiceData['assets']['dashboards'][number];
|
dashboard: ServiceData['assets']['dashboards'][number];
|
||||||
}): JSX.Element {
|
}): JSX.Element {
|
||||||
return (
|
const content = (
|
||||||
<div className="cloud-service-dashboard-item">
|
<>
|
||||||
<div className="cloud-service-dashboard-item__title">{dashboard.title}</div>
|
<div className="cloud-service-dashboard-item__title">{dashboard.title}</div>
|
||||||
<div className="cloud-service-dashboard-item__preview">
|
<div className="cloud-service-dashboard-item__preview">
|
||||||
<img
|
<img
|
||||||
@ -15,6 +17,18 @@ function DashboardItem({
|
|||||||
className="cloud-service-dashboard-item__preview-image"
|
className="cloud-service-dashboard-item__preview-image"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="cloud-service-dashboard-item">
|
||||||
|
{dashboard.url ? (
|
||||||
|
<Link to={dashboard.url} className="cloud-service-dashboard-item__link">
|
||||||
|
{content}
|
||||||
|
</Link>
|
||||||
|
) : (
|
||||||
|
content
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import { Color } from '@signozhq/design-tokens';
|
|
||||||
import { Button, Tabs, TabsProps } from 'antd';
|
import { Button, Tabs, TabsProps } from 'antd';
|
||||||
import { MarkdownRenderer } from 'components/MarkdownRenderer/MarkdownRenderer';
|
import { MarkdownRenderer } from 'components/MarkdownRenderer/MarkdownRenderer';
|
||||||
import Spinner from 'components/Spinner';
|
import Spinner from 'components/Spinner';
|
||||||
@ -8,8 +7,7 @@ import { IServiceStatus } from 'container/CloudIntegrationPage/ServicesSection/t
|
|||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import { useServiceDetails } from 'hooks/integrations/aws/useServiceDetails';
|
import { useServiceDetails } from 'hooks/integrations/aws/useServiceDetails';
|
||||||
import useUrlQuery from 'hooks/useUrlQuery';
|
import useUrlQuery from 'hooks/useUrlQuery';
|
||||||
import { Wrench } from 'lucide-react';
|
import { useMemo, useState } from 'react';
|
||||||
import { useState } from 'react';
|
|
||||||
|
|
||||||
import ConfigureServiceModal from './ConfigureServiceModal';
|
import ConfigureServiceModal from './ConfigureServiceModal';
|
||||||
|
|
||||||
@ -38,7 +36,7 @@ const getStatus = (
|
|||||||
function ServiceStatus({
|
function ServiceStatus({
|
||||||
serviceStatus,
|
serviceStatus,
|
||||||
}: {
|
}: {
|
||||||
serviceStatus: IServiceStatus | null;
|
serviceStatus: IServiceStatus | undefined;
|
||||||
}): JSX.Element {
|
}): JSX.Element {
|
||||||
const logsLastReceivedTimestamp = serviceStatus?.logs?.last_received_ts_ms;
|
const logsLastReceivedTimestamp = serviceStatus?.logs?.last_received_ts_ms;
|
||||||
const metricsLastReceivedTimestamp =
|
const metricsLastReceivedTimestamp =
|
||||||
@ -54,7 +52,7 @@ function ServiceStatus({
|
|||||||
|
|
||||||
function ServiceDetails(): JSX.Element | null {
|
function ServiceDetails(): JSX.Element | null {
|
||||||
const urlQuery = useUrlQuery();
|
const urlQuery = useUrlQuery();
|
||||||
const accountId = urlQuery.get('accountId');
|
const cloudAccountId = urlQuery.get('cloudAccountId');
|
||||||
const serviceId = urlQuery.get('service');
|
const serviceId = urlQuery.get('service');
|
||||||
const [isConfigureServiceModalOpen, setIsConfigureServiceModalOpen] = useState(
|
const [isConfigureServiceModalOpen, setIsConfigureServiceModalOpen] = useState(
|
||||||
false,
|
false,
|
||||||
@ -62,7 +60,24 @@ function ServiceDetails(): JSX.Element | null {
|
|||||||
|
|
||||||
const { data: serviceDetailsData, isLoading } = useServiceDetails(
|
const { data: serviceDetailsData, isLoading } = useServiceDetails(
|
||||||
serviceId || '',
|
serviceId || '',
|
||||||
accountId || undefined,
|
cloudAccountId || undefined,
|
||||||
|
);
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||||
|
const { config, supported_signals } = serviceDetailsData ?? {};
|
||||||
|
|
||||||
|
const totalSupportedSignals = Object.entries(supported_signals || {}).filter(
|
||||||
|
([, value]) => !!value,
|
||||||
|
).length;
|
||||||
|
const enabledSignals = useMemo(
|
||||||
|
() =>
|
||||||
|
Object.values(config || {}).filter((item) => item && item.enabled).length,
|
||||||
|
[config],
|
||||||
|
);
|
||||||
|
|
||||||
|
const isAnySignalConfigured = useMemo(
|
||||||
|
() => !!config?.logs?.enabled || !!config?.metrics?.enabled,
|
||||||
|
[config],
|
||||||
);
|
);
|
||||||
|
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
@ -96,16 +111,22 @@ function ServiceDetails(): JSX.Element | null {
|
|||||||
<div className="service-details__title-bar">
|
<div className="service-details__title-bar">
|
||||||
<div className="service-details__details-title">Details</div>
|
<div className="service-details__details-title">Details</div>
|
||||||
<div className="service-details__right-actions">
|
<div className="service-details__right-actions">
|
||||||
{serviceDetailsData?.status && (
|
<ServiceStatus serviceStatus={serviceDetailsData.status} />
|
||||||
<ServiceStatus serviceStatus={serviceDetailsData.status} />
|
|
||||||
)}
|
{!!cloudAccountId && isAnySignalConfigured ? (
|
||||||
{!!accountId && (
|
|
||||||
<Button
|
<Button
|
||||||
className="configure-button"
|
className="configure-button configure-button--default"
|
||||||
onClick={(): void => setIsConfigureServiceModalOpen(true)}
|
onClick={(): void => setIsConfigureServiceModalOpen(true)}
|
||||||
>
|
>
|
||||||
<Wrench size={12} color={Color.BG_VANILLA_400} />
|
Configure ({enabledSignals}/{totalSupportedSignals})
|
||||||
Configure
|
</Button>
|
||||||
|
) : (
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
className="configure-button configure-button--primary"
|
||||||
|
onClick={(): void => setIsConfigureServiceModalOpen(true)}
|
||||||
|
>
|
||||||
|
Enable Service
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@ -119,15 +140,17 @@ function ServiceDetails(): JSX.Element | null {
|
|||||||
<div className="service-details__tabs">
|
<div className="service-details__tabs">
|
||||||
<Tabs items={tabItems} />
|
<Tabs items={tabItems} />
|
||||||
</div>
|
</div>
|
||||||
<ConfigureServiceModal
|
{isConfigureServiceModalOpen && (
|
||||||
isOpen={isConfigureServiceModalOpen}
|
<ConfigureServiceModal
|
||||||
onClose={(): void => setIsConfigureServiceModalOpen(false)}
|
isOpen
|
||||||
serviceName={serviceDetailsData.title}
|
onClose={(): void => setIsConfigureServiceModalOpen(false)}
|
||||||
serviceId={serviceId || ''}
|
serviceName={serviceDetailsData.title}
|
||||||
cloudAccountId={accountId || ''}
|
serviceId={serviceId || ''}
|
||||||
initialConfig={serviceDetailsData.config}
|
cloudAccountId={cloudAccountId || ''}
|
||||||
supportedSignals={serviceDetailsData.supported_signals || {}}
|
initialConfig={serviceDetailsData.config}
|
||||||
/>
|
supportedSignals={serviceDetailsData.supported_signals || {}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,26 +1,34 @@
|
|||||||
import Spinner from 'components/Spinner';
|
import Spinner from 'components/Spinner';
|
||||||
import { useGetAccountServices } from 'hooks/integrations/aws/useGetAccountServices';
|
import { useGetAccountServices } from 'hooks/integrations/aws/useGetAccountServices';
|
||||||
import useUrlQuery from 'hooks/useUrlQuery';
|
import useUrlQuery from 'hooks/useUrlQuery';
|
||||||
import { useMemo } from 'react';
|
import { useCallback, useEffect, useMemo } from 'react';
|
||||||
import { useNavigate } from 'react-router-dom-v5-compat';
|
import { useNavigate } from 'react-router-dom-v5-compat';
|
||||||
|
|
||||||
import ServiceItem from './ServiceItem';
|
import ServiceItem from './ServiceItem';
|
||||||
|
|
||||||
interface ServicesListProps {
|
interface ServicesListProps {
|
||||||
accountId: string;
|
cloudAccountId: string;
|
||||||
filter: 'all_services' | 'enabled' | 'available';
|
filter: 'all_services' | 'enabled' | 'available';
|
||||||
}
|
}
|
||||||
|
|
||||||
function ServicesList({ accountId, filter }: ServicesListProps): JSX.Element {
|
function ServicesList({
|
||||||
|
cloudAccountId,
|
||||||
|
filter,
|
||||||
|
}: ServicesListProps): JSX.Element {
|
||||||
const urlQuery = useUrlQuery();
|
const urlQuery = useUrlQuery();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { data: services = [], isLoading } = useGetAccountServices(accountId);
|
const { data: services = [], isLoading } = useGetAccountServices(
|
||||||
|
cloudAccountId,
|
||||||
|
);
|
||||||
const activeService = urlQuery.get('service');
|
const activeService = urlQuery.get('service');
|
||||||
|
|
||||||
const handleServiceClick = (serviceId: string): void => {
|
const handleActiveService = useCallback(
|
||||||
urlQuery.set('service', serviceId);
|
(serviceId: string): void => {
|
||||||
navigate({ search: urlQuery.toString() });
|
urlQuery.set('service', serviceId);
|
||||||
};
|
navigate({ search: urlQuery.toString() });
|
||||||
|
},
|
||||||
|
[navigate, urlQuery],
|
||||||
|
);
|
||||||
|
|
||||||
const filteredServices = useMemo(() => {
|
const filteredServices = useMemo(() => {
|
||||||
if (filter === 'all_services') return services;
|
if (filter === 'all_services') return services;
|
||||||
@ -32,6 +40,12 @@ function ServicesList({ accountId, filter }: ServicesListProps): JSX.Element {
|
|||||||
});
|
});
|
||||||
}, [services, filter]);
|
}, [services, filter]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (activeService || !services?.length) return;
|
||||||
|
|
||||||
|
handleActiveService(services[0].id);
|
||||||
|
}, [services, activeService, handleActiveService]);
|
||||||
|
|
||||||
if (isLoading) return <Spinner size="large" height="25vh" />;
|
if (isLoading) return <Spinner size="large" height="25vh" />;
|
||||||
if (!services) return <div>No services found</div>;
|
if (!services) return <div>No services found</div>;
|
||||||
|
|
||||||
@ -41,7 +55,7 @@ function ServicesList({ accountId, filter }: ServicesListProps): JSX.Element {
|
|||||||
<ServiceItem
|
<ServiceItem
|
||||||
key={service.id}
|
key={service.id}
|
||||||
service={service}
|
service={service}
|
||||||
onClick={handleServiceClick}
|
onClick={handleActiveService}
|
||||||
isActive={service.id === activeService}
|
isActive={service.id === activeService}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
|
@ -135,18 +135,25 @@
|
|||||||
color: var(--bg-cherry-400);
|
color: var(--bg-cherry-400);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.configure-button {
|
.configure-button {
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 6px;
|
|
||||||
color: var(--bg-vanilla-400);
|
|
||||||
background: var(--bg-ink-300);
|
|
||||||
border: 1px solid var(--bg-slate-400);
|
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
font-weight: 400;
|
|
||||||
line-height: 10px; /* 83.333% */
|
|
||||||
letter-spacing: 0.12px;
|
letter-spacing: 0.12px;
|
||||||
|
font-weight: 500;
|
||||||
|
width: 116px;
|
||||||
|
box-shadow: none;
|
||||||
|
&--default {
|
||||||
|
color: var(--bg-vanilla-400);
|
||||||
|
background: var(--bg-slate-400);
|
||||||
|
border: 1px solid var(--bg-slate-400);
|
||||||
|
}
|
||||||
|
&--primary {
|
||||||
|
background-color: var(--bg-robin-500);
|
||||||
|
color: var(--bg-vanilla-100);
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--Vanilla-100, #fff);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -20,17 +20,17 @@ export enum ServiceFilterType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface ServicesFilterProps {
|
interface ServicesFilterProps {
|
||||||
accountId: string;
|
cloudAccountId: string;
|
||||||
onFilterChange: (value: ServiceFilterType) => void;
|
onFilterChange: (value: ServiceFilterType) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
function ServicesFilter({
|
function ServicesFilter({
|
||||||
accountId,
|
cloudAccountId,
|
||||||
onFilterChange,
|
onFilterChange,
|
||||||
}: ServicesFilterProps): JSX.Element | null {
|
}: ServicesFilterProps): JSX.Element | null {
|
||||||
const { data: services, isLoading } = useQuery(
|
const { data: services, isLoading } = useQuery(
|
||||||
[REACT_QUERY_KEY.AWS_SERVICES, accountId],
|
[REACT_QUERY_KEY.AWS_SERVICES, cloudAccountId],
|
||||||
() => getAwsServices(accountId),
|
() => getAwsServices(cloudAccountId),
|
||||||
);
|
);
|
||||||
|
|
||||||
const { enabledCount, availableCount } = useMemo(() => {
|
const { enabledCount, availableCount } = useMemo(() => {
|
||||||
@ -77,7 +77,7 @@ function ServicesFilter({
|
|||||||
|
|
||||||
function ServicesSection(): JSX.Element {
|
function ServicesSection(): JSX.Element {
|
||||||
const urlQuery = useUrlQuery();
|
const urlQuery = useUrlQuery();
|
||||||
const accountId = urlQuery.get('accountId') || '';
|
const cloudAccountId = urlQuery.get('cloudAccountId') || '';
|
||||||
|
|
||||||
const [activeFilter, setActiveFilter] = useState<
|
const [activeFilter, setActiveFilter] = useState<
|
||||||
'all_services' | 'enabled' | 'available'
|
'all_services' | 'enabled' | 'available'
|
||||||
@ -86,8 +86,11 @@ function ServicesSection(): JSX.Element {
|
|||||||
return (
|
return (
|
||||||
<div className="services-section">
|
<div className="services-section">
|
||||||
<div className="services-section__sidebar">
|
<div className="services-section__sidebar">
|
||||||
<ServicesFilter accountId={accountId} onFilterChange={setActiveFilter} />
|
<ServicesFilter
|
||||||
<ServicesList accountId={accountId} filter={activeFilter} />
|
cloudAccountId={cloudAccountId}
|
||||||
|
onFilterChange={setActiveFilter}
|
||||||
|
/>
|
||||||
|
<ServicesList cloudAccountId={cloudAccountId} filter={activeFilter} />
|
||||||
</div>
|
</div>
|
||||||
<div className="services-section__content">
|
<div className="services-section__content">
|
||||||
<ServiceDetails />
|
<ServiceDetails />
|
||||||
|
17
frontend/src/hooks/integrations/aws/useConnectionParams.ts
Normal file
17
frontend/src/hooks/integrations/aws/useConnectionParams.ts
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import { getConnectionParams } from 'api/integrations/aws';
|
||||||
|
import { AxiosError } from 'axios';
|
||||||
|
import { REACT_QUERY_KEY } from 'constants/reactQueryKeys';
|
||||||
|
import { useQuery, UseQueryOptions, UseQueryResult } from 'react-query';
|
||||||
|
import { ConnectionParams } from 'types/api/integrations/aws';
|
||||||
|
|
||||||
|
export function useConnectionParams({
|
||||||
|
options,
|
||||||
|
}: {
|
||||||
|
options?: UseQueryOptions<ConnectionParams, AxiosError>;
|
||||||
|
}): UseQueryResult<ConnectionParams, AxiosError> {
|
||||||
|
return useQuery<ConnectionParams, AxiosError>(
|
||||||
|
[REACT_QUERY_KEY.AWS_GET_CONNECTION_PARAMS],
|
||||||
|
getConnectionParams,
|
||||||
|
options,
|
||||||
|
);
|
||||||
|
}
|
@ -4,6 +4,7 @@ import {
|
|||||||
ActiveViewEnum,
|
ActiveViewEnum,
|
||||||
ModalStateEnum,
|
ModalStateEnum,
|
||||||
} from 'container/CloudIntegrationPage/HeroSection/types';
|
} from 'container/CloudIntegrationPage/HeroSection/types';
|
||||||
|
import useAxiosError from 'hooks/useAxiosError';
|
||||||
import {
|
import {
|
||||||
Dispatch,
|
Dispatch,
|
||||||
SetStateAction,
|
SetStateAction,
|
||||||
@ -13,11 +14,13 @@ import {
|
|||||||
useState,
|
useState,
|
||||||
} from 'react';
|
} from 'react';
|
||||||
import {
|
import {
|
||||||
|
ConnectionParams,
|
||||||
ConnectionUrlResponse,
|
ConnectionUrlResponse,
|
||||||
GenerateConnectionUrlPayload,
|
GenerateConnectionUrlPayload,
|
||||||
} from 'types/api/integrations/aws';
|
} from 'types/api/integrations/aws';
|
||||||
import { regions } from 'utils/regions';
|
import { regions } from 'utils/regions';
|
||||||
|
|
||||||
|
import { useConnectionParams } from './useConnectionParams';
|
||||||
import { useGenerateConnectionUrl } from './useGenerateConnectionUrl';
|
import { useGenerateConnectionUrl } from './useGenerateConnectionUrl';
|
||||||
|
|
||||||
interface UseIntegrationModalProps {
|
interface UseIntegrationModalProps {
|
||||||
@ -44,6 +47,8 @@ interface UseIntegrationModal {
|
|||||||
accountId?: string;
|
accountId?: string;
|
||||||
selectedDeploymentRegion: string | undefined;
|
selectedDeploymentRegion: string | undefined;
|
||||||
handleRegionChange: (value: string) => void;
|
handleRegionChange: (value: string) => void;
|
||||||
|
connectionParams?: ConnectionParams;
|
||||||
|
isConnectionParamsLoading: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useIntegrationModal({
|
export function useIntegrationModal({
|
||||||
@ -102,6 +107,12 @@ export function useIntegrationModal({
|
|||||||
isLoading: isGeneratingUrl,
|
isLoading: isGeneratingUrl,
|
||||||
} = useGenerateConnectionUrl();
|
} = useGenerateConnectionUrl();
|
||||||
|
|
||||||
|
const handleError = useAxiosError();
|
||||||
|
const {
|
||||||
|
data: connectionParams,
|
||||||
|
isLoading: isConnectionParamsLoading,
|
||||||
|
} = useConnectionParams({ options: { onError: handleError } });
|
||||||
|
|
||||||
const handleGenerateUrl = useCallback(
|
const handleGenerateUrl = useCallback(
|
||||||
(payload: GenerateConnectionUrlPayload): void => {
|
(payload: GenerateConnectionUrlPayload): void => {
|
||||||
generateUrl(payload, {
|
generateUrl(payload, {
|
||||||
@ -126,6 +137,9 @@ export function useIntegrationModal({
|
|||||||
const payload: GenerateConnectionUrlPayload = {
|
const payload: GenerateConnectionUrlPayload = {
|
||||||
agent_config: {
|
agent_config: {
|
||||||
region: values.region,
|
region: values.region,
|
||||||
|
ingestion_url: connectionParams?.ingestion_url || values.ingestion_url,
|
||||||
|
ingestion_key: connectionParams?.ingestion_key || values.ingestion_key,
|
||||||
|
signoz_api_url: connectionParams?.signoz_api_url || values.signoz_api_url,
|
||||||
},
|
},
|
||||||
account_config: {
|
account_config: {
|
||||||
regions: includeAllRegions ? ['all'] : selectedRegions,
|
regions: includeAllRegions ? ['all'] : selectedRegions,
|
||||||
@ -138,7 +152,13 @@ export function useIntegrationModal({
|
|||||||
} finally {
|
} finally {
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
}
|
}
|
||||||
}, [form, includeAllRegions, selectedRegions, handleGenerateUrl]);
|
}, [
|
||||||
|
form,
|
||||||
|
includeAllRegions,
|
||||||
|
selectedRegions,
|
||||||
|
handleGenerateUrl,
|
||||||
|
connectionParams,
|
||||||
|
]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
form,
|
form,
|
||||||
@ -160,5 +180,7 @@ export function useIntegrationModal({
|
|||||||
setModalState,
|
setModalState,
|
||||||
selectedDeploymentRegion,
|
selectedDeploymentRegion,
|
||||||
handleRegionChange,
|
handleRegionChange,
|
||||||
|
connectionParams,
|
||||||
|
isConnectionParamsLoading,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -5,11 +5,11 @@ import { useQuery, UseQueryResult } from 'react-query';
|
|||||||
|
|
||||||
export function useServiceDetails(
|
export function useServiceDetails(
|
||||||
serviceId: string,
|
serviceId: string,
|
||||||
accountId?: string,
|
cloudAccountId?: string,
|
||||||
): UseQueryResult<ServiceData> {
|
): UseQueryResult<ServiceData> {
|
||||||
return useQuery(
|
return useQuery(
|
||||||
[REACT_QUERY_KEY.AWS_SERVICE_DETAILS, serviceId, accountId],
|
[REACT_QUERY_KEY.AWS_SERVICE_DETAILS, serviceId, cloudAccountId],
|
||||||
() => getServiceDetails(serviceId, accountId),
|
() => getServiceDetails(serviceId, cloudAccountId),
|
||||||
{
|
{
|
||||||
enabled: !!serviceId,
|
enabled: !!serviceId,
|
||||||
},
|
},
|
||||||
|
@ -16,7 +16,7 @@ import { handleContactSupport, INTEGRATION_TYPES } from './utils';
|
|||||||
|
|
||||||
export const AWS_INTEGRATION = {
|
export const AWS_INTEGRATION = {
|
||||||
id: INTEGRATION_TYPES.AWS_INTEGRATION,
|
id: INTEGRATION_TYPES.AWS_INTEGRATION,
|
||||||
title: 'AWS Web Services',
|
title: 'Amazon Web Services',
|
||||||
description: 'One-click setup for AWS monitoring with SigNoz',
|
description: 'One-click setup for AWS monitoring with SigNoz',
|
||||||
author: {
|
author: {
|
||||||
name: 'SigNoz',
|
name: 'SigNoz',
|
||||||
|
@ -19,6 +19,8 @@ export const INTEGRATION_TELEMETRY_EVENTS = {
|
|||||||
'Integrations Detail Page: Clicked remove Integration button for integration',
|
'Integrations Detail Page: Clicked remove Integration button for integration',
|
||||||
INTEGRATIONS_DETAIL_CONFIGURE_INSTRUCTION:
|
INTEGRATIONS_DETAIL_CONFIGURE_INSTRUCTION:
|
||||||
'Integrations Detail Page: Navigated to configure an integration',
|
'Integrations Detail Page: Navigated to configure an integration',
|
||||||
|
AWS_INTEGRATION_ACCOUNT_REMOVED:
|
||||||
|
'AWS Integration Detail page: Clicked remove Integration button for integration',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const INTEGRATION_TYPES = {
|
export const INTEGRATION_TYPES = {
|
||||||
|
@ -1,9 +1,15 @@
|
|||||||
import { CloudAccount } from 'container/CloudIntegrationPage/ServicesSection/types';
|
import { CloudAccount } from 'container/CloudIntegrationPage/ServicesSection/types';
|
||||||
|
|
||||||
|
export interface ConnectionParams {
|
||||||
|
ingestion_url?: string;
|
||||||
|
ingestion_key?: string;
|
||||||
|
signoz_api_url?: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface GenerateConnectionUrlPayload {
|
export interface GenerateConnectionUrlPayload {
|
||||||
agent_config: {
|
agent_config: {
|
||||||
region: string;
|
region: string;
|
||||||
};
|
} & ConnectionParams;
|
||||||
account_config: {
|
account_config: {
|
||||||
regions: string[];
|
regions: string[];
|
||||||
};
|
};
|
||||||
|
Loading…
x
Reference in New Issue
Block a user