Compare commits

...

49 Commits

Author SHA1 Message Date
swapnil-signoz
c7f656bf5b Merge branch 'main' into feat/cloudintegration-azure-impl 2026-04-24 00:31:20 +05:30
swapnil-signoz
04552fa2e9 feat: cloud integration azure service definitions (#11006)
* refactor: moving types to cloud provider specific namespace/pkg

* refactor: separating cloud provider types

* refactor: using upper case key for AWS

* feat: adding cloud integration azure types

* feat: adding azure services

* refactor: updating omitempty tags

* refactor: updating azure integration config

* feat: completing azure types

* refactor: lint issues

* feat: adding service definitions for azure

* refactor: update service names for Azure Blob Storage telemetry

* refactor: updating definitions with metrics and strategy

* refactor: updating command key

* fix: handle optional connection URL in AWS integration

* refactor: updating strategy struct

* refactor: updating telemetry strategy

* refactor: updating blob storage service name

* refactor: updating azure blob storage service name

* refactor: update Azure service identifiers

* refactor: updating service defs

* refactor: updating types
2026-04-23 18:42:10 +00:00
SagarRajput-7
535ee9900c feat(base-path): replace window.open with openInNewTab for internal paths and upgraded lint to error (#11027)
* feat(base-path): replace window.open with openInNewTab for internal paths

* feat(base-path): migrate remaining pattern for window.location.origin + path

* feat(base-path): migrate backend bound urls and eslint upgrade to error (#11028)

* feat(base-path): migrate backend bound urls and eslint upgrade to error

* feat(base-path): migrated the new files added after rebase with main

* feat(base-path): updated lint error comment to oxlint
2026-04-23 18:28:48 +00:00
swapnil-signoz
8533a85dcf refactor: adding missing case for azure service update 2026-04-23 23:36:24 +05:30
Vikrant Gupta
07e7fcac4b feat(authz): add check API for community build (#11056)
* feat(authz): add check API for community build

* feat(authz): move to types

* feat(authz): fix the role corelations

* feat(authz): fix the role corelations

* fix(authz): single line returns
2026-04-23 17:59:46 +00:00
swapnil-signoz
bc49a19f15 Merge branch 'main' into feat/cloudintegration-azure-impl 2026-04-23 23:02:01 +05:30
swapnil-signoz
bb16d92176 Merge branch 'feat/cloudintegration-azure-service-def' into feat/cloudintegration-azure-impl 2026-04-23 23:01:14 +05:30
swapnil-signoz
2efa776de2 Merge branch 'main' into feat/cloudintegration-azure-service-def 2026-04-23 20:39:17 +05:30
swapnil-signoz
97d4f29d3a Merge branch 'feat/cloudintegration-azure-service-def' into feat/cloudintegration-azure-impl 2026-04-23 01:34:51 +05:30
swapnil-signoz
7aa720a6e1 Merge branch 'feat/cloudintegration-azure-types' into feat/cloudintegration-azure-service-def 2026-04-23 01:34:32 +05:30
swapnil-signoz
1296ae627d refactor: updating types 2026-04-23 01:34:10 +05:30
swapnil-signoz
4771391b9d Merge branch 'feat/cloudintegration-azure-impl' of https://github.com/SigNoz/signoz into feat/cloudintegration-azure-impl 2026-04-22 23:56:55 +05:30
swapnil-signoz
79e6485379 refactor: updating deny settings mode 2026-04-22 23:56:39 +05:30
swapnil-signoz
69f0508b15 Merge branch 'main' into feat/cloudintegration-azure-impl 2026-04-22 22:57:19 +05:30
swapnil-signoz
481e94e5d2 fix: update integration account ID and add agent version to Azure CLI and PowerShell commands 2026-04-22 19:33:20 +05:30
swapnil-signoz
4eadc17fd1 Merge branch 'feat/cloudintegration-azure-service-def' into feat/cloudintegration-azure-impl 2026-04-22 17:48:18 +05:30
swapnil-signoz
2acee1a8eb refactor: updating service defs 2026-04-22 17:47:34 +05:30
swapnil-signoz
f3c9129fa6 Merge branch 'feat/cloudintegration-azure-types' into feat/cloudintegration-azure-service-def 2026-04-22 17:43:16 +05:30
swapnil-signoz
3ab42a9012 refactor: update Azure service identifiers 2026-04-22 17:42:23 +05:30
swapnil-signoz
f870efbdac Merge branch 'feat/cloudintegration-azure-service-def' into feat/cloudintegration-azure-impl 2026-04-22 14:43:08 +05:30
swapnil-signoz
5068f412e6 Merge branch 'feat/cloudintegration-azure-types' into feat/cloudintegration-azure-service-def 2026-04-22 14:42:10 +05:30
swapnil-signoz
7f5d1d8ddb refactor: updating azure blob storage service name 2026-04-22 14:41:02 +05:30
swapnil-signoz
a81501bd87 refactor: updating blob storage service name 2026-04-22 14:39:37 +05:30
swapnil-signoz
06cc2b71c6 refactor: updating connection artifact struct 2026-04-22 14:28:05 +05:30
swapnil-signoz
2a23522510 Merge branch 'feat/cloudintegration-azure-service-def' into feat/cloudintegration-azure-impl 2026-04-22 13:58:58 +05:30
swapnil-signoz
f3dfff6de1 refactor: updating telemetry strategy 2026-04-22 13:58:16 +05:30
swapnil-signoz
841c928b90 Merge branch 'feat/cloudintegration-azure-types' into feat/cloudintegration-azure-service-def 2026-04-22 13:53:10 +05:30
swapnil-signoz
e3374d0bf3 refactor: updating strategy struct 2026-04-22 13:52:30 +05:30
swapnil-signoz
a7cd98dd8f feat: wip 2026-04-22 13:46:16 +05:30
swapnil-signoz
2714ded0b5 fix: handle optional connection URL in AWS integration 2026-04-22 01:45:56 +05:30
swapnil-signoz
c54513c327 Merge branch 'main' into feat/cloudintegration-azure-types 2026-04-22 01:44:25 +05:30
swapnil-signoz
7050a9a841 refactor: updating command key 2026-04-20 22:05:42 +05:30
swapnil-signoz
aea5447f55 Merge branch 'feat/cloudintegration-azure-types' into feat/cloudintegration-azure-service-def 2026-04-20 18:48:05 +05:30
swapnil-signoz
d65628d989 Merge branch 'main' into feat/cloudintegration-azure-types 2026-04-20 18:47:53 +05:30
swapnil-signoz
2d96ea84e5 Merge branch 'feat/cloudintegration-azure-types' into feat/cloudintegration-azure-service-def 2026-04-20 18:43:04 +05:30
swapnil-signoz
ff38502517 Merge branch 'main' into feat/cloudintegration-azure-types 2026-04-20 16:32:34 +05:30
swapnil-signoz
e50d0684b3 refactor: updating definitions with metrics and strategy 2026-04-20 15:01:39 +05:30
swapnil-signoz
6463029786 refactor: update service names for Azure Blob Storage telemetry 2026-04-20 15:01:39 +05:30
swapnil-signoz
806108d7b6 feat: adding service definitions for azure 2026-04-20 15:01:39 +05:30
swapnil-signoz
617afeb64b refactor: lint issues 2026-04-20 15:01:26 +05:30
swapnil-signoz
3737905670 feat: completing azure types 2026-04-20 14:37:49 +05:30
swapnil-signoz
54c79642f5 refactor: updating azure integration config 2026-04-20 11:19:41 +05:30
swapnil-signoz
0682c528da refactor: updating omitempty tags 2026-04-20 10:26:08 +05:30
swapnil-signoz
3377bc8a2b feat: adding azure services 2026-04-20 09:51:43 +05:30
swapnil-signoz
3b0d5dcf0e feat: adding cloud integration azure types 2026-04-19 19:20:06 +05:30
swapnil-signoz
aa8c4471dc refactor: using upper case key for AWS 2026-04-19 00:49:02 +05:30
swapnil-signoz
04b8ef4d86 refactor: separating cloud provider types 2026-04-19 00:24:13 +05:30
swapnil-signoz
9aee83607f Merge branch 'main' into refactor/cloudprovider-types-separation 2026-04-19 00:17:15 +05:30
swapnil-signoz
d8abbce47e refactor: moving types to cloud provider specific namespace/pkg 2026-04-17 11:57:41 +05:30
82 changed files with 836 additions and 189 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()
azureCloudProviderModule := implcloudprovider.NewAzureCloudProvider(defStore)
cloudProvidersMap := map[cloudintegrationtypes.CloudProviderType]cloudintegration.CloudProviderModule{
cloudintegrationtypes.CloudProviderTypeAWS: awsCloudProviderModule,
cloudintegrationtypes.CloudProviderTypeAzure: azureCloudProviderModule,

View File

@@ -86,6 +86,28 @@ func (provider *provider) BatchCheck(ctx context.Context, tupleReq map[string]*o
return provider.openfgaServer.BatchCheck(ctx, tupleReq)
}
func (provider *provider) CheckTransactions(ctx context.Context, subject string, orgID valuer.UUID, transactions []*authtypes.Transaction) ([]*authtypes.TransactionWithAuthorization, error) {
tuples, err := authtypes.NewTuplesFromTransactions(transactions, subject, orgID)
if err != nil {
return nil, err
}
batchResults, err := provider.openfgaServer.BatchCheck(ctx, tuples)
if err != nil {
return nil, err
}
results := make([]*authtypes.TransactionWithAuthorization, len(transactions))
for i, txn := range transactions {
result := batchResults[txn.ID.StringValue()]
results[i] = &authtypes.TransactionWithAuthorization{
Transaction: txn,
Authorized: result.Authorized,
}
}
return results, nil
}
func (provider *provider) ListObjects(ctx context.Context, subject string, relation authtypes.Relation, objectType authtypes.Type) ([]*authtypes.Object, error) {
return provider.openfgaServer.ListObjects(ctx, subject, relation, objectType)
}

View File

@@ -2,27 +2,47 @@ package implcloudprovider
import (
"context"
"sort"
"github.com/SigNoz/signoz/pkg/modules/cloudintegration"
"github.com/SigNoz/signoz/pkg/types/cloudintegrationtypes"
)
type azurecloudprovider struct{}
type azurecloudprovider struct {
serviceDefinitions cloudintegrationtypes.ServiceDefinitionStore
}
func NewAzureCloudProvider() cloudintegration.CloudProviderModule {
return &azurecloudprovider{}
func NewAzureCloudProvider(defStore cloudintegrationtypes.ServiceDefinitionStore) cloudintegration.CloudProviderModule {
return &azurecloudprovider{
serviceDefinitions: defStore,
}
}
func (provider *azurecloudprovider) GetConnectionArtifact(ctx context.Context, account *cloudintegrationtypes.Account, req *cloudintegrationtypes.GetConnectionArtifactRequest) (*cloudintegrationtypes.ConnectionArtifact, error) {
panic("implement me")
cliCommand := cloudintegrationtypes.NewAzureConnectionCLICommand(account.ID, req.Config.AgentVersion, req.Credentials, req.Config.Azure)
psCommand := cloudintegrationtypes.NewAzureConnectionPowerShellCommand(account.ID, req.Config.AgentVersion, req.Credentials, req.Config.Azure)
return &cloudintegrationtypes.ConnectionArtifact{
Azure: cloudintegrationtypes.NewAzureConnectionArtifact(cliCommand, psCommand),
}, nil
}
func (provider *azurecloudprovider) ListServiceDefinitions(ctx context.Context) ([]*cloudintegrationtypes.ServiceDefinition, error) {
panic("implement me")
return provider.serviceDefinitions.List(ctx, cloudintegrationtypes.CloudProviderTypeAzure)
}
func (provider *azurecloudprovider) GetServiceDefinition(ctx context.Context, serviceID cloudintegrationtypes.ServiceID) (*cloudintegrationtypes.ServiceDefinition, error) {
panic("implement me")
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
}
func (provider *azurecloudprovider) BuildIntegrationConfig(
@@ -30,5 +50,56 @@ func (provider *azurecloudprovider) BuildIntegrationConfig(
account *cloudintegrationtypes.Account,
services []*cloudintegrationtypes.StorableCloudIntegrationService,
) (*cloudintegrationtypes.ProviderIntegrationConfig, error) {
panic("implement me")
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
}

View File

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

View File

@@ -286,7 +286,7 @@
// Prevents useStore.getState() - export standalone actions instead
"signoz/no-navigator-clipboard": "error",
// Prevents navigator.clipboard - use useCopyToClipboard hook instead (disabled in tests via override)
"signoz/no-raw-absolute-path": "warn",
"signoz/no-raw-absolute-path": "error",
// Prevents window.open(path), window.location.origin + path, window.location.href = path
"no-restricted-imports": [
"error",

View File

@@ -40,12 +40,12 @@
<meta
data-react-helmet="true"
property="og:image"
content="/images/signoz-hero-image.webp"
content="[[.BaseHref]]images/signoz-hero-image.webp"
/>
<meta
data-react-helmet="true"
name="twitter:image"
content="/images/signoz-hero-image.webp"
content="[[.BaseHref]]images/signoz-hero-image.webp"
/>
<meta
data-react-helmet="true"

View File

@@ -9,6 +9,7 @@ import { CreditCard, MessageSquareText, X } from 'lucide-react';
import { SuccessResponseV2 } from 'types/api';
import { CheckoutSuccessPayloadProps } from 'types/api/billing/checkout';
import APIError from 'types/api/error';
import { getBaseUrl } from 'utils/basePath';
export default function ChatSupportGateway(): JSX.Element {
const { notifications } = useNotifications();
@@ -54,7 +55,7 @@ export default function ChatSupportGateway(): JSX.Element {
});
updateCreditCard({
url: window.location.origin,
url: getBaseUrl(),
});
};

View File

@@ -28,6 +28,7 @@ import { useAppContext } from 'providers/App/App';
import { useErrorModal } from 'providers/ErrorModalProvider';
import { useTimezone } from 'providers/Timezone';
import APIError from 'types/api/error';
import { getAbsoluteUrl } from 'utils/basePath';
import { toAPIError } from 'utils/errorUtils';
import DeleteMemberDialog from './DeleteMemberDialog';
@@ -381,7 +382,7 @@ function EditMemberDrawer({
pathParams: { id: member.id },
});
if (response?.data?.token) {
const link = `${window.location.origin}/password-reset?token=${response.data.token}`;
const link = getAbsoluteUrl(`/password-reset?token=${response.data.token}`);
setResetLink(link);
setResetLinkExpiresAt(
response.data.expiresAt

View File

@@ -13,6 +13,7 @@ import GetMinMax from 'lib/getMinMax';
import { Check, Info, Link2 } from 'lucide-react';
import { AppState } from 'store/reducers';
import { GlobalReducer } from 'types/reducer/globalTime';
import { getAbsoluteUrl } from 'utils/basePath';
const routesToBeSharedWithTime = [
ROUTES.LOGS_EXPLORER,
@@ -80,17 +81,13 @@ function ShareURLModal(): JSX.Element {
urlQuery.delete(QueryParams.relativeTime);
currentUrl = `${window.location.origin}${
location.pathname
}?${urlQuery.toString()}`;
currentUrl = getAbsoluteUrl(`${location.pathname}?${urlQuery.toString()}`);
} else {
urlQuery.delete(QueryParams.startTime);
urlQuery.delete(QueryParams.endTime);
urlQuery.set(QueryParams.relativeTime, selectedTime);
currentUrl = `${window.location.origin}${
location.pathname
}?${urlQuery.toString()}`;
currentUrl = getAbsoluteUrl(`${location.pathname}?${urlQuery.toString()}`);
}
}

View File

@@ -17,6 +17,7 @@ import { useErrorModal } from 'providers/ErrorModalProvider';
import APIError from 'types/api/error';
import { ROLES } from 'types/roles';
import { EMAIL_REGEX } from 'utils/app';
import { getBaseUrl } from 'utils/basePath';
import { popupContainer } from 'utils/selectPopupContainer';
import { v4 as uuid } from 'uuid';
@@ -191,7 +192,7 @@ function InviteMembersModal({
email: row.email.trim(),
name: '',
role: row.role as ROLES,
frontendBaseUrl: window.location.origin,
frontendBaseUrl: getBaseUrl(),
});
} else {
await inviteUsers({
@@ -199,7 +200,7 @@ function InviteMembersModal({
email: row.email.trim(),
name: '',
role: row.role,
frontendBaseUrl: window.location.origin,
frontendBaseUrl: getBaseUrl(),
})),
});
}

View File

@@ -14,6 +14,7 @@ import { useAppContext } from 'providers/App/App';
import { SuccessResponseV2 } from 'types/api';
import { CheckoutSuccessPayloadProps } from 'types/api/billing/checkout';
import APIError from 'types/api/error';
import { getBaseUrl } from 'utils/basePath';
import './LaunchChatSupport.styles.scss';
@@ -154,7 +155,7 @@ function LaunchChatSupport({
});
updateCreditCard({
url: window.location.origin,
url: getBaseUrl(),
});
};

View File

@@ -1,6 +1,7 @@
import { Color } from '@signozhq/design-tokens';
import { Button } from 'antd';
import { ArrowUpRight } from 'lucide-react';
import { openInNewTab } from 'utils/navigation';
import './LearnMore.styles.scss';
@@ -14,7 +15,7 @@ function LearnMore({ text, url, onClick }: LearnMoreProps): JSX.Element {
const handleClick = (): void => {
onClick?.();
if (url) {
window.open(url, '_blank');
openInNewTab(url);
}
};
return (

View File

@@ -20,6 +20,7 @@ import {
KAFKA_SETUP_DOC_LINK,
MessagingQueueHealthCheckService,
} from 'pages/MessagingQueues/MessagingQueuesUtils';
import { openInNewTab } from 'utils/navigation';
import { v4 as uuid } from 'uuid';
import './MessagingQueueHealthCheck.styles.scss';
@@ -76,7 +77,7 @@ function ErrorTitleAndKey({
if (isCloudUserVal && !!link) {
history.push(link);
} else {
window.open(KAFKA_SETUP_DOC_LINK, '_blank');
openInNewTab(KAFKA_SETUP_DOC_LINK);
}
};
return {

View File

@@ -9,6 +9,7 @@ import {
} from 'container/ApiMonitoring/utils';
import { UnfoldVertical } from 'lucide-react';
import { SuccessResponse } from 'types/api';
import { openInNewTab } from 'utils/navigation';
import emptyStateUrl from '@/assets/Icons/emptyState.svg';
@@ -94,20 +95,14 @@ function DependentServices({
}}
onRow={(record): { onClick: () => void; className: string } => ({
onClick: (): void => {
const url = new URL(
`/services/${
record.serviceData.serviceName &&
record.serviceData.serviceName !== '-'
? record.serviceData.serviceName
: ''
}`,
window.location.origin,
);
const serviceName =
record.serviceData.serviceName && record.serviceData.serviceName !== '-'
? record.serviceData.serviceName
: '';
const urlQuery = new URLSearchParams();
urlQuery.set(QueryParams.startTime, timeRange.startTime.toString());
urlQuery.set(QueryParams.endTime, timeRange.endTime.toString());
url.search = urlQuery.toString();
window.open(url.toString(), '_blank');
openInNewTab(`/services/${serviceName}?${urlQuery.toString()}`);
},
className: 'clickable-row',
})}

View File

@@ -73,6 +73,7 @@ import {
import { UserPreference } from 'types/api/preferences/preference';
import AppReducer from 'types/reducer/app';
import { USER_ROLES } from 'types/roles';
import { getBaseUrl } from 'utils/basePath';
import { showErrorNotification } from 'utils/error';
import { eventEmitter } from 'utils/getEventEmitter';
import {
@@ -461,7 +462,7 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
const handleFailedPayment = useCallback((): void => {
manageCreditCard({
url: window.location.origin,
url: getBaseUrl(),
});
}, [manageCreditCard]);

View File

@@ -31,6 +31,7 @@ import { isEmpty, pick } from 'lodash-es';
import { useAppContext } from 'providers/App/App';
import { SuccessResponseV2 } from 'types/api';
import { CheckoutSuccessPayloadProps } from 'types/api/billing/checkout';
import { getBaseUrl } from 'utils/basePath';
import { getFormattedDate, getRemainingDays } from 'utils/timeUtils';
import { BillingUsageGraph } from './BillingUsageGraph/BillingUsageGraph';
@@ -324,7 +325,7 @@ export default function BillingContainer(): JSX.Element {
});
updateCreditCard({
url: window.location.origin,
url: getBaseUrl(),
});
} else {
logEvent('Billing : Manage Billing', {
@@ -333,7 +334,7 @@ export default function BillingContainer(): JSX.Element {
});
manageCreditCard({
url: window.location.origin,
url: getBaseUrl(),
});
}
// eslint-disable-next-line react-hooks/exhaustive-deps

View File

@@ -7,6 +7,7 @@ import { FeatureKeys } from 'constants/features';
import { useAppContext } from 'providers/App/App';
import { AlertTypes } from 'types/api/alerts/alertTypes';
import { isModifierKeyPressed } from 'utils/app';
import { openInNewTab } from 'utils/navigation';
import { getOptionList } from './config';
import { AlertTypeCard, SelectTypeContainer } from './styles';
@@ -55,7 +56,7 @@ function SelectAlertType({ onSelect }: SelectAlertTypeProps): JSX.Element {
page: 'New alert data source selection page',
});
window.open(url, '_blank');
openInNewTab(url);
}
const renderOptions = useMemo(
() => (

View File

@@ -14,6 +14,7 @@ import { IUser } from 'providers/App/types';
import { Query } from 'types/api/queryBuilder/queryBuilderData';
import { EQueryType } from 'types/common/dashboard';
import { USER_ROLES } from 'types/roles';
import { openInNewTab } from 'utils/navigation';
import { ROUTING_POLICIES_ROUTE } from './constants';
import { RoutingPolicyBannerProps } from './types';
@@ -387,7 +388,7 @@ export function NotificationChannelsNotFoundContent({
style={{ padding: '0 4px' }}
type="link"
onClick={(): void => {
window.open(ROUTES.CHANNELS_NEW, '_blank');
openInNewTab(ROUTES.CHANNELS_NEW);
}}
>
here.

View File

@@ -46,6 +46,7 @@ function DomainUpdateToast({
className="custom-domain-toast-visit-btn"
suffix={<ExternalLink size={12} />}
onClick={(): void => {
// oxlint-disable-next-line signoz/no-raw-absolute-path
window.open(url, '_blank', 'noopener,noreferrer');
}}
>
@@ -74,10 +75,8 @@ export default function CustomDomainSettings(): JSX.Element {
const [isPollingEnabled, setIsPollingEnabled] = useState(false);
const [hosts, setHosts] = useState<ZeustypesHostDTO[] | null>(null);
const [
updateDomainError,
setUpdateDomainError,
] = useState<AxiosError<RenderErrorResponseDTO> | null>(null);
const [updateDomainError, setUpdateDomainError] =
useState<AxiosError<RenderErrorResponseDTO> | null>(null);
const [customDomainSubdomain, setCustomDomainSubdomain] = useState<
string | undefined
@@ -90,10 +89,8 @@ export default function CustomDomainSettings(): JSX.Element {
refetch: refetchHosts,
} = useGetHosts();
const {
mutate: updateSubDomain,
isLoading: isLoadingUpdateCustomDomain,
} = usePutHost<AxiosError<RenderErrorResponseDTO>>();
const { mutate: updateSubDomain, isLoading: isLoadingUpdateCustomDomain } =
usePutHost<AxiosError<RenderErrorResponseDTO>>();
const stripProtocol = (url: string): string => url?.split('://')[1] ?? url;
@@ -137,7 +134,7 @@ export default function CustomDomainSettings(): JSX.Element {
{
onSuccess: () => {
setIsPollingEnabled(true);
refetchHosts();
void refetchHosts();
setIsEditModalOpen(false);
setCustomDomainSubdomain(subdomain);
const newUrl = `https://${subdomain}.${dnsSuffix}`;

View File

@@ -15,6 +15,8 @@ import { useDashboardStore } from 'providers/Dashboard/store/useDashboardStore';
import { PublicDashboardMetaProps } from 'types/api/dashboard/public/getMeta';
import APIError from 'types/api/error';
import { USER_ROLES } from 'types/roles';
import { getAbsoluteUrl } from 'utils/basePath';
import { openInNewTab } from 'utils/navigation';
import './PublicDashboard.styles.scss';
@@ -212,7 +214,7 @@ function PublicDashboardSetting(): JSX.Element {
try {
setCopyPublicDashboardURL(
`${window.location.origin}${publicDashboardResponse?.data?.publicPath}`,
getAbsoluteUrl(publicDashboardResponse?.data?.publicPath ?? ''),
);
toast.success('Copied Public Dashboard URL successfully');
} catch (error) {
@@ -221,7 +223,7 @@ function PublicDashboardSetting(): JSX.Element {
};
const publicDashboardURL = useMemo(
() => `${window.location.origin}${publicDashboardResponse?.data?.publicPath}`,
() => getAbsoluteUrl(publicDashboardResponse?.data?.publicPath ?? ''),
[publicDashboardResponse],
);
@@ -294,7 +296,7 @@ function PublicDashboardSetting(): JSX.Element {
icon={<ExternalLink size={12} />}
onClick={(): void => {
if (publicDashboardURL) {
window.open(publicDashboardURL, '_blank');
openInNewTab(publicDashboardURL);
}
}}
/>

View File

@@ -9,6 +9,7 @@ import ROUTES from 'constants/routes';
import history from 'lib/history';
import APIError from 'types/api/error';
import { OrgSessionContext } from 'types/api/v2/sessions/context/get';
import { getBaseUrl } from 'utils/basePath';
import tvUrl from '@/assets/svgs/tv.svg';
@@ -104,7 +105,7 @@ function ForgotPassword({
data: {
email: values.email,
orgId: currentOrgId,
frontendBaseURL: window.location.origin,
frontendBaseURL: getBaseUrl(),
},
});
}, [form, forgotPasswordMutate, initialOrgId, hasMultipleOrgs]);

View File

@@ -15,6 +15,7 @@ import { AlertDef, Labels } from 'types/api/alerts/def';
import { Channels } from 'types/api/channels/getAll';
import APIError from 'types/api/error';
import { requireErrorMessage } from 'utils/form/requireErrorMessage';
import { openInNewTab } from 'utils/navigation';
import { popupContainer } from 'utils/selectPopupContainer';
import ChannelSelect from './ChannelSelect';
@@ -87,7 +88,7 @@ function BasicInfo({
dataSource: ALERTS_DATA_SOURCE_MAP[alertDef?.alertType as AlertTypes],
ruleId: isNewRule ? 0 : alertDef?.id,
});
window.open(ROUTES.CHANNELS_NEW, '_blank');
openInNewTab(ROUTES.CHANNELS_NEW);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const hasLoggedEvent = useRef(false);

View File

@@ -55,6 +55,7 @@ import { DataSource } from 'types/common/queryBuilder';
import { GlobalReducer } from 'types/reducer/globalTime';
import { isModifierKeyPressed } from 'utils/app';
import { compositeQueryToQueryEnvelope } from 'utils/compositeQueryToQueryEnvelope';
import { openInNewTab } from 'utils/navigation';
import BasicInfo from './BasicInfo';
import ChartPreview from './ChartPreview';
@@ -777,7 +778,7 @@ function FormAlertRules({
queryType: currentQuery.queryType,
link: url,
});
window.open(url, '_blank');
openInNewTab(url);
}
}

View File

@@ -10,6 +10,7 @@ import Card from 'periscope/components/Card/Card';
import { useAppContext } from 'providers/App/App';
import { Dashboard } from 'types/api/dashboard/getAll';
import { USER_ROLES } from 'types/roles';
import { openInNewTab } from 'utils/navigation';
import dialsUrl from '@/assets/Icons/dials.svg';
@@ -114,7 +115,7 @@ export default function Dashboards({
dashboardName: dashboard.data.title,
});
if (event.metaKey || event.ctrlKey) {
window.open(getLink(), '_blank');
openInNewTab(getLink());
} else {
safeNavigate(getLink());
}

View File

@@ -9,6 +9,7 @@ import { Link2 } from 'lucide-react';
import Card from 'periscope/components/Card/Card';
import { useAppContext } from 'providers/App/App';
import { LicensePlatform } from 'types/api/licensesV3/getActive';
import { openInNewTab } from 'utils/navigation';
import containerPlusUrl from '@/assets/Icons/container-plus.svg';
import helloWaveUrl from '@/assets/Icons/hello-wave.svg';
@@ -51,7 +52,7 @@ function DataSourceInfo({
if (activeLicense && activeLicense.platform === LicensePlatform.CLOUD) {
history.push(ROUTES.GET_STARTED_WITH_CLOUD);
} else {
window?.open(DOCS_LINKS.ADD_DATA_SOURCE, '_blank', 'noopener noreferrer');
openInNewTab(DOCS_LINKS.ADD_DATA_SOURCE);
}
};

View File

@@ -8,6 +8,7 @@ import { ArrowRight, ArrowRightToLine, BookOpenText } from 'lucide-react';
import { useAppContext } from 'providers/App/App';
import { LicensePlatform } from 'types/api/licensesV3/getActive';
import { USER_ROLES } from 'types/roles';
import { openInNewTab } from 'utils/navigation';
import './HomeChecklist.styles.scss';
@@ -99,11 +100,7 @@ function HomeChecklist({
) {
history.push(item.toRoute || '');
} else {
window?.open(
item.docsLink || '',
'_blank',
'noopener noreferrer',
);
openInNewTab(item.docsLink || '');
}
}}
>
@@ -119,7 +116,7 @@ function HomeChecklist({
step: item.id,
});
window?.open(item.docsLink, '_blank', 'noopener noreferrer');
openInNewTab(item.docsLink ?? '');
}}
>
<BookOpenText size={16} />

View File

@@ -31,6 +31,7 @@ import { GlobalReducer } from 'types/reducer/globalTime';
import { Tags } from 'types/reducer/trace';
import { USER_ROLES } from 'types/roles';
import { isModifierKeyPressed } from 'utils/app';
import { openInNewTab } from 'utils/navigation';
import triangleRulerUrl from '@/assets/Icons/triangle-ruler.svg';
@@ -79,11 +80,7 @@ const EmptyState = memo(
) {
history.push(ROUTES.GET_STARTED_WITH_CLOUD);
} else {
window?.open(
DOCS_LINKS.ADD_DATA_SOURCE,
'_blank',
'noopener noreferrer',
);
openInNewTab(DOCS_LINKS.ADD_DATA_SOURCE);
}
}}
>

View File

@@ -17,6 +17,7 @@ import { ServicesList } from 'types/api/metrics/getService';
import { GlobalReducer } from 'types/reducer/globalTime';
import { USER_ROLES } from 'types/roles';
import { isModifierKeyPressed } from 'utils/app';
import { openInNewTab } from 'utils/navigation';
import triangleRulerUrl from '@/assets/Icons/triangle-ruler.svg';
@@ -133,11 +134,7 @@ export default function ServiceTraces({
) {
history.push(ROUTES.GET_STARTED_WITH_CLOUD);
} else {
window?.open(
DOCS_LINKS.ADD_DATA_SOURCE,
'_blank',
'noopener noreferrer',
);
openInNewTab(DOCS_LINKS.ADD_DATA_SOURCE);
}
}}
>

View File

@@ -51,6 +51,7 @@ import {
LogsAggregatorOperator,
TracesAggregatorOperator,
} from 'types/common/queryBuilder';
import { openInNewTab } from 'utils/navigation';
import { v4 as uuidv4 } from 'uuid';
import { filterDuplicateFilters } from '../commonUtils';
@@ -569,10 +570,7 @@ function K8sBaseDetails<T>({
urlQuery.set('compositeQuery', JSON.stringify(compositeQuery));
window.open(
`${window.location.origin}${ROUTES.LOGS_EXPLORER}?${urlQuery.toString()}`,
'_blank',
);
openInNewTab(`${ROUTES.LOGS_EXPLORER}?${urlQuery.toString()}`);
} else if (selectedView === VIEW_TYPES.TRACES) {
const compositeQuery = {
...initialQueryState,
@@ -591,10 +589,7 @@ function K8sBaseDetails<T>({
urlQuery.set('compositeQuery', JSON.stringify(compositeQuery));
window.open(
`${window.location.origin}${ROUTES.TRACES_EXPLORER}?${urlQuery.toString()}`,
'_blank',
);
openInNewTab(`${ROUTES.TRACES_EXPLORER}?${urlQuery.toString()}`);
}
};

View File

@@ -4,6 +4,7 @@ import {
CloudintegrationtypesServiceDTO,
} from 'api/generated/services/sigNoz.schemas';
import { useSafeNavigate } from 'hooks/useSafeNavigate';
import { withBasePath } from 'utils/basePath';
import './ServiceDashboards.styles.scss';
@@ -44,7 +45,11 @@ function ServiceDashboards({
return;
}
if (event.metaKey || event.ctrlKey) {
window.open(dashboardUrl, '_blank', 'noopener,noreferrer');
window.open(
withBasePath(dashboardUrl),
'_blank',
'noopener,noreferrer',
);
return;
}
safeNavigate(dashboardUrl);
@@ -54,7 +59,11 @@ function ServiceDashboards({
return;
}
if (event.button === 1) {
window.open(dashboardUrl, '_blank', 'noopener,noreferrer');
window.open(
withBasePath(dashboardUrl),
'_blank',
'noopener,noreferrer',
);
}
}}
onKeyDown={(event): void => {

View File

@@ -1,5 +1,6 @@
import { ArrowRightOutlined } from '@ant-design/icons';
import { Typography } from 'antd';
import { openInNewTab } from 'utils/navigation';
interface AlertInfoCardProps {
header: string;
@@ -19,7 +20,7 @@ function AlertInfoCard({
className="alert-info-card"
onClick={(): void => {
onClick();
window.open(link, '_blank');
openInNewTab(link);
}}
>
<div className="alert-card-text">

View File

@@ -1,5 +1,6 @@
import { ArrowRightOutlined, PlayCircleFilled } from '@ant-design/icons';
import { Flex, Typography } from 'antd';
import { openInNewTab } from 'utils/navigation';
interface InfoLinkTextProps {
infoText: string;
@@ -20,7 +21,7 @@ function InfoLinkText({
<Flex
onClick={(): void => {
onClick();
window.open(link, '_blank');
openInNewTab(link);
}}
className="info-link-container"
>

View File

@@ -83,6 +83,8 @@ import {
} from 'types/api/dashboard/getAll';
import APIError from 'types/api/error';
import { isModifierKeyPressed } from 'utils/app';
import { getAbsoluteUrl } from 'utils/basePath';
import { openInNewTab } from 'utils/navigation';
import awwSnapUrl from '@/assets/Icons/awwSnap.svg';
import dashboardsUrl from '@/assets/Icons/dashboards.svg';
@@ -457,7 +459,7 @@ function DashboardsList(): JSX.Element {
onClick={(e): void => {
e.stopPropagation();
e.preventDefault();
window.open(getLink(), '_blank');
openInNewTab(getLink());
}}
>
Open in New Tab
@@ -469,7 +471,7 @@ function DashboardsList(): JSX.Element {
onClick={(e): void => {
e.stopPropagation();
e.preventDefault();
setCopy(`${window.location.origin}${getLink()}`);
setCopy(getAbsoluteUrl(getLink()));
}}
>
Copy Link

View File

@@ -1,6 +1,7 @@
import { LockFilled } from '@ant-design/icons';
import ROUTES from 'constants/routes';
import history from 'lib/history';
import { openInNewTab } from 'utils/navigation';
import { Data } from '../DashboardsList';
import { TableLinkText } from './styles';
@@ -12,7 +13,7 @@ function Name(name: Data['name'], data: Data): JSX.Element {
const onClickHandler = (event: React.MouseEvent<HTMLElement>): void => {
if (event.metaKey || event.ctrlKey) {
window.open(getLink(), '_blank');
openInNewTab(getLink());
} else {
history.push(getLink());
}

View File

@@ -17,6 +17,7 @@ import useUrlQuery from 'hooks/useUrlQuery';
import { ILog } from 'types/api/logs/log';
import { Query, TagFilter } from 'types/api/queryBuilder/queryBuilderData';
import { DataSource, StringOperators } from 'types/common/queryBuilder';
import { withBasePath } from 'utils/basePath';
import { useContextLogData } from './useContextLogData';
@@ -116,7 +117,7 @@ function ContextLogRenderer({
);
const link = `${ROUTES.LOGS_EXPLORER}?${urlQuery.toString()}`;
window.open(link, '_blank', 'noopener,noreferrer');
window.open(withBasePath(link), '_blank', 'noopener,noreferrer');
},
[query, urlQuery],
);

View File

@@ -34,6 +34,7 @@ import { SET_DETAILED_LOG_DATA } from 'types/actions/logs';
import { IField } from 'types/api/logs/fields';
import { ILog } from 'types/api/logs/log';
import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse';
import { openInNewTab } from 'utils/navigation';
import { ActionItemProps } from './ActionItem';
import FieldRenderer from './FieldRenderer';
@@ -191,7 +192,7 @@ function TableView({
if (event.ctrlKey || event.metaKey) {
// open the trace in new tab
window.open(route, '_blank');
openInNewTab(route);
} else {
history.push(route);
}

View File

@@ -60,10 +60,8 @@ function Login(): JSX.Element {
const [sessionsContext, setSessionsContext] = useState<SessionsContext>();
const [isSubmitting, setIsSubmitting] = useState<boolean>(false);
const [sessionsOrgId, setSessionsOrgId] = useState<string>('');
const [
sessionsContextLoading,
setIsLoadingSessionsContext,
] = useState<boolean>(false);
const [sessionsContextLoading, setIsLoadingSessionsContext] =
useState<boolean>(false);
const [form] = Form.useForm<FormValues>();
const [errorMessage, setErrorMessage] = useState<APIError>();
@@ -213,6 +211,7 @@ function Login(): JSX.Element {
if (isCallbackAuthN) {
const url = form.getFieldValue('url');
// oxlint-disable-next-line signoz/no-raw-absolute-path
window.location.href = url;
}
} catch (error) {
@@ -328,7 +327,6 @@ function Login(): JSX.Element {
data-testid="email"
required
placeholder="e.g. john@signoz.io"
autoFocus
disabled={versionLoading}
className="login-form-input"
onPressEnter={onNextHandler}

View File

@@ -34,6 +34,7 @@ import ROUTES from 'constants/routes';
import { useActiveLog } from 'hooks/logs/useActiveLog';
import { useIsDarkMode } from 'hooks/useDarkMode';
import useDragColumns from 'hooks/useDragColumns';
import { getAbsoluteUrl } from 'utils/basePath';
import { infinityDefaultStyles } from '../InfinityTableView/config';
import { TanStackTableStyled } from '../InfinityTableView/styles';
@@ -239,7 +240,7 @@ const TanStackTableView = forwardRef<TableVirtuosoHandle, InfinityTableProps>(
urlQuery.delete(QueryParams.activeLogId);
urlQuery.delete(QueryParams.relativeTime);
urlQuery.set(QueryParams.activeLogId, `"${logId}"`);
const link = `${window.location.origin}${pathname}?${urlQuery.toString()}`;
const link = getAbsoluteUrl(`${pathname}?${urlQuery.toString()}`);
setCopy(link);
toast.success('Copied to clipboard', { position: 'top-right' });

View File

@@ -1,5 +1,6 @@
import { QueryParams } from 'constants/query';
import ROUTES from 'constants/routes';
import { withBasePath } from 'utils/basePath';
import { TopOperationList } from './TopOperationsTable';
import { NavigateToTraceProps } from './types';
@@ -37,7 +38,7 @@ export const navigateToTrace = ({
}=${JSONCompositeQuery}`;
if (openInNewTab) {
window.open(newTraceExplorerPath, '_blank');
window.open(withBasePath(newTraceExplorerPath), '_blank');
} else {
safeNavigate(newTraceExplorerPath);
}

View File

@@ -9,6 +9,7 @@ import {
import { QueryParams } from 'constants/query';
import ROUTES from 'constants/routes';
import { Bell, Grid } from 'lucide-react';
import { openInNewTab } from 'utils/navigation';
import { pluralize } from 'utils/pluralize';
import { DashboardsAndAlertsPopoverProps } from './types';
@@ -67,9 +68,8 @@ function DashboardsAndAlertsPopover({
<Typography.Link
key={alert.alertId}
onClick={(): void => {
window.open(
openInNewTab(
`${ROUTES.ALERT_OVERVIEW}?${QueryParams.ruleId}=${alert.alertId}`,
'_blank',
);
}}
className="dashboards-popover-content-item"
@@ -90,11 +90,10 @@ function DashboardsAndAlertsPopover({
<Typography.Link
key={dashboard.dashboardId}
onClick={(): void => {
window.open(
openInNewTab(
generatePath(ROUTES.DASHBOARD, {
dashboardId: dashboard.dashboardId,
}),
'_blank',
);
}}
className="dashboards-popover-content-item"

View File

@@ -18,6 +18,7 @@ import {
import useContextVariables from 'hooks/dashboard/useContextVariables';
import { Plus, Trash2 } from 'lucide-react';
import { ContextLinkProps, Widgets } from 'types/api/dashboard/getAll';
import { getBaseUrl } from 'utils/basePath';
import VariablesDropdown from './VariablesDropdown';
@@ -84,7 +85,7 @@ function UpdateContextLinks({
);
// Function to get current domain
const getCurrentDomain = (): string => window.location.origin;
const getCurrentDomain = (): string => getBaseUrl();
// Function to handle variable selection from dropdown
const handleVariableSelect = (

View File

@@ -6,6 +6,7 @@ import history from 'lib/history';
import { ArrowUpRight } from 'lucide-react';
import { DataSource } from 'types/common/queryBuilder';
import DOCLINKS from 'utils/docLinks';
import { openInNewTab } from 'utils/navigation';
import eyesEmojiUrl from '@/assets/Images/eyesEmoji.svg';
@@ -42,11 +43,11 @@ export default function NoLogs({
}
history.push(link);
} else if (dataSource === 'traces') {
window.open(DOCLINKS.TRACES_EXPLORER_EMPTY_STATE, '_blank');
openInNewTab(DOCLINKS.TRACES_EXPLORER_EMPTY_STATE);
} else if (dataSource === DataSource.METRICS) {
window.open(DOCLINKS.METRICS_EXPLORER_EMPTY_STATE, '_blank');
openInNewTab(DOCLINKS.METRICS_EXPLORER_EMPTY_STATE);
} else {
window.open(`${DOCLINKS.USER_GUIDE}${dataSource}/`, '_blank');
openInNewTab(`${DOCLINKS.USER_GUIDE}${dataSource}/`);
}
};
return (

View File

@@ -16,6 +16,7 @@ import {
Trash2,
} from 'lucide-react';
import APIError from 'types/api/error';
import { getBaseUrl } from 'utils/basePath';
import { v4 as uuid } from 'uuid';
import { OnboardingQuestionHeader } from '../OnboardingQuestionHeader';
@@ -58,7 +59,7 @@ function InviteTeamMembers({
email: '',
role: '',
name: '',
frontendBaseUrl: window.location.origin,
frontendBaseUrl: getBaseUrl(),
id: '',
};

View File

@@ -8,6 +8,7 @@ import { DOCS_BASE_URL } from 'constants/app';
import { useGetGlobalConfig } from 'hooks/globalConfig/useGetGlobalConfig';
import { useNotifications } from 'hooks/useNotifications';
import { ArrowUpRight, Copy, Info, Key, TriangleAlert } from 'lucide-react';
import { withBasePath } from 'utils/basePath';
import './IngestionDetails.styles.scss';
@@ -215,7 +216,7 @@ export default function OnboardingIngestionDetails(): JSX.Element {
</a>
. To create a new one,{' '}
<a
href="/settings/ingestion-settings"
href={withBasePath('/settings/ingestion-settings')}
target="_blank"
className="learn-more"
rel="noreferrer"

View File

@@ -8,6 +8,7 @@ import { useNotifications } from 'hooks/useNotifications';
import { cloneDeep, debounce, isEmpty } from 'lodash-es';
import { ArrowRight, CheckCircle, Plus, TriangleAlert, X } from 'lucide-react';
import APIError from 'types/api/error';
import { getBaseUrl } from 'utils/basePath';
import { v4 as uuid } from 'uuid';
import './InviteTeamMembers.styles.scss';
@@ -56,7 +57,7 @@ function InviteTeamMembers({
email: '',
role: 'EDITOR',
name: '',
frontendBaseUrl: window.location.origin,
frontendBaseUrl: getBaseUrl(),
id: '',
};

View File

@@ -17,6 +17,7 @@ import ErrorContent from 'components/ErrorModal/components/ErrorContent';
import CopyToClipboard from 'periscope/components/CopyToClipboard';
import { useErrorModal } from 'providers/ErrorModalProvider';
import APIError from 'types/api/error';
import { getAbsoluteUrl } from 'utils/basePath';
import CreateEdit from './CreateEdit/CreateEdit';
import SSOEnforcementToggle from './SSOEnforcementToggle';
@@ -144,7 +145,7 @@ function AuthDomain(): JSX.Element {
return <span className="auth-domain-list-na">N/A</span>;
}
const href = `${window.location.origin}/${relayPath}`;
const href = getAbsoluteUrl(`/${relayPath}`);
return <CopyToClipboard textToCopy={href} />;
},
},

View File

@@ -4,6 +4,7 @@ import { Button, Form, FormInstance, Modal } from 'antd';
import sendInvite from 'api/v1/invite/create';
import { useNotifications } from 'hooks/useNotifications';
import APIError from 'types/api/error';
import { getBaseUrl } from 'utils/basePath';
import InviteTeamMembers from '../InviteTeamMembers';
import { InviteMemberFormValues } from '../utils';
@@ -40,7 +41,7 @@ function InviteUserModal(props: InviteUserModalProps): JSX.Element {
email: member.email,
name: member?.name,
role: member.role,
frontendBaseUrl: window.location.origin,
frontendBaseUrl: getBaseUrl(),
});
notifications.success({

View File

@@ -14,6 +14,7 @@ import ContextMenu from 'periscope/components/ContextMenu';
import { useDashboardStore } from 'providers/Dashboard/store/useDashboardStore';
import { ContextLinksData } from 'types/api/dashboard/getAll';
import { Query } from 'types/api/queryBuilder/queryBuilderData';
import { openInNewTab } from 'utils/navigation';
import { ContextMenuItem } from './contextConfig';
import { getDataLinks } from './dataLinksUtils';
@@ -115,7 +116,7 @@ const useBaseAggregateOptions = ({
key={id}
icon={<LinkOutlined />}
onClick={(): void => {
window.open(url, '_blank');
openInNewTab(url);
onClose?.();
}}
>

View File

@@ -14,6 +14,7 @@ import { ModalTitle } from 'container/PipelinePage/PipelineListsView/styles';
import { Check, Loader, X } from 'lucide-react';
import { useAppContext } from 'providers/App/App';
import { USER_ROLES } from 'types/roles';
import { openInNewTab } from 'utils/navigation';
import { INITIAL_ROUTING_POLICY_DETAILS_FORM_STATE } from './constants';
import {
@@ -76,7 +77,7 @@ function RoutingPolicyDetails({
style={{ padding: '0 4px' }}
type="link"
onClick={(): void => {
window.open(ROUTES.CHANNELS_NEW, '_blank');
openInNewTab(ROUTES.CHANNELS_NEW);
}}
>
here.

View File

@@ -818,7 +818,7 @@ function SideNav({ isPinned }: { isPinned: boolean }): JSX.Element {
);
if (item && !('type' in item) && item.isExternal && item.url) {
window.open(item.url, '_blank');
openInNewTab(item.url);
}
const event = (info as SidebarItem & { domEvent?: MouseEvent }).domEvent;

View File

@@ -28,6 +28,7 @@ import {
} from 'types/api/queryBuilder/queryAutocompleteResponse';
import { TagFilter } from 'types/api/queryBuilder/queryBuilderData';
import { DataSource } from 'types/common/queryBuilder';
import { openInNewTab } from 'utils/navigation';
import { v4 as uuid } from 'uuid';
import noDataUrl from '@/assets/Icons/no-data.svg';
@@ -143,7 +144,7 @@ function SpanLogs({
const url = `${ROUTES.LOGS_EXPLORER}?${createQueryParams(queryParams)}`;
window.open(url, '_blank');
openInNewTab(url);
},
[
isLogSpanRelated,

View File

@@ -17,6 +17,7 @@ import { BarChart2, Compass, X } from 'lucide-react';
import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
import { Span } from 'types/api/trace/getTraceV2';
import { DataSource, LogsAggregatorOperator } from 'types/common/queryBuilder';
import { getAbsoluteUrl } from 'utils/basePath';
import { RelatedSignalsViews } from '../constants';
import SpanLogs from '../SpanLogs/SpanLogs';
@@ -158,9 +159,7 @@ function SpanRelatedSignals({
searchParams.set(QueryParams.endTime, endTimeMs.toString());
window.open(
`${window.location.origin}${
ROUTES.LOGS_EXPLORER
}?${searchParams.toString()}`,
getAbsoluteUrl(`${ROUTES.LOGS_EXPLORER}?${searchParams.toString()}`),
'_blank',
'noopener,noreferrer',
);

View File

@@ -31,6 +31,7 @@ import {
UPDATE_SPANS_AGGREGATE_PAGE_SIZE,
} from 'types/actions/trace';
import { TraceReducer } from 'types/reducer/trace';
import { openInNewTab } from 'utils/navigation';
import { v4 } from 'uuid';
dayjs.extend(duration);
@@ -214,7 +215,7 @@ function TraceTable(): JSX.Element {
event.preventDefault();
event.stopPropagation();
if (event.metaKey || event.ctrlKey) {
window.open(getLink(record), '_blank');
openInNewTab(getLink(record));
} else {
history.push(getLink(record));
}

View File

@@ -28,6 +28,7 @@ import { useTimezone } from 'providers/Timezone';
import { SuccessResponse } from 'types/api';
import { Widgets } from 'types/api/dashboard/getAll';
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
import { openInNewTab } from 'utils/navigation';
import './TracesTableComponent.styles.scss';
@@ -86,7 +87,7 @@ function TracesTableComponent({
event.preventDefault();
event.stopPropagation();
if (event.metaKey || event.ctrlKey) {
window.open(getTraceLink(record), '_blank');
openInNewTab(getTraceLink(record));
} else {
history.push(getTraceLink(record));
}

View File

@@ -72,7 +72,7 @@ export function useIntegrationModal({
ModalStateEnum.FORM,
);
const [isLoading, setIsLoading] = useState(false);
const [accountId, setAccountId] = useState<string | undefined>(undefined);
const [accountId, setAccountId] = useState<string | undefined>();
const [activeView, setActiveView] = useState<ActiveViewEnum>(
ActiveViewEnum.FORM,
);
@@ -80,7 +80,7 @@ export function useIntegrationModal({
const [includeAllRegions, setIncludeAllRegions] = useState(false);
const [selectedDeploymentRegion, setSelectedDeploymentRegion] = useState<
string | undefined
>(undefined);
>();
const allRegions = useMemo(
() => regions.flatMap((r) => r.subRegions.map((sr) => sr.name)),
[],
@@ -108,7 +108,7 @@ export function useIntegrationModal({
const handleConnectionSuccess = useCallback(
(payload: { cloudAccountId: string; status?: unknown }): void => {
logEvent('AWS Integration: Account connected', {
void logEvent('AWS Integration: Account connected', {
cloudAccountId: payload.cloudAccountId,
status: payload.status,
});
@@ -126,7 +126,7 @@ export function useIntegrationModal({
const handleConnectionTimeout = useCallback(
(payload: { id?: string }): void => {
setModalState(ModalStateEnum.ERROR);
logEvent('AWS Integration: Account connection attempt timed out', {
void logEvent('AWS Integration: Account connection attempt timed out', {
id: payload.id,
});
},
@@ -140,19 +140,17 @@ export function useIntegrationModal({
const { mutate: generateUrl, isLoading: isGeneratingUrl } = useCreateAccount();
const handleError = useAxiosError();
const {
data: connectionParams,
isLoading: isConnectionParamsLoading,
} = useGetConnectionCredentials<GetConnectionCredentialsQueryResult>(
{
cloudProvider: INTEGRATION_TYPES.AWS,
},
{
query: {
onError: handleError,
const { data: connectionParams, isLoading: isConnectionParamsLoading } =
useGetConnectionCredentials<GetConnectionCredentialsQueryResult>(
{
cloudProvider: INTEGRATION_TYPES.AWS,
},
},
);
{
query: {
onError: handleError,
},
},
);
const handleGenerateUrl = useCallback(
(payload: CloudintegrationtypesPostableAccountDTO): void => {
@@ -167,12 +165,13 @@ export function useIntegrationModal({
const connectionUrl =
response.data.connectionArtifact.aws?.connectionUrl ?? '';
logEvent(
void logEvent(
'AWS Integration: Account connection attempt redirected to AWS',
{
id: accountId,
},
);
// oxlint-disable-next-line signoz/no-raw-absolute-path -- connectionUrl is an external AWS console URL, not an internal path
window.open(connectionUrl, '_blank');
setModalState(ModalStateEnum.WAITING);
setAccountId(accountId);

View File

@@ -17,6 +17,7 @@ import useUrlQuery from 'hooks/useUrlQuery';
import useUrlQueryData from 'hooks/useUrlQueryData';
import { AppState } from 'store/reducers';
import { GlobalReducer } from 'types/reducer/globalTime';
import { getAbsoluteUrl } from 'utils/basePath';
import { HIGHLIGHTED_DELAY } from './configs';
import { UseCopyLogLink } from './types';
@@ -60,7 +61,7 @@ export const useCopyLogLink = (logId?: string): UseCopyLogLink => {
urlQuery.set(QueryParams.startTime, minTime?.toString() || '');
urlQuery.set(QueryParams.endTime, maxTime?.toString() || '');
const link = `${window.location.origin}${pathname}?${urlQuery.toString()}`;
const link = getAbsoluteUrl(`${pathname}?${urlQuery.toString()}`);
setCopy(link);

View File

@@ -21,6 +21,7 @@ import { useDashboardStore } from 'providers/Dashboard/store/useDashboardStore';
import { AppState } from 'store/reducers';
import { Widgets } from 'types/api/dashboard/getAll';
import { GlobalReducer } from 'types/reducer/globalTime';
import { withBasePath } from 'utils/basePath';
import { getGraphType } from 'utils/getGraphType';
const useCreateAlerts = (widget?: Widgets, caller?: string): VoidFunction => {
@@ -95,7 +96,7 @@ const useCreateAlerts = (widget?: Widgets, caller?: string): VoidFunction => {
const url = `${ROUTES.ALERTS_NEW}?${params.toString()}`;
window.open(url, '_blank', 'noreferrer');
window.open(withBasePath(url), '_blank', 'noreferrer');
},
onError: () => {
notifications.error({

View File

@@ -4,6 +4,7 @@ import { useCopyToClipboard } from 'react-use';
import { useNotifications } from 'hooks/useNotifications';
import useUrlQuery from 'hooks/useUrlQuery';
import { Span } from 'types/api/trace/getTraceV2';
import { getAbsoluteUrl } from 'utils/basePath';
export const useCopySpanLink = (
span?: Span,
@@ -28,7 +29,7 @@ export const useCopySpanLink = (
urlQuery.set('spanId', span?.spanId);
}
const link = `${window.location.origin}${pathname}?${urlQuery.toString()}`;
const link = getAbsoluteUrl(`${pathname}?${urlQuery.toString()}`);
setCopy(link);
notifications.success({

View File

@@ -26,12 +26,9 @@ const areUrlsEffectivelySame = (url1: URL, url2: URL): boolean => {
const params1 = new URLSearchParams(url1.search);
const params2 = new URLSearchParams(url2.search);
const allParams = new Set([
...Array.from(params1.keys()),
...Array.from(params2.keys()),
]);
const allParams = new Set([...params1.keys(), ...params2.keys()]);
return Array.from(allParams).every((param) => {
return [...allParams].every((param) => {
if (param === 'compositeQuery') {
try {
const query1 = params1.get('compositeQuery');
@@ -84,11 +81,11 @@ const isDefaultNavigation = (currentUrl: URL, targetUrl: URL): boolean => {
}
// Case 2: Check for new params that didn't exist before
const currentKeys = new Set(Array.from(currentParams.keys()));
const targetKeys = new Set(Array.from(targetParams.keys()));
const currentKeys = new Set(currentParams.keys());
const targetKeys = new Set(targetParams.keys());
// Find keys that exist in target but not in current
const newKeys = Array.from(targetKeys).filter((key) => !currentKeys.has(key));
const newKeys = [...targetKeys].filter((key) => !currentKeys.has(key));
return newKeys.length > 0;
};
@@ -108,19 +105,18 @@ export const useSafeNavigate = (
const safeNavigate = useCallback(
// eslint-disable-next-line sonarjs/cognitive-complexity
(to: string | SafeNavigateParams, options?: NavigateOptions) => {
const currentUrl = new URL(
`${location.pathname}${location.search}`,
window.location.origin,
);
// oxlint-disable-next-line signoz/no-raw-absolute-path
const base = window.location.origin;
const currentUrl = new URL(`${location.pathname}${location.search}`, base);
let targetUrl: URL;
if (typeof to === 'string') {
targetUrl = new URL(to, window.location.origin);
targetUrl = new URL(to, base);
} else {
targetUrl = new URL(
`${to.pathname || location.pathname}${to.search || ''}`,
window.location.origin,
base,
);
}

View File

@@ -19,6 +19,7 @@ import {
} from 'pages/MessagingQueues/MessagingQueuesUtils';
import { AppState } from 'store/reducers';
import { GlobalReducer } from 'types/reducer/globalTime';
import { openInNewTab } from 'utils/navigation';
import {
convertToMilliseconds,
@@ -93,7 +94,7 @@ export function getColumns(
key={item}
className="traceid-text"
onClick={(): void => {
window.open(`${ROUTES.TRACE}/${item}`, '_blank');
openInNewTab(`${ROUTES.TRACE}/${item}`);
logEvent(`MQ Kafka: Drop Rate - traceid navigation`, {
item,
});
@@ -123,7 +124,7 @@ export function getColumns(
onClick={(e): void => {
e.preventDefault();
e.stopPropagation();
window.open(`/services/${encodeURIComponent(text)}`, '_blank');
openInNewTab(`/services/${encodeURIComponent(text)}`);
}}
>
{text}

View File

@@ -59,7 +59,7 @@ function MessagingQueues(): JSX.Element {
history.push(link);
}
} else {
window.open(KAFKA_SETUP_DOC_LINK, '_blank');
openInNewTab(KAFKA_SETUP_DOC_LINK);
}
};

View File

@@ -20,6 +20,8 @@ import { useAppContext } from 'providers/App/App';
import { SuccessResponseV2 } from 'types/api';
import { CheckoutSuccessPayloadProps } from 'types/api/billing/checkout';
import APIError from 'types/api/error';
import { getBaseUrl } from 'utils/basePath';
import { openInNewTab } from 'utils/navigation';
import './Support.styles.scss';
@@ -92,7 +94,7 @@ export default function Support(): JSX.Element {
const { pathname } = useLocation();
const handleChannelWithRedirects = (url: string): void => {
window.open(url, '_blank');
openInNewTab(url);
};
useEffect(() => {
@@ -150,7 +152,7 @@ export default function Support(): JSX.Element {
});
updateCreditCard({
url: window.location.origin,
url: getBaseUrl(),
});
};

View File

@@ -29,6 +29,7 @@ import { useAppContext } from 'providers/App/App';
import APIError from 'types/api/error';
import { LicensePlatform } from 'types/api/licensesV3/getActive';
import { isModifierKeyPressed } from 'utils/app';
import { getBaseUrl } from 'utils/basePath';
import { getFormattedDate } from 'utils/timeUtils';
import CustomerStoryCard from './CustomerStoryCard';
@@ -115,7 +116,7 @@ export default function WorkspaceBlocked(): JSX.Element {
logEvent('Workspace Blocked: User Clicked Update Credit Card', {});
updateCreditCard({
url: window.location.origin,
url: getBaseUrl(),
});
}, [updateCreditCard]);

View File

@@ -21,6 +21,7 @@ import history from 'lib/history';
import { useAppContext } from 'providers/App/App';
import APIError from 'types/api/error';
import { LicensePlatform, LicenseState } from 'types/api/licensesV3/getActive';
import { getBaseUrl } from 'utils/basePath';
import { getFormattedDateWithMinutes } from 'utils/timeUtils';
import featureGraphicCorrelationUrl from '@/assets/Images/feature-graphic-correlation.svg';
@@ -57,7 +58,7 @@ function WorkspaceSuspended(): JSX.Element {
const handleUpdateCreditCard = useCallback(async () => {
manageCreditCard({
url: window.location.origin,
url: getBaseUrl(),
});
}, [manageCreditCard]);

View File

@@ -22,6 +22,7 @@ import { LOCALSTORAGE } from 'constants/localStorage';
import { EventListener, EventSourcePolyfill } from 'event-source-polyfill';
import { useNotifications } from 'hooks/useNotifications';
import APIError from 'types/api/error';
import { withBasePath } from 'utils/basePath';
interface IEventSourceContext {
eventSourceInstance: EventSourcePolyfill | null;
@@ -129,9 +130,12 @@ export function EventSourceProvider({
const handleStartOpenConnection = useCallback(
(filterExpression?: string): void => {
const eventSourceUrl = `${
ENVIRONMENT.baseURL
}${apiV3}logs/livetail?filter=${encodeURIComponent(filterExpression || '')}`;
const apiPath = `${apiV3}logs/livetail?filter=${encodeURIComponent(
filterExpression || '',
)}`;
const eventSourceUrl = ENVIRONMENT.baseURL
? `${ENVIRONMENT.baseURL}${apiPath}`
: withBasePath(apiPath);
eventSourceRef.current = new EventSourcePolyfill(eventSourceUrl, {
headers: {

View File

@@ -35,6 +35,7 @@ export function withBasePath(path: string): string {
* getAbsoluteUrl(ROUTES.LOGS_EXPLORER) → 'https://host/signoz/logs/logs-explorer'
*/
export function getAbsoluteUrl(path: string): string {
// oxlint-disable-next-line signoz/no-raw-absolute-path
return window.location.origin + withBasePath(path);
}
@@ -44,7 +45,7 @@ export function getAbsoluteUrl(path: string): string {
* getBaseUrl() → 'https://host/signoz'
*/
export function getBaseUrl(): string {
return (
window.location.origin + (_basePath === '/' ? '' : _basePath.slice(0, -1))
);
// oxlint-disable-next-line signoz/no-raw-absolute-path
const origin = window.location.origin;
return origin + (_basePath === '/' ? '' : _basePath.slice(0, -1));
}

View File

@@ -22,6 +22,10 @@ type AuthZ interface {
// BatchCheck accepts a map of ID → tuple and returns a map of ID → authorization result.
BatchCheck(context.Context, map[string]*openfgav1.TupleKey) (map[string]*authtypes.TupleKeyAuthorization, error)
// CheckTransactions checks whether the given subject is authorized for the given transactions.
// Returns results in the same order as the input transactions.
CheckTransactions(ctx context.Context, subject string, orgID valuer.UUID, transactions []*authtypes.Transaction) ([]*authtypes.TransactionWithAuthorization, error)
// Write accepts the insertion tuples and the deletion tuples.
Write(context.Context, []*openfgav1.TupleKey, []*openfgav1.TupleKey) error

View File

@@ -18,25 +18,31 @@ import (
)
type provider struct {
server *openfgaserver.Server
store authtypes.RoleStore
server *openfgaserver.Server
store authtypes.RoleStore
registry []authz.RegisterTypeable
managedRolesByTransaction map[string][]string
}
func NewProviderFactory(sqlstore sqlstore.SQLStore, openfgaSchema []openfgapkgtransformer.ModuleFile, openfgaDataStore storage.OpenFGADatastore) factory.ProviderFactory[authz.AuthZ, authz.Config] {
func NewProviderFactory(sqlstore sqlstore.SQLStore, openfgaSchema []openfgapkgtransformer.ModuleFile, openfgaDataStore storage.OpenFGADatastore, registry ...authz.RegisterTypeable) factory.ProviderFactory[authz.AuthZ, authz.Config] {
return factory.NewProviderFactory(factory.MustNewName("openfga"), func(ctx context.Context, ps factory.ProviderSettings, config authz.Config) (authz.AuthZ, error) {
return newOpenfgaProvider(ctx, ps, config, sqlstore, openfgaSchema, openfgaDataStore)
return newOpenfgaProvider(ctx, ps, config, sqlstore, openfgaSchema, openfgaDataStore, registry)
})
}
func newOpenfgaProvider(ctx context.Context, settings factory.ProviderSettings, config authz.Config, sqlstore sqlstore.SQLStore, openfgaSchema []openfgapkgtransformer.ModuleFile, openfgaDataStore storage.OpenFGADatastore) (authz.AuthZ, error) {
func newOpenfgaProvider(ctx context.Context, settings factory.ProviderSettings, config authz.Config, sqlstore sqlstore.SQLStore, openfgaSchema []openfgapkgtransformer.ModuleFile, openfgaDataStore storage.OpenFGADatastore, registry []authz.RegisterTypeable) (authz.AuthZ, error) {
server, err := openfgaserver.NewOpenfgaServer(ctx, settings, config, sqlstore, openfgaSchema, openfgaDataStore)
if err != nil {
return nil, err
}
managedRolesByTransaction := buildManagedRolesByTransaction(registry)
return &provider{
server: server,
store: sqlauthzstore.NewSqlAuthzStore(sqlstore),
server: server,
store: sqlauthzstore.NewSqlAuthzStore(sqlstore),
registry: registry,
managedRolesByTransaction: managedRolesByTransaction,
}, nil
}
@@ -209,6 +215,42 @@ func (provider *provider) Delete(_ context.Context, _ valuer.UUID, _ valuer.UUID
return errors.Newf(errors.TypeUnsupported, authtypes.ErrCodeRoleUnsupported, "not implemented")
}
func (provider *provider) CheckTransactions(ctx context.Context, subject string, orgID valuer.UUID, transactions []*authtypes.Transaction) ([]*authtypes.TransactionWithAuthorization, error) {
if len(transactions) == 0 {
return make([]*authtypes.TransactionWithAuthorization, 0), nil
}
tuples, preResolved, roleCorrelations, err := authtypes.NewTuplesFromTransactionsWithManagedRoles(transactions, subject, orgID, provider.managedRolesByTransaction)
if err != nil {
return nil, err
}
if len(tuples) == 0 {
return authtypes.NewTransactionWithAuthorizationFromBatchResults(transactions, nil, preResolved, roleCorrelations), nil
}
batchResults, err := provider.server.BatchCheck(ctx, tuples)
if err != nil {
return nil, err
}
return authtypes.NewTransactionWithAuthorizationFromBatchResults(transactions, batchResults, preResolved, roleCorrelations), nil
}
func buildManagedRolesByTransaction(registry []authz.RegisterTypeable) map[string][]string {
managedRolesByTransaction := make(map[string][]string)
for _, register := range registry {
for roleName, transactions := range register.MustGetManagedRoleTransactions() {
for _, txn := range transactions {
key := txn.TransactionKey()
managedRolesByTransaction[key] = append(managedRolesByTransaction[key], roleName)
}
}
}
return managedRolesByTransaction
}
func (provider *provider) MustGetTypeables() []authtypes.Typeable {
return nil
}

View File

@@ -272,17 +272,11 @@ func (handler *handler) Check(rw http.ResponseWriter, r *http.Request) {
return
}
tuples, err := authtypes.NewTuplesFromTransactions(transactions, subject, orgID)
results, err := handler.authz.CheckTransactions(ctx, subject, orgID, transactions)
if err != nil {
render.Error(rw, err)
return
}
results, err := handler.authz.BatchCheck(ctx, tuples)
if err != nil {
render.Error(rw, err)
return
}
render.Success(rw, http.StatusOK, authtypes.NewGettableTransaction(transactions, results))
render.Success(rw, http.StatusOK, authtypes.NewGettableTransaction(results))
}

View File

@@ -0,0 +1 @@
<svg id="b300f0d1-2ad8-4418-a1c5-23d0b9d21841" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 18 18"><defs><linearGradient id="b8cad6fd-ec7f-45e9-be2a-125e8b87bd03" x1="10.79" y1="2.17" x2="10.79" y2="16.56" gradientUnits="userSpaceOnUse"><stop offset="0.18" stop-color="#5ea0ef"/><stop offset="1" stop-color="#0078d4"/></linearGradient></defs><title>Icon-web-43</title><rect x="3.7" y="5.49" width="1.18" height="5.26" rx="0.52" transform="translate(-3.83 12.41) rotate(-90)" fill="#b3b3b3"/><rect x="2.04" y="7.88" width="1.18" height="5.26" rx="0.52" transform="translate(-7.88 13.14) rotate(-90)" fill="#a3a3a3"/><rect x="3.7" y="10.26" width="1.18" height="5.26" rx="0.52" transform="translate(-8.6 17.19) rotate(-90)" fill="#7a7a7a"/><path d="M18,11a3.28,3.28,0,0,0-2.81-3.18,4.13,4.13,0,0,0-4.21-4,4.23,4.23,0,0,0-4,2.8,3.89,3.89,0,0,0-3.38,3.8,4,4,0,0,0,4.06,3.86l.36,0h6.58l.17,0A3.32,3.32,0,0,0,18,11Z" fill="url(#b8cad6fd-ec7f-45e9-be2a-125e8b87bd03)"/></svg>

After

Width:  |  Height:  |  Size: 971 B

View File

@@ -0,0 +1,167 @@
{
"id": "cdnprofile",
"title": "CDN Profile",
"icon": "file://icon.svg",
"overview": "file://overview.md",
"supportedSignals": {
"metrics": true,
"logs": true
},
"dataCollected": {
"metrics": [
{
"name": "azure_activewebsocketconnections_total",
"unit": "Count",
"type": "Gauge",
"description": ""
},
{
"name": "azure_averagewebsocketconnectionduration_average",
"unit": "Milliseconds",
"type": "Gauge",
"description": ""
},
{
"name": "azure_backendhealthpercentage_average",
"unit": "Percent",
"type": "Gauge",
"description": ""
},
{
"name": "azure_backendrequestcount_total",
"unit": "Count",
"type": "Gauge",
"description": ""
},
{
"name": "azure_backendrequestlatency_average",
"unit": "Milliseconds",
"type": "Gauge",
"description": ""
},
{
"name": "azure_billableresponsesize_total",
"unit": "Bytes",
"type": "Gauge",
"description": ""
},
{
"name": "azure_bytehitratio_average",
"unit": "Percent",
"type": "Gauge",
"description": ""
},
{
"name": "azure_originhealthpercentage_average",
"unit": "Percent",
"type": "Gauge",
"description": ""
},
{
"name": "azure_originlatency_average",
"unit": "Milliseconds",
"type": "Gauge",
"description": ""
},
{
"name": "azure_originrequestcount_total",
"unit": "Count",
"type": "Gauge",
"description": ""
},
{
"name": "azure_originshieldoriginrequestcount_total",
"unit": "Count",
"type": "Gauge",
"description": ""
},
{
"name": "azure_originshieldratelimitrequestcount_total",
"unit": "Count",
"type": "Gauge",
"description": ""
},
{
"name": "azure_originshieldrequestcount_total",
"unit": "Count",
"type": "Gauge",
"description": ""
},
{
"name": "azure_originshieldrequestsize_total",
"unit": "Bytes",
"type": "Gauge",
"description": ""
},
{
"name": "azure_percentage4xx_average",
"unit": "Percent",
"type": "Gauge",
"description": ""
},
{
"name": "azure_percentage5xx_average",
"unit": "Percent",
"type": "Gauge",
"description": ""
},
{
"name": "azure_requestcount_total",
"unit": "Count",
"type": "Gauge",
"description": ""
},
{
"name": "azure_requestsize_total",
"unit": "Bytes",
"type": "Gauge",
"description": ""
},
{
"name": "azure_responsesize_total",
"unit": "Bytes",
"type": "Gauge",
"description": ""
},
{
"name": "azure_totallatency_average",
"unit": "Milliseconds",
"type": "Gauge",
"description": ""
},
{
"name": "azure_webapplicationfirewallrequestcount_total",
"unit": "Count",
"type": "Gauge",
"description": ""
}
],
"logs": [
{
"name": "Resource ID",
"path": "resources.azure.resource.id",
"type": "string"
}
]
},
"telemetryCollectionStrategy": {
"azure": {
"resourceProvider": "Microsoft.Cdn",
"resourceType": "profiles",
"metrics": {},
"logs": {
"categoryGroups": ["allLogs"]
}
}
},
"assets": {
"dashboards": [
{
"id": "overview",
"title": "CDN Profile Overview",
"description": "Overview of CDN Profile metrics",
"definition": "file://assets/dashboards/overview.json"
}
]
}
}

View File

@@ -0,0 +1,3 @@
### Monitor Azure CDN Profile with SigNoz
Collect key CDN Profile metrics and view them with an out of the box dashboard.

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 18 18"><defs><linearGradient id="a9c62307-1cd0-400c-911b-17ec6a9110ce" x1="9" y1="15.834" x2="9" y2="5.788" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#32bedd"/><stop offset="0.775" stop-color="#32d4f5"/></linearGradient></defs><title>MsPortalFx.base.images-7</title><g id="f31d214e-f09e-49e3-b3d2-7c5d55682d09"><g><path d="M.5,5.788h17a0,0,0,0,1,0,0v9.478a.568.568,0,0,1-.568.568H1.068A.568.568,0,0,1,.5,15.266V5.788A0,0,0,0,1,.5,5.788Z" fill="url(#a9c62307-1cd0-400c-911b-17ec6a9110ce)"/><path d="M1.071,2.166H16.929a.568.568,0,0,1,.568.568V5.788a0,0,0,0,1,0,0H.5a0,0,0,0,1,0,0V2.734A.568.568,0,0,1,1.071,2.166Z" fill="#0078d4"/><rect x="2.328" y="7.049" width="6.281" height="3.408" rx="0.283" fill="#0078d4"/><rect x="9.336" y="7.049" width="6.281" height="3.408" rx="0.283" fill="#fff"/><rect x="2.296" y="11.128" width="6.281" height="3.408" rx="0.283" fill="#0078d4"/><rect x="9.304" y="11.128" width="6.281" height="3.408" rx="0.283" fill="#0078d4"/></g></g></svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@@ -0,0 +1,107 @@
{
"id": "storageaccountsblob",
"title": "Storage Accounts Blob Storage",
"icon": "file://icon.svg",
"overview": "file://overview.md",
"supportedSignals": {
"metrics": true,
"logs": true
},
"dataCollected": {
"metrics": [
{
"name": "azure_availability_average",
"unit": "Percent",
"type": "Gauge",
"description": ""
},
{
"name": "azure_blobcapacity_average",
"unit": "Bytes",
"type": "Gauge",
"description": ""
},
{
"name": "azure_blobcount_average",
"unit": "Count",
"type": "Gauge",
"description": ""
},
{
"name": "azure_blobprovisionedsize_average",
"unit": "Bytes",
"type": "Gauge",
"description": ""
},
{
"name": "azure_containercount_average",
"unit": "Count",
"type": "Gauge",
"description": ""
},
{
"name": "azure_egress_total",
"unit": "Bytes",
"type": "Gauge",
"description": ""
},
{
"name": "azure_indexcapacity_average",
"unit": "Bytes",
"type": "Gauge",
"description": ""
},
{
"name": "azure_ingress_total",
"unit": "Bytes",
"type": "Gauge",
"description": ""
},
{
"name": "azure_successe2elatency_average",
"unit": "Milliseconds",
"type": "Gauge",
"description": ""
},
{
"name": "azure_successserverlatency_average",
"unit": "Milliseconds",
"type": "Gauge",
"description": ""
},
{
"name": "azure_transactions_total",
"unit": "Count",
"type": "Gauge",
"description": ""
}
],
"logs": [
{
"name": "Resource ID",
"path": "resources.azure.resource.id",
"type": "string"
}
]
},
"telemetryCollectionStrategy": {
"azure": {
"resourceProvider": "Microsoft.Storage",
"resourceType": "storageAccounts/blobServices",
"metrics": {},
"logs": {
"categoryGroups": ["allLogs"]
}
}
},
"assets": {
"dashboards": [
{
"id": "overview",
"title": "Blob Storage Overview",
"description": "Overview of Blob Storage",
"definition": "file://assets/dashboards/overview.json"
}
]
}
}

View File

@@ -0,0 +1,3 @@
### Monitor Storage Accounts Blob Storage with SigNoz
Collect key Blob Storage metrics and view them with an out of the box dashboard.

View File

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

View File

@@ -20,6 +20,11 @@ type GettableTransaction struct {
Authorized bool `json:"authorized" required:"true"`
}
type TransactionWithAuthorization struct {
Transaction *Transaction
Authorized bool
}
func NewTransaction(relation Relation, object Object) (*Transaction, error) {
if !slices.Contains(TypeableRelations[object.Resource.Type], relation) {
return nil, errors.Newf(errors.TypeInvalidInput, ErrCodeAuthZInvalidRelation, "invalid relation %s for type %s", relation.StringValue(), object.Resource.Type.StringValue())
@@ -28,13 +33,12 @@ func NewTransaction(relation Relation, object Object) (*Transaction, error) {
return &Transaction{ID: valuer.GenerateUUID(), Relation: relation, Object: object}, nil
}
func NewGettableTransaction(transactions []*Transaction, results map[string]*TupleKeyAuthorization) []*GettableTransaction {
gettableTransactions := make([]*GettableTransaction, len(transactions))
for i, txn := range transactions {
result := results[txn.ID.StringValue()]
func NewGettableTransaction(results []*TransactionWithAuthorization) []*GettableTransaction {
gettableTransactions := make([]*GettableTransaction, len(results))
for i, result := range results {
gettableTransactions[i] = &GettableTransaction{
Relation: txn.Relation,
Object: txn.Object,
Relation: result.Transaction.Relation,
Object: result.Transaction.Object,
Authorized: result.Authorized,
}
}
@@ -42,6 +46,54 @@ func NewGettableTransaction(transactions []*Transaction, results map[string]*Tup
return gettableTransactions
}
// NewTransactionWithAuthorizationFromBatchResults merges batch check results into an ordered
// slice of TransactionWithAuthorization matching the input transactions order.
// preResolved contains txn IDs whose authorization was determined without BatchCheck.
// roleCorrelations maps txn IDs to correlation IDs used for managed role checks.
func NewTransactionWithAuthorizationFromBatchResults(
transactions []*Transaction,
batchResults map[string]*TupleKeyAuthorization,
preResolved map[string]bool,
roleCorrelations map[string][]string,
) []*TransactionWithAuthorization {
output := make([]*TransactionWithAuthorization, len(transactions))
for i, txn := range transactions {
txnID := txn.ID.StringValue()
if authorized, ok := preResolved[txnID]; ok {
output[i] = &TransactionWithAuthorization{
Transaction: txn,
Authorized: authorized,
}
continue
}
if txn.Object.Resource.Type == TypeRole && txn.Relation == RelationAssignee {
output[i] = &TransactionWithAuthorization{
Transaction: txn,
Authorized: batchResults[txnID].Authorized,
}
continue
}
correlationIDs := roleCorrelations[txnID]
authorized := false
for _, correlationID := range correlationIDs {
if result, exists := batchResults[correlationID]; exists && result.Authorized {
authorized = true
break
}
}
output[i] = &TransactionWithAuthorization{
Transaction: txn,
Authorized: authorized,
}
}
return output
}
func (transaction *Transaction) UnmarshalJSON(data []byte) error {
var shadow = struct {
Relation Relation

View File

@@ -10,6 +10,11 @@ type TupleKeyAuthorization struct {
Authorized bool
}
// TransactionKey returns a composite key for matching transactions to managed roles.
func (transaction *Transaction) TransactionKey() string {
return transaction.Relation.StringValue() + ":" + transaction.Object.Resource.Type.StringValue() + ":" + transaction.Object.Resource.Name.String()
}
func NewTuplesFromTransactions(transactions []*Transaction, subject string, orgID valuer.UUID) (map[string]*openfgav1.TupleKey, error) {
tuples := make(map[string]*openfgav1.TupleKey, len(transactions))
for _, txn := range transactions {
@@ -29,3 +34,57 @@ func NewTuplesFromTransactions(transactions []*Transaction, subject string, orgI
return tuples, nil
}
// NewTuplesFromTransactionsWithManagedRoles converts transactions to tuples for BatchCheck.
// Direct role-assignment transactions (TypeRole + RelationAssignee) produce one tuple keyed by txn ID.
// Other transactions are expanded via managedRolesByTransaction into role-assignee checks, keyed by "txnID:roleName".
// Transactions with no managed role mapping are marked as pre-resolved (false) in the returned map.
func NewTuplesFromTransactionsWithManagedRoles(
transactions []*Transaction,
subject string,
orgID valuer.UUID,
managedRolesByTransaction map[string][]string,
) (tuples map[string]*openfgav1.TupleKey, preResolved map[string]bool, roleCorrelations map[string][]string, err error) {
tuples = make(map[string]*openfgav1.TupleKey)
preResolved = make(map[string]bool)
roleCorrelations = make(map[string][]string)
for _, txn := range transactions {
txnID := txn.ID.StringValue()
if txn.Object.Resource.Type == TypeRole && txn.Relation == RelationAssignee {
typeable, err := NewTypeableFromType(txn.Object.Resource.Type, txn.Object.Resource.Name)
if err != nil {
return nil, nil, nil, err
}
txnTuples, err := typeable.Tuples(subject, txn.Relation, []Selector{txn.Object.Selector}, orgID)
if err != nil {
return nil, nil, nil, err
}
tuples[txnID] = txnTuples[0]
continue
}
roleNames, found := managedRolesByTransaction[txn.TransactionKey()]
if !found || len(roleNames) == 0 {
preResolved[txnID] = false
continue
}
for _, roleName := range roleNames {
roleSelector := MustNewSelector(TypeRole, roleName)
roleTuples, err := TypeableRole.Tuples(subject, RelationAssignee, []Selector{roleSelector}, orgID)
if err != nil {
return nil, nil, nil, err
}
correlationID := valuer.GenerateUUID().StringValue()
tuples[correlationID] = roleTuples[0]
roleCorrelations[txnID] = append(roleCorrelations[txnID], correlationID)
}
}
return tuples, preResolved, roleCorrelations, nil
}

View File

@@ -1,5 +1,17 @@
package cloudintegrationtypes
import (
"fmt"
"strings"
"github.com/SigNoz/signoz/pkg/valuer"
)
var (
AgentArmTemplateS3Path = valuer.NewString("https://signoz-integrations.s3.us-east-1.amazonaws.com/azure-arm-template-%s.json")
AgentDeploymentStackName = valuer.NewString("signoz-integration")
)
type AzureAccountConfig struct {
DeploymentRegion string `json:"deploymentRegion" required:"true"`
ResourceGroups []string `json:"resourceGroups" required:"true" nullable:"false"`
@@ -62,3 +74,75 @@ func NewAzureIntegrationConfig(
TelemetryCollectionStrategy: strategies,
}
}
func NewAzureConnectionArtifact(cliCommand, cloudPowerShellCommand string) *AzureConnectionArtifact {
return &AzureConnectionArtifact{
CLICommand: cliCommand,
CloudPowerShellCommand: cloudPowerShellCommand,
}
}
func NewAzureConnectionCLICommand(
accountID valuer.UUID,
agentVersion string,
creds *Credentials,
cfg *AzurePostableAccountConfig,
) string {
templateURL := fmt.Sprintf(AgentArmTemplateS3Path.StringValue(), agentVersion)
lines := []string{
"az stack sub create",
fmt.Sprintf(" --name %s", AgentDeploymentStackName.StringValue()),
fmt.Sprintf(" --location %s", cfg.DeploymentRegion),
fmt.Sprintf(" --template-uri %s", templateURL),
" --parameters",
fmt.Sprintf(" location='%s'", cfg.DeploymentRegion),
fmt.Sprintf(" signozApiKey='%s'", creds.SigNozAPIKey),
fmt.Sprintf(" signozApiUrl='%s'", creds.SigNozAPIURL),
fmt.Sprintf(" signozIngestionUrl='%s'", creds.IngestionURL),
fmt.Sprintf(" signozIngestionKey='%s'", creds.IngestionKey),
fmt.Sprintf(" signozIntegrationAccountId='%s'", accountID.StringValue()),
fmt.Sprintf(" signozIntegrationAgentVersion='%s'", agentVersion),
" --action-on-unmanage deleteAll",
" --deny-settings-mode denyDelete",
}
return strings.Join(lines, " \\\n")
}
func NewAzureConnectionPowerShellCommand(
accountID valuer.UUID,
agentVersion string,
creds *Credentials,
cfg *AzurePostableAccountConfig,
) string {
params := []struct{ k, v string }{
{"location", cfg.DeploymentRegion},
{"signozApiKey", creds.SigNozAPIKey},
{"signozApiUrl", creds.SigNozAPIURL},
{"signozIngestionUrl", creds.IngestionURL},
{"signozIngestionKey", creds.IngestionKey},
{"signozIntegrationAccountId", accountID.StringValue()},
{"signozIntegrationAgentVersion", agentVersion},
{"rgName", "signoz-integration-rg"},
{"containerEnvName", "signoz-integration-agent-env"},
{"deploymentEnv", "production"},
}
const keyWidth = 36
var paramLines []string
for _, p := range params {
paramLines = append(paramLines, fmt.Sprintf(" %-*s= \"%s\"", keyWidth, p.k, p.v))
}
templateURL := fmt.Sprintf(AgentArmTemplateS3Path.StringValue(), agentVersion)
return strings.Join([]string{
"New-AzSubscriptionDeploymentStack `",
fmt.Sprintf(" -Name \"%s\" `", AgentDeploymentStackName.StringValue()),
fmt.Sprintf(" -Location \"%s\" `", cfg.DeploymentRegion),
fmt.Sprintf(" -TemplateUri \"%s\" `", templateURL),
" -TemplateParameterObject @{",
strings.Join(paramLines, "\n"),
" } `",
" -ActionOnUnmanage \"deleteAll\" `",
" -DenySettingsMode \"denyDelete\"",
}, "\n")
}

View File

@@ -239,6 +239,10 @@ 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())
}