Compare commits

..

2 Commits

Author SHA1 Message Date
Vishal Sharma
bb10f51cc5 feat(settings): add SigNoz MCP Server setup page (#11025)
Some checks are pending
build-staging / prepare (push) Waiting to run
build-staging / js-build (push) Blocked by required conditions
build-staging / go-build (push) Blocked by required conditions
build-staging / staging (push) Blocked by required conditions
Release Drafter / update_release_draft (push) Waiting to run
* feat(settings): add SigNoz MCP Server setup page

Add a new Settings page at /settings/mcp-server that guides Cloud users
through connecting AI assistants (Cursor, VS Code, Claude Desktop, Claude
Code, Codex) to SigNoz via the Model Context Protocol, and provides a
deep-link into Service Accounts for creating the API key the OAuth flow
needs.

* feat(settings): point MCP Server onboarding tile to in-app settings page

The onboarding-config-with-links entry for SigNoz MCP Server previously
linked out to the docs page. Now that we ship an in-product setup page
at /settings/mcp-server, route Cloud users there directly via the
existing internalRedirect pattern. Self-hosted users still see the
in-page fallback card with a docs link.

* fix(settings): fire MCP Server page-viewed event reliably on mount

Previously gated the PAGE_VIEWED analytics event on
isGlobalConfigFetched to avoid a double-fire when the async
ingestion_url resolved. Side effect: if /global/config was slow or
errored out, the event never fired. Fire once on mount instead with
hostname-derived region metadata, which is synchronous and reliable.

* feat(mcp-page): ui refactor and redesign

* feat(mcp-page): global config and page access changes

* feat(mcp-page): refactor and added fallback page

* feat(mcp-page): added test cases

* feat(mcp-page): formatting lint

* feat(mcp-page): code refactor

* feat(mcp-page): cleanup and migrated global config api to open api spec

* feat(mcp-page): removed translation json and add endpoint url to copy

* feat(mcp-page): added mcp server to menu always for cloud and enterprise

---------

Co-authored-by: SagarRajput-7 <sagar@signoz.io>
Co-authored-by: SagarRajput-7 <162284829+SagarRajput-7@users.noreply.github.com>
2026-04-24 21:25:00 +00:00
Vinicius Lourenço
cd16081a1e chore(oxlint): remove unicorn & other rules that breaks the code on --fix (#11097)
Some checks failed
build-staging / prepare (push) Has been cancelled
build-staging / js-build (push) Has been cancelled
build-staging / go-build (push) Has been cancelled
build-staging / staging (push) Has been cancelled
Release Drafter / update_release_draft (push) Has been cancelled
2026-04-24 17:42:49 +00:00
51 changed files with 1528 additions and 4284 deletions

View File

@@ -167,7 +167,7 @@ func runServer(ctx context.Context, config signoz.Config, logger *slog.Logger) e
if err != nil {
return nil, err
}
azureCloudProviderModule := implcloudprovider.NewAzureCloudProvider(defStore)
azureCloudProviderModule := implcloudprovider.NewAzureCloudProvider()
cloudProvidersMap := map[cloudintegrationtypes.CloudProviderType]cloudintegration.CloudProviderModule{
cloudintegrationtypes.CloudProviderTypeAWS: awsCloudProviderModule,
cloudintegrationtypes.CloudProviderTypeAzure: azureCloudProviderModule,

View File

@@ -18,7 +18,6 @@ func NewAWSCloudProvider(defStore cloudintegrationtypes.ServiceDefinitionStore)
return &awscloudprovider{serviceDefinitions: defStore}, nil
}
// TODO: move URL construction logic to cloudintegrationtypes and add unit tests for it.
func (provider *awscloudprovider) GetConnectionArtifact(ctx context.Context, account *cloudintegrationtypes.Account, req *cloudintegrationtypes.GetConnectionArtifactRequest) (*cloudintegrationtypes.ConnectionArtifact, error) {
baseURL := fmt.Sprintf(cloudintegrationtypes.CloudFormationQuickCreateBaseURL.StringValue(), req.Config.AWS.DeploymentRegion)
u, _ := url.Parse(baseURL)

View File

@@ -2,48 +2,27 @@ package implcloudprovider
import (
"context"
"sort"
"github.com/SigNoz/signoz/pkg/modules/cloudintegration"
"github.com/SigNoz/signoz/pkg/types/cloudintegrationtypes"
)
type azurecloudprovider struct {
serviceDefinitions cloudintegrationtypes.ServiceDefinitionStore
}
type azurecloudprovider struct{}
func NewAzureCloudProvider(defStore cloudintegrationtypes.ServiceDefinitionStore) cloudintegration.CloudProviderModule {
return &azurecloudprovider{
serviceDefinitions: defStore,
}
func NewAzureCloudProvider() cloudintegration.CloudProviderModule {
return &azurecloudprovider{}
}
func (provider *azurecloudprovider) GetConnectionArtifact(ctx context.Context, account *cloudintegrationtypes.Account, req *cloudintegrationtypes.GetConnectionArtifactRequest) (*cloudintegrationtypes.ConnectionArtifact, error) {
connectionArtifact, err := cloudintegrationtypes.NewAzureConnectionArtifact(account.ID, req.Config.AgentVersion, req.Credentials, req.Config.Azure)
if err != nil {
return nil, err
}
return &cloudintegrationtypes.ConnectionArtifact{
Azure: connectionArtifact,
}, nil
panic("implement me")
}
func (provider *azurecloudprovider) ListServiceDefinitions(ctx context.Context) ([]*cloudintegrationtypes.ServiceDefinition, error) {
return provider.serviceDefinitions.List(ctx, cloudintegrationtypes.CloudProviderTypeAzure)
panic("implement me")
}
func (provider *azurecloudprovider) GetServiceDefinition(ctx context.Context, serviceID cloudintegrationtypes.ServiceID) (*cloudintegrationtypes.ServiceDefinition, error) {
serviceDef, err := provider.serviceDefinitions.Get(ctx, cloudintegrationtypes.CloudProviderTypeAzure, serviceID)
if err != nil {
return nil, err
}
// override cloud integration dashboard id.
for index, dashboard := range serviceDef.Assets.Dashboards {
serviceDef.Assets.Dashboards[index].ID = cloudintegrationtypes.GetCloudIntegrationDashboardID(cloudintegrationtypes.CloudProviderTypeAzure, serviceID.StringValue(), dashboard.ID)
}
return serviceDef, nil
panic("implement me")
}
func (provider *azurecloudprovider) BuildIntegrationConfig(
@@ -51,56 +30,5 @@ func (provider *azurecloudprovider) BuildIntegrationConfig(
account *cloudintegrationtypes.Account,
services []*cloudintegrationtypes.StorableCloudIntegrationService,
) (*cloudintegrationtypes.ProviderIntegrationConfig, error) {
sort.Slice(services, func(i, j int) bool {
return services[i].Type.StringValue() < services[j].Type.StringValue()
})
var strategies []*cloudintegrationtypes.AzureTelemetryCollectionStrategy
for _, storedSvc := range services {
svcCfg, err := cloudintegrationtypes.NewServiceConfigFromJSON(cloudintegrationtypes.CloudProviderTypeAzure, storedSvc.Config)
if err != nil {
return nil, err
}
svcDef, err := provider.GetServiceDefinition(ctx, storedSvc.Type)
if err != nil {
return nil, err
}
strategy := svcDef.TelemetryCollectionStrategy.Azure
if strategy == nil {
continue
}
logsEnabled := svcCfg.IsLogsEnabled(cloudintegrationtypes.CloudProviderTypeAzure)
metricsEnabled := svcCfg.IsMetricsEnabled(cloudintegrationtypes.CloudProviderTypeAzure)
if !logsEnabled && !metricsEnabled {
continue
}
entry := &cloudintegrationtypes.AzureTelemetryCollectionStrategy{
ResourceProvider: strategy.ResourceProvider,
ResourceType: strategy.ResourceType,
}
if metricsEnabled && strategy.Metrics != nil {
entry.Metrics = strategy.Metrics
}
if logsEnabled && strategy.Logs != nil {
entry.Logs = strategy.Logs
}
strategies = append(strategies, entry)
}
return &cloudintegrationtypes.ProviderIntegrationConfig{
Azure: cloudintegrationtypes.NewAzureIntegrationConfig(
account.Config.Azure.DeploymentRegion,
account.Config.Azure.ResourceGroups,
strategies,
),
}, nil
panic("implement me")
}

View File

@@ -429,14 +429,10 @@ func (module *module) Collect(ctx context.Context, orgID valuer.UUID) (map[strin
stats["cloudintegration.aws.connectedaccounts.count"] = awsAccountsCount
}
// get connected accounts for Azure
azureAccountsCount, err := module.store.CountConnectedAccounts(ctx, orgID, cloudintegrationtypes.CloudProviderTypeAzure)
if err == nil {
stats["cloudintegration.azure.connectedaccounts.count"] = azureAccountsCount
}
// NOTE: not adding stats for services for now.
// TODO: add more cloud providers when supported
return stats, nil
}

View File

@@ -9,7 +9,6 @@
"react",
"react-perf",
"typescript",
"unicorn",
"jsx-a11y",
"import",
"jest",
@@ -206,6 +205,8 @@
"@typescript-eslint/explicit-function-return-type": "error",
// Requires explicit return types on functions
"@typescript-eslint/no-var-requires": "error",
"@typescript-eslint/no-useless-default-assignment": "off", // provide unsafe fixes in our codebase due to bad typing
"@typescript-eslint/no-duplicate-type-constituents": "off", // provide fixes that breaks some assumptions, eg: type A = L, B = L, C = A | B (removes B)
// Disallows require() in TypeScript (use import instead)
// Disabled - using TypeScript instead
"react/jsx-props-no-spreading": "off",
@@ -338,82 +339,6 @@
"react/no-array-index-key": "warn",
// TODO: Changed to warn during oxlint migration, should be changed to error,
"unicorn/error-message": "warn",
"unicorn/escape-case": "warn",
"unicorn/new-for-builtins": "warn",
"unicorn/no-abusive-eslint-disable": "warn",
"unicorn/no-console-spaces": "warn",
"unicorn/no-instanceof-array": "warn",
"unicorn/no-invalid-remove-event-listener": "warn",
"unicorn/no-new-array": "warn",
"unicorn/no-new-buffer": "warn",
"unicorn/no-thenable": "warn",
"unicorn/no-unreadable-array-destructuring": "warn",
"unicorn/no-useless-fallback-in-spread": "warn",
"unicorn/no-useless-length-check": "warn",
"unicorn/no-useless-promise-resolve-reject": "warn",
"unicorn/no-useless-spread": "warn",
"unicorn/no-zero-fractions": "warn",
"unicorn/number-literal-case": "warn",
"unicorn/prefer-array-find": "warn",
"unicorn/prefer-array-flat": "warn",
"unicorn/prefer-array-flat-map": "warn",
"unicorn/prefer-array-index-of": "warn",
"unicorn/prefer-array-some": "warn",
"unicorn/prefer-at": "warn",
"unicorn/prefer-code-point": "warn",
"unicorn/prefer-date-now": "warn",
"unicorn/prefer-default-parameters": "warn",
"unicorn/prefer-includes": "warn",
"unicorn/prefer-modern-math-apis": "warn",
"unicorn/prefer-native-coercion-functions": "warn",
"unicorn/prefer-node-protocol": "off",
"unicorn/prefer-number-properties": "warn",
"unicorn/prefer-optional-catch-binding": "warn",
"unicorn/prefer-regexp-test": "warn",
"unicorn/prefer-set-has": "warn",
"unicorn/prefer-string-replace-all": "warn",
"unicorn/prefer-string-slice": "warn",
"unicorn/prefer-string-starts-ends-with": "warn",
"unicorn/prefer-string-trim-start-end": "warn",
"unicorn/prefer-type-error": "warn",
"unicorn/require-array-join-separator": "warn",
"unicorn/require-number-to-fixed-digits-argument": "warn",
"unicorn/throw-new-error": "warn",
"unicorn/consistent-function-scoping": "warn",
"unicorn/explicit-length-check": "warn",
"unicorn/filename-case": [
"warn",
{
"case": "kebabCase"
}
],
"unicorn/no-array-for-each": "warn",
"unicorn/no-lonely-if": "warn",
"unicorn/no-negated-condition": "warn",
"unicorn/no-null": "warn",
"unicorn/no-object-as-default-parameter": "warn",
"unicorn/no-static-only-class": "warn",
"unicorn/no-this-assignment": "warn",
"unicorn/no-unreadable-iife": "warn",
"unicorn/no-useless-switch-case": "warn",
"unicorn/no-useless-undefined": "warn",
"unicorn/prefer-add-event-listener": "warn",
"unicorn/prefer-dom-node-append": "warn",
"unicorn/prefer-dom-node-dataset": "warn",
"unicorn/prefer-dom-node-remove": "warn",
"unicorn/prefer-dom-node-text-content": "warn",
"unicorn/prefer-keyboard-event-key": "warn",
"unicorn/prefer-math-trunc": "warn",
"unicorn/prefer-modern-dom-apis": "warn",
"unicorn/prefer-negative-index": "warn",
"unicorn/prefer-prototype-methods": "warn",
"unicorn/prefer-query-selector": "warn",
"unicorn/prefer-reflect-apply": "warn",
"unicorn/prefer-set-size": "warn",
"unicorn/prefer-spread": "warn",
"unicorn/prefer-ternary": "warn",
"unicorn/require-post-message-target-origin": "warn",
"oxc/bad-array-method-on-arguments": "error",
"oxc/bad-bitwise-operator": "error",
"oxc/bad-comparison-sequence": "error",

View File

@@ -214,9 +214,9 @@
"msw": "1.3.2",
"npm-run-all": "latest",
"orval": "7.18.0",
"oxfmt": "0.41.0",
"oxlint": "1.59.0",
"oxlint-tsgolint": "0.20.0",
"oxfmt": "0.46.0",
"oxlint": "1.61.0",
"oxlint-tsgolint": "0.21.1",
"portfinder-sync": "^0.0.2",
"postcss": "8.5.6",
"postcss-scss": "4.0.9",

View File

@@ -16,5 +16,6 @@
"roles": "Roles",
"role_details": "Role Details",
"members": "Members",
"service_accounts": "Service Accounts"
"service_accounts": "Service Accounts",
"mcp_server": "MCP Server"
}

View File

@@ -53,5 +53,6 @@
"METER": "SigNoz | Meter",
"ROLES_SETTINGS": "SigNoz | Roles",
"MEMBERS_SETTINGS": "SigNoz | Members",
"SERVICE_ACCOUNTS_SETTINGS": "SigNoz | Service Accounts"
"SERVICE_ACCOUNTS_SETTINGS": "SigNoz | Service Accounts",
"MCP_SERVER": "SigNoz | MCP Server"
}

View File

@@ -16,5 +16,6 @@
"roles": "Roles",
"role_details": "Role Details",
"members": "Members",
"service_accounts": "Service Accounts"
"service_accounts": "Service Accounts",
"mcp_server": "MCP Server"
}

View File

@@ -76,5 +76,6 @@
"METER": "SigNoz | Meter",
"ROLES_SETTINGS": "SigNoz | Roles",
"MEMBERS_SETTINGS": "SigNoz | Members",
"SERVICE_ACCOUNTS_SETTINGS": "SigNoz | Service Accounts"
"SERVICE_ACCOUNTS_SETTINGS": "SigNoz | Service Accounts",
"MCP_SERVER": "SigNoz | MCP Server"
}

View File

@@ -1,25 +0,0 @@
import axios from 'api';
import { ErrorResponseHandlerV2 } from 'api/ErrorResponseHandlerV2';
import { AxiosError } from 'axios';
import { ErrorV2Resp, SuccessResponseV2 } from 'types/api';
import {
GlobalConfigData,
GlobalConfigDataProps,
} from 'types/api/globalConfig/types';
const getGlobalConfig = async (): Promise<
SuccessResponseV2<GlobalConfigData>
> => {
try {
const response = await axios.get<GlobalConfigDataProps>(`/global/config`);
return {
httpStatusCode: response.status,
data: response.data.data,
};
} catch (error) {
ErrorResponseHandlerV2(error as AxiosError<ErrorV2Resp>);
}
};
export default getGlobalConfig;

View File

@@ -32,6 +32,7 @@ function CreateServiceAccountModal(): JSX.Element {
SA_QUERY_PARAMS.CREATE_SA,
parseAsBoolean.withDefault(false),
);
const [, setSelectedAccountId] = useQueryState(SA_QUERY_PARAMS.ACCOUNT);
const { showErrorModal, isErrorModalVisible } = useErrorModal();
@@ -50,11 +51,12 @@ function CreateServiceAccountModal(): JSX.Element {
const { mutate: createServiceAccount, isLoading: isSubmitting } =
useCreateServiceAccount({
mutation: {
onSuccess: async () => {
onSuccess: async (response) => {
toast.success('Service account created successfully');
reset();
await setIsOpen(null);
await invalidateListServiceAccounts(queryClient);
await setSelectedAccountId(response.data.id);
},
onError: (err) => {
const errMessage = convertToApiError(
@@ -67,7 +69,7 @@ function CreateServiceAccountModal(): JSX.Element {
function handleClose(): void {
reset();
setIsOpen(null);
void setIsOpen(null);
}
function handleCreate(values: FormValues): void {

View File

@@ -12,6 +12,12 @@
flex-shrink: 0;
}
&__layout {
display: flex;
flex-direction: column;
height: 100%;
}
&__tab-group {
[data-slot='toggle-group'] {
border-radius: 2px;

View File

@@ -87,6 +87,7 @@ const ROUTES = {
HOME_PAGE: '/',
PUBLIC_DASHBOARD: '/public/dashboard/:dashboardId',
SERVICE_ACCOUNTS_SETTINGS: '/settings/service-accounts',
MCP_SERVER: '/settings/mcp-server',
} as const;
export default ROUTES;

View File

@@ -48,7 +48,8 @@ import { initialQueryMeterWithType } from 'constants/queryBuilder';
import ROUTES from 'constants/routes';
import { INITIAL_ALERT_THRESHOLD_STATE } from 'container/CreateAlertV2/context/constants';
import dayjs from 'dayjs';
import { useGetGlobalConfig } from 'hooks/globalConfig/useGetGlobalConfig';
import { convertToApiError } from 'api/ErrorResponseHandlerForGeneratedAPIs';
import { useGetGlobalConfig } from 'api/generated/services/global';
import useDebouncedFn from 'hooks/useDebouncedFunction';
import { useNotifications } from 'hooks/useNotifications';
import { cloneDeep, isNil, isUndefined } from 'lodash-es';
@@ -358,6 +359,8 @@ function MultiIngestionSettings(): JSX.Element {
error: globalConfigError,
} = useGetGlobalConfig();
const globalConfigApiError = convertToApiError(globalConfigError);
const { mutate: createIngestionKey, isLoading: isLoadingCreateAPIKey } =
useCreateIngestionKey<AxiosError<RenderErrorResponseDTO>>();
@@ -966,7 +969,7 @@ function MultiIngestionSettings(): JSX.Element {
<div className="ingestion-key-value">
<Typography.Text>
{APIKey?.value?.substring(0, 2)}********
{APIKey?.value?.slice(0, 2)}********
{APIKey?.value
?.substring(APIKey?.value?.length ? APIKey.value.length - 2 : 0)
?.trim()}
@@ -1604,11 +1607,11 @@ function MultiIngestionSettings(): JSX.Element {
title={
<div className="ingestion-url-error-content">
<Typography.Text className="ingestion-url-error-code">
{globalConfigError?.getErrorCode()}
{globalConfigApiError?.getErrorCode()}
</Typography.Text>
<Typography.Text className="ingestion-url-error-message">
{globalConfigError?.getErrorMessage()}
{globalConfigApiError?.getErrorMessage()}
</Typography.Text>
</div>
}

View File

@@ -0,0 +1,83 @@
.mcp-auth-card {
display: flex;
flex-direction: column;
gap: var(--spacing-8);
padding: var(--padding-4) var(--padding-5);
border: 1px solid var(--l2-border);
border-radius: 8px;
background: var(--l2-background);
&__title {
display: flex;
align-items: center;
gap: var(--spacing-5);
font-size: var(--label-medium-500-font-size);
font-weight: var(--label-medium-500-font-weight);
color: var(--l1-foreground);
margin: 0;
}
&__description {
font-size: var(--font-size-xs);
color: var(--foreground);
line-height: var(--line-height-18);
margin: 0;
}
&__field {
display: flex;
flex-direction: column;
gap: var(--spacing-3);
}
&__field-label {
font-size: var(--font-size-xs);
font-weight: 500;
color: var(--foreground);
letter-spacing: 0.01em;
}
&__endpoint-value {
display: inline-flex;
align-items: center;
gap: var(--spacing-2);
padding: var(--padding-2) var(--padding-3);
border: 1px solid var(--l3-border);
border-radius: 6px;
background: var(--l3-background);
font-family: var(--font-family-sf-mono, monospace);
font-size: var(--paragraph-base-400-font-size);
color: var(--l1-foreground);
width: 100%;
justify-content: space-between;
}
&__cta-row {
display: flex;
align-items: center;
gap: var(--spacing-10);
flex-wrap: wrap;
}
&__helper-text {
font-size: var(--paragraph-small-400-font-size);
color: var(--foreground);
line-height: var(--paragraph-small-400-line-height);
}
&__info-banner {
display: flex;
align-items: flex-start;
gap: var(--spacing-2);
padding: var(--padding-2) var(--padding-3);
border-left: 3px solid var(--bg-robin-400);
background: var(--l3-background);
border-radius: 4px;
svg {
flex-shrink: 0;
color: var(--bg-robin-400);
margin-top: 2px;
}
}
}

View File

@@ -0,0 +1,70 @@
import { render, screen, userEvent } from 'tests/test-utils';
import AuthCard from './AuthCard';
const mockOnCopyInstanceUrl = jest.fn();
const mockOnCreateServiceAccount = jest.fn();
const defaultProps = {
instanceUrl: 'http://localhost',
onCopyInstanceUrl: mockOnCopyInstanceUrl,
onCreateServiceAccount: mockOnCreateServiceAccount,
};
describe('AuthCard', () => {
afterEach(() => {
jest.clearAllMocks();
});
it('renders the instance URL', () => {
render(<AuthCard {...defaultProps} isAdmin />);
expect(screen.getByTestId('mcp-instance-url')).toHaveTextContent(
'http://localhost',
);
});
it('shows Create Service Account button for admin', () => {
render(<AuthCard {...defaultProps} isAdmin />);
expect(screen.getByText('Create service account')).toBeInTheDocument();
expect(
screen.queryByText(
'Only admins can create API keys. Ask your workspace admin for a key with read access, then paste it into the API Key field.',
),
).not.toBeInTheDocument();
});
it('shows info banner for non-admin', () => {
render(<AuthCard {...defaultProps} isAdmin={false} />);
expect(
screen.getByText(
'Only admins can create API keys. Ask your workspace admin for a key with read access, then paste it into the API Key field.',
),
).toBeInTheDocument();
expect(screen.queryByText('Create service account')).not.toBeInTheDocument();
});
it('calls onCopyInstanceUrl when copy button is clicked', async () => {
const user = userEvent.setup({ pointerEventsCheck: 0 });
render(<AuthCard {...defaultProps} isAdmin />);
await user.click(
screen.getByRole('button', { name: 'Copy SigNoz instance URL' }),
);
expect(mockOnCopyInstanceUrl).toHaveBeenCalledTimes(1);
});
it('calls onCreateServiceAccount when admin clicks the CTA', async () => {
const user = userEvent.setup({ pointerEventsCheck: 0 });
render(<AuthCard {...defaultProps} isAdmin />);
await user.click(screen.getByText('Create service account'));
expect(mockOnCreateServiceAccount).toHaveBeenCalledTimes(1);
});
});

View File

@@ -0,0 +1,75 @@
import { Badge, Button } from '@signozhq/ui';
import { Info, KeyRound } from '@signozhq/icons';
import CopyIconButton from '../CopyIconButton';
import './AuthCard.styles.scss';
interface AuthCardProps {
isAdmin: boolean;
instanceUrl: string;
onCopyInstanceUrl: () => void;
onCreateServiceAccount: () => void;
}
function AuthCard({
isAdmin,
instanceUrl,
onCopyInstanceUrl,
onCreateServiceAccount,
}: AuthCardProps): JSX.Element {
return (
<section className="mcp-auth-card">
<h3 className="mcp-auth-card__title">
<Badge color="secondary" variant="default">
2
</Badge>
Authenticate from your client
</h3>
<p className="mcp-auth-card__description">
On first connect, your client opens a SigNoz authorization page asking for
two values:
</p>
<div className="mcp-auth-card__field">
<span className="mcp-auth-card__field-label">SigNoz Instance URL</span>
<div className="mcp-auth-card__endpoint-value">
<span data-testid="mcp-instance-url">{instanceUrl}</span>
<CopyIconButton
ariaLabel="Copy SigNoz instance URL"
onCopy={onCopyInstanceUrl}
/>
</div>
</div>
<div className="mcp-auth-card__field">
<span className="mcp-auth-card__field-label">API Key</span>
{isAdmin ? (
<div className="mcp-auth-card__cta-row">
<Button
variant="solid"
color="primary"
prefix={<KeyRound size={14} />}
onClick={onCreateServiceAccount}
>
Create service account
</Button>
<span className="mcp-auth-card__helper-text">
Create a service account, then add a new key inside it - paste that key
into the API Key field.
</span>
</div>
) : (
<div className="mcp-auth-card__info-banner">
<Info size={14} />
<span className="mcp-auth-card__helper-text">
Only admins can create API keys. Ask your workspace admin for a key with
read access, then paste it into the API Key field.
</span>
</div>
)}
</div>
</section>
);
}
export default AuthCard;

View File

@@ -0,0 +1,66 @@
.mcp-client-tabs-root {
button[data-variant='primary'] {
border: none;
background: transparent;
box-shadow: none;
}
// Remove default tab content padding/margin — the card provides spacing.
--tab-content-padding: 0;
--tab-content-margin: var(--spacing-4) 0 0;
}
.mcp-client-tabs {
&__snippet-wrapper {
display: flex;
flex-direction: column;
gap: var(--spacing-6);
margin-top: var(--spacing-2);
.learn-more {
width: fit-content;
font-size: var(--font-size-xs);
}
}
&__install-row {
display: flex;
align-items: center;
gap: var(--spacing-10);
flex-wrap: wrap;
}
&__helper-text {
font-size: var(--paragraph-small-400-font-size);
color: var(--foreground);
line-height: var(--paragraph-small-400-line-height);
}
&__endpoint-value {
display: inline-flex;
align-items: center;
gap: var(--spacing-2);
padding: var(--padding-2) var(--padding-3);
border: 1px solid var(--l3-border);
border-radius: 6px;
background: var(--l3-background);
font-family: var(--font-family-sf-mono, monospace);
font-size: var(--paragraph-base-400-font-size);
color: var(--l1-foreground);
width: 100%;
justify-content: space-between;
}
&__snippet-pre {
margin: 0;
white-space: pre-wrap;
word-break: break-all;
}
&__instructions {
font-size: var(--font-size-xs);
color: var(--foreground);
line-height: var(--line-height-20);
margin: 0;
}
}

View File

@@ -0,0 +1,108 @@
import { render, screen, userEvent } from 'tests/test-utils';
import ClientTabs from './ClientTabs';
import { MCP_CLIENTS } from '../clients';
jest.mock('utils/navigation', () => ({
openInNewTab: jest.fn(),
}));
const mockOnTabChange = jest.fn();
const mockOnCopySnippet = jest.fn();
const mockOnInstallClick = jest.fn();
const mockOnDocsLinkClick = jest.fn();
const MCP_ENDPOINT = 'https://mcp.us.signoz.cloud/mcp';
const defaultProps = {
endpoint: MCP_ENDPOINT,
activeTab: MCP_CLIENTS[0].key,
onTabChange: mockOnTabChange,
onCopySnippet: mockOnCopySnippet,
onInstallClick: mockOnInstallClick,
onDocsLinkClick: mockOnDocsLinkClick,
};
describe('ClientTabs', () => {
afterEach(() => {
jest.clearAllMocks();
});
it('renders a tab for each MCP client', () => {
render(<ClientTabs {...defaultProps} />);
MCP_CLIENTS.forEach((client) => {
expect(screen.getByText(client.label)).toBeInTheDocument();
});
});
it('renders the snippet for clients that provide one (Cursor)', () => {
render(<ClientTabs {...defaultProps} activeTab="cursor" />);
// The snippet is rendered inside a <pre> element; check its content
const snippetPre = document.querySelector('.mcp-client-tabs__snippet-pre');
expect(snippetPre).toBeInTheDocument();
expect(snippetPre?.textContent).toContain(MCP_ENDPOINT);
expect(snippetPre?.textContent).toContain('mcpServers');
});
it('renders endpoint block and instructions text for clients without a snippet (Claude Desktop)', () => {
render(<ClientTabs {...defaultProps} activeTab="claude-desktop" />);
const snippetPre = document.querySelector('.mcp-client-tabs__snippet-pre');
expect(snippetPre?.textContent).toBe(MCP_ENDPOINT);
expect(
screen.getByText(
'Open Claude Desktop, go to Settings → Connectors → Add custom connector, and paste the endpoint URL above. Claude Desktop does not read remote MCP servers from claude_desktop_config.json - the connector UI is the only supported path.',
),
).toBeInTheDocument();
});
it('shows enabled install button when endpoint is set (Cursor)', () => {
render(<ClientTabs {...defaultProps} activeTab="cursor" />);
const installBtn = screen.getByRole('button', {
name: 'Add to Cursor',
});
expect(installBtn).toBeEnabled();
});
it('shows disabled install button when endpoint is missing (Cursor)', () => {
render(<ClientTabs {...defaultProps} endpoint="" activeTab="cursor" />);
const installBtn = screen.getByRole('button', {
name: 'Add to Cursor',
});
expect(installBtn).toBeDisabled();
});
it('calls onCopySnippet with client key and snippet on copy', async () => {
const user = userEvent.setup({ pointerEventsCheck: 0 });
const cursorClient = MCP_CLIENTS.find((c) => c.key === 'cursor')!;
render(<ClientTabs {...defaultProps} activeTab="cursor" />);
await user.click(
screen.getByRole('button', {
name: `Copy ${cursorClient.label} config`,
}),
);
expect(mockOnCopySnippet).toHaveBeenCalledWith(
'cursor',
cursorClient.snippet!(MCP_ENDPOINT),
);
});
it('copy button is disabled when no endpoint', () => {
render(<ClientTabs {...defaultProps} endpoint="" activeTab="cursor" />);
const cursorClient = MCP_CLIENTS.find((c) => c.key === 'cursor')!;
const copyBtn = screen.getByRole('button', {
name: `Copy ${cursorClient.label} config`,
});
expect(copyBtn).toBeDisabled();
});
});

View File

@@ -0,0 +1,126 @@
import { useMemo } from 'react';
import { Button, Tabs } from '@signozhq/ui';
import LearnMore from 'components/LearnMore/LearnMore';
import { Download } from '@signozhq/icons';
import { openInNewTab } from 'utils/navigation';
import CopyIconButton from '../CopyIconButton';
import { docsUrl, MCP_CLIENTS, McpClient } from '../clients';
import './ClientTabs.styles.scss';
const ENDPOINT_PLACEHOLDER = 'https://mcp.<region>.signoz.cloud/mcp';
interface ClientTabsProps {
endpoint: string;
activeTab: string;
onTabChange: (key: string) => void;
onCopySnippet: (clientKey: string, snippet: string) => void;
onInstallClick: (clientKey: string) => void;
onDocsLinkClick: (target: string) => void;
}
function ClientTabs({
endpoint,
activeTab,
onTabChange,
onCopySnippet,
onInstallClick,
onDocsLinkClick,
}: ClientTabsProps): JSX.Element {
const items = useMemo(
() =>
MCP_CLIENTS.map((client: McpClient) => {
const snippet = client.snippet
? client.snippet(endpoint || ENDPOINT_PLACEHOLDER)
: null;
const installHref =
client.installUrl && endpoint ? client.installUrl(endpoint) : null;
const installLabel = client.installLabel ?? `Add to ${client.label}`;
return {
key: client.key,
label: client.label,
children: (
<div className="mcp-client-tabs__snippet-wrapper">
{snippet !== null ? (
<div className="mcp-client-tabs__endpoint-value mcp-client-tabs__snippet">
<pre className="mcp-client-tabs__snippet-pre">{snippet}</pre>
<CopyIconButton
ariaLabel={`Copy ${client.label} config`}
disabled={!endpoint}
onCopy={(): void => onCopySnippet(client.key, snippet)}
/>
</div>
) : (
<>
<div className="mcp-client-tabs__endpoint-value mcp-client-tabs__snippet">
<pre className="mcp-client-tabs__snippet-pre">
{endpoint || ENDPOINT_PLACEHOLDER}
</pre>
<CopyIconButton
ariaLabel="Copy MCP endpoint"
disabled={!endpoint}
onCopy={(): void => onCopySnippet(client.key, endpoint)}
/>
</div>
<p className="mcp-client-tabs__instructions">
{client.instructions ?? ''}
</p>
</>
)}
{client.installUrl && (
<div className="mcp-client-tabs__install-row">
{installHref ? (
<Button
variant="solid"
color="primary"
prefix={<Download size={14} />}
onClick={(): void => {
onInstallClick(client.key);
openInNewTab(installHref);
}}
>
{installLabel}
</Button>
) : (
<Button
variant="solid"
color="primary"
disabled
prefix={<Download size={14} />}
>
{installLabel}
</Button>
)}
<span className="mcp-client-tabs__helper-text">
Or copy the config below for manual setup.
</span>
</div>
)}
<LearnMore
text={`${client.label} setup docs`}
url={docsUrl(client.docsPath)}
onClick={(): void => onDocsLinkClick(`client-${client.key}`)}
/>
</div>
),
};
}),
// eslint-disable-next-line react-hooks/exhaustive-deps
[endpoint, onCopySnippet, onInstallClick, onDocsLinkClick],
);
return (
<Tabs
className="mcp-client-tabs-root"
value={activeTab}
onChange={onTabChange}
items={items}
/>
);
}
export default ClientTabs;

View File

@@ -0,0 +1,5 @@
.mcp-copy-btn {
&:hover {
background-color: var(--l3-background-hover) !important;
}
}

View File

@@ -0,0 +1,40 @@
import { Button, Tooltip, TooltipProvider } from '@signozhq/ui';
import { Copy } from '@signozhq/icons';
import './CopyIconButton.styles.scss';
interface CopyIconButtonProps {
ariaLabel: string;
onCopy: () => void;
disabled?: boolean;
}
function CopyIconButton({
ariaLabel,
onCopy,
disabled = false,
}: CopyIconButtonProps): JSX.Element {
const tooltipTitle = disabled
? 'Enter your Cloud region first'
: 'Copy to clipboard';
return (
<TooltipProvider>
<Tooltip title={tooltipTitle}>
<span>
<Button
color="secondary"
variant="ghost"
size="icon"
aria-label={ariaLabel}
disabled={disabled}
className="mcp-copy-btn"
prefix={<Copy size={14} />}
onClick={onCopy}
/>
</span>
</Tooltip>
</TooltipProvider>
);
}
export default CopyIconButton;

View File

@@ -0,0 +1,60 @@
.mcp-settings {
display: flex;
flex-direction: column;
gap: var(--spacing-12);
padding: var(--padding-8) var(--padding-2) var(--padding-6) var(--padding-4);
max-width: 880px;
margin: 0 auto;
width: 100%;
&__header {
display: flex;
flex-direction: column;
gap: var(--spacing-2);
}
&__header-title {
font-size: var(--label-large-500-font-size);
font-weight: var(--label-large-500-font-weight);
color: var(--l1-foreground);
letter-spacing: -0.09px;
line-height: var(--line-height-normal);
margin: 0;
}
&__header-subtitle {
font-size: var(--paragraph-base-400-font-size);
font-weight: var(--paragraph-base-400-font-weight);
color: var(--foreground);
letter-spacing: -0.07px;
line-height: var(--paragraph-base-400-line-height);
margin: 0;
}
}
.mcp-settings__card {
display: flex;
flex-direction: column;
gap: var(--spacing-8);
padding: var(--padding-4) var(--padding-5);
border: 1px solid var(--l2-border);
border-radius: 8px;
background: var(--l2-background);
&-title {
display: flex;
align-items: center;
gap: var(--spacing-5);
font-size: var(--label-medium-500-font-size);
font-weight: var(--label-medium-500-font-weight);
color: var(--l1-foreground);
margin: 0;
}
&-description {
font-size: var(--font-size-xs);
color: var(--foreground);
line-height: var(--line-height-18);
margin: 0;
}
}

View File

@@ -0,0 +1,161 @@
import { render, screen, userEvent } from 'tests/test-utils';
import MCPServerSettings from './MCPServerSettings';
const mockLogEvent = jest.fn();
const mockCopyToClipboard = jest.fn();
const mockHistoryPush = jest.fn();
const mockUseGetGlobalConfig = jest.fn();
const mockToastSuccess = jest.fn();
const mockToastWarning = jest.fn();
jest.mock('api/common/logEvent', () => ({
__esModule: true,
default: (...args: unknown[]): unknown => mockLogEvent(...args),
}));
jest.mock('api/generated/services/global', () => ({
useGetGlobalConfig: (...args: unknown[]): unknown =>
mockUseGetGlobalConfig(...args),
}));
jest.mock('react-use', () => ({
__esModule: true,
useCopyToClipboard: (): [unknown, jest.Mock] => [null, mockCopyToClipboard],
}));
jest.mock('@signozhq/ui', () => ({
...jest.requireActual('@signozhq/ui'),
toast: {
success: (...args: unknown[]): unknown => mockToastSuccess(...args),
warning: (...args: unknown[]): unknown => mockToastWarning(...args),
},
}));
jest.mock('lib/history', () => ({
__esModule: true,
default: {
push: (...args: unknown[]): unknown => mockHistoryPush(...args),
location: { pathname: '/', search: '', hash: '', state: null },
},
}));
jest.mock('utils/basePath', () => ({
getBaseUrl: (): string => 'http://localhost',
getBasePath: (): string => '/',
withBasePath: (p: string): string => p,
}));
const MCP_URL = 'https://mcp.us.signoz.cloud/mcp';
function setupGlobalConfig({ mcpUrl }: { mcpUrl: string | null }): void {
mockUseGetGlobalConfig.mockReturnValue({
data: { data: { mcp_url: mcpUrl, ingestion_url: '' }, status: 'success' },
isLoading: false,
});
}
describe('MCPServerSettings', () => {
afterEach(() => {
jest.clearAllMocks();
});
it('shows loading spinner while config is loading', () => {
mockUseGetGlobalConfig.mockReturnValue({
data: undefined,
isLoading: true,
});
render(<MCPServerSettings />);
expect(document.querySelector('.ant-spin-spinning')).toBeInTheDocument();
expect(screen.queryByTestId('mcp-settings')).not.toBeInTheDocument();
});
it('shows fallback page when mcp_url is not configured', () => {
setupGlobalConfig({ mcpUrl: null });
render(<MCPServerSettings />);
expect(
screen.getByText('MCP Server is available on SigNoz'),
).toBeInTheDocument();
expect(screen.queryByTestId('mcp-settings')).not.toBeInTheDocument();
});
it('renders main settings page when mcp_url is present', () => {
setupGlobalConfig({ mcpUrl: MCP_URL });
render(<MCPServerSettings />);
expect(screen.getByTestId('mcp-settings')).toBeInTheDocument();
expect(screen.getByText('SigNoz MCP Server')).toBeInTheDocument();
expect(screen.getByText('Configure your client')).toBeInTheDocument();
expect(screen.getByText('Authenticate from your client')).toBeInTheDocument();
});
it('fires PAGE_VIEWED analytics event on mount', () => {
setupGlobalConfig({ mcpUrl: MCP_URL });
render(<MCPServerSettings />, undefined, { role: 'ADMIN' });
expect(mockLogEvent).toHaveBeenCalledWith('MCP Settings: Page viewed', {
role: 'ADMIN',
});
});
it('admin sees the Create Service Account CTA', () => {
setupGlobalConfig({ mcpUrl: MCP_URL });
render(<MCPServerSettings />, undefined, { role: 'ADMIN' });
expect(screen.getByText('Create service account')).toBeInTheDocument();
expect(
screen.queryByText(
'Only admins can create API keys. Ask your workspace admin for a key with read access, then paste it into the API Key field.',
),
).not.toBeInTheDocument();
});
it('non-admin sees an info banner instead of the CTA', () => {
setupGlobalConfig({ mcpUrl: MCP_URL });
render(<MCPServerSettings />, undefined, { role: 'VIEWER' });
expect(
screen.getByText(
'Only admins can create API keys. Ask your workspace admin for a key with read access, then paste it into the API Key field.',
),
).toBeInTheDocument();
expect(screen.queryByText('Create service account')).not.toBeInTheDocument();
});
it('navigates to service accounts when admin clicks Create CTA', async () => {
setupGlobalConfig({ mcpUrl: MCP_URL });
const user = userEvent.setup({ pointerEventsCheck: 0 });
render(<MCPServerSettings />, undefined, { role: 'ADMIN' });
await user.click(screen.getByText('Create service account'));
expect(mockHistoryPush).toHaveBeenCalledWith(
'/settings/service-accounts?create-sa=true',
);
});
it('copies instance URL and shows success toast', async () => {
setupGlobalConfig({ mcpUrl: MCP_URL });
const user = userEvent.setup({ pointerEventsCheck: 0 });
render(<MCPServerSettings />);
await user.click(
screen.getByRole('button', { name: 'Copy SigNoz instance URL' }),
);
expect(mockCopyToClipboard).toHaveBeenCalledWith('http://localhost');
expect(mockToastSuccess).toHaveBeenCalledWith(
'Instance URL copied to clipboard',
);
});
});

View File

@@ -0,0 +1,144 @@
import { useCallback, useEffect, useState } from 'react';
import { useCopyToClipboard } from 'react-use';
import logEvent from 'api/common/logEvent';
import ROUTES from 'constants/routes';
import { SA_QUERY_PARAMS } from 'container/ServiceAccountsSettings/constants';
import { useGetGlobalConfig } from 'api/generated/services/global';
import history from 'lib/history';
import { useAppContext } from 'providers/App/App';
import { USER_ROLES } from 'types/roles';
import { getBaseUrl } from 'utils/basePath';
import { Badge, toast } from '@signozhq/ui';
import Spinner from 'components/Spinner';
import AuthCard from './AuthCard/AuthCard';
import ClientTabs from './ClientTabs/ClientTabs';
import NotCloudFallback from './NotCloudFallback/NotCloudFallback';
import UseCasesCard from './UseCasesCard/UseCasesCard';
import { MCP_CLIENTS } from './clients';
import './MCPServerSettings.styles.scss';
const ANALYTICS = {
PAGE_VIEWED: 'MCP Settings: Page viewed',
CREATE_SA_CLICKED: 'MCP Settings: Create service account clicked',
CLIENT_TAB_SELECTED: 'MCP Settings: Client tab selected',
SNIPPET_COPIED: 'MCP Settings: Client snippet copied',
ONE_CLICK_INSTALL_CLICKED: 'MCP Settings: One-click install clicked',
INSTANCE_URL_COPIED: 'MCP Settings: Instance URL copied',
DOCS_LINK_CLICKED: 'MCP Settings: Docs link clicked',
} as const;
function MCPServerSettings(): JSX.Element {
const { user } = useAppContext();
const [, copyToClipboard] = useCopyToClipboard();
const isAdmin = user.role === USER_ROLES.ADMIN;
const instanceUrl = getBaseUrl();
const { data: globalConfig, isLoading: isConfigLoading } =
useGetGlobalConfig();
const endpoint = globalConfig?.data?.mcp_url ?? '';
const [activeTab, setActiveTab] = useState<string>(MCP_CLIENTS[0]?.key ?? '');
useEffect(() => {
void logEvent(ANALYTICS.PAGE_VIEWED, {
role: user.role,
});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const handleCopySnippet = useCallback(
(clientKey: string, snippet: string) => {
if (!endpoint) {
toast.warning('Enter your Cloud region before copying');
return;
}
copyToClipboard(snippet);
toast.success('Snippet copied to clipboard');
void logEvent(ANALYTICS.SNIPPET_COPIED, { client: clientKey });
},
[endpoint, copyToClipboard],
);
const handleCreateServiceAccount = useCallback(() => {
void logEvent(ANALYTICS.CREATE_SA_CLICKED, {});
history.push(
`${ROUTES.SERVICE_ACCOUNTS_SETTINGS}?${SA_QUERY_PARAMS.CREATE_SA}=true`,
);
}, []);
const handleCopyInstanceUrl = useCallback(() => {
copyToClipboard(instanceUrl);
toast.success('Instance URL copied to clipboard');
void logEvent(ANALYTICS.INSTANCE_URL_COPIED, {});
}, [copyToClipboard, instanceUrl]);
const handleDocsLinkClick = useCallback((target: string) => {
void logEvent(ANALYTICS.DOCS_LINK_CLICKED, { target });
}, []);
const handleInstallClick = useCallback((clientKey: string) => {
void logEvent(ANALYTICS.ONE_CLICK_INSTALL_CLICKED, { client: clientKey });
}, []);
const handleTabChange = useCallback((key: string) => {
setActiveTab(key);
void logEvent(ANALYTICS.CLIENT_TAB_SELECTED, { client: key });
}, []);
if (isConfigLoading) {
return <Spinner tip="Loading..." height="70vh" />;
}
if (!endpoint) {
return <NotCloudFallback />;
}
return (
<div className="mcp-settings" data-testid="mcp-settings">
<header className="mcp-settings__header">
<h1 className="mcp-settings__header-title">SigNoz MCP Server</h1>
<p className="mcp-settings__header-subtitle">
Connect AI assistants like Claude, Cursor, VS Code, and Codex to your
SigNoz data via the Model Context Protocol. Authenticate from your MCP
client with a service-account API key.
</p>
</header>
<section className="mcp-settings__card">
<h3 className="mcp-settings__card-title">
<Badge color="secondary" variant="default">
1
</Badge>
Configure your client
</h3>
<p className="mcp-settings__card-description">
Add SigNoz to your MCP client. Use a one-click install where available, or
copy the config for manual setup. On first connect, the client will open a
SigNoz authorization page - use the instance URL and API key from step 2.
</p>
<ClientTabs
endpoint={endpoint}
activeTab={activeTab}
onTabChange={handleTabChange}
onCopySnippet={handleCopySnippet}
onInstallClick={handleInstallClick}
onDocsLinkClick={handleDocsLinkClick}
/>
</section>
<AuthCard
isAdmin={isAdmin}
instanceUrl={instanceUrl}
onCopyInstanceUrl={handleCopyInstanceUrl}
onCreateServiceAccount={handleCreateServiceAccount}
/>
<UseCasesCard onDocsLinkClick={handleDocsLinkClick} />
</div>
);
}
export default MCPServerSettings;

View File

@@ -0,0 +1,41 @@
.not-cloud-fallback {
display: flex;
align-items: center;
justify-content: center;
height: 100%;
min-height: 60vh;
&__content {
display: flex;
flex-direction: column;
gap: var(--spacing-6);
padding: var(--padding-6);
border: 1px dashed var(--l2-border);
border-radius: 8px;
background: var(--l2-background);
max-width: 50%;
.learn-more {
width: fit-content;
font-size: var(--font-size-xs);
}
}
&__title {
display: flex;
align-items: center;
gap: var(--spacing-2);
font-size: var(--label-large-500-font-size);
font-weight: var(--label-large-500-font-weight);
color: var(--l1-foreground);
margin: 0;
}
&__body {
font-size: var(--paragraph-base-400-font-size);
font-weight: var(--paragraph-base-400-font-weight);
color: var(--foreground);
line-height: var(--paragraph-base-400-line-height);
margin: 0;
}
}

View File

@@ -0,0 +1,35 @@
import { useCallback } from 'react';
import logEvent from 'api/common/logEvent';
import LearnMore from 'components/LearnMore/LearnMore';
import './NotCloudFallback.styles.scss';
import { MCP_DOCS_URL } from '../clients';
const DOCS_LINK_EVENT = 'MCP Settings: Docs link clicked';
function NotCloudFallback(): JSX.Element {
const handleDocsClick = useCallback(() => {
void logEvent(DOCS_LINK_EVENT, { target: 'fallback' });
}, []);
return (
<div className="not-cloud-fallback">
<div className="not-cloud-fallback__content">
<h2 className="not-cloud-fallback__title">
MCP Server is available on SigNoz
</h2>
<p className="not-cloud-fallback__body">
Users can follow the docs to setup the MCP server against their SigNoz
instance.
</p>
<LearnMore
text="View MCP Server docs"
url={MCP_DOCS_URL}
onClick={handleDocsClick}
/>
</div>
</div>
);
}
export default NotCloudFallback;

View File

@@ -0,0 +1,35 @@
.mcp-use-cases-card {
display: flex;
flex-direction: column;
gap: var(--spacing-4);
padding: var(--padding-4) var(--padding-5);
border: 1px solid var(--l2-border);
border-radius: 8px;
background: var(--l2-background);
&__title {
font-size: var(--label-medium-500-font-size);
font-weight: var(--label-medium-500-font-weight);
color: var(--l1-foreground);
margin: 0;
}
.learn-more {
width: fit-content;
font-size: var(--font-size-xs);
}
&__list {
display: flex;
flex-direction: column;
gap: var(--spacing-1);
padding-left: var(--padding-4);
margin: 0;
li {
font-size: var(--font-size-xs);
color: var(--l1-foreground);
line-height: var(--line-height-20);
}
}
}

View File

@@ -0,0 +1,35 @@
import { useCallback } from 'react';
import LearnMore from 'components/LearnMore/LearnMore';
import { MCP_USE_CASES_URL } from '../clients';
import './UseCasesCard.styles.scss';
interface UseCasesCardProps {
onDocsLinkClick: (target: string) => void;
}
function UseCasesCard({ onDocsLinkClick }: UseCasesCardProps): JSX.Element {
const handleClick = useCallback(
() => onDocsLinkClick('use-cases'),
[onDocsLinkClick],
);
return (
<section className="mcp-use-cases-card">
<h3 className="mcp-use-cases-card__title">What you can do with it</h3>
<ul className="mcp-use-cases-card__list">
<li>Ask your AI assistant to investigate a spiking error rate.</li>
<li>Debug a slow service by walking through recent traces.</li>
<li>Summarize an alert and suggest likely root causes.</li>
<li>Generate dashboards or queries from a natural-language description.</li>
</ul>
<LearnMore
text="See more use cases"
url={MCP_USE_CASES_URL}
onClick={handleClick}
/>
</section>
);
}
export default UseCasesCard;

View File

@@ -0,0 +1,108 @@
import { DOCS_BASE_URL } from 'constants/app';
export interface McpClient {
key: string;
label: string;
docsPath: string;
snippet: ((endpoint: string) => string) | null;
instructions?: string;
installUrl?: (endpoint: string) => string;
installLabel?: string;
}
function b64url(input: string): string {
if (typeof btoa === 'function') {
return btoa(input);
}
// fallback for non-browser TS contexts (never hit at runtime)
return Buffer.from(input, 'utf8').toString('base64');
}
export const MCP_CLIENTS: McpClient[] = [
{
key: 'cursor',
label: 'Cursor',
docsPath: '/docs/ai/signoz-mcp-server/#cursor',
snippet: (endpoint): string =>
JSON.stringify(
{
mcpServers: {
signoz: {
url: endpoint,
},
},
},
null,
2,
),
installUrl: (endpoint): string => {
const config = b64url(JSON.stringify({ url: endpoint }));
return `cursor://anysphere.cursor-deeplink/mcp/install?name=SigNoz&config=${config}`;
},
installLabel: 'Add to Cursor',
},
{
key: 'claude-code',
label: 'Claude Code',
docsPath: '/docs/ai/signoz-mcp-server/#claude-code',
snippet: (endpoint): string =>
`claude mcp add --scope user --transport http signoz ${endpoint}`,
},
{
key: 'vscode',
label: 'VS Code',
docsPath: '/docs/ai/signoz-mcp-server/#vs-code',
snippet: (endpoint): string =>
JSON.stringify(
{
servers: {
signoz: {
type: 'http',
url: endpoint,
},
},
},
null,
2,
),
installUrl: (endpoint): string => {
const payload = encodeURIComponent(
JSON.stringify({
name: 'signoz',
config: { type: 'http', url: endpoint },
}),
);
return `vscode:mcp/install?${payload}`;
},
installLabel: 'Add to VS Code',
},
{
key: 'claude-desktop',
label: 'Claude Desktop',
docsPath: '/docs/ai/signoz-mcp-server/#claude-desktop',
snippet: null,
instructions:
'Open Claude Desktop, go to Settings → Connectors → Add custom connector, and paste the endpoint URL above. Claude Desktop does not read remote MCP servers from claude_desktop_config.json - the connector UI is the only supported path.',
},
{
key: 'codex',
label: 'Codex',
docsPath: '/docs/ai/signoz-mcp-server/#codex',
snippet: (endpoint): string => `codex mcp add signoz --url ${endpoint}`,
},
{
key: 'other',
label: 'Other',
docsPath: '/docs/ai/signoz-mcp-server/',
snippet: null,
instructions:
'Most MCP clients that support remote HTTP servers will accept the endpoint URL above. Add it as a new MCP server in your client and paste your SigNoz API key when the client prompts for authentication. See the docs for client-specific instructions.',
},
];
export function docsUrl(path: string): string {
return `${DOCS_BASE_URL}${path}`;
}
export const MCP_DOCS_URL = `${DOCS_BASE_URL}/docs/ai/signoz-mcp-server/`;
export const MCP_USE_CASES_URL = `${DOCS_BASE_URL}/docs/ai/use-cases/`;

View File

@@ -15,7 +15,7 @@ import logEvent from 'api/common/logEvent';
import LaunchChatSupport from 'components/LaunchChatSupport/LaunchChatSupport';
import { DOCS_BASE_URL } from 'constants/app';
import ROUTES from 'constants/routes';
import { useGetGlobalConfig } from 'hooks/globalConfig/useGetGlobalConfig';
import { useGetGlobalConfig } from 'api/generated/services/global';
import useDebouncedFn from 'hooks/useDebouncedFunction';
import { useSafeNavigate } from 'hooks/useSafeNavigate';
import { isEmpty } from 'lodash-es';

View File

@@ -5,7 +5,8 @@ import logEvent from 'api/common/logEvent';
import { useGetIngestionKeys } from 'api/generated/services/gateway';
import { GatewaytypesIngestionKeyDTO } from 'api/generated/services/sigNoz.schemas';
import { DOCS_BASE_URL } from 'constants/app';
import { useGetGlobalConfig } from 'hooks/globalConfig/useGetGlobalConfig';
import { convertToApiError } from 'api/ErrorResponseHandlerForGeneratedAPIs';
import { useGetGlobalConfig } from 'api/generated/services/global';
import { useNotifications } from 'hooks/useNotifications';
import { ArrowUpRight, Copy, Info, Key, TriangleAlert } from 'lucide-react';
import { withBasePath } from 'utils/basePath';
@@ -59,6 +60,8 @@ export default function OnboardingIngestionDetails(): JSX.Element {
error: globalConfigError,
} = useGetGlobalConfig();
const globalConfigApiError = convertToApiError(globalConfigError);
const handleCopyKey = (text: string): void => {
handleCopyToClipboard(text);
notifications.success({
@@ -155,11 +158,11 @@ export default function OnboardingIngestionDetails(): JSX.Element {
title={
<div className="ingestion-url-error-content">
<Typography.Text className="ingestion-url-error-code">
{globalConfigError?.getErrorCode()}
{globalConfigApiError?.getErrorCode()}
</Typography.Text>
<Typography.Text className="ingestion-url-error-message">
{globalConfigError?.getErrorMessage()}
{globalConfigApiError?.getErrorMessage()}
</Typography.Text>
</div>
}

View File

@@ -192,7 +192,8 @@ const onboardingConfigWithLinks = [
'setup',
],
imgUrl: signozBrandLogoUrl,
link: '/docs/ai/signoz-mcp-server/',
link: '/settings/mcp-server',
internalRedirect: true,
},
{
dataSource: 'migrate-from-datadog',

View File

@@ -32,6 +32,7 @@ import {
Settings,
Shield,
Slack,
Sparkles,
Unplug,
User,
UserPlus,
@@ -337,6 +338,13 @@ export const settingsNavSections: SettingsNavSection[] = [
isEnabled: false,
itemKey: 'integrations',
},
{
key: ROUTES.MCP_SERVER,
label: 'MCP Server',
icon: <Sparkles size={16} />,
isEnabled: false,
itemKey: 'mcp-server',
},
],
},

View File

@@ -157,6 +157,7 @@ export const routesToSkip = [
ROUTES.ORG_SETTINGS,
ROUTES.MEMBERS_SETTINGS,
ROUTES.SERVICE_ACCOUNTS_SETTINGS,
ROUTES.MCP_SERVER,
ROUTES.INGESTION_SETTINGS,
ROUTES.ERROR_DETAIL,
ROUTES.LOGS_PIPELINES,

View File

@@ -1,14 +0,0 @@
import { useQuery, UseQueryResult } from 'react-query';
import getGlobalConfig from 'api/globalConfig/getGlobalConfig';
import { SuccessResponseV2 } from 'types/api';
import APIError from 'types/api/error';
import { GlobalConfigData } from 'types/api/globalConfig/types';
export const useGetGlobalConfig = (): UseQueryResult<
SuccessResponseV2<GlobalConfigData>,
APIError
> =>
useQuery<SuccessResponseV2<GlobalConfigData>, APIError>({
queryKey: ['getGlobalConfig'],
queryFn: () => getGlobalConfig(),
});

View File

@@ -83,7 +83,8 @@ function SettingsPage(): JSX.Element {
item.key === ROUTES.ORG_SETTINGS ||
item.key === ROUTES.MEMBERS_SETTINGS ||
item.key === ROUTES.SERVICE_ACCOUNTS_SETTINGS ||
item.key === ROUTES.SHORTCUTS
item.key === ROUTES.SHORTCUTS ||
item.key === ROUTES.MCP_SERVER
? true
: item.isEnabled,
}));
@@ -95,7 +96,8 @@ function SettingsPage(): JSX.Element {
isEnabled:
item.key === ROUTES.INGESTION_SETTINGS ||
item.key === ROUTES.INTEGRATIONS ||
item.key === ROUTES.SHORTCUTS
item.key === ROUTES.SHORTCUTS ||
item.key === ROUTES.MCP_SERVER
? true
: item.isEnabled,
}));
@@ -114,7 +116,8 @@ function SettingsPage(): JSX.Element {
item.key === ROUTES.ORG_SETTINGS ||
item.key === ROUTES.MEMBERS_SETTINGS ||
item.key === ROUTES.SERVICE_ACCOUNTS_SETTINGS ||
item.key === ROUTES.INGESTION_SETTINGS
item.key === ROUTES.INGESTION_SETTINGS ||
item.key === ROUTES.MCP_SERVER
? true
: item.isEnabled,
}));
@@ -125,7 +128,8 @@ function SettingsPage(): JSX.Element {
...item,
isEnabled:
item.key === ROUTES.INTEGRATIONS ||
item.key === ROUTES.INGESTION_SETTINGS
item.key === ROUTES.INGESTION_SETTINGS ||
item.key === ROUTES.MCP_SERVER
? true
: item.isEnabled,
}));

View File

@@ -56,6 +56,7 @@ describe('SettingsPage nav sections', () => {
'sso',
'integrations',
'ingestion',
'mcp-server',
])('renders "%s" element', (id) => {
expect(screen.getByTestId(id)).toBeInTheDocument();
});
@@ -81,9 +82,12 @@ describe('SettingsPage nav sections', () => {
expect(screen.getByTestId(id)).toBeInTheDocument();
});
it.each(['billing', 'roles'])('does not render "%s" element', (id) => {
expect(screen.queryByTestId(id)).not.toBeInTheDocument();
});
it.each(['billing', 'roles', 'mcp-server'])(
'does not render "%s" element',
(id) => {
expect(screen.queryByTestId(id)).not.toBeInTheDocument();
},
);
});
describe('Self-hosted Admin', () => {
@@ -95,12 +99,16 @@ describe('SettingsPage nav sections', () => {
});
});
it.each(['roles', 'members', 'integrations', 'sso', 'ingestion'])(
'renders "%s" element',
(id) => {
expect(screen.getByTestId(id)).toBeInTheDocument();
},
);
it.each([
'roles',
'members',
'integrations',
'sso',
'ingestion',
'mcp-server',
])('renders "%s" element', (id) => {
expect(screen.getByTestId(id)).toBeInTheDocument();
});
});
describe('section structure', () => {

View File

@@ -8,6 +8,7 @@ import GeneralSettings from 'container/GeneralSettings';
import GeneralSettingsCloud from 'container/GeneralSettingsCloud';
import IngestionSettings from 'container/IngestionSettings/IngestionSettings';
import MultiIngestionSettings from 'container/IngestionSettings/MultiIngestionSettings';
import MCPServerSettings from 'container/MCPServerSettings/MCPServerSettings';
import MySettings from 'container/MySettings';
import OrganizationSettings from 'container/OrganizationSettings';
import RolesSettings from 'container/RolesSettings';
@@ -24,6 +25,7 @@ import {
Pencil,
Plus,
Shield,
Sparkles,
User,
Users,
} from 'lucide-react';
@@ -205,6 +207,19 @@ export const serviceAccountsSettings = (
},
];
export const mcpServerSettings = (t: TFunction): RouteTabProps['routes'] => [
{
Component: MCPServerSettings,
name: (
<div className="periscope-tab">
<Sparkles size={16} /> {t('routes:mcp_server').toString()}
</div>
),
route: ROUTES.MCP_SERVER,
key: ROUTES.MCP_SERVER,
},
];
export const createAlertChannels = (t: TFunction): RouteTabProps['routes'] => [
{
Component: (): JSX.Element => (

View File

@@ -10,6 +10,7 @@ import {
generalSettings,
ingestionSettings,
keyboardShortcuts,
mcpServerSettings,
membersSettings,
multiIngestionSettings,
mySettings,
@@ -79,6 +80,7 @@ export const getRoutes = (
...createAlertChannels(t),
...editAlertChannels(t),
...keyboardShortcuts(t),
...mcpServerSettings(t),
);
return settings;

View File

@@ -1,9 +0,0 @@
export interface GlobalConfigData {
external_url: string;
ingestion_url: string;
}
export interface GlobalConfigDataProps {
status: string;
data: GlobalConfigData;
}

View File

@@ -132,4 +132,5 @@ export const routePermission: Record<keyof typeof ROUTES, ROLES[]> = {
METER_EXPLORER_VIEWS: ['ADMIN', 'EDITOR', 'VIEWER'],
PUBLIC_DASHBOARD: ['ADMIN', 'EDITOR', 'VIEWER'],
ALERT_TYPE_SELECTION: ['ADMIN', 'EDITOR'],
MCP_SERVER: ['ADMIN', 'EDITOR', 'VIEWER'],
};

View File

@@ -4216,225 +4216,225 @@
resolved "https://registry.yarnpkg.com/@oxc-project/types/-/types-0.101.0.tgz#5692200d09d6f87341eac3f8e70e403173c5283e"
integrity sha512-nuFhqlUzJX+gVIPPfuE6xurd4lST3mdcWOhyK/rZO0B9XWMKm79SuszIQEnSMmmDhq1DC8WWVYGVd+6F93o1gQ==
"@oxfmt/binding-android-arm-eabi@0.41.0":
version "0.41.0"
resolved "https://registry.yarnpkg.com/@oxfmt/binding-android-arm-eabi/-/binding-android-arm-eabi-0.41.0.tgz#09438448ab9479730582cd00fb86e4c33795a364"
integrity sha512-REfrqeMKGkfMP+m/ScX4f5jJBSmVNYcpoDF8vP8f8eYPDuPGZmzp56NIUsYmx3h7f6NzC6cE3gqh8GDWrJHCKw==
"@oxfmt/binding-android-arm-eabi@0.46.0":
version "0.46.0"
resolved "https://registry.yarnpkg.com/@oxfmt/binding-android-arm-eabi/-/binding-android-arm-eabi-0.46.0.tgz#d2da704ab6c741e535de3445d0994290b77b3fc0"
integrity sha512-b1doV4WRcJU+BESSlCvCjV+5CEr/T6h0frArAdV26Nir+gGNFNaylvDiiMPfF1pxeV0txZEs38ojzJaxBYg+ng==
"@oxfmt/binding-android-arm64@0.41.0":
version "0.41.0"
resolved "https://registry.yarnpkg.com/@oxfmt/binding-android-arm64/-/binding-android-arm64-0.41.0.tgz#b01eef08ff2a6be90f5805fe28339ec0130a955c"
integrity sha512-s0b1dxNgb2KomspFV2LfogC2XtSJB42POXF4bMCLJyvQmAGos4ZtjGPfQreToQEaY0FQFjz3030ggI36rF1q5g==
"@oxfmt/binding-android-arm64@0.46.0":
version "0.46.0"
resolved "https://registry.yarnpkg.com/@oxfmt/binding-android-arm64/-/binding-android-arm64-0.46.0.tgz#203341eaf489c325a01e247c6209f13f39f0777f"
integrity sha512-v6+HhjsoV3GO0u2u9jLSAZrvWfTraDxKofUIQ7/ktS7tzS+epVsxdHmeM+XxuNcAY/nWxxU1Sg4JcGTNRXraBA==
"@oxfmt/binding-darwin-arm64@0.41.0":
version "0.41.0"
resolved "https://registry.yarnpkg.com/@oxfmt/binding-darwin-arm64/-/binding-darwin-arm64-0.41.0.tgz#a2e24b7c52d40e3bb01939fa9c994d56923294ce"
integrity sha512-EGXGualADbv/ZmamE7/2DbsrYmjoPlAmHEpTL4vapLF4EfVD6fr8/uQDFnPJkUBjiSWFJZtFNsGeN1B6V3owmA==
"@oxfmt/binding-darwin-arm64@0.46.0":
version "0.46.0"
resolved "https://registry.yarnpkg.com/@oxfmt/binding-darwin-arm64/-/binding-darwin-arm64-0.46.0.tgz#ce653c87e00305c84edb380ecac3db789c3ea9a4"
integrity sha512-3eeooJGrqGIlI5MyryDZsAcKXSmKIgAD4yYtfRrRJzXZ0UTFZtiSveIur56YPrGMYZwT4XyVhHsMqrNwr1XeFA==
"@oxfmt/binding-darwin-x64@0.41.0":
version "0.41.0"
resolved "https://registry.yarnpkg.com/@oxfmt/binding-darwin-x64/-/binding-darwin-x64-0.41.0.tgz#c114c0a30195a65cfe0247e2ffd000416df4d4bc"
integrity sha512-WxySJEvdQQYMmyvISH3qDpTvoS0ebnIP63IMxLLWowJyPp/AAH0hdWtlo+iGNK5y3eVfa5jZguwNaQkDKWpGSw==
"@oxfmt/binding-darwin-x64@0.46.0":
version "0.46.0"
resolved "https://registry.yarnpkg.com/@oxfmt/binding-darwin-x64/-/binding-darwin-x64-0.46.0.tgz#71016678036fab249172c763d2f59a3535f0fe91"
integrity sha512-QG8BDM0CXWbu84k2SKmCqfEddPQPFiBicwtYnLqHRWZZl57HbtOLRMac/KTq2NO4AEc4ICCBpFxJIV9zcqYfkQ==
"@oxfmt/binding-freebsd-x64@0.41.0":
version "0.41.0"
resolved "https://registry.yarnpkg.com/@oxfmt/binding-freebsd-x64/-/binding-freebsd-x64-0.41.0.tgz#a9057a31860ec79970a16b5a0a801a51fab99168"
integrity sha512-Y2kzMkv3U3oyuYaR4wTfGjOTYTXiFC/hXmG0yVASKkbh02BJkvD98Ij8bIevr45hNZ0DmZEgqiXF+9buD4yMYQ==
"@oxfmt/binding-freebsd-x64@0.46.0":
version "0.46.0"
resolved "https://registry.yarnpkg.com/@oxfmt/binding-freebsd-x64/-/binding-freebsd-x64-0.46.0.tgz#fd939be2e39c29fead415f41c70eb52f94d53d6a"
integrity sha512-9DdCqS/n2ncu/Chazvt3cpgAjAmIGQDz7hFKSrNItMApyV/Ja9mz3hD4JakIE3nS8PW9smEbPWnb389QLBY4nw==
"@oxfmt/binding-linux-arm-gnueabihf@0.41.0":
version "0.41.0"
resolved "https://registry.yarnpkg.com/@oxfmt/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-0.41.0.tgz#1a2be3cbbf2d9e90808858ba8fc38b24023ac44c"
integrity sha512-ptazDjdUyhket01IjPTT6ULS1KFuBfTUU97osTP96X5y/0oso+AgAaJzuH81oP0+XXyrWIHbRzozSAuQm4p48g==
"@oxfmt/binding-linux-arm-gnueabihf@0.46.0":
version "0.46.0"
resolved "https://registry.yarnpkg.com/@oxfmt/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-0.46.0.tgz#09f4f3894e140340256a02665832bdcf66d8b5c3"
integrity sha512-Dgs7VeE2jT0LHMhw6tPEt0xQYe54kBqHEovmWsv4FVQlegCOvlIJNx0S8n4vj8WUtpT+Z6BD2HhKJPLglLxvZg==
"@oxfmt/binding-linux-arm-musleabihf@0.41.0":
version "0.41.0"
resolved "https://registry.yarnpkg.com/@oxfmt/binding-linux-arm-musleabihf/-/binding-linux-arm-musleabihf-0.41.0.tgz#f0a12feca29bfaf7437d94c32919ff3ac60c213f"
integrity sha512-UkoL2OKxFD+56bPEBcdGn+4juTW4HRv/T6w1dIDLnvKKWr6DbarB/mtHXlADKlFiJubJz8pRkttOR7qjYR6lTA==
"@oxfmt/binding-linux-arm-musleabihf@0.46.0":
version "0.46.0"
resolved "https://registry.yarnpkg.com/@oxfmt/binding-linux-arm-musleabihf/-/binding-linux-arm-musleabihf-0.46.0.tgz#35ca79c2c643dd5dd7eb1e1c0cbc5205eecba8ee"
integrity sha512-Zxn3adhTH13JKnU4xXJj8FeEfF680XjXh3gSShKl57HCMBRde2tUJTgogV/1MSHA80PJEVrDa7r66TLVq3Ia7Q==
"@oxfmt/binding-linux-arm64-gnu@0.41.0":
version "0.41.0"
resolved "https://registry.yarnpkg.com/@oxfmt/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-0.41.0.tgz#b304c7ede72119f7fe7ce7861007c0ee6eb60c08"
integrity sha512-gofu0PuumSOHYczD8p62CPY4UF6ee+rSLZJdUXkpwxg6pILiwSDBIouPskjF/5nF3A7QZTz2O9KFNkNxxFN9tA==
"@oxfmt/binding-linux-arm64-gnu@0.46.0":
version "0.46.0"
resolved "https://registry.yarnpkg.com/@oxfmt/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-0.46.0.tgz#5f30f09b2d109ec18f1d9de64414718f4c9975be"
integrity sha512-+TWipjrgVM8D7aIdDD0tlr3teLTTvQTn7QTE5BpT10H1Fj82gfdn9X6nn2sDgx/MepuSCfSnzFNJq2paLL0OiA==
"@oxfmt/binding-linux-arm64-musl@0.41.0":
version "0.41.0"
resolved "https://registry.yarnpkg.com/@oxfmt/binding-linux-arm64-musl/-/binding-linux-arm64-musl-0.41.0.tgz#295d6ce8d846ca1a287165bf4ff6e7b3cd51f823"
integrity sha512-VfVZxL0+6RU86T8F8vKiDBa+iHsr8PAjQmKGBzSCAX70b6x+UOMFl+2dNihmKmUwqkCazCPfYjt6SuAPOeQJ3g==
"@oxfmt/binding-linux-arm64-musl@0.46.0":
version "0.46.0"
resolved "https://registry.yarnpkg.com/@oxfmt/binding-linux-arm64-musl/-/binding-linux-arm64-musl-0.46.0.tgz#a550718469f40dcb8a382eb5a89b031d7f47ee16"
integrity sha512-aAUPBWJ1lGwwnxZUEDLJ94+Iy6MuwJwPxUgO4sCA5mEEyDk7b+cDQ+JpX1VR150Zoyd+D49gsrUzpUK5h587Eg==
"@oxfmt/binding-linux-ppc64-gnu@0.41.0":
version "0.41.0"
resolved "https://registry.yarnpkg.com/@oxfmt/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-0.41.0.tgz#6c702f37bd69bb88bf78264eef5df9dbcdcada49"
integrity sha512-bwzokz2eGvdfJbc0i+zXMJ4BBjQPqg13jyWpEEZDOrBCQ91r8KeY2Mi2kUeuMTZNFXju+jcAbAbpyJxRGla0eg==
"@oxfmt/binding-linux-ppc64-gnu@0.46.0":
version "0.46.0"
resolved "https://registry.yarnpkg.com/@oxfmt/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-0.46.0.tgz#51d25e82d6c965aa09660ef31a19e6d8a79253a9"
integrity sha512-ufBCJukyFX/UDrokP/r6BGDoTInnsDs7bxyzKAgMiZlt2Qu8GPJSJ6Zm6whIiJzKk0naxA8ilwmbO1LMw6Htxw==
"@oxfmt/binding-linux-riscv64-gnu@0.41.0":
version "0.41.0"
resolved "https://registry.yarnpkg.com/@oxfmt/binding-linux-riscv64-gnu/-/binding-linux-riscv64-gnu-0.41.0.tgz#9dac4f7f4cf10d94ade5a109554f810a43525595"
integrity sha512-POLM//PCH9uqDeNDwWL3b3DkMmI3oI2cU6hwc2lnztD1o7dzrQs3R9nq555BZ6wI7t2lyhT9CS+CRaz5X0XqLA==
"@oxfmt/binding-linux-riscv64-gnu@0.46.0":
version "0.46.0"
resolved "https://registry.yarnpkg.com/@oxfmt/binding-linux-riscv64-gnu/-/binding-linux-riscv64-gnu-0.46.0.tgz#02565fd79a18de150f9a76a00f76fdd3a5e7f0fa"
integrity sha512-eqtlC2YmPqjun76R1gVfGLuKWx7NuEnLEAudZ7n6ipSKbCZTqIKSs1b5Y8K/JHZsRpLkeSmAAjig5HOIg8fQzQ==
"@oxfmt/binding-linux-riscv64-musl@0.41.0":
version "0.41.0"
resolved "https://registry.yarnpkg.com/@oxfmt/binding-linux-riscv64-musl/-/binding-linux-riscv64-musl-0.41.0.tgz#ddd573ad4fc8e5d017ab27177eaabea700d68754"
integrity sha512-NNK7PzhFqLUwx/G12Xtm6scGv7UITvyGdAR5Y+TlqsG+essnuRWR4jRNODWRjzLZod0T3SayRbnkSIWMBov33w==
"@oxfmt/binding-linux-riscv64-musl@0.46.0":
version "0.46.0"
resolved "https://registry.yarnpkg.com/@oxfmt/binding-linux-riscv64-musl/-/binding-linux-riscv64-musl-0.46.0.tgz#d148eb5b52a0c96ce90361ed733443df2f03e5c5"
integrity sha512-yccVOO2nMXkQLGgy0He3EQEwKD7NF0zEk+/OWmroznkqXyJdN6bfK0LtNnr6/14Bh3FjpYq7bP33l/VloCnxpA==
"@oxfmt/binding-linux-s390x-gnu@0.41.0":
version "0.41.0"
resolved "https://registry.yarnpkg.com/@oxfmt/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-0.41.0.tgz#3300ee81681382c1ec0a4013807c362c27ef4821"
integrity sha512-qVf/zDC5cN9eKe4qI/O/m445er1IRl6swsSl7jHkqmOSVfknwCe5JXitYjZca+V/cNJSU/xPlC5EFMabMMFDpw==
"@oxfmt/binding-linux-s390x-gnu@0.46.0":
version "0.46.0"
resolved "https://registry.yarnpkg.com/@oxfmt/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-0.46.0.tgz#22cbee991e79147e7a0d5db2cc9aa74bf5bd6d89"
integrity sha512-aAf7fG23OQCey6VRPj9IeCraoYtpgtx0ZyJ1CXkPyT1wjzBE7c3xtuxHe/AdHaJfVVb/SXpSk8Gl1LzyQupSqw==
"@oxfmt/binding-linux-x64-gnu@0.41.0":
version "0.41.0"
resolved "https://registry.yarnpkg.com/@oxfmt/binding-linux-x64-gnu/-/binding-linux-x64-gnu-0.41.0.tgz#be9068d48bb521eea83ea6bea2b0e08b39d7638b"
integrity sha512-ojxYWu7vUb6ysYqVCPHuAPVZHAI40gfZ0PDtZAMwVmh2f0V8ExpPIKoAKr7/8sNbAXJBBpZhs2coypIo2jJX4w==
"@oxfmt/binding-linux-x64-gnu@0.46.0":
version "0.46.0"
resolved "https://registry.yarnpkg.com/@oxfmt/binding-linux-x64-gnu/-/binding-linux-x64-gnu-0.46.0.tgz#35fdda5137b108ec5cc0a8656a04fabdd0edd652"
integrity sha512-q0JPsTMyJNjYrBvYFDz4WbVsafNZaPCZv4RnFypRotLqpKROtBZcEaXQW4eb9YmvLU3NckVemLJnzkSZSdmOxw==
"@oxfmt/binding-linux-x64-musl@0.41.0":
version "0.41.0"
resolved "https://registry.yarnpkg.com/@oxfmt/binding-linux-x64-musl/-/binding-linux-x64-musl-0.41.0.tgz#e0232abd879874823213b71016042d001a740acb"
integrity sha512-O2exZLBxoCMIv2vlvcbkdedazJPTdG0VSup+0QUCfYQtx751zCZNboX2ZUOiQ/gDTdhtXvSiot0h6GEGkOyalA==
"@oxfmt/binding-linux-x64-musl@0.46.0":
version "0.46.0"
resolved "https://registry.yarnpkg.com/@oxfmt/binding-linux-x64-musl/-/binding-linux-x64-musl-0.46.0.tgz#dc399b372b27b26534d516a5a68927843f400603"
integrity sha512-7LsLY9Cw57GPkhSR+duI3mt9baRczK/DtHYSldQ4BEU92da9igBQNl4z7Vq5U9NNPsh1FmpKvv1q9WDtiUQR1A==
"@oxfmt/binding-openharmony-arm64@0.41.0":
version "0.41.0"
resolved "https://registry.yarnpkg.com/@oxfmt/binding-openharmony-arm64/-/binding-openharmony-arm64-0.41.0.tgz#3e5893cde8cae02494cbb5493de9c46613e77058"
integrity sha512-N+31/VoL+z+NNBt8viy3I4NaIdPbiYeOnB884LKqvXldaE2dRztdPv3q5ipfZYv0RwFp7JfqS4I27K/DSHCakg==
"@oxfmt/binding-openharmony-arm64@0.46.0":
version "0.46.0"
resolved "https://registry.yarnpkg.com/@oxfmt/binding-openharmony-arm64/-/binding-openharmony-arm64-0.46.0.tgz#b7c89ecd6e1827dca86779f4d08dc200bad5915c"
integrity sha512-lHiBOz8Duaku7JtRNLlps3j++eOaICPZSd8FCVmTDM4DFOPT71Bjn7g6iar1z7StXlKRweUKxWUs4sA+zWGDXg==
"@oxfmt/binding-win32-arm64-msvc@0.41.0":
version "0.41.0"
resolved "https://registry.yarnpkg.com/@oxfmt/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-0.41.0.tgz#31d8dfeeddc855dbecc9f45d11cd892635d2e42f"
integrity sha512-Z7NAtu/RN8kjCQ1y5oDD0nTAeRswh3GJ93qwcW51srmidP7XPBmZbLlwERu1W5veCevQJtPS9xmkpcDTYsGIwQ==
"@oxfmt/binding-win32-arm64-msvc@0.46.0":
version "0.46.0"
resolved "https://registry.yarnpkg.com/@oxfmt/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-0.46.0.tgz#ec0ea70875374bf0a1c949bac6a2177e23425bb8"
integrity sha512-/5ktYUliP89RhgC37DBH1x20U5zPSZMy3cMEcO0j3793rbHP9MWsknBwQB6eozRzWmYrh0IFM/p20EbPvDlYlg==
"@oxfmt/binding-win32-ia32-msvc@0.41.0":
version "0.41.0"
resolved "https://registry.yarnpkg.com/@oxfmt/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-0.41.0.tgz#873fb408a3e8952fae67763b225e4ea77356e926"
integrity sha512-uNxxP3l4bJ6VyzIeRqCmBU2Q0SkCFgIhvx9/9dJ9V8t/v+jP1IBsuaLwCXGR8JPHtkj4tFp+RHtUmU2ZYAUpMA==
"@oxfmt/binding-win32-ia32-msvc@0.46.0":
version "0.46.0"
resolved "https://registry.yarnpkg.com/@oxfmt/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-0.46.0.tgz#fa7b6bf31602c814ffdd8e3bfad355569b2f08da"
integrity sha512-3WTnoiuIr8XvV0DIY7SN+1uJSwKf4sPpcbHfobcRT9JutGcLaef/miyBB87jxd3aqH+mS0+G5lsgHuXLUwjjpQ==
"@oxfmt/binding-win32-x64-msvc@0.41.0":
version "0.41.0"
resolved "https://registry.yarnpkg.com/@oxfmt/binding-win32-x64-msvc/-/binding-win32-x64-msvc-0.41.0.tgz#0adf46bca6908c6b49c790e598f4972b6f4cd191"
integrity sha512-49ZSpbZ1noozyPapE8SUOSm3IN0Ze4b5nkO+4+7fq6oEYQQJFhE0saj5k/Gg4oewVPdjn0L3ZFeWk2Vehjcw7A==
"@oxfmt/binding-win32-x64-msvc@0.46.0":
version "0.46.0"
resolved "https://registry.yarnpkg.com/@oxfmt/binding-win32-x64-msvc/-/binding-win32-x64-msvc-0.46.0.tgz#0ddd94829568c94101abe88896e48f724891e4f2"
integrity sha512-IXxiQpkYnOwNfP23vzwSfhdpxJzyiPTY7eTn6dn3DsriKddESzM8i6kfq9R7CD/PUJwCvQT22NgtygBeug3KoA==
"@oxlint-tsgolint/darwin-arm64@0.20.0":
version "0.20.0"
resolved "https://registry.yarnpkg.com/@oxlint-tsgolint/darwin-arm64/-/darwin-arm64-0.20.0.tgz#6d704883904fa6e5fc922a7cef882bad4c734a0b"
integrity sha512-KKQcIHZHMxqpHUA1VXIbOG6chNCFkUWbQy6M+AFVtPKkA/3xAeJkJ3njoV66bfzwPHRcWQO+kcj5XqtbkjakoA==
"@oxlint-tsgolint/darwin-arm64@0.21.1":
version "0.21.1"
resolved "https://registry.yarnpkg.com/@oxlint-tsgolint/darwin-arm64/-/darwin-arm64-0.21.1.tgz#b4390b047d246608b130e2bde238306659c2b292"
integrity sha512-7TLjyWe4wG9saJc992VWmaHq2hwKfOEEVTjheReXJXaDhavMZI4X9a6nKhbEng4IVkYtzjD2jw16vw2WFXLYLw==
"@oxlint-tsgolint/darwin-x64@0.20.0":
version "0.20.0"
resolved "https://registry.yarnpkg.com/@oxlint-tsgolint/darwin-x64/-/darwin-x64-0.20.0.tgz#676d7a1cf82216d91e9bc143f676e7dbe59ce596"
integrity sha512-7HeVMuclGfG+NLZi2ybY0T4fMI7/XxO/208rJk+zEIloKkVnlh11Wd241JMGwgNFXn+MLJbOqOfojDb2Dt4L1g==
"@oxlint-tsgolint/darwin-x64@0.21.1":
version "0.21.1"
resolved "https://registry.yarnpkg.com/@oxlint-tsgolint/darwin-x64/-/darwin-x64-0.21.1.tgz#18f607e47c04459962d2ddf1461491f5690c3f8f"
integrity sha512-7wf9Wf75nTzA7zpL9myhFe2RKvfuqGUOADNvUooCjEWvh7hmPz3lSEqTMh5Z/VQhzsG04mM9ACyghxhRzq7zFw==
"@oxlint-tsgolint/linux-arm64@0.20.0":
version "0.20.0"
resolved "https://registry.yarnpkg.com/@oxlint-tsgolint/linux-arm64/-/linux-arm64-0.20.0.tgz#5a2e67cf18394b6b868301cf3d84c8b8be7ab6b9"
integrity sha512-zxhUwz+WSxE6oWlZLK2z2ps9yC6ebmgoYmjAl0Oa48+GqkZ56NVgo+wb8DURNv6xrggzHStQxqQxe3mK51HZag==
"@oxlint-tsgolint/linux-arm64@0.21.1":
version "0.21.1"
resolved "https://registry.yarnpkg.com/@oxlint-tsgolint/linux-arm64/-/linux-arm64-0.21.1.tgz#da5932c940362faa261c67085c9eb215a8f48bab"
integrity sha512-IPuQN/Vd0Rjklg/cCGBbQyUuRBp2f6LQXpZYwk5ivOR6V/+CgiYsv8pn/PVY7gjeyoNvPQrXB7xMjHUO2YZbdw==
"@oxlint-tsgolint/linux-x64@0.20.0":
version "0.20.0"
resolved "https://registry.yarnpkg.com/@oxlint-tsgolint/linux-x64/-/linux-x64-0.20.0.tgz#68468ad9253668748736191b6a7af51ae737c0a4"
integrity sha512-/1l6FnahC9im8PK+Ekkx/V3yetO/PzZnJegE2FXcv/iXEhbeVxP/ouiTYcUQu9shT1FWJCSNti1VJHH+21Y1dg==
"@oxlint-tsgolint/linux-x64@0.21.1":
version "0.21.1"
resolved "https://registry.yarnpkg.com/@oxlint-tsgolint/linux-x64/-/linux-x64-0.21.1.tgz#26c916b25c852bb63c9bd95f61bd0228400ddef0"
integrity sha512-d1niGuTbh2qiv7dR7tqkbOcM5cIR63of0lMBFdEQavL1KrJV8zuRdwdi68K7MNGdgoR+J5A9ajpGGvsHwp1bPg==
"@oxlint-tsgolint/win32-arm64@0.20.0":
version "0.20.0"
resolved "https://registry.yarnpkg.com/@oxlint-tsgolint/win32-arm64/-/win32-arm64-0.20.0.tgz#32a0eba60f0a88cd6d6d53cc03eb1b2e43ca0c50"
integrity sha512-oPZ5Yz8sVdo7P/5q+i3IKeix31eFZ55JAPa1+RGPoe9PoaYVsdMvR6Jvib6YtrqoJnFPlg3fjEjlEPL8VBKYJA==
"@oxlint-tsgolint/win32-arm64@0.21.1":
version "0.21.1"
resolved "https://registry.yarnpkg.com/@oxlint-tsgolint/win32-arm64/-/win32-arm64-0.21.1.tgz#c6e78be4394f29abd9106137c5f0a9e7ee4e0d97"
integrity sha512-ICu9y2JLnFPvFqstnWPPNqBM8LK8BWw2OTeaR0UgEMm4hOSbrZAKv1/hwZYyiLqnCNjBL87AGSQIgTHCYlsipw==
"@oxlint-tsgolint/win32-x64@0.20.0":
version "0.20.0"
resolved "https://registry.yarnpkg.com/@oxlint-tsgolint/win32-x64/-/win32-x64-0.20.0.tgz#90d5582b1df2ae877b2c5d7620e3a55acf9cc45f"
integrity sha512-4stx8RHj3SP9vQyRF/yZbz5igtPvYMEUR8CUoha4BVNZihi39DpCR8qkU7lpjB5Ga1DRMo2pHaA4bdTOMaY4mw==
"@oxlint-tsgolint/win32-x64@0.21.1":
version "0.21.1"
resolved "https://registry.yarnpkg.com/@oxlint-tsgolint/win32-x64/-/win32-x64-0.21.1.tgz#e0ff1eebe8837effe74af3f76c9ae6afc8992e58"
integrity sha512-cTEFCFjCj6iXfrSHcvajSPNqhEA4TxSzU3gFxbdGSAUTNXGToU99IbdhWAPSbhcucoym0XE4Zl7E41NiSkNTug==
"@oxlint/binding-android-arm-eabi@1.59.0":
version "1.59.0"
resolved "https://registry.yarnpkg.com/@oxlint/binding-android-arm-eabi/-/binding-android-arm-eabi-1.59.0.tgz#d16603910761e22b7346b10e1293fd22c9051020"
integrity sha512-etYDw/UaEv936AQUd/CRMBVd+e+XuuU6wC+VzOv1STvsTyZenLChepLWqLtnyTTp4YMlM22ypzogDDwqYxv5cg==
"@oxlint/binding-android-arm-eabi@1.61.0":
version "1.61.0"
resolved "https://registry.yarnpkg.com/@oxlint/binding-android-arm-eabi/-/binding-android-arm-eabi-1.61.0.tgz#c7a8fb22ac3084d6f4c873cdc9779f4f9e38b805"
integrity sha512-6eZBPgiigK5txqoVgRqxbaxiom4lM8AP8CyKPPvpzKnQ3iFRFOIDc+0AapF+qsUSwjOzr5SGk4SxQDpQhkSJMQ==
"@oxlint/binding-android-arm64@1.59.0":
version "1.59.0"
resolved "https://registry.yarnpkg.com/@oxlint/binding-android-arm64/-/binding-android-arm64-1.59.0.tgz#a54b26068fcaf6ab7f6cae9d84f0c3d7e29d3c9c"
integrity sha512-TgLc7XVLKH2a4h8j3vn1MDjfK33i9MY60f/bKhRGWyVzbk5LCZ4X01VZG7iHrMmi5vYbAp8//Ponigx03CLsdw==
"@oxlint/binding-android-arm64@1.61.0":
version "1.61.0"
resolved "https://registry.yarnpkg.com/@oxlint/binding-android-arm64/-/binding-android-arm64-1.61.0.tgz#5fe7c707e12513ede292da015d13e93781771e9f"
integrity sha512-CkwLR69MUnyv5wjzebvbbtTSUwqLxM35CXE79bHqDIK+NtKmPEUpStTcLQRZMCo4MP0qRT6TXIQVpK0ZVScnMA==
"@oxlint/binding-darwin-arm64@1.59.0":
version "1.59.0"
resolved "https://registry.yarnpkg.com/@oxlint/binding-darwin-arm64/-/binding-darwin-arm64-1.59.0.tgz#097fecd40271af5882fadcdb6ea904973c642e97"
integrity sha512-DXyFPf5ZKldMLloRHx/B9fsxsiTQomaw7cmEW3YIJko2HgCh+GUhp9gGYwHrqlLJPsEe3dYj9JebjX92D3j3AA==
"@oxlint/binding-darwin-arm64@1.61.0":
version "1.61.0"
resolved "https://registry.yarnpkg.com/@oxlint/binding-darwin-arm64/-/binding-darwin-arm64-1.61.0.tgz#1f58a4c592b76e293e5b9072cc0e4e7a5bef8191"
integrity sha512-8JbefTkbmvqkqWjmQrHke+MdpgT2UghhD/ktM4FOQSpGeCgbMToJEKdl9zwhr/YWTl92i4QI1KiTwVExpcUN8A==
"@oxlint/binding-darwin-x64@1.59.0":
version "1.59.0"
resolved "https://registry.yarnpkg.com/@oxlint/binding-darwin-x64/-/binding-darwin-x64-1.59.0.tgz#b7b3a898406c9c05a44647b43a2e3f646dfcc2bc"
integrity sha512-LgvrsdgVLX1qWqIEmNsSmMXJhpAWdtUQ0M+oR0CySwi+9IHWyOGuIL8w8+u/kbZNMyZr4WUyYB5i0+D+AKgkLg==
"@oxlint/binding-darwin-x64@1.61.0":
version "1.61.0"
resolved "https://registry.yarnpkg.com/@oxlint/binding-darwin-x64/-/binding-darwin-x64-1.61.0.tgz#eade7425cc943c3146f3e0f0fb4d08d615ee78a9"
integrity sha512-uWpoxDT47hTnDLcdEh5jVbso8rlTTu5o0zuqa9J8E0JAKmIWn7kGFEIB03Pycn2hd2vKxybPGLhjURy/9We5FQ==
"@oxlint/binding-freebsd-x64@1.59.0":
version "1.59.0"
resolved "https://registry.yarnpkg.com/@oxlint/binding-freebsd-x64/-/binding-freebsd-x64-1.59.0.tgz#e7a513e5d06d9f0df18d75bd2ed0466420e5078f"
integrity sha512-bOJhqX/ny4hrFuTPlyk8foSRx/vLRpxJh0jOOKN2NWW6FScXHPAA5rQbrwdQPcgGB5V8Ua51RS03fke8ssBcug==
"@oxlint/binding-freebsd-x64@1.61.0":
version "1.61.0"
resolved "https://registry.yarnpkg.com/@oxlint/binding-freebsd-x64/-/binding-freebsd-x64-1.61.0.tgz#8d82382a07ce4895c91694a810dee7f39866951b"
integrity sha512-K/o4hEyW7flfMel0iBVznmMBt7VIMHGdjADocHKpK1DUF9erpWnJ+BSSWd2W0c8K3mPtpph+CuHzRU6CI3l9jQ==
"@oxlint/binding-linux-arm-gnueabihf@1.59.0":
version "1.59.0"
resolved "https://registry.yarnpkg.com/@oxlint/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.59.0.tgz#38d0d20b3fd6b25672a34328125e99c45fff39d1"
integrity sha512-vVUXxYMF9trXCsz4m9H6U0IjehosVHxBzVgJUxly1uz4W1PdDyicaBnpC0KRXsHYretLVe+uS9pJy8iM57Kujw==
"@oxlint/binding-linux-arm-gnueabihf@1.61.0":
version "1.61.0"
resolved "https://registry.yarnpkg.com/@oxlint/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.61.0.tgz#df76a5700651cda73ca5578de7308f9479b7cb1b"
integrity sha512-P6040ZkcyweJ0Po9yEFqJCdvZnf3VNCGs1SIHgXDf8AAQNC6ID/heXQs9iSgo2FH7gKaKq32VWc59XZwL34C5Q==
"@oxlint/binding-linux-arm-musleabihf@1.59.0":
version "1.59.0"
resolved "https://registry.yarnpkg.com/@oxlint/binding-linux-arm-musleabihf/-/binding-linux-arm-musleabihf-1.59.0.tgz#76b57f78729d4854458886b68cedfb776586356b"
integrity sha512-TULQW8YBPGRWg5yZpFPL54HLOnJ3/HiX6VenDPi6YfxB/jlItwSMFh3/hCeSNbh+DAMaE1Py0j5MOaivHkI/9Q==
"@oxlint/binding-linux-arm-musleabihf@1.61.0":
version "1.61.0"
resolved "https://registry.yarnpkg.com/@oxlint/binding-linux-arm-musleabihf/-/binding-linux-arm-musleabihf-1.61.0.tgz#b198e5697cfb6cc55eff5ab5b9766732e143492d"
integrity sha512-bwxrGCzTZkuB+THv2TQ1aTkVEfv5oz8sl+0XZZCpoYzErJD8OhPQOTA0ENPd1zJz8QsVdSzSrS2umKtPq4/JXg==
"@oxlint/binding-linux-arm64-gnu@1.59.0":
version "1.59.0"
resolved "https://registry.yarnpkg.com/@oxlint/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.59.0.tgz#64ab5de73c894265726c83cc7e431ba924436e2a"
integrity sha512-Gt54Y4eqSgYJ90xipm24xeyaPV854706o/kiT8oZvUt3VDY7qqxdqyGqchMaujd87ib+/MXvnl9WkK8Cc1BExg==
"@oxlint/binding-linux-arm64-gnu@1.61.0":
version "1.61.0"
resolved "https://registry.yarnpkg.com/@oxlint/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.61.0.tgz#a5cdae7ef9f9b79526ef764fb174bb362c3737ee"
integrity sha512-vkhb9/wKguMkLlrm3FoJW/Xmdv31GgYAE+x8lxxQ+7HeOxXUySI0q36a3NTVIuQUdLzxCI1zzMGsk1o37FOe3w==
"@oxlint/binding-linux-arm64-musl@1.59.0":
version "1.59.0"
resolved "https://registry.yarnpkg.com/@oxlint/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.59.0.tgz#d6bc4914a8d80125e06b61891c125aa1acf326a7"
integrity sha512-3CtsKp7NFB3OfqQzbuAecrY7GIZeiv7AD+xutU4tefVQzlfmTI7/ygWLrvkzsDEjTlMq41rYHxgsn6Yh8tybmA==
"@oxlint/binding-linux-arm64-musl@1.61.0":
version "1.61.0"
resolved "https://registry.yarnpkg.com/@oxlint/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.61.0.tgz#227f6d94a3c00140a88ea338436783bd9da6f4ae"
integrity sha512-bl1dQh8LnVqsj6oOQAcxwbuOmNJkwc4p6o//HTBZhNTzJy21TLDwAviMqUFNUxDHkPGpmdKTSN4tWTjLryP8xg==
"@oxlint/binding-linux-ppc64-gnu@1.59.0":
version "1.59.0"
resolved "https://registry.yarnpkg.com/@oxlint/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.59.0.tgz#c999194b32e6fc75709d451070bf56720af6d537"
integrity sha512-K0diOpT3ncDmOfl9I1HuvpEsAuTxkts0VYwIv/w6Xiy9CdwyPBVX88Ga9l8VlGgMrwBMnSY4xIvVlVY/fkQk7Q==
"@oxlint/binding-linux-ppc64-gnu@1.61.0":
version "1.61.0"
resolved "https://registry.yarnpkg.com/@oxlint/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.61.0.tgz#e504e6cd8691fe460b202599d4c962fc7ad98f94"
integrity sha512-QoOX6KB2IiEpyOj/HKqaxi+NQHPnOgNgnr22n9N4ANJCzXkUlj1UmeAbFb4PpqdlHIzvGDM5xZ0OKtcLq9RhiQ==
"@oxlint/binding-linux-riscv64-gnu@1.59.0":
version "1.59.0"
resolved "https://registry.yarnpkg.com/@oxlint/binding-linux-riscv64-gnu/-/binding-linux-riscv64-gnu-1.59.0.tgz#d85938028f4c4666fe299bbc3c87cceae907fd2b"
integrity sha512-xAU7+QDU6kTJJ7mJLOGgo7oOjtAtkKyFZ0Yjdb5cEo3DiCCPFLvyr08rWiQh6evZ7RiUTf+o65NY/bqttzJiQQ==
"@oxlint/binding-linux-riscv64-gnu@1.61.0":
version "1.61.0"
resolved "https://registry.yarnpkg.com/@oxlint/binding-linux-riscv64-gnu/-/binding-linux-riscv64-gnu-1.61.0.tgz#efe70d9e570756fe19fb60d05a39dd58e9e0f32d"
integrity sha512-1TGcTerjY6p152wCof3oKElccq3xHljS/Mucp04gV/4ATpP6nO7YNnp7opEg6SHkv2a57/b4b8Ndm9znJ1/qAw==
"@oxlint/binding-linux-riscv64-musl@1.59.0":
version "1.59.0"
resolved "https://registry.yarnpkg.com/@oxlint/binding-linux-riscv64-musl/-/binding-linux-riscv64-musl-1.59.0.tgz#9225a568a0138fe0f252785cfffe86d2b35d2e92"
integrity sha512-KUmZmKlTTyauOnvUNVxK7G40sSSx0+w5l1UhaGsC6KPpOYHenx2oqJTnabmpLJicok7IC+3Y6fXAUOMyexaeJQ==
"@oxlint/binding-linux-riscv64-musl@1.61.0":
version "1.61.0"
resolved "https://registry.yarnpkg.com/@oxlint/binding-linux-riscv64-musl/-/binding-linux-riscv64-musl-1.61.0.tgz#f9c42095192f235b03a5d3d1f1fbfb45002d5a93"
integrity sha512-65wXEmZIrX2ADwC8i/qFL4EWLSbeuBpAm3suuX1vu4IQkKd+wLT/HU/BOl84kp91u2SxPkPDyQgu4yrqp8vwVA==
"@oxlint/binding-linux-s390x-gnu@1.59.0":
version "1.59.0"
resolved "https://registry.yarnpkg.com/@oxlint/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.59.0.tgz#75dc546c7c92f3a2c52885ffe5031810bea2f759"
integrity sha512-4usRxC8gS0PGdkHnRmwJt/4zrQNZyk6vL0trCxwZSsAKM+OxhB8nKiR+mhjdBbl8lbMh2gc3bZpNN/ik8c4c2A==
"@oxlint/binding-linux-s390x-gnu@1.61.0":
version "1.61.0"
resolved "https://registry.yarnpkg.com/@oxlint/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.61.0.tgz#23773cd6f8087f98c54383caccb98808fab6cca0"
integrity sha512-TVvhgMvor7Qa6COeXxCJ7ENOM+lcAOGsQ0iUdPSCv2hxb9qSHLQ4XF1h50S6RE1gBOJ0WV3rNukg4JJJP1LWRA==
"@oxlint/binding-linux-x64-gnu@1.59.0":
version "1.59.0"
resolved "https://registry.yarnpkg.com/@oxlint/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.59.0.tgz#aee13be2b05d1ec9859b00693f7729c0438370f8"
integrity sha512-s/rNE2gDmbwAOOP493xk2X7M8LZfI1LJFSSW1+yanz3vuQCFPiHkx4GY+O1HuLUDtkzGlhtMrIcxxzyYLv308w==
"@oxlint/binding-linux-x64-gnu@1.61.0":
version "1.61.0"
resolved "https://registry.yarnpkg.com/@oxlint/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.61.0.tgz#f46b036cf74b382c03f6a00e18cd1ae53096517f"
integrity sha512-SjpS5uYuFoDnDdZPwZE59ndF95AsY47R5MliuneTWR1pDm2CxGJaYXbKULI71t5TVfLQUWmrHEGRL9xvuq6dnA==
"@oxlint/binding-linux-x64-musl@1.59.0":
version "1.59.0"
resolved "https://registry.yarnpkg.com/@oxlint/binding-linux-x64-musl/-/binding-linux-x64-musl-1.59.0.tgz#0a068ef0041332b1cd08132ba0ce70b86c271991"
integrity sha512-+yYj1udJa2UvvIUmEm0IcKgc0UlPMgz0nsSTvkPL2y6n0uU5LgIHSwVu4AHhrve6j9BpVSoRksnz8c9QcvITJA==
"@oxlint/binding-linux-x64-musl@1.61.0":
version "1.61.0"
resolved "https://registry.yarnpkg.com/@oxlint/binding-linux-x64-musl/-/binding-linux-x64-musl-1.61.0.tgz#cff6c4c24de0863cecbce1c1f43adcafacd9011d"
integrity sha512-gGfAeGD4sNJGILZbc/yKcIimO9wQnPMoYp9swAaKeEtwsSQAbU+rsdQze5SBtIP6j0QDzeYd4XSSUCRCF+LIeQ==
"@oxlint/binding-openharmony-arm64@1.59.0":
version "1.59.0"
resolved "https://registry.yarnpkg.com/@oxlint/binding-openharmony-arm64/-/binding-openharmony-arm64-1.59.0.tgz#cc7cdce8aeebcee2d5e8e7fac3d3712d012df77f"
integrity sha512-bUplUb48LYsB3hHlQXP2ZMOenpieWoOyppLAnnAhuPag3MGPnt+7caxE3w/Vl9wpQsTA3gzLntQi9rxWrs7Xqg==
"@oxlint/binding-openharmony-arm64@1.61.0":
version "1.61.0"
resolved "https://registry.yarnpkg.com/@oxlint/binding-openharmony-arm64/-/binding-openharmony-arm64-1.61.0.tgz#9fdd87fc10c2c51c510470f7c4136ab2df9ee7b3"
integrity sha512-OlVT0LrG/ct33EVtWRyR+B/othwmDWeRxfi13wUdPeb3lAT5TgTcFDcfLfarZtzB4W1nWF/zICMgYdkggX2WmQ==
"@oxlint/binding-win32-arm64-msvc@1.59.0":
version "1.59.0"
resolved "https://registry.yarnpkg.com/@oxlint/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.59.0.tgz#6fe74088bac2c9a560aba8475071e5ff4e8be9ba"
integrity sha512-/HLsLuz42rWl7h7ePdmMTpHm2HIDmPtcEMYgm5BBEHiEiuNOrzMaUpd2z7UnNni5LGN9obJy2YoAYBLXQwazrA==
"@oxlint/binding-win32-arm64-msvc@1.61.0":
version "1.61.0"
resolved "https://registry.yarnpkg.com/@oxlint/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.61.0.tgz#25a87f62859a6703f2520d665f98f6c0f88a70e0"
integrity sha512-vI//NZPJk6DToiovPtaiwD4iQ7kO1r5ReWQD0sOOyKRtP3E2f6jxin4uvwi3OvDzHA2EFfd7DcZl5dtkQh7g1w==
"@oxlint/binding-win32-ia32-msvc@1.59.0":
version "1.59.0"
resolved "https://registry.yarnpkg.com/@oxlint/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-1.59.0.tgz#9223a0a4ffa47fd9c0e56e8cf7933253abd93688"
integrity sha512-rUPy+JnanpPwV/aJCPnxAD1fW50+XPI0VkWr7f0vEbqcdsS8NpB24Rw6RsS7SdpFv8Dw+8ugCwao5nCFbqOUSg==
"@oxlint/binding-win32-ia32-msvc@1.61.0":
version "1.61.0"
resolved "https://registry.yarnpkg.com/@oxlint/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-1.61.0.tgz#06f7df42b074f28ef27540d38be69875e5d5afcb"
integrity sha512-0ySj4/4zd2XjePs3XAQq7IigIstN4LPQZgCyigX5/ERMLjdWAJfnxcTsrtxZxuij8guJW8foXuHmhGxW0H4dDA==
"@oxlint/binding-win32-x64-msvc@1.59.0":
version "1.59.0"
resolved "https://registry.yarnpkg.com/@oxlint/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.59.0.tgz#e2d94ab9567a2c1302e15ef927a7363675b740ba"
integrity sha512-xkE7puteDS/vUyRngLXW0t8WgdWoS/tfxXjhP/P7SMqPDx+hs44SpssO3h3qmTqECYEuXBUPzcAw5257Ka+ofA==
"@oxlint/binding-win32-x64-msvc@1.61.0":
version "1.61.0"
resolved "https://registry.yarnpkg.com/@oxlint/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.61.0.tgz#7bc84b261bb8b515ade20f1044547cc8bbee44ce"
integrity sha512-0xgSiyeqDLDZxXoe9CVJrOx3TUVsfyoOY7cNi03JbItNcC9WCZqrSNdrAbHONxhSPaVh/lzfnDcON1RqSUMhHw==
"@parcel/watcher-android-arm64@2.5.1":
version "2.5.1"
@@ -15039,69 +15039,69 @@ own-keys@^1.0.1:
object-keys "^1.1.1"
safe-push-apply "^1.0.0"
oxfmt@0.41.0:
version "0.41.0"
resolved "https://registry.yarnpkg.com/oxfmt/-/oxfmt-0.41.0.tgz#7dbfb63d19704a85412f36678c3ace092ce88673"
integrity sha512-sKLdJZdQ3bw6x9qKiT7+eID4MNEXlDHf5ZacfIircrq6Qwjk0L6t2/JQlZZrVHTXJawK3KaMuBoJnEJPcqCEdg==
oxfmt@0.46.0:
version "0.46.0"
resolved "https://registry.yarnpkg.com/oxfmt/-/oxfmt-0.46.0.tgz#33789146055129820102203b4397eb4932ada687"
integrity sha512-CopwJOwPAjZ9p76fCvz+mSOJTw9/NY3cSksZK3VO/bUQ8UoEcketNgUuYS0UB3p+R9XnXe7wGGXUmyFxc7QxJA==
dependencies:
tinypool "2.1.0"
optionalDependencies:
"@oxfmt/binding-android-arm-eabi" "0.41.0"
"@oxfmt/binding-android-arm64" "0.41.0"
"@oxfmt/binding-darwin-arm64" "0.41.0"
"@oxfmt/binding-darwin-x64" "0.41.0"
"@oxfmt/binding-freebsd-x64" "0.41.0"
"@oxfmt/binding-linux-arm-gnueabihf" "0.41.0"
"@oxfmt/binding-linux-arm-musleabihf" "0.41.0"
"@oxfmt/binding-linux-arm64-gnu" "0.41.0"
"@oxfmt/binding-linux-arm64-musl" "0.41.0"
"@oxfmt/binding-linux-ppc64-gnu" "0.41.0"
"@oxfmt/binding-linux-riscv64-gnu" "0.41.0"
"@oxfmt/binding-linux-riscv64-musl" "0.41.0"
"@oxfmt/binding-linux-s390x-gnu" "0.41.0"
"@oxfmt/binding-linux-x64-gnu" "0.41.0"
"@oxfmt/binding-linux-x64-musl" "0.41.0"
"@oxfmt/binding-openharmony-arm64" "0.41.0"
"@oxfmt/binding-win32-arm64-msvc" "0.41.0"
"@oxfmt/binding-win32-ia32-msvc" "0.41.0"
"@oxfmt/binding-win32-x64-msvc" "0.41.0"
"@oxfmt/binding-android-arm-eabi" "0.46.0"
"@oxfmt/binding-android-arm64" "0.46.0"
"@oxfmt/binding-darwin-arm64" "0.46.0"
"@oxfmt/binding-darwin-x64" "0.46.0"
"@oxfmt/binding-freebsd-x64" "0.46.0"
"@oxfmt/binding-linux-arm-gnueabihf" "0.46.0"
"@oxfmt/binding-linux-arm-musleabihf" "0.46.0"
"@oxfmt/binding-linux-arm64-gnu" "0.46.0"
"@oxfmt/binding-linux-arm64-musl" "0.46.0"
"@oxfmt/binding-linux-ppc64-gnu" "0.46.0"
"@oxfmt/binding-linux-riscv64-gnu" "0.46.0"
"@oxfmt/binding-linux-riscv64-musl" "0.46.0"
"@oxfmt/binding-linux-s390x-gnu" "0.46.0"
"@oxfmt/binding-linux-x64-gnu" "0.46.0"
"@oxfmt/binding-linux-x64-musl" "0.46.0"
"@oxfmt/binding-openharmony-arm64" "0.46.0"
"@oxfmt/binding-win32-arm64-msvc" "0.46.0"
"@oxfmt/binding-win32-ia32-msvc" "0.46.0"
"@oxfmt/binding-win32-x64-msvc" "0.46.0"
oxlint-tsgolint@0.20.0:
version "0.20.0"
resolved "https://registry.yarnpkg.com/oxlint-tsgolint/-/oxlint-tsgolint-0.20.0.tgz#e011319219faa572faf340c40d65708404b37e10"
integrity sha512-/Uc9TQyN1l8w9QNvXtVHYtz+SzDJHKpb5X0UnHodl0BVzijUPk0LPlDOHAvogd1UI+iy9ZSF6gQxEqfzUxCULQ==
oxlint-tsgolint@0.21.1:
version "0.21.1"
resolved "https://registry.yarnpkg.com/oxlint-tsgolint/-/oxlint-tsgolint-0.21.1.tgz#92e87d455283b346087fbff6d0481da9b5276640"
integrity sha512-O2hxiT14C2HJkwzBU6CQBFPoagSd/IcV+Tt3e3UUaXFwbW4BO5DSDPSSboc3UM5MIDY+MLyepvtQwBQafNxWdw==
optionalDependencies:
"@oxlint-tsgolint/darwin-arm64" "0.20.0"
"@oxlint-tsgolint/darwin-x64" "0.20.0"
"@oxlint-tsgolint/linux-arm64" "0.20.0"
"@oxlint-tsgolint/linux-x64" "0.20.0"
"@oxlint-tsgolint/win32-arm64" "0.20.0"
"@oxlint-tsgolint/win32-x64" "0.20.0"
"@oxlint-tsgolint/darwin-arm64" "0.21.1"
"@oxlint-tsgolint/darwin-x64" "0.21.1"
"@oxlint-tsgolint/linux-arm64" "0.21.1"
"@oxlint-tsgolint/linux-x64" "0.21.1"
"@oxlint-tsgolint/win32-arm64" "0.21.1"
"@oxlint-tsgolint/win32-x64" "0.21.1"
oxlint@1.59.0:
version "1.59.0"
resolved "https://registry.yarnpkg.com/oxlint/-/oxlint-1.59.0.tgz#7bc0b9abe87b96ed7d640f90cb0956b9558f3b96"
integrity sha512-0xBLeGGjP4vD9pygRo8iuOkOzEU1MqOnfiOl7KYezL/QvWL8NUg6n03zXc7ZVqltiOpUxBk2zgHI3PnRIEdAvw==
oxlint@1.61.0:
version "1.61.0"
resolved "https://registry.yarnpkg.com/oxlint/-/oxlint-1.61.0.tgz#e714742cfe7b815713feb14600b0ffe963d539a4"
integrity sha512-ZC0ALuhDZ6ivOFG+sy0D0pEDN49EvsId98zVlmYdkcXHsEM14m/qTNUEsUpiFiCVbpIxYtVBmmLE87nsbUHohQ==
optionalDependencies:
"@oxlint/binding-android-arm-eabi" "1.59.0"
"@oxlint/binding-android-arm64" "1.59.0"
"@oxlint/binding-darwin-arm64" "1.59.0"
"@oxlint/binding-darwin-x64" "1.59.0"
"@oxlint/binding-freebsd-x64" "1.59.0"
"@oxlint/binding-linux-arm-gnueabihf" "1.59.0"
"@oxlint/binding-linux-arm-musleabihf" "1.59.0"
"@oxlint/binding-linux-arm64-gnu" "1.59.0"
"@oxlint/binding-linux-arm64-musl" "1.59.0"
"@oxlint/binding-linux-ppc64-gnu" "1.59.0"
"@oxlint/binding-linux-riscv64-gnu" "1.59.0"
"@oxlint/binding-linux-riscv64-musl" "1.59.0"
"@oxlint/binding-linux-s390x-gnu" "1.59.0"
"@oxlint/binding-linux-x64-gnu" "1.59.0"
"@oxlint/binding-linux-x64-musl" "1.59.0"
"@oxlint/binding-openharmony-arm64" "1.59.0"
"@oxlint/binding-win32-arm64-msvc" "1.59.0"
"@oxlint/binding-win32-ia32-msvc" "1.59.0"
"@oxlint/binding-win32-x64-msvc" "1.59.0"
"@oxlint/binding-android-arm-eabi" "1.61.0"
"@oxlint/binding-android-arm64" "1.61.0"
"@oxlint/binding-darwin-arm64" "1.61.0"
"@oxlint/binding-darwin-x64" "1.61.0"
"@oxlint/binding-freebsd-x64" "1.61.0"
"@oxlint/binding-linux-arm-gnueabihf" "1.61.0"
"@oxlint/binding-linux-arm-musleabihf" "1.61.0"
"@oxlint/binding-linux-arm64-gnu" "1.61.0"
"@oxlint/binding-linux-arm64-musl" "1.61.0"
"@oxlint/binding-linux-ppc64-gnu" "1.61.0"
"@oxlint/binding-linux-riscv64-gnu" "1.61.0"
"@oxlint/binding-linux-riscv64-musl" "1.61.0"
"@oxlint/binding-linux-s390x-gnu" "1.61.0"
"@oxlint/binding-linux-x64-gnu" "1.61.0"
"@oxlint/binding-linux-x64-musl" "1.61.0"
"@oxlint/binding-openharmony-arm64" "1.61.0"
"@oxlint/binding-win32-arm64-msvc" "1.61.0"
"@oxlint/binding-win32-ia32-msvc" "1.61.0"
"@oxlint/binding-win32-x64-msvc" "1.61.0"
p-cancelable@^2.0.0:
version "2.1.1"

View File

@@ -134,24 +134,6 @@
"unit": "Count",
"type": "Gauge",
"description": ""
},
{
"name": "azure_webapplicationfirewallcaptcharequestcount_total",
"unit": "Count",
"type": "Gauge",
"description": ""
},
{
"name": "azure_webapplicationfirewalljsrequestcount_total",
"unit": "Count",
"type": "Gauge",
"description": ""
},
{
"name": "azure_websocketconnections_total",
"unit": "Count",
"type": "Gauge",
"description": ""
}
],
"logs": [

View File

@@ -440,4 +440,3 @@ func (handler *handler) AgentCheckIn(rw http.ResponseWriter, r *http.Request) {
render.Success(rw, http.StatusOK, cloudintegrationtypes.NewGettableAgentCheckIn(provider, resp))
}

View File

@@ -1,39 +1,5 @@
package cloudintegrationtypes
import (
"bytes"
"fmt"
"text/template"
"github.com/SigNoz/signoz/pkg/valuer"
)
var (
AgentArmTemplateStorePath = "https://signoz-integrations.s3.us-east-1.amazonaws.com/azure-arm-template-%s.json"
AgentDeploymentStackName = "signoz-integration"
// Default values for fixed ARM template parameters.
armDefaultRgName = "signoz-integration-rg"
armDefaultContainerEnvName = "signoz-integration-agent-env"
armDefaultDeploymentEnv = "production"
// ARM template parameter key names used in both CLI and PowerShell deployment commands.
armParamLocation = "location"
armParamSignozAPIKey = "signozApiKey"
armParamSignozAPIUrl = "signozApiUrl"
armParamSignozIngestionURL = "signozIngestionUrl"
armParamSignozIngestionKey = "signozIngestionKey"
armParamAccountID = "signozIntegrationAccountId"
armParamAgentVersion = "signozIntegrationAgentVersion"
armParamRgName = "rgName"
armParamContainerEnvName = "containerEnvName"
armParamDeploymentEnv = "deploymentEnv"
// command templates.
azureCLITemplate = template.Must(template.New("azureCLI").Parse(azureCLITemplateStr()))
azurePowerShellTemplate = template.Must(template.New("azurePS").Parse(azurePowerShellTemplateStr()))
)
type AzureAccountConfig struct {
DeploymentRegion string `json:"deploymentRegion" required:"true"`
ResourceGroups []string `json:"resourceGroups" required:"true" nullable:"false"`
@@ -85,36 +51,6 @@ type AzureIntegrationConfig struct {
TelemetryCollectionStrategy []*AzureTelemetryCollectionStrategy `json:"telemetryCollectionStrategy" required:"true" nullable:"false"`
}
// azureTemplateData is the data struct passed to both command templates.
// All fields are exported so text/template can access them.
type azureTemplateData struct {
// Deploy parameter values.
TemplateURL string
Location string
SignozAPIKey string
SignozAPIUrl string
SignozIngestionURL string
SignozIngestionKey string
AccountID string
AgentVersion string
// ARM parameter key names (from package-level vars).
StackName string
ParamLocation string
ParamSignozAPIKey string
ParamSignozAPIUrl string
ParamSignozIngestionURL string
ParamSignozIngestionKey string
ParamAccountID string
ParamAgentVersion string
ParamRgName string
ParamContainerEnvName string
ParamDeploymentEnv string
// Fixed default values.
DefaultRgName string
DefaultContainerEnvName string
DefaultDeploymentEnv string
}
func NewAzureIntegrationConfig(
deploymentRegion string,
resourceGroups []string,
@@ -126,105 +62,3 @@ func NewAzureIntegrationConfig(
TelemetryCollectionStrategy: strategies,
}
}
func NewAzureConnectionArtifact(
accountID valuer.UUID,
agentVersion string,
creds *Credentials,
cfg *AzurePostableAccountConfig,
) (*AzureConnectionArtifact, error) {
data := azureTemplateData{
TemplateURL: fmt.Sprintf(AgentArmTemplateStorePath, agentVersion),
Location: cfg.DeploymentRegion,
SignozAPIKey: creds.SigNozAPIKey,
SignozAPIUrl: creds.SigNozAPIURL,
SignozIngestionURL: creds.IngestionURL,
SignozIngestionKey: creds.IngestionKey,
AccountID: accountID.StringValue(),
AgentVersion: agentVersion,
StackName: AgentDeploymentStackName,
ParamLocation: armParamLocation,
ParamSignozAPIKey: armParamSignozAPIKey,
ParamSignozAPIUrl: armParamSignozAPIUrl,
ParamSignozIngestionURL: armParamSignozIngestionURL,
ParamSignozIngestionKey: armParamSignozIngestionKey,
ParamAccountID: armParamAccountID,
ParamAgentVersion: armParamAgentVersion,
ParamRgName: armParamRgName,
ParamContainerEnvName: armParamContainerEnvName,
ParamDeploymentEnv: armParamDeploymentEnv,
DefaultRgName: armDefaultRgName,
DefaultContainerEnvName: armDefaultContainerEnvName,
DefaultDeploymentEnv: armDefaultDeploymentEnv,
}
cliCommand, err := newAzureConnectionCLICommand(data)
if err != nil {
return nil, err
}
psCommand, err := newAzureConnectionPowerShellCommand(data)
if err != nil {
return nil, err
}
return &AzureConnectionArtifact{
CLICommand: cliCommand,
CloudPowerShellCommand: psCommand,
}, nil
}
func newAzureConnectionCLICommand(data azureTemplateData) (string, error) {
var buf bytes.Buffer
err := azureCLITemplate.Execute(&buf, data)
if err != nil {
return "", err
}
return buf.String(), nil
}
func newAzureConnectionPowerShellCommand(data azureTemplateData) (string, error) {
var buf bytes.Buffer
err := azurePowerShellTemplate.Execute(&buf, data)
if err != nil {
return "", err
}
return buf.String(), nil
}
func azureCLITemplateStr() string {
return `az stack sub create \
--name {{.StackName}} \
--location {{.Location}} \
--template-uri {{.TemplateURL}} \
--parameters \
{{.ParamLocation}}='{{.Location}}' \
{{.ParamSignozAPIKey}}='{{.SignozAPIKey}}' \
{{.ParamSignozAPIUrl}}='{{.SignozAPIUrl}}' \
{{.ParamSignozIngestionURL}}='{{.SignozIngestionURL}}' \
{{.ParamSignozIngestionKey}}='{{.SignozIngestionKey}}' \
{{.ParamAccountID}}='{{.AccountID}}' \
{{.ParamAgentVersion}}='{{.AgentVersion}}' \
--action-on-unmanage deleteAll \
--deny-settings-mode denyDelete`
}
func azurePowerShellTemplateStr() string {
return "New-AzSubscriptionDeploymentStack `\n" +
" -Name \"{{.StackName}}\" `\n" +
" -Location \"{{.Location}}\" `\n" +
" -TemplateUri \"{{.TemplateURL}}\" `\n" +
" -TemplateParameterObject @{\n" +
" {{.ParamLocation}} = \"{{.Location}}\"\n" +
" {{.ParamSignozAPIKey}} = \"{{.SignozAPIKey}}\"\n" +
" {{.ParamSignozAPIUrl}} = \"{{.SignozAPIUrl}}\"\n" +
" {{.ParamSignozIngestionURL}} = \"{{.SignozIngestionURL}}\"\n" +
" {{.ParamSignozIngestionKey}} = \"{{.SignozIngestionKey}}\"\n" +
" {{.ParamAccountID}} = \"{{.AccountID}}\"\n" +
" {{.ParamAgentVersion}} = \"{{.AgentVersion}}\"\n" +
" {{.ParamRgName}} = \"{{.DefaultRgName}}\"\n" +
" {{.ParamContainerEnvName}} = \"{{.DefaultContainerEnvName}}\"\n" +
" {{.ParamDeploymentEnv}} = \"{{.DefaultDeploymentEnv}}\"\n" +
" } `\n" +
" -ActionOnUnmanage \"deleteAll\" `\n" +
" -DenySettingsMode \"denyDelete\""
}

View File

@@ -1,142 +0,0 @@
// file is generated by AI
package cloudintegrationtypes
import (
"fmt"
"strings"
"testing"
"github.com/SigNoz/signoz/pkg/valuer"
)
var testTemplateData = azureTemplateData{
TemplateURL: fmt.Sprintf(AgentArmTemplateStorePath, "v0.1.0"),
Location: "eastus",
SignozAPIKey: "test-api-key",
SignozAPIUrl: "https://signoz.example.com",
SignozIngestionURL: "https://ingest.example.com",
SignozIngestionKey: "test-ingest-key",
AccountID: "acct-123",
AgentVersion: "v0.1.0",
StackName: AgentDeploymentStackName,
ParamLocation: armParamLocation,
ParamSignozAPIKey: armParamSignozAPIKey,
ParamSignozAPIUrl: armParamSignozAPIUrl,
ParamSignozIngestionURL: armParamSignozIngestionURL,
ParamSignozIngestionKey: armParamSignozIngestionKey,
ParamAccountID: armParamAccountID,
ParamAgentVersion: armParamAgentVersion,
ParamRgName: armParamRgName,
ParamContainerEnvName: armParamContainerEnvName,
ParamDeploymentEnv: armParamDeploymentEnv,
DefaultRgName: armDefaultRgName,
DefaultContainerEnvName: armDefaultContainerEnvName,
DefaultDeploymentEnv: armDefaultDeploymentEnv,
}
func TestNewAzureConnectionCLICommand(t *testing.T) {
cmd, err := newAzureConnectionCLICommand(testTemplateData)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
mustContain := []string{
"az stack sub create",
fmt.Sprintf("--name %s", AgentDeploymentStackName),
fmt.Sprintf("--location %s", testTemplateData.Location),
fmt.Sprintf("--template-uri %s", testTemplateData.TemplateURL),
fmt.Sprintf("%s='%s'", armParamLocation, testTemplateData.Location),
fmt.Sprintf("%s='%s'", armParamSignozAPIKey, testTemplateData.SignozAPIKey),
fmt.Sprintf("%s='%s'", armParamSignozAPIUrl, testTemplateData.SignozAPIUrl),
fmt.Sprintf("%s='%s'", armParamSignozIngestionURL, testTemplateData.SignozIngestionURL),
fmt.Sprintf("%s='%s'", armParamSignozIngestionKey, testTemplateData.SignozIngestionKey),
fmt.Sprintf("%s='%s'", armParamAccountID, testTemplateData.AccountID),
fmt.Sprintf("%s='%s'", armParamAgentVersion, testTemplateData.AgentVersion),
"--action-on-unmanage deleteAll",
"--deny-settings-mode denyDelete",
}
for _, fragment := range mustContain {
if !strings.Contains(cmd, fragment) {
t.Errorf("CLI command missing %q\ngot:\n%s", fragment, cmd)
}
}
// Lines must be joined with the bash line-continuation separator.
if !strings.Contains(cmd, " \\\n") {
t.Errorf("CLI command missing line-continuation separator ' \\\\\\n'\ngot:\n%s", cmd)
}
}
func TestNewAzureConnectionPowerShellCommand(t *testing.T) {
cmd, err := newAzureConnectionPowerShellCommand(testTemplateData)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
mustContain := []string{
"New-AzSubscriptionDeploymentStack",
fmt.Sprintf("-Name \"%s\"", AgentDeploymentStackName),
fmt.Sprintf("-Location \"%s\"", testTemplateData.Location),
fmt.Sprintf("-TemplateUri \"%s\"", testTemplateData.TemplateURL),
armParamLocation,
armParamSignozAPIKey,
armParamSignozAPIUrl,
armParamSignozIngestionURL,
armParamSignozIngestionKey,
armParamAccountID,
armParamAgentVersion,
armParamRgName,
armParamContainerEnvName,
armParamDeploymentEnv,
armDefaultRgName,
armDefaultContainerEnvName,
armDefaultDeploymentEnv,
"-ActionOnUnmanage \"deleteAll\"",
"-DenySettingsMode \"denyDelete\"",
}
for _, fragment := range mustContain {
if !strings.Contains(cmd, fragment) {
t.Errorf("PowerShell command missing %q\ngot:\n%s", fragment, cmd)
}
}
// Final command must not end with a line-continuation backtick.
if strings.HasSuffix(strings.TrimSpace(cmd), "`") {
t.Errorf("PowerShell command must not end with a backtick\ngot:\n%s", cmd)
}
}
func TestNewAzureConnectionArtifact(t *testing.T) {
accountID := valuer.GenerateUUID()
agentVersion := "v0.1.0"
creds := &Credentials{
SigNozAPIURL: "https://signoz.example.com",
SigNozAPIKey: "test-api-key",
IngestionURL: "https://ingest.example.com",
IngestionKey: "test-ingest-key",
}
cfg := &AzurePostableAccountConfig{
DeploymentRegion: "eastus",
ResourceGroups: []string{"rg1"},
}
artifact, err := NewAzureConnectionArtifact(accountID, agentVersion, creds, cfg)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if artifact.CLICommand == "" {
t.Error("CLICommand must not be empty")
}
if artifact.CloudPowerShellCommand == "" {
t.Error("CloudPowerShellCommand must not be empty")
}
if !strings.Contains(artifact.CLICommand, accountID.StringValue()) {
t.Errorf("CLICommand must contain accountID %q", accountID.StringValue())
}
if !strings.Contains(artifact.CloudPowerShellCommand, accountID.StringValue()) {
t.Errorf("CloudPowerShellCommand must contain accountID %q", accountID.StringValue())
}
}

View File

@@ -239,10 +239,6 @@ func (service *CloudIntegrationService) Update(provider CloudProviderType, servi
}
// other validations happen in newStorableServiceConfig
case CloudProviderTypeAzure:
if config.Azure == nil {
return errors.NewInvalidInputf(ErrCodeCloudProviderInvalidInput, "Azure config is required for Azure service")
}
default:
return errors.NewInvalidInputf(ErrCodeCloudProviderInvalidInput, "invalid cloud provider: %s", provider.StringValue())
}