mirror of
https://github.com/SigNoz/signoz.git
synced 2026-04-22 20:00:29 +01:00
Compare commits
20 Commits
base-path-
...
platform-p
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7684d64ffe | ||
|
|
9d0889b49e | ||
|
|
db4d0ceab8 | ||
|
|
9a7af1dff6 | ||
|
|
484b22c12a | ||
|
|
5b599babd5 | ||
|
|
3080c3c493 | ||
|
|
b0e9fbe24f | ||
|
|
aeb71469d9 | ||
|
|
1ff9a748ee | ||
|
|
dfa8625e3d | ||
|
|
a60f8551dd | ||
|
|
e607908b29 | ||
|
|
210ac2e74b | ||
|
|
5971a9efbf | ||
|
|
d43f3de049 | ||
|
|
7917540662 | ||
|
|
addb234c8c | ||
|
|
be6a663e4b | ||
|
|
ed17003329 |
8
.github/CODEOWNERS
vendored
8
.github/CODEOWNERS
vendored
@@ -10,6 +10,7 @@
|
||||
/frontend/src/container/OnboardingV2Container/AddDataSource/AddDataSource.tsx @makeavish
|
||||
|
||||
# CI
|
||||
|
||||
/deploy/ @therealpandey
|
||||
.github @therealpandey
|
||||
go.mod @therealpandey
|
||||
@@ -108,6 +109,7 @@ go.mod @therealpandey
|
||||
/pkg/modules/role/ @therealpandey
|
||||
|
||||
# IdentN Owners
|
||||
|
||||
/pkg/identn/ @therealpandey
|
||||
/pkg/http/middleware/identn.go @therealpandey
|
||||
|
||||
@@ -115,6 +117,10 @@ go.mod @therealpandey
|
||||
|
||||
/tests/integration/ @therealpandey
|
||||
|
||||
# Flagger Owners
|
||||
|
||||
/pkg/flagger/ @therealpandey
|
||||
|
||||
# OpenAPI types generator
|
||||
|
||||
/frontend/src/api @SigNoz/frontend-maintainers
|
||||
@@ -134,6 +140,7 @@ go.mod @therealpandey
|
||||
/frontend/src/container/ListOfDashboard/ @SigNoz/pulse-frontend
|
||||
|
||||
# Dashboard Widget Page
|
||||
|
||||
/frontend/src/pages/DashboardWidget/ @SigNoz/pulse-frontend
|
||||
/frontend/src/container/NewWidget/ @SigNoz/pulse-frontend
|
||||
|
||||
@@ -149,6 +156,7 @@ go.mod @therealpandey
|
||||
/frontend/src/container/PublicDashboardContainer/ @SigNoz/pulse-frontend
|
||||
|
||||
## Dashboard Libs + Components
|
||||
|
||||
/frontend/src/lib/uPlotV2/ @SigNoz/pulse-frontend
|
||||
/frontend/src/lib/dashboard/ @SigNoz/pulse-frontend
|
||||
/frontend/src/lib/dashboardVariables/ @SigNoz/pulse-frontend
|
||||
|
||||
@@ -190,7 +190,7 @@ services:
|
||||
# - ../common/clickhouse/storage.xml:/etc/clickhouse-server/config.d/storage.xml
|
||||
signoz:
|
||||
!!merge <<: *db-depend
|
||||
image: signoz/signoz:v0.119.0
|
||||
image: signoz/signoz:v0.120.0
|
||||
ports:
|
||||
- "8080:8080" # signoz port
|
||||
# - "6060:6060" # pprof port
|
||||
@@ -213,7 +213,7 @@ services:
|
||||
retries: 3
|
||||
otel-collector:
|
||||
!!merge <<: *db-depend
|
||||
image: signoz/signoz-otel-collector:v0.144.2
|
||||
image: signoz/signoz-otel-collector:v0.144.3
|
||||
entrypoint:
|
||||
- /bin/sh
|
||||
command:
|
||||
@@ -241,7 +241,7 @@ services:
|
||||
replicas: 3
|
||||
signoz-telemetrystore-migrator:
|
||||
!!merge <<: *db-depend
|
||||
image: signoz/signoz-otel-collector:v0.144.2
|
||||
image: signoz/signoz-otel-collector:v0.144.3
|
||||
environment:
|
||||
- SIGNOZ_OTEL_COLLECTOR_CLICKHOUSE_DSN=tcp://clickhouse:9000
|
||||
- SIGNOZ_OTEL_COLLECTOR_CLICKHOUSE_CLUSTER=cluster
|
||||
|
||||
@@ -117,7 +117,7 @@ services:
|
||||
# - ../common/clickhouse/storage.xml:/etc/clickhouse-server/config.d/storage.xml
|
||||
signoz:
|
||||
!!merge <<: *db-depend
|
||||
image: signoz/signoz:v0.119.0
|
||||
image: signoz/signoz:v0.120.0
|
||||
ports:
|
||||
- "8080:8080" # signoz port
|
||||
volumes:
|
||||
@@ -139,7 +139,7 @@ services:
|
||||
retries: 3
|
||||
otel-collector:
|
||||
!!merge <<: *db-depend
|
||||
image: signoz/signoz-otel-collector:v0.144.2
|
||||
image: signoz/signoz-otel-collector:v0.144.3
|
||||
entrypoint:
|
||||
- /bin/sh
|
||||
command:
|
||||
@@ -167,7 +167,7 @@ services:
|
||||
replicas: 3
|
||||
signoz-telemetrystore-migrator:
|
||||
!!merge <<: *db-depend
|
||||
image: signoz/signoz-otel-collector:v0.144.2
|
||||
image: signoz/signoz-otel-collector:v0.144.3
|
||||
environment:
|
||||
- SIGNOZ_OTEL_COLLECTOR_CLICKHOUSE_DSN=tcp://clickhouse:9000
|
||||
- SIGNOZ_OTEL_COLLECTOR_CLICKHOUSE_CLUSTER=cluster
|
||||
|
||||
@@ -181,7 +181,7 @@ services:
|
||||
# - ../common/clickhouse/storage.xml:/etc/clickhouse-server/config.d/storage.xml
|
||||
signoz:
|
||||
!!merge <<: *db-depend
|
||||
image: signoz/signoz:${VERSION:-v0.119.0}
|
||||
image: signoz/signoz:${VERSION:-v0.120.0}
|
||||
container_name: signoz
|
||||
ports:
|
||||
- "8080:8080" # signoz port
|
||||
@@ -204,7 +204,7 @@ services:
|
||||
retries: 3
|
||||
otel-collector:
|
||||
!!merge <<: *db-depend
|
||||
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-v0.144.2}
|
||||
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-v0.144.3}
|
||||
container_name: signoz-otel-collector
|
||||
entrypoint:
|
||||
- /bin/sh
|
||||
@@ -229,7 +229,7 @@ services:
|
||||
- "4318:4318" # OTLP HTTP receiver
|
||||
signoz-telemetrystore-migrator:
|
||||
!!merge <<: *db-depend
|
||||
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-v0.144.2}
|
||||
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-v0.144.3}
|
||||
container_name: signoz-telemetrystore-migrator
|
||||
environment:
|
||||
- SIGNOZ_OTEL_COLLECTOR_CLICKHOUSE_DSN=tcp://clickhouse:9000
|
||||
|
||||
@@ -109,7 +109,7 @@ services:
|
||||
# - ../common/clickhouse/storage.xml:/etc/clickhouse-server/config.d/storage.xml
|
||||
signoz:
|
||||
!!merge <<: *db-depend
|
||||
image: signoz/signoz:${VERSION:-v0.119.0}
|
||||
image: signoz/signoz:${VERSION:-v0.120.0}
|
||||
container_name: signoz
|
||||
ports:
|
||||
- "8080:8080" # signoz port
|
||||
@@ -132,7 +132,7 @@ services:
|
||||
retries: 3
|
||||
otel-collector:
|
||||
!!merge <<: *db-depend
|
||||
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-v0.144.2}
|
||||
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-v0.144.3}
|
||||
container_name: signoz-otel-collector
|
||||
entrypoint:
|
||||
- /bin/sh
|
||||
@@ -157,7 +157,7 @@ services:
|
||||
- "4318:4318" # OTLP HTTP receiver
|
||||
signoz-telemetrystore-migrator:
|
||||
!!merge <<: *db-depend
|
||||
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-v0.144.2}
|
||||
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-v0.144.3}
|
||||
container_name: signoz-telemetrystore-migrator
|
||||
environment:
|
||||
- SIGNOZ_OTEL_COLLECTOR_CLICKHOUSE_DSN=tcp://clickhouse:9000
|
||||
|
||||
@@ -78,6 +78,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, typeable authtypes.Typeable) ([]*authtypes.Object, error) {
|
||||
return provider.openfgaServer.ListObjects(ctx, subject, relation, typeable)
|
||||
}
|
||||
|
||||
@@ -66,8 +66,6 @@ module.exports = {
|
||||
rules: {
|
||||
// Asset migration — base-path safety
|
||||
'rulesdir/no-unsupported-asset-pattern': 'error',
|
||||
// Base-path safety — window.open and origin-concat patterns
|
||||
'rulesdir/no-raw-absolute-path': 'error',
|
||||
|
||||
// Code quality rules
|
||||
'prefer-const': 'error', // Enforces const for variables never reassigned
|
||||
@@ -215,31 +213,6 @@ module.exports = {
|
||||
message:
|
||||
'Avoid calling .getState() directly. Export a standalone action from the store instead.',
|
||||
},
|
||||
{
|
||||
selector:
|
||||
"MemberExpression[object.name='window'][property.name='localStorage']",
|
||||
message:
|
||||
'Use getLocalStorageKey/setLocalStorageKey/removeLocalStorageKey from api/browser/localstorage instead.',
|
||||
},
|
||||
{
|
||||
selector:
|
||||
"MemberExpression[object.name='window'][property.name='sessionStorage']",
|
||||
message:
|
||||
'Use getSessionStorageApi/setSessionStorageApi/removeSessionStorageApi from api/browser/sessionstorage instead.',
|
||||
},
|
||||
],
|
||||
'no-restricted-globals': [
|
||||
'error',
|
||||
{
|
||||
name: 'localStorage',
|
||||
message:
|
||||
'Use getLocalStorageKey/setLocalStorageKey/removeLocalStorageKey from api/browser/localstorage instead.',
|
||||
},
|
||||
{
|
||||
name: 'sessionStorage',
|
||||
message:
|
||||
'Use getSessionStorageApi/setSessionStorageApi/removeSessionStorageApi from api/browser/sessionstorage instead.',
|
||||
},
|
||||
],
|
||||
},
|
||||
overrides: [
|
||||
@@ -273,11 +246,6 @@ module.exports = {
|
||||
'sonarjs/cognitive-complexity': 'off', // Tests can be complex
|
||||
'sonarjs/no-identical-functions': 'off', // Similar test patterns are OK
|
||||
'sonarjs/no-small-switch': 'off', // Small switches are OK in tests
|
||||
// Test assertions intentionally reference window.location.origin for expected-value checks
|
||||
'rulesdir/no-raw-absolute-path': 'off',
|
||||
// Tests may access storage directly for setup/assertion/spy purposes
|
||||
'no-restricted-globals': 'off',
|
||||
'no-restricted-syntax': 'off',
|
||||
},
|
||||
},
|
||||
{
|
||||
|
||||
@@ -1,153 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* ESLint rule: no-raw-absolute-path
|
||||
*
|
||||
* Catches patterns that break at runtime when the app is served from a
|
||||
* sub-path (e.g. /signoz/):
|
||||
*
|
||||
* 1. window.open(path, '_blank')
|
||||
* → use openInNewTab(path) which calls withBasePath internally
|
||||
*
|
||||
* 2. window.location.origin + path / `${window.location.origin}${path}`
|
||||
* → use getAbsoluteUrl(path)
|
||||
*
|
||||
* 3. frontendBaseUrl: window.location.origin (bare origin usage)
|
||||
* → use getBaseUrl() to include the base path
|
||||
*
|
||||
* 4. window.location.href = path
|
||||
* → use withBasePath(path) or navigate() for internal navigation
|
||||
*
|
||||
* External URLs (first arg starts with "http") are explicitly allowed.
|
||||
*/
|
||||
|
||||
function isOriginAccess(node) {
|
||||
return (
|
||||
node.type === 'MemberExpression' &&
|
||||
!node.computed &&
|
||||
node.property.name === 'origin' &&
|
||||
node.object.type === 'MemberExpression' &&
|
||||
!node.object.computed &&
|
||||
node.object.property.name === 'location' &&
|
||||
node.object.object.type === 'Identifier' &&
|
||||
node.object.object.name === 'window'
|
||||
);
|
||||
}
|
||||
|
||||
function isHrefAccess(node) {
|
||||
return (
|
||||
node.type === 'MemberExpression' &&
|
||||
!node.computed &&
|
||||
node.property.name === 'href' &&
|
||||
node.object.type === 'MemberExpression' &&
|
||||
!node.object.computed &&
|
||||
node.object.property.name === 'location' &&
|
||||
node.object.object.type === 'Identifier' &&
|
||||
node.object.object.name === 'window'
|
||||
);
|
||||
}
|
||||
|
||||
function isExternalUrl(node) {
|
||||
if (node.type === 'Literal' && typeof node.value === 'string') {
|
||||
return node.value.startsWith('http://') || node.value.startsWith('https://');
|
||||
}
|
||||
if (node.type === 'TemplateLiteral' && node.quasis.length > 0) {
|
||||
const raw = node.quasis[0].value.raw;
|
||||
return raw.startsWith('http://') || raw.startsWith('https://');
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// window.open(withBasePath(x)) and window.open(getAbsoluteUrl(x)) are already safe.
|
||||
function isSafeHelperCall(node) {
|
||||
return (
|
||||
node.type === 'CallExpression' &&
|
||||
node.callee.type === 'Identifier' &&
|
||||
(node.callee.name === 'withBasePath' || node.callee.name === 'getAbsoluteUrl')
|
||||
);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
meta: {
|
||||
type: 'suggestion',
|
||||
docs: {
|
||||
description:
|
||||
'Disallow raw window.open and origin-concatenation patterns that miss the runtime base path',
|
||||
category: 'Base Path Safety',
|
||||
},
|
||||
schema: [],
|
||||
messages: {
|
||||
windowOpen:
|
||||
'Use openInNewTab(path) instead of window.open(path, "_blank") — openInNewTab prepends the base path automatically.',
|
||||
originConcat:
|
||||
'Use getAbsoluteUrl(path) instead of window.location.origin + path — getAbsoluteUrl prepends the base path automatically.',
|
||||
originDirect:
|
||||
'Use getBaseUrl() instead of window.location.origin — getBaseUrl includes the base path.',
|
||||
hrefAssign:
|
||||
'Use withBasePath(path) or navigate() instead of window.location.href = path — ensures the base path is included.',
|
||||
},
|
||||
},
|
||||
|
||||
create(context) {
|
||||
return {
|
||||
// window.open(path, ...) — allow only external first-arg URLs
|
||||
CallExpression(node) {
|
||||
const { callee, arguments: args } = node;
|
||||
if (
|
||||
callee.type !== 'MemberExpression' ||
|
||||
callee.object.type !== 'Identifier' ||
|
||||
callee.object.name !== 'window' ||
|
||||
callee.property.name !== 'open'
|
||||
)
|
||||
return;
|
||||
if (args.length < 1) return;
|
||||
if (isExternalUrl(args[0])) return;
|
||||
if (isSafeHelperCall(args[0])) return;
|
||||
|
||||
context.report({ node, messageId: 'windowOpen' });
|
||||
},
|
||||
|
||||
// window.location.origin + path
|
||||
BinaryExpression(node) {
|
||||
if (node.operator !== '+') return;
|
||||
if (isOriginAccess(node.left) || isOriginAccess(node.right)) {
|
||||
context.report({ node, messageId: 'originConcat' });
|
||||
}
|
||||
},
|
||||
|
||||
// `${window.location.origin}${path}`
|
||||
TemplateLiteral(node) {
|
||||
if (node.expressions.some(isOriginAccess)) {
|
||||
context.report({ node, messageId: 'originConcat' });
|
||||
}
|
||||
},
|
||||
|
||||
// window.location.origin used directly (not in concatenation)
|
||||
// Catches: frontendBaseUrl: window.location.origin
|
||||
MemberExpression(node) {
|
||||
if (!isOriginAccess(node)) return;
|
||||
|
||||
const parent = node.parent;
|
||||
// Skip if parent is BinaryExpression with + (handled by BinaryExpression visitor)
|
||||
if (parent.type === 'BinaryExpression' && parent.operator === '+') return;
|
||||
// Skip if inside TemplateLiteral (handled by TemplateLiteral visitor)
|
||||
if (parent.type === 'TemplateLiteral') return;
|
||||
|
||||
context.report({ node, messageId: 'originDirect' });
|
||||
},
|
||||
|
||||
// window.location.href = path
|
||||
AssignmentExpression(node) {
|
||||
if (node.operator !== '=') return;
|
||||
if (!isHrefAccess(node.left)) return;
|
||||
|
||||
// Allow external URLs
|
||||
if (isExternalUrl(node.right)) return;
|
||||
// Allow safe helper calls
|
||||
if (isSafeHelperCall(node.right)) return;
|
||||
|
||||
context.report({ node, messageId: 'hrefAssign' });
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
@@ -2,7 +2,6 @@
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<base href="[[.BaseHref]]" />
|
||||
<meta
|
||||
http-equiv="Cache-Control"
|
||||
content="no-cache, no-store, must-revalidate, max-age: 0"
|
||||
@@ -40,12 +39,12 @@
|
||||
<meta
|
||||
data-react-helmet="true"
|
||||
property="og:image"
|
||||
content="[[.BaseHref]]images/signoz-hero-image.webp"
|
||||
content="/images/signoz-hero-image.webp"
|
||||
/>
|
||||
<meta
|
||||
data-react-helmet="true"
|
||||
name="twitter:image"
|
||||
content="[[.BaseHref]]images/signoz-hero-image.webp"
|
||||
content="/images/signoz-hero-image.webp"
|
||||
/>
|
||||
<meta
|
||||
data-react-helmet="true"
|
||||
@@ -60,7 +59,7 @@
|
||||
<meta data-react-helmet="true" name="docusaurus_locale" content="en" />
|
||||
<meta data-react-helmet="true" name="docusaurus_tag" content="default" />
|
||||
<meta name="robots" content="noindex" />
|
||||
<link data-react-helmet="true" rel="shortcut icon" href="favicon.ico" />
|
||||
<link data-react-helmet="true" rel="shortcut icon" href="/favicon.ico" />
|
||||
</head>
|
||||
<body data-theme="default">
|
||||
<script>
|
||||
@@ -137,7 +136,7 @@
|
||||
})(document, 'script');
|
||||
}
|
||||
</script>
|
||||
<link rel="stylesheet" href="css/uPlot.min.css" />
|
||||
<link rel="stylesheet" href="/css/uPlot.min.css" />
|
||||
<script type="module" src="./src/index.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -45,7 +45,8 @@ const config: Config.InitialOptions = {
|
||||
'^.+\\.(js|jsx)$': 'babel-jest',
|
||||
},
|
||||
transformIgnorePatterns: [
|
||||
'node_modules/(?!(lodash-es|react-dnd|core-dnd|@react-dnd|dnd-core|react-dnd-html5-backend|axios|@signozhq/design-tokens|@signozhq/table|@signozhq/calendar|@signozhq/input|@signozhq/popover|@signozhq/button|@signozhq/*|date-fns|d3-interpolate|d3-color|api|@codemirror|@lezer|@marijn|@grafana|nuqs)/)',
|
||||
// @chenglou/pretext is ESM-only; @signozhq/ui pulls it in via text-ellipsis.
|
||||
'node_modules/(?!(lodash-es|react-dnd|core-dnd|@react-dnd|dnd-core|react-dnd-html5-backend|axios|@chenglou/pretext|@signozhq/design-tokens|@signozhq/table|@signozhq/calendar|@signozhq/input|@signozhq/popover|@signozhq/*|date-fns|d3-interpolate|d3-color|api|@codemirror|@lezer|@marijn|@grafana|nuqs)/)',
|
||||
],
|
||||
setupFilesAfterEnv: ['<rootDir>/jest.setup.ts'],
|
||||
testPathIgnorePatterns: ['/node_modules/', '/public/'],
|
||||
|
||||
@@ -24,6 +24,10 @@ window.matchMedia =
|
||||
};
|
||||
};
|
||||
|
||||
if (!HTMLElement.prototype.scrollIntoView) {
|
||||
HTMLElement.prototype.scrollIntoView = function (): void {};
|
||||
}
|
||||
|
||||
// Patch getComputedStyle to handle CSS parsing errors from @signozhq/* packages.
|
||||
// These packages inject CSS at import time via style-inject / vite-plugin-css-injected-by-js.
|
||||
// jsdom's nwsapi cannot parse some of the injected selectors (e.g. Tailwind's :animate-in),
|
||||
|
||||
@@ -48,24 +48,10 @@
|
||||
"@radix-ui/react-tooltip": "1.0.7",
|
||||
"@sentry/react": "8.41.0",
|
||||
"@sentry/vite-plugin": "2.22.6",
|
||||
"@signozhq/button": "0.0.5",
|
||||
"@signozhq/calendar": "0.1.1",
|
||||
"@signozhq/callout": "0.0.4",
|
||||
"@signozhq/checkbox": "0.0.4",
|
||||
"@signozhq/combobox": "0.0.4",
|
||||
"@signozhq/command": "0.0.2",
|
||||
"@signozhq/design-tokens": "2.1.4",
|
||||
"@signozhq/dialog": "0.0.4",
|
||||
"@signozhq/drawer": "0.0.6",
|
||||
"@signozhq/icons": "0.1.0",
|
||||
"@signozhq/input": "0.0.4",
|
||||
"@signozhq/popover": "0.1.2",
|
||||
"@signozhq/radio-group": "0.0.4",
|
||||
"@signozhq/resizable": "0.0.2",
|
||||
"@signozhq/tabs": "0.0.11",
|
||||
"@signozhq/table": "0.3.8",
|
||||
"@signozhq/toggle-group": "0.0.3",
|
||||
"@signozhq/ui": "0.0.5",
|
||||
"@signozhq/ui": "0.0.9",
|
||||
"@tanstack/react-table": "8.21.3",
|
||||
"@tanstack/react-virtual": "3.13.22",
|
||||
"@uiw/codemirror-theme-copilot": "4.23.11",
|
||||
|
||||
@@ -2,7 +2,6 @@ import { initReactI18next } from 'react-i18next';
|
||||
import i18n from 'i18next';
|
||||
import LanguageDetector from 'i18next-browser-languagedetector';
|
||||
import Backend from 'i18next-http-backend';
|
||||
import { getBasePath } from 'utils/basePath';
|
||||
|
||||
import cacheBursting from '../../i18n-translations-hash.json';
|
||||
|
||||
@@ -25,7 +24,7 @@ i18n
|
||||
const ns = namespace[0];
|
||||
const pathkey = `/${language}/${ns}`;
|
||||
const hash = cacheBursting[pathkey as keyof typeof cacheBursting] || '';
|
||||
return `${getBasePath()}locales/${language}/${namespace}.json?h=${hash}`;
|
||||
return `/locales/${language}/${namespace}.json?h=${hash}`;
|
||||
},
|
||||
},
|
||||
react: {
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
import axios from 'api';
|
||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
import { PayloadProps, Props } from 'types/api/alerts/create';
|
||||
|
||||
const create = async (
|
||||
props: Props,
|
||||
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
|
||||
const response = await axios.post('/rules', {
|
||||
...props.data,
|
||||
});
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
error: null,
|
||||
message: response.data.status,
|
||||
payload: response.data.data,
|
||||
};
|
||||
};
|
||||
|
||||
export default create;
|
||||
@@ -1,28 +0,0 @@
|
||||
import axios from 'api';
|
||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
import {
|
||||
AlertRuleV2,
|
||||
PostableAlertRuleV2,
|
||||
} from 'types/api/alerts/alertTypesV2';
|
||||
|
||||
export interface CreateAlertRuleResponse {
|
||||
data: AlertRuleV2;
|
||||
status: string;
|
||||
}
|
||||
|
||||
const createAlertRule = async (
|
||||
props: PostableAlertRuleV2,
|
||||
): Promise<SuccessResponse<CreateAlertRuleResponse> | ErrorResponse> => {
|
||||
const response = await axios.post(`/rules`, {
|
||||
...props,
|
||||
});
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
error: null,
|
||||
message: response.data.status,
|
||||
payload: response.data.data,
|
||||
};
|
||||
};
|
||||
|
||||
export default createAlertRule;
|
||||
@@ -1,18 +0,0 @@
|
||||
import axios from 'api';
|
||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
import { PayloadProps, Props } from 'types/api/alerts/delete';
|
||||
|
||||
const deleteAlerts = async (
|
||||
props: Props,
|
||||
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
|
||||
const response = await axios.delete(`/rules/${props.id}`);
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
error: null,
|
||||
message: response.data.status,
|
||||
payload: response.data.data.rules,
|
||||
};
|
||||
};
|
||||
|
||||
export default deleteAlerts;
|
||||
@@ -1,16 +0,0 @@
|
||||
import axios from 'api';
|
||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
import { PayloadProps, Props } from 'types/api/alerts/get';
|
||||
|
||||
const get = async (
|
||||
props: Props,
|
||||
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
|
||||
const response = await axios.get(`/rules/${props.id}`);
|
||||
return {
|
||||
statusCode: 200,
|
||||
error: null,
|
||||
message: response.data.status,
|
||||
payload: response.data,
|
||||
};
|
||||
};
|
||||
export default get;
|
||||
@@ -1,24 +0,0 @@
|
||||
import axios from 'api';
|
||||
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
|
||||
import { AxiosError } from 'axios';
|
||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
import { PayloadProps } from 'types/api/alerts/getAll';
|
||||
|
||||
const getAll = async (): Promise<
|
||||
SuccessResponse<PayloadProps> | ErrorResponse
|
||||
> => {
|
||||
try {
|
||||
const response = await axios.get('/rules');
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
error: null,
|
||||
message: response.data.status,
|
||||
payload: response.data.data.rules,
|
||||
};
|
||||
} catch (error) {
|
||||
return ErrorResponseHandler(error as AxiosError);
|
||||
}
|
||||
};
|
||||
|
||||
export default getAll;
|
||||
@@ -1,29 +0,0 @@
|
||||
import { AxiosAlertManagerInstance } from 'api';
|
||||
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
|
||||
import { AxiosError } from 'axios';
|
||||
import convertObjectIntoParams from 'lib/query/convertObjectIntoParams';
|
||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
import { PayloadProps, Props } from 'types/api/alerts/getGroups';
|
||||
|
||||
const getGroups = async (
|
||||
props: Props,
|
||||
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
|
||||
try {
|
||||
const queryParams = convertObjectIntoParams(props);
|
||||
|
||||
const response = await AxiosAlertManagerInstance.get(
|
||||
`/alerts/groups?${queryParams}`,
|
||||
);
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
error: null,
|
||||
message: response.data.status,
|
||||
payload: response.data,
|
||||
};
|
||||
} catch (error) {
|
||||
return ErrorResponseHandler(error as AxiosError);
|
||||
}
|
||||
};
|
||||
|
||||
export default getGroups;
|
||||
@@ -1,20 +0,0 @@
|
||||
import axios from 'api';
|
||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
import { PayloadProps, Props } from 'types/api/alerts/patch';
|
||||
|
||||
const patch = async (
|
||||
props: Props,
|
||||
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
|
||||
const response = await axios.patch(`/rules/${props.id}`, {
|
||||
...props.data,
|
||||
});
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
error: null,
|
||||
message: response.data.status,
|
||||
payload: response.data.data,
|
||||
};
|
||||
};
|
||||
|
||||
export default patch;
|
||||
12
frontend/src/api/alerts/patchRulePartial.ts
Normal file
12
frontend/src/api/alerts/patchRulePartial.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { patchRuleByID } from 'api/generated/services/rules';
|
||||
import type { RuletypesPostableRuleDTO } from 'api/generated/services/sigNoz.schemas';
|
||||
|
||||
// why: patchRuleByID's generated body type is the full RuletypesPostableRuleDTO
|
||||
// because the backend OpenAPI spec currently advertises PostableRule. The
|
||||
// endpoint itself accepts any subset of fields. Until the backend introduces
|
||||
// PatchableRule, this wrapper localizes the cast so callers stay typed.
|
||||
export const patchRulePartial = (
|
||||
id: string,
|
||||
patch: Partial<RuletypesPostableRuleDTO>,
|
||||
): ReturnType<typeof patchRuleByID> =>
|
||||
patchRuleByID({ id }, patch as RuletypesPostableRuleDTO);
|
||||
@@ -1,20 +0,0 @@
|
||||
import axios from 'api';
|
||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
import { PayloadProps, Props } from 'types/api/alerts/save';
|
||||
|
||||
const put = async (
|
||||
props: Props,
|
||||
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
|
||||
const response = await axios.put(`/rules/${props.id}`, {
|
||||
...props.data,
|
||||
});
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
error: null,
|
||||
message: response.data.status,
|
||||
payload: response.data.data,
|
||||
};
|
||||
};
|
||||
|
||||
export default put;
|
||||
@@ -1,18 +0,0 @@
|
||||
import { isEmpty } from 'lodash-es';
|
||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
import { PayloadProps, Props } from 'types/api/alerts/save';
|
||||
|
||||
import create from './create';
|
||||
import put from './put';
|
||||
|
||||
const save = async (
|
||||
props: Props,
|
||||
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
|
||||
if (props.id && !isEmpty(props.id)) {
|
||||
return put({ ...props });
|
||||
}
|
||||
|
||||
return create({ ...props });
|
||||
};
|
||||
|
||||
export default save;
|
||||
@@ -1,26 +0,0 @@
|
||||
import axios from 'api';
|
||||
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
|
||||
import { AxiosError } from 'axios';
|
||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
import { PayloadProps, Props } from 'types/api/alerts/testAlert';
|
||||
|
||||
const testAlert = async (
|
||||
props: Props,
|
||||
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
|
||||
try {
|
||||
const response = await axios.post('/testRule', {
|
||||
...props.data,
|
||||
});
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
error: null,
|
||||
message: response.data.status,
|
||||
payload: response.data.data,
|
||||
};
|
||||
} catch (error) {
|
||||
return ErrorResponseHandler(error as AxiosError);
|
||||
}
|
||||
};
|
||||
|
||||
export default testAlert;
|
||||
@@ -1,28 +0,0 @@
|
||||
import axios from 'api';
|
||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
import { PostableAlertRuleV2 } from 'types/api/alerts/alertTypesV2';
|
||||
|
||||
export interface TestAlertRuleResponse {
|
||||
data: {
|
||||
alertCount: number;
|
||||
message: string;
|
||||
};
|
||||
status: string;
|
||||
}
|
||||
|
||||
const testAlertRule = async (
|
||||
props: PostableAlertRuleV2,
|
||||
): Promise<SuccessResponse<TestAlertRuleResponse> | ErrorResponse> => {
|
||||
const response = await axios.post(`/testRule`, {
|
||||
...props,
|
||||
});
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
error: null,
|
||||
message: response.data.status,
|
||||
payload: response.data.data,
|
||||
};
|
||||
};
|
||||
|
||||
export default testAlertRule;
|
||||
@@ -1,26 +0,0 @@
|
||||
import axios from 'api';
|
||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
import { PostableAlertRuleV2 } from 'types/api/alerts/alertTypesV2';
|
||||
|
||||
export interface UpdateAlertRuleResponse {
|
||||
data: string;
|
||||
status: string;
|
||||
}
|
||||
|
||||
const updateAlertRule = async (
|
||||
id: string,
|
||||
postableAlertRule: PostableAlertRuleV2,
|
||||
): Promise<SuccessResponse<UpdateAlertRuleResponse> | ErrorResponse> => {
|
||||
const response = await axios.put(`/rules/${id}`, {
|
||||
...postableAlertRule,
|
||||
});
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
error: null,
|
||||
message: response.data.status,
|
||||
payload: response.data.data,
|
||||
};
|
||||
};
|
||||
|
||||
export default updateAlertRule;
|
||||
@@ -1,9 +1,6 @@
|
||||
/* eslint-disable no-restricted-globals */
|
||||
import { getScopedKey } from 'utils/storage';
|
||||
|
||||
const get = (key: string): string | null => {
|
||||
try {
|
||||
return localStorage.getItem(getScopedKey(key));
|
||||
return localStorage.getItem(key);
|
||||
} catch (e) {
|
||||
return '';
|
||||
}
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
/* eslint-disable no-restricted-globals */
|
||||
import { getScopedKey } from 'utils/storage';
|
||||
|
||||
const remove = (key: string): boolean => {
|
||||
try {
|
||||
localStorage.removeItem(getScopedKey(key));
|
||||
window.localStorage.removeItem(key);
|
||||
return true;
|
||||
} catch (e) {
|
||||
return false;
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
/* eslint-disable no-restricted-globals */
|
||||
import { getScopedKey } from 'utils/storage';
|
||||
|
||||
const set = (key: string, value: string): boolean => {
|
||||
try {
|
||||
localStorage.setItem(getScopedKey(key), value);
|
||||
localStorage.setItem(key, value);
|
||||
return true;
|
||||
} catch (e) {
|
||||
return false;
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
/* eslint-disable no-restricted-globals */
|
||||
import { getScopedKey } from 'utils/storage';
|
||||
|
||||
const get = (key: string): string | null => {
|
||||
try {
|
||||
return sessionStorage.getItem(getScopedKey(key));
|
||||
} catch (e) {
|
||||
return '';
|
||||
}
|
||||
};
|
||||
|
||||
export default get;
|
||||
@@ -1,13 +0,0 @@
|
||||
/* eslint-disable no-restricted-globals */
|
||||
import { getScopedKey } from 'utils/storage';
|
||||
|
||||
const remove = (key: string): boolean => {
|
||||
try {
|
||||
sessionStorage.removeItem(getScopedKey(key));
|
||||
return true;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
export default remove;
|
||||
@@ -1,13 +0,0 @@
|
||||
/* eslint-disable no-restricted-globals */
|
||||
import { getScopedKey } from 'utils/storage';
|
||||
|
||||
const set = (key: string, value: string): boolean => {
|
||||
try {
|
||||
sessionStorage.setItem(getScopedKey(key), value);
|
||||
return true;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
export default set;
|
||||
@@ -1,6 +1,5 @@
|
||||
import {
|
||||
interceptorRejected,
|
||||
interceptorsRequestBasePath,
|
||||
interceptorsRequestResponse,
|
||||
interceptorsResponse,
|
||||
} from 'api';
|
||||
@@ -18,7 +17,6 @@ export const GeneratedAPIInstance = <T>(
|
||||
return generatedAPIAxiosInstance({ ...config }).then(({ data }) => data);
|
||||
};
|
||||
|
||||
generatedAPIAxiosInstance.interceptors.request.use(interceptorsRequestBasePath);
|
||||
generatedAPIAxiosInstance.interceptors.request.use(interceptorsRequestResponse);
|
||||
generatedAPIAxiosInstance.interceptors.response.use(
|
||||
interceptorsResponse,
|
||||
|
||||
@@ -11,7 +11,6 @@ import axios, {
|
||||
import { ENVIRONMENT } from 'constants/env';
|
||||
import { Events } from 'constants/events';
|
||||
import { LOCALSTORAGE } from 'constants/localStorage';
|
||||
import { getBasePath } from 'utils/basePath';
|
||||
import { eventEmitter } from 'utils/getEventEmitter';
|
||||
|
||||
import apiV1, { apiAlertManager, apiV2, apiV3, apiV4, apiV5 } from './apiV1';
|
||||
@@ -68,39 +67,6 @@ export const interceptorsRequestResponse = (
|
||||
return value;
|
||||
};
|
||||
|
||||
// Strips the leading '/' from path and joins with base — idempotent if already prefixed.
|
||||
// e.g. prependBase('/signoz/', '/api/v1/') → '/signoz/api/v1/'
|
||||
function prependBase(base: string, path: string): string {
|
||||
return path.startsWith(base) ? path : base + path.slice(1);
|
||||
}
|
||||
|
||||
// Prepends the runtime base path to outgoing requests so API calls work under
|
||||
// a URL prefix (e.g. /signoz/api/v1/…). No-op for root deployments and dev
|
||||
// (dev baseURL is a full http:// URL, not an absolute path).
|
||||
export const interceptorsRequestBasePath = (
|
||||
value: InternalAxiosRequestConfig,
|
||||
): InternalAxiosRequestConfig => {
|
||||
const basePath = getBasePath();
|
||||
if (basePath === '/') {
|
||||
return value;
|
||||
}
|
||||
|
||||
if (value.baseURL?.startsWith('/')) {
|
||||
// Production relative baseURL: '/api/v1/' → '/signoz/api/v1/'
|
||||
value.baseURL = prependBase(basePath, value.baseURL);
|
||||
} else if (value.baseURL?.startsWith('http')) {
|
||||
// Dev absolute baseURL (VITE_FRONTEND_API_ENDPOINT): 'https://host/api/v1/' → 'https://host/signoz/api/v1/'
|
||||
const url = new URL(value.baseURL);
|
||||
url.pathname = prependBase(basePath, url.pathname);
|
||||
value.baseURL = url.toString();
|
||||
} else if (!value.baseURL && value.url?.startsWith('/')) {
|
||||
// Orval-generated client (empty baseURL, path in url): '/api/signoz/v1/rules' → '/signoz/api/signoz/v1/rules'
|
||||
value.url = prependBase(basePath, value.url);
|
||||
}
|
||||
|
||||
return value;
|
||||
};
|
||||
|
||||
export const interceptorRejected = async (
|
||||
value: AxiosResponse<any>,
|
||||
): Promise<AxiosResponse<any>> => {
|
||||
@@ -167,7 +133,6 @@ const instance = axios.create({
|
||||
});
|
||||
|
||||
instance.interceptors.request.use(interceptorsRequestResponse);
|
||||
instance.interceptors.request.use(interceptorsRequestBasePath);
|
||||
instance.interceptors.response.use(interceptorsResponse, interceptorRejected);
|
||||
|
||||
export const AxiosAlertManagerInstance = axios.create({
|
||||
@@ -182,7 +147,6 @@ ApiV2Instance.interceptors.response.use(
|
||||
interceptorRejected,
|
||||
);
|
||||
ApiV2Instance.interceptors.request.use(interceptorsRequestResponse);
|
||||
ApiV2Instance.interceptors.request.use(interceptorsRequestBasePath);
|
||||
|
||||
// axios V3
|
||||
export const ApiV3Instance = axios.create({
|
||||
@@ -194,7 +158,6 @@ ApiV3Instance.interceptors.response.use(
|
||||
interceptorRejected,
|
||||
);
|
||||
ApiV3Instance.interceptors.request.use(interceptorsRequestResponse);
|
||||
ApiV3Instance.interceptors.request.use(interceptorsRequestBasePath);
|
||||
//
|
||||
|
||||
// axios V4
|
||||
@@ -207,7 +170,6 @@ ApiV4Instance.interceptors.response.use(
|
||||
interceptorRejected,
|
||||
);
|
||||
ApiV4Instance.interceptors.request.use(interceptorsRequestResponse);
|
||||
ApiV4Instance.interceptors.request.use(interceptorsRequestBasePath);
|
||||
//
|
||||
|
||||
// axios V5
|
||||
@@ -220,7 +182,6 @@ ApiV5Instance.interceptors.response.use(
|
||||
interceptorRejected,
|
||||
);
|
||||
ApiV5Instance.interceptors.request.use(interceptorsRequestResponse);
|
||||
ApiV5Instance.interceptors.request.use(interceptorsRequestBasePath);
|
||||
//
|
||||
|
||||
// axios Base
|
||||
@@ -233,7 +194,6 @@ LogEventAxiosInstance.interceptors.response.use(
|
||||
interceptorRejectedBase,
|
||||
);
|
||||
LogEventAxiosInstance.interceptors.request.use(interceptorsRequestResponse);
|
||||
LogEventAxiosInstance.interceptors.request.use(interceptorsRequestBasePath);
|
||||
//
|
||||
|
||||
AxiosAlertManagerInstance.interceptors.response.use(
|
||||
@@ -241,7 +201,6 @@ AxiosAlertManagerInstance.interceptors.response.use(
|
||||
interceptorRejected,
|
||||
);
|
||||
AxiosAlertManagerInstance.interceptors.request.use(interceptorsRequestResponse);
|
||||
AxiosAlertManagerInstance.interceptors.request.use(interceptorsRequestBasePath);
|
||||
|
||||
export { apiV1 };
|
||||
export default instance;
|
||||
|
||||
@@ -3,16 +3,13 @@ import getLocalStorageKey from 'api/browser/localstorage/get';
|
||||
import { ENVIRONMENT } from 'constants/env';
|
||||
import { LOCALSTORAGE } from 'constants/localStorage';
|
||||
import { EventSourcePolyfill } from 'event-source-polyfill';
|
||||
import { withBasePath } from 'utils/basePath';
|
||||
|
||||
// 10 min in ms
|
||||
const TIMEOUT_IN_MS = 10 * 60 * 1000;
|
||||
|
||||
export const LiveTail = (queryParams: string): EventSourcePolyfill =>
|
||||
new EventSourcePolyfill(
|
||||
ENVIRONMENT.baseURL
|
||||
? `${ENVIRONMENT.baseURL}${apiV1}logs/tail?${queryParams}`
|
||||
: withBasePath(`${apiV1}logs/tail?${queryParams}`),
|
||||
`${ENVIRONMENT.baseURL}${apiV1}logs/tail?${queryParams}`,
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${getLocalStorageKey(LOCALSTORAGE.AUTH_TOKEN)}`,
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
import axios from 'api';
|
||||
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
|
||||
import { AxiosError } from 'axios';
|
||||
import { Dayjs } from 'dayjs';
|
||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
|
||||
import { Recurrence } from './getAllDowntimeSchedules';
|
||||
|
||||
export interface DowntimeSchedulePayload {
|
||||
name: string;
|
||||
description?: string;
|
||||
alertIds: string[];
|
||||
schedule: {
|
||||
timezone?: string;
|
||||
startTime?: string | Dayjs;
|
||||
endTime?: string | Dayjs;
|
||||
recurrence?: Recurrence;
|
||||
};
|
||||
}
|
||||
|
||||
export interface PayloadProps {
|
||||
status: string;
|
||||
data: string;
|
||||
}
|
||||
|
||||
const createDowntimeSchedule = async (
|
||||
props: DowntimeSchedulePayload,
|
||||
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
|
||||
try {
|
||||
const response = await axios.post('/downtime_schedules', {
|
||||
...props,
|
||||
});
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
error: null,
|
||||
message: response.data.status,
|
||||
payload: response.data.data,
|
||||
};
|
||||
} catch (error) {
|
||||
return ErrorResponseHandler(error as AxiosError);
|
||||
}
|
||||
};
|
||||
|
||||
export default createDowntimeSchedule;
|
||||
@@ -1,19 +0,0 @@
|
||||
import { useMutation, UseMutationResult } from 'react-query';
|
||||
import axios from 'api';
|
||||
|
||||
export interface DeleteDowntimeScheduleProps {
|
||||
id?: number;
|
||||
}
|
||||
|
||||
export interface DeleteSchedulePayloadProps {
|
||||
status: string;
|
||||
data: string;
|
||||
}
|
||||
|
||||
export const useDeleteDowntimeSchedule = (
|
||||
props: DeleteDowntimeScheduleProps,
|
||||
): UseMutationResult<DeleteSchedulePayloadProps, Error, number> =>
|
||||
useMutation({
|
||||
mutationKey: [props.id],
|
||||
mutationFn: () => axios.delete(`/downtime_schedules/${props.id}`),
|
||||
});
|
||||
@@ -1,51 +0,0 @@
|
||||
import { useQuery, UseQueryResult } from 'react-query';
|
||||
import axios from 'api';
|
||||
import { AxiosError, AxiosResponse } from 'axios';
|
||||
import { Option } from 'container/PlannedDowntime/PlannedDowntimeutils';
|
||||
|
||||
export type Recurrence = {
|
||||
startTime?: string | null;
|
||||
endTime?: string | null;
|
||||
duration?: number | string | null;
|
||||
repeatType?: string | Option | null;
|
||||
repeatOn?: string[] | null;
|
||||
};
|
||||
|
||||
type Schedule = {
|
||||
timezone: string | null;
|
||||
startTime: string | null;
|
||||
endTime: string | null;
|
||||
recurrence: Recurrence | null;
|
||||
};
|
||||
|
||||
export interface DowntimeSchedules {
|
||||
id: number;
|
||||
name: string | null;
|
||||
description: string | null;
|
||||
schedule: Schedule | null;
|
||||
alertIds: string[] | null;
|
||||
createdAt: string | null;
|
||||
createdBy: string | null;
|
||||
updatedAt: string | null;
|
||||
updatedBy: string | null;
|
||||
kind: string | null;
|
||||
}
|
||||
export type PayloadProps = { data: DowntimeSchedules[] };
|
||||
|
||||
export const getAllDowntimeSchedules = async (
|
||||
props?: GetAllDowntimeSchedulesPayloadProps,
|
||||
): Promise<AxiosResponse<PayloadProps>> =>
|
||||
axios.get('/downtime_schedules', { params: props });
|
||||
|
||||
export interface GetAllDowntimeSchedulesPayloadProps {
|
||||
active?: boolean;
|
||||
recurrence?: boolean;
|
||||
}
|
||||
|
||||
export const useGetAllDowntimeSchedules = (
|
||||
props?: GetAllDowntimeSchedulesPayloadProps,
|
||||
): UseQueryResult<AxiosResponse<PayloadProps>, AxiosError> =>
|
||||
useQuery<AxiosResponse<PayloadProps>, AxiosError>({
|
||||
queryKey: ['getAllDowntimeSchedules', props],
|
||||
queryFn: () => getAllDowntimeSchedules(props),
|
||||
});
|
||||
@@ -1,37 +0,0 @@
|
||||
import axios from 'api';
|
||||
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
|
||||
import { AxiosError } from 'axios';
|
||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
|
||||
import { DowntimeSchedulePayload } from './createDowntimeSchedule';
|
||||
|
||||
export interface DowntimeScheduleUpdatePayload {
|
||||
data: DowntimeSchedulePayload;
|
||||
id?: number;
|
||||
}
|
||||
|
||||
export interface PayloadProps {
|
||||
status: string;
|
||||
data: string;
|
||||
}
|
||||
|
||||
const updateDowntimeSchedule = async (
|
||||
props: DowntimeScheduleUpdatePayload,
|
||||
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
|
||||
try {
|
||||
const response = await axios.put(`/downtime_schedules/${props.id}`, {
|
||||
...props.data,
|
||||
});
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
error: null,
|
||||
message: response.data.status,
|
||||
payload: response.data.data,
|
||||
};
|
||||
} catch (error) {
|
||||
return ErrorResponseHandler(error as AxiosError);
|
||||
}
|
||||
};
|
||||
|
||||
export default updateDowntimeSchedule;
|
||||
14
frontend/src/auto-import-registry.d.ts
vendored
14
frontend/src/auto-import-registry.d.ts
vendored
@@ -10,21 +10,7 @@
|
||||
// PR for reference: https://github.com/SigNoz/signoz/pull/9694
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
import '@signozhq/button';
|
||||
import '@signozhq/calendar';
|
||||
import '@signozhq/callout';
|
||||
import '@signozhq/checkbox';
|
||||
import '@signozhq/combobox';
|
||||
import '@signozhq/command';
|
||||
import '@signozhq/design-tokens';
|
||||
import '@signozhq/dialog';
|
||||
import '@signozhq/drawer';
|
||||
import '@signozhq/icons';
|
||||
import '@signozhq/input';
|
||||
import '@signozhq/popover';
|
||||
import '@signozhq/radio-group';
|
||||
import '@signozhq/resizable';
|
||||
import '@signozhq/tabs';
|
||||
import '@signozhq/table';
|
||||
import '@signozhq/toggle-group';
|
||||
import '@signozhq/ui';
|
||||
|
||||
@@ -80,12 +80,12 @@
|
||||
|
||||
mask-image: radial-gradient(
|
||||
circle at 50% 0,
|
||||
color-mix(in srgb, var(--background) 10%, transparent) 0,
|
||||
color-mix(in srgb, var(--l1-background) 10%, transparent) 0,
|
||||
transparent 100%
|
||||
);
|
||||
-webkit-mask-image: radial-gradient(
|
||||
circle at 50% 0,
|
||||
color-mix(in srgb, var(--background) 10%, transparent) 0,
|
||||
color-mix(in srgb, var(--l1-background) 10%, transparent) 0,
|
||||
transparent 100%
|
||||
);
|
||||
}
|
||||
|
||||
@@ -4,13 +4,14 @@
|
||||
animation: horizontal-shaking 300ms ease-out;
|
||||
|
||||
.error-content {
|
||||
background: color-mix(in srgb, var(--bg-cherry-500) 10%, transparent);
|
||||
border: 1px solid color-mix(in srgb, var(--bg-cherry-500) 20%, transparent);
|
||||
background: color-mix(in srgb, var(--danger-background) 10%, transparent);
|
||||
border: 1px solid
|
||||
color-mix(in srgb, var(--danger-background) 20%, transparent);
|
||||
border-radius: 4px;
|
||||
|
||||
&__summary-section {
|
||||
border-bottom: 1px solid
|
||||
color-mix(in srgb, var(--bg-cherry-500) 20%, transparent);
|
||||
color-mix(in srgb, var(--danger-background) 20%, transparent);
|
||||
}
|
||||
|
||||
&__summary {
|
||||
@@ -58,7 +59,7 @@
|
||||
&__message-badge-line {
|
||||
background-image: radial-gradient(
|
||||
circle,
|
||||
color-mix(in srgb, var(--bg-cherry-500) 30%, transparent) 1px,
|
||||
color-mix(in srgb, var(--danger-background) 30%, transparent) 1px,
|
||||
transparent 2px
|
||||
);
|
||||
}
|
||||
@@ -84,7 +85,7 @@
|
||||
}
|
||||
|
||||
&__scroll-hint {
|
||||
background: color-mix(in srgb, var(--bg-cherry-500) 20%, transparent);
|
||||
background: color-mix(in srgb, var(--danger-background) 20%, transparent);
|
||||
}
|
||||
|
||||
&__scroll-hint-text {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useCallback } from 'react';
|
||||
import { Button } from '@signozhq/button';
|
||||
import { Button } from '@signozhq/ui';
|
||||
import { LifeBuoy } from 'lucide-react';
|
||||
|
||||
import signozBrandLogoUrl from '@/assets/Logos/signoz-brand-logo.svg';
|
||||
@@ -23,8 +23,10 @@ function AuthHeader(): JSX.Element {
|
||||
</div>
|
||||
<Button
|
||||
className="auth-header-help-button"
|
||||
prefixIcon={<LifeBuoy size={12} />}
|
||||
prefix={<LifeBuoy size={12} />}
|
||||
onClick={handleGetHelp}
|
||||
variant="solid"
|
||||
color="none"
|
||||
>
|
||||
Get Help
|
||||
</Button>
|
||||
|
||||
@@ -43,12 +43,12 @@
|
||||
.masked-dots {
|
||||
mask-image: radial-gradient(
|
||||
circle at 50% 0%,
|
||||
color-mix(in srgb, var(--background) 10%, transparent) 0%,
|
||||
color-mix(in srgb, var(--l1-background) 10%, transparent) 0%,
|
||||
transparent 56.77%
|
||||
);
|
||||
-webkit-mask-image: radial-gradient(
|
||||
circle at 50% 0%,
|
||||
color-mix(in srgb, var(--background) 10%, transparent) 0%,
|
||||
color-mix(in srgb, var(--l1-background) 10%, transparent) 0%,
|
||||
transparent 56.77%
|
||||
);
|
||||
}
|
||||
|
||||
@@ -12,7 +12,6 @@ import { AppState } from 'store/reducers';
|
||||
import { Query, TagFilterItem } from 'types/api/queryBuilder/queryBuilderData';
|
||||
import { DataSource, MetricAggregateOperator } from 'types/common/queryBuilder';
|
||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
import { withBasePath } from 'utils/basePath';
|
||||
|
||||
export interface NavigateToExplorerProps {
|
||||
filters: TagFilterItem[];
|
||||
@@ -80,7 +79,7 @@ export function useNavigateToExplorer(): (
|
||||
);
|
||||
|
||||
const { getUpdatedQuery } = useUpdatedQuery();
|
||||
const { selectedDashboard } = useDashboardStore();
|
||||
const { dashboardData } = useDashboardStore();
|
||||
const { notifications } = useNotifications();
|
||||
|
||||
return useCallback(
|
||||
@@ -112,7 +111,7 @@ export function useNavigateToExplorer(): (
|
||||
panelTypes: PANEL_TYPES.TIME_SERIES,
|
||||
timePreferance: 'GLOBAL_TIME',
|
||||
},
|
||||
selectedDashboard,
|
||||
dashboardData,
|
||||
})
|
||||
.then((query) => {
|
||||
preparedQuery = query;
|
||||
@@ -134,14 +133,14 @@ export function useNavigateToExplorer(): (
|
||||
QueryParams.compositeQuery
|
||||
}=${JSONCompositeQuery}`;
|
||||
|
||||
window.open(withBasePath(newExplorerPath), sameTab ? '_self' : '_blank');
|
||||
window.open(newExplorerPath, sameTab ? '_self' : '_blank');
|
||||
},
|
||||
[
|
||||
prepareQuery,
|
||||
minTime,
|
||||
maxTime,
|
||||
getUpdatedQuery,
|
||||
selectedDashboard,
|
||||
dashboardData,
|
||||
notifications,
|
||||
],
|
||||
);
|
||||
|
||||
@@ -26,14 +26,14 @@
|
||||
}
|
||||
}
|
||||
&--negative {
|
||||
background: color-mix(in srgb, var(--bg-cherry-500) 10%, transparent);
|
||||
background: color-mix(in srgb, var(--danger-background) 10%, transparent);
|
||||
|
||||
.change-percentage-pill {
|
||||
&__icon {
|
||||
color: var(--bg-cherry-500);
|
||||
color: var(--danger-background);
|
||||
}
|
||||
&__label {
|
||||
color: var(--bg-cherry-500);
|
||||
color: var(--danger-background);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@ 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();
|
||||
@@ -55,7 +54,7 @@ export default function ChatSupportGateway(): JSX.Element {
|
||||
});
|
||||
|
||||
updateCreditCard({
|
||||
url: getBaseUrl(),
|
||||
url: window.location.origin,
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
import { Controller, useForm } from 'react-hook-form';
|
||||
import { useQueryClient } from 'react-query';
|
||||
import { Button } from '@signozhq/button';
|
||||
import { DialogFooter, DialogWrapper } from '@signozhq/dialog';
|
||||
import { X } from '@signozhq/icons';
|
||||
import { Input } from '@signozhq/input';
|
||||
import { toast } from '@signozhq/ui';
|
||||
import {
|
||||
Button,
|
||||
DialogFooter,
|
||||
DialogWrapper,
|
||||
Input,
|
||||
toast,
|
||||
} from '@signozhq/ui';
|
||||
import { convertToApiError } from 'api/ErrorResponseHandlerForGeneratedAPIs';
|
||||
import {
|
||||
invalidateListServiceAccounts,
|
||||
@@ -50,9 +53,7 @@ function CreateServiceAccountModal(): JSX.Element {
|
||||
} = useCreateServiceAccount({
|
||||
mutation: {
|
||||
onSuccess: async () => {
|
||||
toast.success('Service account created successfully', {
|
||||
richColors: true,
|
||||
});
|
||||
toast.success('Service account created successfully');
|
||||
reset();
|
||||
await setIsOpen(null);
|
||||
await invalidateListServiceAccounts(queryClient);
|
||||
@@ -128,7 +129,6 @@ function CreateServiceAccountModal(): JSX.Element {
|
||||
type="button"
|
||||
variant="solid"
|
||||
color="secondary"
|
||||
size="sm"
|
||||
onClick={handleClose}
|
||||
>
|
||||
<X size={12} />
|
||||
@@ -137,10 +137,10 @@ function CreateServiceAccountModal(): JSX.Element {
|
||||
|
||||
<Button
|
||||
type="submit"
|
||||
// @ts-expect-error -- form prop not in @signozhq/ui Button type - TODO: Fix this - @SagarRajput
|
||||
form="create-sa-form"
|
||||
variant="solid"
|
||||
color="primary"
|
||||
size="sm"
|
||||
loading={isSubmitting}
|
||||
disabled={!isValid}
|
||||
>
|
||||
|
||||
@@ -1,7 +1,13 @@
|
||||
import { toast } from '@signozhq/ui';
|
||||
import { rest, server } from 'mocks-server/server';
|
||||
import { NuqsTestingAdapter } from 'nuqs/adapters/testing';
|
||||
import { render, screen, userEvent, waitFor } from 'tests/test-utils';
|
||||
import {
|
||||
render,
|
||||
screen,
|
||||
userEvent,
|
||||
waitFor,
|
||||
waitForElementToBeRemoved,
|
||||
} from 'tests/test-utils';
|
||||
|
||||
import CreateServiceAccountModal from '../CreateServiceAccountModal';
|
||||
|
||||
@@ -69,7 +75,6 @@ describe('CreateServiceAccountModal', () => {
|
||||
await waitFor(() => {
|
||||
expect(mockToast.success).toHaveBeenCalledWith(
|
||||
'Service account created successfully',
|
||||
expect.anything(),
|
||||
);
|
||||
});
|
||||
|
||||
@@ -121,12 +126,12 @@ describe('CreateServiceAccountModal', () => {
|
||||
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
||||
renderModal();
|
||||
|
||||
await screen.findByRole('dialog', { name: /New Service Account/i });
|
||||
const dialog = await screen.findByRole('dialog', {
|
||||
name: /New Service Account/i,
|
||||
});
|
||||
await user.click(screen.getByRole('button', { name: /Cancel/i }));
|
||||
|
||||
expect(
|
||||
screen.queryByRole('dialog', { name: /New Service Account/i }),
|
||||
).not.toBeInTheDocument();
|
||||
await waitForElementToBeRemoved(dialog);
|
||||
});
|
||||
|
||||
it('shows "Name is required" after clearing the name field', async () => {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Calendar } from '@signozhq/calendar';
|
||||
import { Calendar } from '@signozhq/ui';
|
||||
import { Button } from 'antd';
|
||||
import { DATE_TIME_FORMATS } from 'constants/dateTimeFormats';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
useState,
|
||||
} from 'react';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { Button } from '@signozhq/button';
|
||||
import { Button } from '@signozhq/ui';
|
||||
import { Input, InputRef, Popover, Tooltip } from 'antd';
|
||||
import cx from 'classnames';
|
||||
import { DATE_TIME_FORMATS } from 'constants/dateTimeFormats';
|
||||
@@ -661,7 +661,9 @@ function CustomTimePicker({
|
||||
onClick={handleZoomOut}
|
||||
disabled={zoomOutDisabled}
|
||||
data-testid="zoom-out-btn"
|
||||
prefixIcon={<ZoomOut size={14} />}
|
||||
prefix={<ZoomOut size={14} />}
|
||||
variant="solid"
|
||||
color="none"
|
||||
/>
|
||||
</Tooltip>
|
||||
)}
|
||||
|
||||
@@ -1,14 +1,8 @@
|
||||
.download-popover {
|
||||
.ant-popover-inner {
|
||||
border-radius: 4px;
|
||||
border: 1px solid var(--l1-border);
|
||||
background: linear-gradient(
|
||||
139deg,
|
||||
var(--l2-background) 0%,
|
||||
var(--l3-background) 98.68%
|
||||
);
|
||||
background-color: var(--l2-background);
|
||||
box-shadow: 4px 10px 16px 2px rgba(0, 0, 0, 0.2);
|
||||
backdrop-filter: blur(20px);
|
||||
padding: 0 8px 12px 8px;
|
||||
margin: 6px 0;
|
||||
}
|
||||
@@ -19,7 +13,7 @@
|
||||
|
||||
.title {
|
||||
display: flex;
|
||||
color: var(--l3-foreground);
|
||||
color: var(--l1-foreground);
|
||||
font-family: Inter;
|
||||
font-size: 11px;
|
||||
font-style: normal;
|
||||
@@ -38,7 +32,7 @@
|
||||
flex-direction: column;
|
||||
|
||||
:global(.ant-radio-wrapper) {
|
||||
color: var(--foreground);
|
||||
color: var(--l1-foreground);
|
||||
font-family: Inter;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { Button } from '@signozhq/button';
|
||||
import { DialogFooter, DialogWrapper } from '@signozhq/dialog';
|
||||
import { Trash2, X } from '@signozhq/icons';
|
||||
import { Button, DialogWrapper } from '@signozhq/ui';
|
||||
import { MemberRow } from 'components/MembersTable/MembersTable';
|
||||
|
||||
interface DeleteMemberDialogProps {
|
||||
@@ -36,6 +35,24 @@ function DeleteMemberDialog({
|
||||
</>
|
||||
);
|
||||
|
||||
const footer = (
|
||||
<>
|
||||
<Button variant="solid" color="secondary" onClick={onClose}>
|
||||
<X size={12} />
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
variant="solid"
|
||||
color="destructive"
|
||||
disabled={isDeleting}
|
||||
onClick={onConfirm}
|
||||
>
|
||||
<Trash2 size={12} />
|
||||
{isDeleting ? 'Processing...' : title}
|
||||
</Button>
|
||||
</>
|
||||
);
|
||||
|
||||
return (
|
||||
<DialogWrapper
|
||||
open={open}
|
||||
@@ -49,25 +66,9 @@ function DeleteMemberDialog({
|
||||
className="alert-dialog delete-dialog"
|
||||
showCloseButton={false}
|
||||
disableOutsideClick={false}
|
||||
footer={footer}
|
||||
>
|
||||
<p className="delete-dialog__body">{body}</p>
|
||||
|
||||
<DialogFooter className="delete-dialog__footer">
|
||||
<Button variant="solid" color="secondary" size="sm" onClick={onClose}>
|
||||
<X size={12} />
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
variant="solid"
|
||||
color="destructive"
|
||||
size="sm"
|
||||
disabled={isDeleting}
|
||||
onClick={onConfirm}
|
||||
>
|
||||
<Trash2 size={12} />
|
||||
{isDeleting ? 'Processing...' : title}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
{body}
|
||||
</DialogWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
&__layout {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: calc(100vh - 48px);
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
&__body {
|
||||
@@ -11,7 +11,6 @@
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-8);
|
||||
padding: var(--padding-5) var(--padding-4);
|
||||
}
|
||||
|
||||
&__field {
|
||||
@@ -50,6 +49,7 @@
|
||||
border-radius: 2px;
|
||||
background: var(--l2-background);
|
||||
border: 1px solid var(--l1-border);
|
||||
box-sizing: border-box;
|
||||
|
||||
&--disabled {
|
||||
cursor: not-allowed;
|
||||
@@ -120,17 +120,11 @@
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
height: 56px;
|
||||
padding: 0 var(--padding-4);
|
||||
border-top: 1px solid var(--l1-border);
|
||||
flex-shrink: 0;
|
||||
background: var(--card);
|
||||
}
|
||||
|
||||
&__footer-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-8);
|
||||
}
|
||||
|
||||
&__footer-right {
|
||||
@@ -223,10 +217,6 @@
|
||||
color: var(--l1-foreground);
|
||||
}
|
||||
|
||||
[data-slot='dialog-description'] {
|
||||
width: 510px;
|
||||
}
|
||||
|
||||
&__content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { useCopyToClipboard } from 'react-use';
|
||||
import { Button } from '@signozhq/button';
|
||||
import { DrawerWrapper } from '@signozhq/drawer';
|
||||
import { LockKeyhole, RefreshCw, Trash2, X } from '@signozhq/icons';
|
||||
import { Input } from '@signozhq/input';
|
||||
import { Badge, toast } from '@signozhq/ui';
|
||||
import { Badge, Button, DrawerWrapper, Input, toast } from '@signozhq/ui';
|
||||
import { Skeleton, Tooltip } from 'antd';
|
||||
import { convertToApiError } from 'api/ErrorResponseHandlerForGeneratedAPIs';
|
||||
import type { RenderErrorResponseDTO } from 'api/generated/services/sigNoz.schemas';
|
||||
@@ -31,7 +28,6 @@ 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';
|
||||
@@ -208,7 +204,7 @@ function EditMemberDrawer({
|
||||
onSuccess: (): void => {
|
||||
toast.success(
|
||||
isInvited ? 'Invite revoked successfully' : 'Member deleted successfully',
|
||||
{ richColors: true, position: 'top-right' },
|
||||
{ position: 'top-right' },
|
||||
);
|
||||
setShowDeleteConfirm(false);
|
||||
onComplete();
|
||||
@@ -343,10 +339,7 @@ function EditMemberDrawer({
|
||||
if (errors.length > 0) {
|
||||
setSaveErrors(errors);
|
||||
} else {
|
||||
toast.success('Member details updated successfully', {
|
||||
richColors: true,
|
||||
position: 'top-right',
|
||||
});
|
||||
toast.success('Member details updated successfully');
|
||||
onComplete();
|
||||
}
|
||||
|
||||
@@ -388,7 +381,7 @@ function EditMemberDrawer({
|
||||
pathParams: { id: member.id },
|
||||
});
|
||||
if (response?.data?.token) {
|
||||
const link = getAbsoluteUrl(`/password-reset?token=${response.data.token}`);
|
||||
const link = `${window.location.origin}/password-reset?token=${response.data.token}`;
|
||||
setResetLink(link);
|
||||
setResetLinkExpiresAt(
|
||||
response.data.expiresAt
|
||||
@@ -404,7 +397,6 @@ function EditMemberDrawer({
|
||||
onClose();
|
||||
} else {
|
||||
toast.error('Failed to generate password reset link', {
|
||||
richColors: true,
|
||||
position: 'top-right',
|
||||
});
|
||||
}
|
||||
@@ -428,15 +420,12 @@ function EditMemberDrawer({
|
||||
linkType === 'invite'
|
||||
? 'Invite link copied to clipboard'
|
||||
: 'Reset link copied to clipboard';
|
||||
toast.success(message, { richColors: true, position: 'top-right' });
|
||||
toast.success(message);
|
||||
}, [resetLink, copyToClipboard, linkType]);
|
||||
|
||||
useEffect(() => {
|
||||
if (copyState.error) {
|
||||
toast.error('Failed to copy link', {
|
||||
richColors: true,
|
||||
position: 'top-right',
|
||||
});
|
||||
toast.error('Failed to copy link');
|
||||
}
|
||||
}, [copyState.error]);
|
||||
|
||||
@@ -597,16 +586,21 @@ function EditMemberDrawer({
|
||||
const drawerContent = (
|
||||
<div className="edit-member-drawer__layout">
|
||||
<div className="edit-member-drawer__body">{drawerBody}</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
const footer = (
|
||||
<div className="edit-member-drawer__footer">
|
||||
{!isDeleted && (
|
||||
<div className="edit-member-drawer__footer">
|
||||
<>
|
||||
<div className="edit-member-drawer__footer-left">
|
||||
<Tooltip title={getDeleteTooltip(isRootUser, isSelf)}>
|
||||
<span className="edit-member-drawer__tooltip-wrapper">
|
||||
<Button
|
||||
className="edit-member-drawer__footer-btn edit-member-drawer__footer-btn--danger"
|
||||
onClick={(): void => setShowDeleteConfirm(true)}
|
||||
disabled={isRootUser || isSelf}
|
||||
variant="link"
|
||||
color="destructive"
|
||||
>
|
||||
<Trash2 size={12} />
|
||||
{isInvited ? 'Revoke Invite' : 'Delete Member'}
|
||||
@@ -618,9 +612,10 @@ function EditMemberDrawer({
|
||||
<Tooltip title={isRootUser ? ROOT_USER_TOOLTIP : undefined}>
|
||||
<span className="edit-member-drawer__tooltip-wrapper">
|
||||
<Button
|
||||
className="edit-member-drawer__footer-btn edit-member-drawer__footer-btn--warning"
|
||||
onClick={handleGenerateResetLink}
|
||||
disabled={isGeneratingLink || isRootUser || isLoadingTokenStatus}
|
||||
variant="link"
|
||||
color="warning"
|
||||
>
|
||||
<RefreshCw size={12} />
|
||||
{isGeneratingLink
|
||||
@@ -639,7 +634,7 @@ function EditMemberDrawer({
|
||||
</div>
|
||||
|
||||
<div className="edit-member-drawer__footer-right">
|
||||
<Button variant="solid" color="secondary" size="sm" onClick={handleClose}>
|
||||
<Button variant="solid" color="secondary" onClick={handleClose}>
|
||||
<X size={14} />
|
||||
Cancel
|
||||
</Button>
|
||||
@@ -647,14 +642,13 @@ function EditMemberDrawer({
|
||||
<Button
|
||||
variant="solid"
|
||||
color="primary"
|
||||
size="sm"
|
||||
disabled={!isDirty || isSaving || isRootUser}
|
||||
onClick={handleSave}
|
||||
>
|
||||
{isSaving ? 'Saving...' : 'Save Member Details'}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
@@ -669,14 +663,14 @@ function EditMemberDrawer({
|
||||
}
|
||||
}}
|
||||
direction="right"
|
||||
type="panel"
|
||||
showCloseButton
|
||||
showOverlay={false}
|
||||
allowOutsideClick
|
||||
header={{ title: 'Member Details' }}
|
||||
content={drawerContent}
|
||||
className="edit-member-drawer"
|
||||
/>
|
||||
title="Member Details"
|
||||
footer={footer}
|
||||
width="wide"
|
||||
>
|
||||
{drawerContent}
|
||||
</DrawerWrapper>
|
||||
|
||||
<ResetLinkDialog
|
||||
open={showResetLinkDialog}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { Button } from '@signozhq/button';
|
||||
import { DialogWrapper } from '@signozhq/dialog';
|
||||
import { Check, Copy } from '@signozhq/icons';
|
||||
import { Button, DialogWrapper } from '@signozhq/ui';
|
||||
|
||||
interface ResetLinkDialogProps {
|
||||
open: boolean;
|
||||
@@ -49,7 +48,7 @@ function ResetLinkDialog({
|
||||
color="secondary"
|
||||
size="sm"
|
||||
onClick={onCopy}
|
||||
prefixIcon={hasCopied ? <Check size={12} /> : <Copy size={12} />}
|
||||
prefix={hasCopied ? <Check size={12} /> : <Copy size={12} />}
|
||||
className="reset-link-dialog__copy-btn"
|
||||
>
|
||||
{hasCopied ? 'Copied!' : 'Copy'}
|
||||
|
||||
@@ -20,36 +20,6 @@ import { render, screen, userEvent, waitFor } from 'tests/test-utils';
|
||||
|
||||
import EditMemberDrawer, { EditMemberDrawerProps } from '../EditMemberDrawer';
|
||||
|
||||
jest.mock('@signozhq/drawer', () => ({
|
||||
DrawerWrapper: ({
|
||||
content,
|
||||
open,
|
||||
}: {
|
||||
content?: ReactNode;
|
||||
open: boolean;
|
||||
}): JSX.Element | null => (open ? <div>{content}</div> : null),
|
||||
}));
|
||||
|
||||
jest.mock('@signozhq/dialog', () => ({
|
||||
DialogWrapper: ({
|
||||
children,
|
||||
open,
|
||||
title,
|
||||
}: {
|
||||
children?: ReactNode;
|
||||
open: boolean;
|
||||
title?: string;
|
||||
}): JSX.Element | null =>
|
||||
open ? (
|
||||
<div role="dialog" aria-label={title}>
|
||||
{children}
|
||||
</div>
|
||||
) : null,
|
||||
DialogFooter: ({ children }: { children?: ReactNode }): JSX.Element => (
|
||||
<div>{children}</div>
|
||||
),
|
||||
}));
|
||||
|
||||
jest.mock('api/generated/services/users', () => ({
|
||||
useDeleteUser: jest.fn(),
|
||||
useGetUser: jest.fn(),
|
||||
@@ -66,6 +36,41 @@ jest.mock('api/ErrorResponseHandlerForGeneratedAPIs', () => ({
|
||||
|
||||
jest.mock('@signozhq/ui', () => ({
|
||||
...jest.requireActual('@signozhq/ui'),
|
||||
DrawerWrapper: ({
|
||||
children,
|
||||
footer,
|
||||
open,
|
||||
}: {
|
||||
children?: ReactNode;
|
||||
footer?: ReactNode;
|
||||
open: boolean;
|
||||
}): JSX.Element | null =>
|
||||
open ? (
|
||||
<div>
|
||||
{children}
|
||||
{footer}
|
||||
</div>
|
||||
) : null,
|
||||
DialogWrapper: ({
|
||||
children,
|
||||
footer,
|
||||
open,
|
||||
title,
|
||||
}: {
|
||||
children?: ReactNode;
|
||||
footer?: ReactNode;
|
||||
open: boolean;
|
||||
title?: string;
|
||||
}): JSX.Element | null =>
|
||||
open ? (
|
||||
<div role="dialog" aria-label={title}>
|
||||
{children}
|
||||
{footer}
|
||||
</div>
|
||||
) : null,
|
||||
DialogFooter: ({ children }: { children?: ReactNode }): JSX.Element => (
|
||||
<div>{children}</div>
|
||||
),
|
||||
toast: {
|
||||
success: jest.fn(),
|
||||
error: jest.fn(),
|
||||
@@ -160,6 +165,8 @@ function renderDrawer(
|
||||
describe('EditMemberDrawer', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
mockCopyState.value = undefined;
|
||||
mockCopyState.error = undefined;
|
||||
showErrorModal.mockClear();
|
||||
server.use(
|
||||
rest.get(ROLES_ENDPOINT, (_, res, ctx) =>
|
||||
@@ -726,16 +733,16 @@ describe('EditMemberDrawer', () => {
|
||||
await user.click(screen.getByRole('button', { name: /^copy$/i }));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockCopyToClipboard).toHaveBeenCalledWith(
|
||||
expect.stringContaining('reset-tok-abc'),
|
||||
);
|
||||
expect(
|
||||
screen.getByRole('button', { name: /copied!/i }),
|
||||
).toBeInTheDocument();
|
||||
expect(mockToast.success).toHaveBeenCalledWith(
|
||||
'Reset link copied to clipboard',
|
||||
expect.anything(),
|
||||
);
|
||||
});
|
||||
|
||||
expect(mockCopyToClipboard).toHaveBeenCalledWith(
|
||||
expect.stringContaining('reset-tok-abc'),
|
||||
);
|
||||
expect(screen.getByRole('button', { name: /copied!/i })).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -5,12 +5,12 @@
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
border-radius: 20px;
|
||||
background: color-mix(in srgb, var(--bg-cherry-500) 20%, transparent);
|
||||
background: color-mix(in srgb, var(--danger-background) 20%, transparent);
|
||||
padding-left: 3px;
|
||||
padding-right: 8px;
|
||||
cursor: pointer;
|
||||
span {
|
||||
color: var(--bg-cherry-500);
|
||||
color: var(--danger-background);
|
||||
font-size: 10px;
|
||||
font-weight: 500;
|
||||
line-height: 20px; /* 200% */
|
||||
@@ -21,7 +21,7 @@
|
||||
&__wrap {
|
||||
background: linear-gradient(
|
||||
180deg,
|
||||
color-mix(in srgb, var(--background) 12%, transparent) 0.07%,
|
||||
color-mix(in srgb, var(--l1-background) 12%, transparent) 0.07%,
|
||||
color-mix(in srgb, var(--bg-sakura-950) 24%, transparent) 50.04%,
|
||||
color-mix(in srgb, var(--bg-sakura-800) 36%, transparent) 75.02%,
|
||||
color-mix(in srgb, var(--bg-sakura-600) 48%, transparent) 87.51%,
|
||||
@@ -40,15 +40,17 @@
|
||||
margin: auto;
|
||||
}
|
||||
}
|
||||
|
||||
&__body {
|
||||
padding: 0;
|
||||
background: var(--l2-background);
|
||||
overflow: hidden;
|
||||
border-top-left-radius: 4px;
|
||||
border-top-right-radius: 4px;
|
||||
}
|
||||
|
||||
&__header {
|
||||
background: none !important;
|
||||
|
||||
.ant-modal-title {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
@@ -80,6 +82,7 @@
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
|
||||
.close-button {
|
||||
padding: 3px 7px;
|
||||
background: var(--l2-background);
|
||||
@@ -90,15 +93,15 @@
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
|
||||
&__footer {
|
||||
margin: 0 !important;
|
||||
height: 6px;
|
||||
background: var(--bg-sakura-500);
|
||||
}
|
||||
|
||||
&__content {
|
||||
padding: 0 !important;
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
background: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
&__summary-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border-bottom: 1px solid var(--l1-border);
|
||||
}
|
||||
|
||||
&__summary {
|
||||
|
||||
@@ -13,7 +13,6 @@ 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,
|
||||
@@ -81,13 +80,17 @@ function ShareURLModal(): JSX.Element {
|
||||
|
||||
urlQuery.delete(QueryParams.relativeTime);
|
||||
|
||||
currentUrl = getAbsoluteUrl(`${location.pathname}?${urlQuery.toString()}`);
|
||||
currentUrl = `${window.location.origin}${
|
||||
location.pathname
|
||||
}?${urlQuery.toString()}`;
|
||||
} else {
|
||||
urlQuery.delete(QueryParams.startTime);
|
||||
urlQuery.delete(QueryParams.endTime);
|
||||
|
||||
urlQuery.set(QueryParams.relativeTime, selectedTime);
|
||||
currentUrl = getAbsoluteUrl(`${location.pathname}?${urlQuery.toString()}`);
|
||||
currentUrl = `${window.location.origin}${
|
||||
location.pathname
|
||||
}?${urlQuery.toString()}`;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
border-radius: 2px 0px 0px 2px;
|
||||
|
||||
.label {
|
||||
color: var(--l2-foreground);
|
||||
font-size: 12px;
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
@@ -21,8 +20,9 @@
|
||||
padding: 0px 8px;
|
||||
|
||||
border-radius: 2px 0px 0px 2px;
|
||||
border: 1px solid var(--l1-border);
|
||||
background: var(--l3-background);
|
||||
border: 1px solid var(--l2-border);
|
||||
background: var(--l2-background);
|
||||
color: var(--l2-foreground);
|
||||
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
@@ -37,6 +37,7 @@
|
||||
|
||||
border-radius: 2px 0px 0px 2px;
|
||||
border: 1px solid var(--l1-border);
|
||||
background: var(--l2-background);
|
||||
|
||||
border-right: none;
|
||||
border-left: none;
|
||||
@@ -46,6 +47,7 @@
|
||||
border-bottom-left-radius: 0px;
|
||||
font-size: 12px !important;
|
||||
line-height: 27px;
|
||||
|
||||
&::placeholder {
|
||||
color: var(--l2-foreground) !important;
|
||||
font-size: 12px !important;
|
||||
@@ -61,8 +63,8 @@
|
||||
|
||||
.close-btn {
|
||||
border-radius: 0px 2px 2px 0px;
|
||||
border: 1px solid var(--l1-border);
|
||||
background: var(--l3-background);
|
||||
border: 1px solid var(--l2-border);
|
||||
background: var(--l2-background);
|
||||
height: 38px;
|
||||
width: 38px;
|
||||
}
|
||||
@@ -71,7 +73,7 @@
|
||||
.input {
|
||||
border-radius: 0px 2px 2px 0px;
|
||||
border: 1px solid var(--l1-border);
|
||||
background: var(--l3-background);
|
||||
background: var(--l2-background);
|
||||
border-top-right-radius: 0px;
|
||||
border-bottom-right-radius: 0px;
|
||||
}
|
||||
|
||||
@@ -181,7 +181,7 @@
|
||||
box-shadow: none;
|
||||
|
||||
&:hover {
|
||||
background: color-mix(in srgb, var(--bg-cherry-500) 10%, transparent);
|
||||
background: color-mix(in srgb, var(--danger-background) 10%, transparent);
|
||||
opacity: 0.9;
|
||||
}
|
||||
}
|
||||
@@ -196,12 +196,16 @@
|
||||
}
|
||||
|
||||
.invite-team-members-error-callout {
|
||||
background: color-mix(in srgb, var(--bg-cherry-500) 10%, transparent);
|
||||
border: 1px solid color-mix(in srgb, var(--bg-cherry-500) 20%, transparent);
|
||||
background: color-mix(in srgb, var(--danger-background) 10%, transparent);
|
||||
border: 1px solid color-mix(in srgb, var(--danger-background) 20%, transparent);
|
||||
border-radius: 4px;
|
||||
animation: horizontal-shaking 300ms ease-out;
|
||||
}
|
||||
|
||||
.invite-members-modal__error-callout {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
@keyframes horizontal-shaking {
|
||||
0% {
|
||||
transform: translateX(0);
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { Button } from '@signozhq/button';
|
||||
import { Callout } from '@signozhq/callout';
|
||||
import { Style } from '@signozhq/design-tokens';
|
||||
import { DialogFooter, DialogWrapper } from '@signozhq/dialog';
|
||||
import { ChevronDown, CircleAlert, Plus, Trash2, X } from '@signozhq/icons';
|
||||
import { Input } from '@signozhq/input';
|
||||
import { toast } from '@signozhq/ui';
|
||||
import {
|
||||
Button,
|
||||
Callout,
|
||||
DialogFooter,
|
||||
DialogWrapper,
|
||||
Input,
|
||||
toast,
|
||||
} from '@signozhq/ui';
|
||||
import { Select } from 'antd';
|
||||
import inviteUsers from 'api/v1/invite/bulk/create';
|
||||
import sendInvite from 'api/v1/invite/create';
|
||||
@@ -14,7 +17,6 @@ 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';
|
||||
|
||||
@@ -189,7 +191,7 @@ function InviteMembersModal({
|
||||
email: row.email.trim(),
|
||||
name: '',
|
||||
role: row.role as ROLES,
|
||||
frontendBaseUrl: getBaseUrl(),
|
||||
frontendBaseUrl: window.location.origin,
|
||||
});
|
||||
} else {
|
||||
await inviteUsers({
|
||||
@@ -197,14 +199,11 @@ function InviteMembersModal({
|
||||
email: row.email.trim(),
|
||||
name: '',
|
||||
role: row.role,
|
||||
frontendBaseUrl: getBaseUrl(),
|
||||
frontendBaseUrl: window.location.origin,
|
||||
})),
|
||||
});
|
||||
}
|
||||
toast.success('Invites sent successfully', {
|
||||
richColors: true,
|
||||
position: 'top-right',
|
||||
});
|
||||
toast.success('Invites sent successfully', { position: 'top-right' });
|
||||
resetAndClose();
|
||||
onComplete?.();
|
||||
} catch (err) {
|
||||
@@ -275,7 +274,6 @@ function InviteMembersModal({
|
||||
<Button
|
||||
variant="ghost"
|
||||
color="destructive"
|
||||
className="remove-team-member-button"
|
||||
onClick={(): void => removeRow(row.id)}
|
||||
aria-label="Remove row"
|
||||
>
|
||||
@@ -290,14 +288,16 @@ function InviteMembersModal({
|
||||
</div>
|
||||
|
||||
{(hasInvalidEmails || hasInvalidRoles) && (
|
||||
<Callout
|
||||
type="error"
|
||||
size="small"
|
||||
showIcon
|
||||
icon={<CircleAlert size={12} />}
|
||||
className="invite-team-members-error-callout"
|
||||
description={getValidationErrorMessage()}
|
||||
/>
|
||||
<div className="invite-members-modal__error-callout">
|
||||
<Callout
|
||||
type="error"
|
||||
size="small"
|
||||
showIcon
|
||||
icon={<CircleAlert size={12} />}
|
||||
>
|
||||
{getValidationErrorMessage()}
|
||||
</Callout>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -305,9 +305,8 @@ function InviteMembersModal({
|
||||
<Button
|
||||
variant="dashed"
|
||||
color="secondary"
|
||||
size="sm"
|
||||
className="add-another-member-button"
|
||||
prefixIcon={<Plus size={12} color={Style.L1_FOREGROUND} />}
|
||||
prefix={<Plus size={12} color={Style.L1_FOREGROUND} />}
|
||||
onClick={addRow}
|
||||
>
|
||||
Add another
|
||||
@@ -318,7 +317,6 @@ function InviteMembersModal({
|
||||
type="button"
|
||||
variant="solid"
|
||||
color="secondary"
|
||||
size="sm"
|
||||
onClick={resetAndClose}
|
||||
>
|
||||
<X size={12} />
|
||||
@@ -328,7 +326,6 @@ function InviteMembersModal({
|
||||
<Button
|
||||
variant="solid"
|
||||
color="primary"
|
||||
size="sm"
|
||||
onClick={handleSubmit}
|
||||
disabled={isSubmitDisabled}
|
||||
loading={isSubmitting}
|
||||
|
||||
@@ -14,7 +14,6 @@ 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';
|
||||
|
||||
@@ -155,7 +154,7 @@ function LaunchChatSupport({
|
||||
});
|
||||
|
||||
updateCreditCard({
|
||||
url: getBaseUrl(),
|
||||
url: window.location.origin,
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { Color } from '@signozhq/design-tokens';
|
||||
import { Button } from 'antd';
|
||||
import { ArrowUpRight } from 'lucide-react';
|
||||
import { openInNewTab } from 'utils/navigation';
|
||||
|
||||
import './LearnMore.styles.scss';
|
||||
|
||||
@@ -15,7 +14,7 @@ function LearnMore({ text, url, onClick }: LearnMoreProps): JSX.Element {
|
||||
const handleClick = (): void => {
|
||||
onClick?.();
|
||||
if (url) {
|
||||
openInNewTab(url);
|
||||
window.open(url, '_blank');
|
||||
}
|
||||
};
|
||||
return (
|
||||
|
||||
@@ -110,7 +110,7 @@
|
||||
}
|
||||
|
||||
&.ERROR {
|
||||
border-color: var(--bg-cherry-500);
|
||||
border-color: var(--danger-background);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,8 @@ import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useSelector } from 'react-redux'; // old code, TODO: fix this correctly
|
||||
import { useCopyToClipboard, useLocation } from 'react-use';
|
||||
import { Color, Spacing } from '@signozhq/design-tokens';
|
||||
import { Button, Divider, Drawer, Radio, Tooltip, Typography } from 'antd';
|
||||
import { Button } from '@signozhq/ui';
|
||||
import { Divider, Drawer, Radio, Tooltip, Typography } from 'antd';
|
||||
import type { RadioChangeEvent } from 'antd/lib';
|
||||
import cx from 'classnames';
|
||||
import { LogType } from 'components/Logs/LogStateIndicator/LogStateIndicator';
|
||||
@@ -363,7 +364,9 @@ function LogDetailInner({
|
||||
mouseLeaveDelay={0}
|
||||
>
|
||||
<Button
|
||||
icon={<ChevronUp size={14} />}
|
||||
variant="outlined"
|
||||
color="secondary"
|
||||
prefix={<ChevronUp size={14} />}
|
||||
className="log-arrow-btn log-arrow-btn-up"
|
||||
disabled={isPrevDisabled}
|
||||
onClick={(): void => handleNavigateLog({ direction: 'previous' })}
|
||||
@@ -375,7 +378,9 @@ function LogDetailInner({
|
||||
mouseLeaveDelay={0}
|
||||
>
|
||||
<Button
|
||||
icon={<ChevronDown size={14} />}
|
||||
variant="outlined"
|
||||
color="secondary"
|
||||
prefix={<ChevronDown size={14} />}
|
||||
className="log-arrow-btn log-arrow-btn-down"
|
||||
disabled={isNextDisabled}
|
||||
onClick={(): void => handleNavigateLog({ direction: 'next' })}
|
||||
@@ -385,8 +390,10 @@ function LogDetailInner({
|
||||
{showOpenInExplorerBtn && (
|
||||
<div>
|
||||
<Button
|
||||
variant="outlined"
|
||||
color="secondary"
|
||||
prefix={<Compass size={16} />}
|
||||
className="open-in-explorer-btn"
|
||||
icon={<Compass size={16} />}
|
||||
onClick={handleOpenInExplorer}
|
||||
>
|
||||
Open in Explorer
|
||||
@@ -482,8 +489,10 @@ function LogDetailInner({
|
||||
mouseLeaveDelay={0}
|
||||
>
|
||||
<Button
|
||||
className="action-btn"
|
||||
icon={<Filter size={16} />}
|
||||
variant="link"
|
||||
color="secondary"
|
||||
size="sm"
|
||||
prefix={<Filter size={12} />}
|
||||
onClick={handleFilterVisible}
|
||||
/>
|
||||
</Tooltip>
|
||||
@@ -498,8 +507,10 @@ function LogDetailInner({
|
||||
mouseLeaveDelay={0}
|
||||
>
|
||||
<Button
|
||||
className="action-btn"
|
||||
icon={<Copy size={16} />}
|
||||
variant="link"
|
||||
color="secondary"
|
||||
size="sm"
|
||||
prefix={<Copy size={12} />}
|
||||
onClick={selectedView === VIEW_TYPES.JSON ? handleJSONCopy : onLogCopy}
|
||||
/>
|
||||
</Tooltip>
|
||||
|
||||
@@ -94,7 +94,7 @@
|
||||
background-color: var(--bg-cherry-600);
|
||||
}
|
||||
&.severity-error-1 {
|
||||
background-color: var(--bg-cherry-500);
|
||||
background-color: var(--danger-background);
|
||||
}
|
||||
&.severity-error-2 {
|
||||
background-color: var(--bg-cherry-400);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { CSSProperties } from 'react';
|
||||
import { Color } from '@signozhq/design-tokens';
|
||||
import { TableProps } from 'antd';
|
||||
|
||||
export function getDefaultCellStyle(isDarkMode?: boolean): CSSProperties {
|
||||
@@ -7,7 +8,7 @@ export function getDefaultCellStyle(isDarkMode?: boolean): CSSProperties {
|
||||
paddingBottom: 6,
|
||||
paddingRight: 8,
|
||||
paddingLeft: 8,
|
||||
color: isDarkMode ? 'var(--bg-vanilla-400)' : 'var(--bg-slate-400)',
|
||||
color: isDarkMode ? Color.BG_VANILLA_100 : Color.BG_INK_400,
|
||||
fontSize: '14px',
|
||||
fontStyle: 'normal',
|
||||
fontWeight: 400,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { Color } from '@signozhq/design-tokens';
|
||||
import { FontSize } from 'container/OptionsMenu/types';
|
||||
import styled from 'styled-components';
|
||||
|
||||
@@ -10,7 +11,7 @@ interface TableBodyContentProps {
|
||||
export const TableBodyContent = styled.div<TableBodyContentProps>`
|
||||
margin-bottom: 0;
|
||||
color: ${(props): string =>
|
||||
props.isDarkMode ? 'var(--bg-vanilla-400, #c0c1c3)' : 'var(--bg-slate-400)'};
|
||||
props.isDarkMode ? Color.BG_VANILLA_100 : Color.BG_INK_400};
|
||||
font-size: 14px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
|
||||
@@ -33,8 +33,9 @@
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
p {
|
||||
margin-bottom: 0;
|
||||
.timestamp-text {
|
||||
color: var(--l1-foreground);
|
||||
margin: 0 !important;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -123,7 +123,7 @@ export const useTableView = (props: UseTableViewProps): UseTableViewResult => {
|
||||
return {
|
||||
children: (
|
||||
<div className="table-timestamp">
|
||||
<p className={cx('text', fontSize)}>{date}</p>
|
||||
<p className={cx('timestamp-text text', fontSize)}>{date}</p>
|
||||
</div>
|
||||
),
|
||||
};
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
}
|
||||
|
||||
.text {
|
||||
color: var(--muted-foreground);
|
||||
color: var(--l1-foreground);
|
||||
font-family: Inter;
|
||||
font-size: 11px;
|
||||
font-style: normal;
|
||||
@@ -93,7 +93,7 @@
|
||||
gap: 12px;
|
||||
|
||||
.title {
|
||||
color: var(--muted-foreground);
|
||||
color: var(--l1-foreground);
|
||||
font-family: Inter;
|
||||
font-size: 11px;
|
||||
font-style: normal;
|
||||
@@ -139,7 +139,8 @@
|
||||
line-height: 18px;
|
||||
letter-spacing: 0.08em;
|
||||
text-align: left;
|
||||
color: var(--muted-foreground);
|
||||
|
||||
color: var(--l1-foreground);
|
||||
}
|
||||
|
||||
.menu-items {
|
||||
@@ -177,7 +178,7 @@
|
||||
padding: 12px;
|
||||
|
||||
.title {
|
||||
color: var(--muted-foreground);
|
||||
color: var(--l1-foreground);
|
||||
font-family: Inter;
|
||||
font-size: 11px;
|
||||
font-style: normal;
|
||||
@@ -330,7 +331,7 @@
|
||||
}
|
||||
|
||||
.title {
|
||||
color: var(--muted-foreground);
|
||||
color: var(--l1-foreground);
|
||||
font-family: Inter;
|
||||
font-size: 11px;
|
||||
font-style: normal;
|
||||
@@ -486,169 +487,3 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.lightMode {
|
||||
.format-options-popover {
|
||||
.ant-popover-inner {
|
||||
border: 1px solid var(--l1-border);
|
||||
background: linear-gradient(
|
||||
139deg,
|
||||
rgba(255, 255, 255, 0.8) 0%,
|
||||
rgba(255, 255, 255, 0.9) 98.68%
|
||||
);
|
||||
box-shadow: 4px 10px 16px 2px rgba(255, 255, 255, 0.2);
|
||||
|
||||
.nested-menu-container {
|
||||
.font-size-dropdown {
|
||||
.back-btn {
|
||||
.text {
|
||||
color: var(--l2-background);
|
||||
}
|
||||
}
|
||||
|
||||
.content {
|
||||
.option-btn {
|
||||
.text {
|
||||
color: var(--l2-background);
|
||||
}
|
||||
|
||||
.text:hover {
|
||||
color: var(--l3-background);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.add-new-column-container {
|
||||
.add-new-column-header {
|
||||
.title {
|
||||
color: var(--l2-foreground);
|
||||
}
|
||||
}
|
||||
|
||||
.add-new-column-content {
|
||||
.column-format-new-options {
|
||||
.column-name {
|
||||
color: var(--l2-background);
|
||||
|
||||
&.selected {
|
||||
background-color: var(--l3-background);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.loading-container {
|
||||
color: var(--l2-background);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.font-size-container {
|
||||
.title {
|
||||
color: var(--l2-foreground);
|
||||
}
|
||||
|
||||
.value {
|
||||
.font-value {
|
||||
color: var(--l2-background);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.horizontal-line {
|
||||
background: var(--l3-background);
|
||||
}
|
||||
|
||||
.item-content {
|
||||
.column-divider {
|
||||
border-top: 2px solid var(--l1-border);
|
||||
}
|
||||
}
|
||||
|
||||
.max-lines-per-row {
|
||||
.title {
|
||||
color: var(--l2-foreground);
|
||||
|
||||
.lucide {
|
||||
color: var(--l1-foreground);
|
||||
}
|
||||
}
|
||||
|
||||
.max-lines-per-row-input {
|
||||
border: 1px solid var(--l1-border);
|
||||
|
||||
.periscope-btn {
|
||||
background: var(--l3-background);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.menu-container {
|
||||
.title {
|
||||
color: var(--l2-foreground);
|
||||
}
|
||||
|
||||
.item {
|
||||
.item-label {
|
||||
color: var(--l2-background);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.selected-item-content-container {
|
||||
.title {
|
||||
color: var(--l2-foreground);
|
||||
|
||||
.lucide {
|
||||
color: var(--l1-foreground);
|
||||
}
|
||||
}
|
||||
|
||||
.horizontal-line {
|
||||
background: var(--l3-background);
|
||||
}
|
||||
|
||||
.item-content {
|
||||
.max-lines-per-row-input {
|
||||
border: 1px solid var(--l1-border);
|
||||
|
||||
.periscope-btn {
|
||||
background: var(--l3-background);
|
||||
}
|
||||
}
|
||||
|
||||
.column-format,
|
||||
.column-format-new-options {
|
||||
.column-name {
|
||||
color: var(--l1-foreground);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.active {
|
||||
.nested-menu-container {
|
||||
backdrop-filter: blur(18px);
|
||||
|
||||
.item {
|
||||
.item-label {
|
||||
color: var(--l1-foreground);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.selected-item-content-container {
|
||||
border: 1px solid var(--l1-border);
|
||||
background: linear-gradient(
|
||||
139deg,
|
||||
rgba(255, 255, 255, 0.8) 0%,
|
||||
rgba(255, 255, 255, 0.9) 98.68%
|
||||
);
|
||||
|
||||
box-shadow: 4px 10px 16px 2px rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
.ant-table-thead {
|
||||
> tr > th,
|
||||
> tr > td {
|
||||
background: var(--background);
|
||||
background: var(--l1-background);
|
||||
font-size: var(--paragraph-small-600-font-size);
|
||||
font-weight: var(--paragraph-small-600-font-weight);
|
||||
line-height: var(--paragraph-small-600-line-height);
|
||||
|
||||
@@ -20,7 +20,6 @@ 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';
|
||||
@@ -77,7 +76,7 @@ function ErrorTitleAndKey({
|
||||
if (isCloudUserVal && !!link) {
|
||||
history.push(link);
|
||||
} else {
|
||||
openInNewTab(KAFKA_SETUP_DOC_LINK);
|
||||
window.open(KAFKA_SETUP_DOC_LINK, '_blank');
|
||||
}
|
||||
};
|
||||
return {
|
||||
|
||||
@@ -1445,11 +1445,22 @@ const CustomMultiSelect: React.FC<CustomMultiSelectProps> = ({
|
||||
|
||||
// Custom dropdown render with sections support
|
||||
const customDropdownRender = useCallback((): React.ReactElement => {
|
||||
// Process options based on current search
|
||||
const processedOptions =
|
||||
selectedValues.length > 0 && isEmpty(searchText)
|
||||
? prioritizeOrAddOptionForMultiSelect(filteredOptions, selectedValues)
|
||||
: filteredOptions;
|
||||
// When ALL is selected and the options contain sections (groups),
|
||||
// skip prioritization so section headers (e.g. "Related values" /
|
||||
// "All values") remain visible instead of being collapsed away by
|
||||
// every option getting hoisted to the top. For flat option lists we
|
||||
// still prioritize so selected/synthesized values stay rendered.
|
||||
const hasSections = filteredOptions.some(
|
||||
(opt) => 'options' in opt && Array.isArray(opt.options),
|
||||
);
|
||||
const shouldPrioritize =
|
||||
selectedValues.length > 0 &&
|
||||
isEmpty(searchText) &&
|
||||
!(hasSections && (allOptionShown || isAllSelected));
|
||||
|
||||
const processedOptions = shouldPrioritize
|
||||
? prioritizeOrAddOptionForMultiSelect(filteredOptions, selectedValues)
|
||||
: filteredOptions;
|
||||
|
||||
const { sectionOptions, nonSectionOptions } = splitOptions(processedOptions);
|
||||
|
||||
@@ -1747,6 +1758,8 @@ const CustomMultiSelect: React.FC<CustomMultiSelectProps> = ({
|
||||
}, [
|
||||
selectedValues,
|
||||
searchText,
|
||||
allOptionShown,
|
||||
isAllSelected,
|
||||
filteredOptions,
|
||||
splitOptions,
|
||||
isLabelPresent,
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
screen,
|
||||
waitFor,
|
||||
} from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
|
||||
import CustomMultiSelect from '../CustomMultiSelect';
|
||||
|
||||
@@ -283,4 +284,68 @@ describe('CustomMultiSelect Component', () => {
|
||||
// When all options are selected, component shows ALL tag instead
|
||||
expect(screen.getByText('ALL')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
describe('section visibility when ALL is selected', () => {
|
||||
it('keeps group headers visible when every grouped value is selected', async () => {
|
||||
const user = userEvent.setup();
|
||||
renderWithVirtuoso(
|
||||
<CustomMultiSelect
|
||||
options={mockGroupedOptions}
|
||||
value={['g1-option1', 'g1-option2', 'g2-option1', 'g2-option2']}
|
||||
/>,
|
||||
);
|
||||
|
||||
await user.click(screen.getByRole('combobox'));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Group 1')).toBeInTheDocument();
|
||||
expect(screen.getByText('Group 2')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('keeps every grouped option visible within its section when all are selected', async () => {
|
||||
const user = userEvent.setup();
|
||||
renderWithVirtuoso(
|
||||
<CustomMultiSelect
|
||||
options={mockGroupedOptions}
|
||||
value={['g1-option1', 'g1-option2', 'g2-option1', 'g2-option2']}
|
||||
/>,
|
||||
);
|
||||
|
||||
await user.click(screen.getByRole('combobox'));
|
||||
|
||||
await waitFor(() => {
|
||||
const group1Region = screen.getByRole('group', {
|
||||
name: 'Group 1 options',
|
||||
});
|
||||
const group2Region = screen.getByRole('group', {
|
||||
name: 'Group 2 options',
|
||||
});
|
||||
|
||||
// Each option stays inside its original section rather than being
|
||||
// hoisted into a flat selected-first list.
|
||||
expect(group1Region).toHaveTextContent('Group 1 - Option 1');
|
||||
expect(group1Region).toHaveTextContent('Group 1 - Option 2');
|
||||
expect(group2Region).toHaveTextContent('Group 2 - Option 1');
|
||||
expect(group2Region).toHaveTextContent('Group 2 - Option 2');
|
||||
});
|
||||
});
|
||||
|
||||
it('keeps group headers visible when value is the ALL sentinel', async () => {
|
||||
const user = userEvent.setup();
|
||||
renderWithVirtuoso(
|
||||
<CustomMultiSelect
|
||||
options={mockGroupedOptions}
|
||||
value={('__ALL__' as unknown) as string[]}
|
||||
/>,
|
||||
);
|
||||
|
||||
await user.click(screen.getByRole('combobox'));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Group 1')).toBeInTheDocument();
|
||||
expect(screen.getByText('Group 2')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -355,7 +355,7 @@ $custom-border-color: #2c3044;
|
||||
.navigation-error {
|
||||
.navigation-text,
|
||||
.navigation-icons {
|
||||
color: var(--bg-cherry-500) !important;
|
||||
color: var(--danger-background) !important;
|
||||
}
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// TODO: Improve the styling of the query aggregation container and its components. - @YounixM , @H4ad
|
||||
|
||||
.query-builder-v2 {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
@@ -274,7 +276,7 @@
|
||||
.ant-input-group-addon {
|
||||
border-top-left-radius: 0px !important;
|
||||
border-top-right-radius: 0px !important;
|
||||
background: var(--l3-background);
|
||||
background: var(--l2-background);
|
||||
color: var(--l2-foreground);
|
||||
font-size: 12px;
|
||||
font-weight: 300;
|
||||
|
||||
@@ -50,8 +50,8 @@ const havingOperators = [
|
||||
value: 'IN',
|
||||
},
|
||||
{
|
||||
label: 'NOT_IN',
|
||||
value: 'NOT_IN',
|
||||
label: 'NOT IN',
|
||||
value: 'NOT IN',
|
||||
},
|
||||
];
|
||||
|
||||
@@ -129,7 +129,7 @@ function HavingFilter({
|
||||
const operator = havingOperators[j];
|
||||
newOptions.push({
|
||||
label: `${opt.func}(${opt.arg}) ${operator.label}`,
|
||||
value: `${opt.func}(${opt.arg}) ${operator.label} `,
|
||||
value: `${opt.func}(${opt.arg}) ${operator.value} `,
|
||||
apply: (
|
||||
view: EditorView,
|
||||
completion: { label: string; value: string },
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// TODO: Improve the styling of the query aggregation container and its components. - @YounixM , @H4ad
|
||||
|
||||
.query-add-ons {
|
||||
width: 100%;
|
||||
}
|
||||
@@ -102,7 +104,7 @@
|
||||
border-top-right-radius: 0px;
|
||||
border-bottom-right-radius: 0px;
|
||||
padding: 0px !important;
|
||||
background-color: var(--card) !important;
|
||||
background-color: var(--l2-background) !important;
|
||||
|
||||
&:focus-within {
|
||||
border-color: var(--l1-border);
|
||||
@@ -211,7 +213,7 @@
|
||||
.cm-line {
|
||||
line-height: 36px !important;
|
||||
font-family: 'Space Mono', monospace !important;
|
||||
background-color: var(--card) !important;
|
||||
background-color: var(--l2-background) !important;
|
||||
|
||||
::-moz-selection {
|
||||
background: var(--l3-background) !important;
|
||||
@@ -249,8 +251,8 @@
|
||||
|
||||
.close-btn {
|
||||
border-radius: 0px 2px 2px 0px;
|
||||
border: 1px solid var(--l1-border);
|
||||
background: var(--l3-background);
|
||||
border: 1px solid var(--l2-border);
|
||||
background: var(--l2-background);
|
||||
height: 38px;
|
||||
width: 38px;
|
||||
|
||||
@@ -284,108 +286,3 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.lightMode {
|
||||
.add-ons-list {
|
||||
.add-ons-tabs {
|
||||
.add-on-tab-title {
|
||||
color: var(--l1-foreground) !important;
|
||||
}
|
||||
|
||||
.tab {
|
||||
border: 1px solid var(--l1-border) !important;
|
||||
background: var(--l1-background) !important;
|
||||
|
||||
&:first-child {
|
||||
border-left: 1px solid var(--l1-border) !important;
|
||||
}
|
||||
}
|
||||
|
||||
.tab::before {
|
||||
background: var(--l3-background) !important;
|
||||
}
|
||||
|
||||
.selected-view {
|
||||
color: var(--primary-background) !important;
|
||||
border: 1px solid var(--l1-border) !important;
|
||||
}
|
||||
|
||||
.selected-view::before {
|
||||
background: var(--l3-background) !important;
|
||||
}
|
||||
}
|
||||
|
||||
.compass-button {
|
||||
border: 1px solid var(--l1-border) !important;
|
||||
background: var(--l1-background) !important;
|
||||
}
|
||||
}
|
||||
|
||||
.having-filter-container {
|
||||
.having-filter-select-container {
|
||||
.having-filter-select-editor {
|
||||
.cm-editor {
|
||||
&:focus-within {
|
||||
border-color: var(--l1-border) !important;
|
||||
}
|
||||
|
||||
.cm-content {
|
||||
border: 1px solid var(--l1-border) !important;
|
||||
background: var(--l1-background) !important;
|
||||
|
||||
&:focus-within {
|
||||
border-color: var(--l1-border) !important;
|
||||
}
|
||||
}
|
||||
|
||||
.cm-tooltip-autocomplete {
|
||||
background: var(--l1-background) !important;
|
||||
border: 1px solid var(--l1-border) !important;
|
||||
color: var(--l1-foreground) !important;
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1) !important;
|
||||
|
||||
ul {
|
||||
li {
|
||||
color: var(--l1-foreground) !important;
|
||||
&:hover {
|
||||
background: var(--l3-background) !important;
|
||||
}
|
||||
|
||||
&[aria-selected='true'] {
|
||||
background: var(--l3-background) !important;
|
||||
font-weight: 600 !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.cm-line {
|
||||
background-color: var(--l1-background) !important;
|
||||
|
||||
::-moz-selection {
|
||||
background: var(--l1-background) !important;
|
||||
}
|
||||
|
||||
::selection {
|
||||
background: var(--l3-background) !important;
|
||||
}
|
||||
|
||||
.chip-decorator {
|
||||
background: var(--l3-background) !important;
|
||||
color: var(--l1-foreground) !important;
|
||||
}
|
||||
}
|
||||
|
||||
.cm-selectionBackground {
|
||||
background: var(--l1-background) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.close-btn {
|
||||
border: 1px solid var(--l1-border) !important;
|
||||
background: var(--l1-background) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// TODO: Improve the styling of the query aggregation container and its components. - @YounixM , @H4ad
|
||||
|
||||
.query-aggregation-container {
|
||||
display: block;
|
||||
|
||||
@@ -26,7 +28,7 @@
|
||||
&.error {
|
||||
.cm-editor {
|
||||
.cm-content {
|
||||
border-color: var(--bg-cherry-500) !important;
|
||||
border-color: var(--danger-background) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -140,7 +142,7 @@
|
||||
.cm-line {
|
||||
line-height: 36px !important;
|
||||
font-family: 'Space Mono', monospace !important;
|
||||
background-color: var(--l1-background) !important;
|
||||
background-color: var(--l2-background) !important;
|
||||
|
||||
::-moz-selection {
|
||||
background: var(--l3-background) !important;
|
||||
@@ -184,7 +186,7 @@
|
||||
max-width: 300px;
|
||||
|
||||
.query-aggregation-error-message {
|
||||
color: var(--bg-cherry-500);
|
||||
color: var(--danger-background);
|
||||
font-size: 12px;
|
||||
line-height: 16px;
|
||||
}
|
||||
@@ -196,6 +198,7 @@
|
||||
min-width: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.close-btn {
|
||||
border-radius: 0px 2px 2px 0px;
|
||||
border: 1px solid var(--l1-border);
|
||||
@@ -270,7 +273,7 @@
|
||||
|
||||
.cm-line {
|
||||
::-moz-selection {
|
||||
background: var(--l1-background) !important;
|
||||
background: var(--l2-background) !important;
|
||||
opacity: 0.5 !important;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// TODO: Improve the styling of the query aggregation container and its components. - @YounixM , @H4ad
|
||||
|
||||
.code-mirror-where-clause {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
@@ -30,7 +32,7 @@
|
||||
border-left: none !important;
|
||||
|
||||
&.hasErrors {
|
||||
border-color: var(--bg-cherry-500);
|
||||
border-color: var(--danger-background);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -39,7 +41,7 @@
|
||||
&.hasErrors {
|
||||
.cm-editor {
|
||||
.cm-content {
|
||||
border-color: var(--bg-cherry-500);
|
||||
border-color: var(--danger-background);
|
||||
border-top-right-radius: 0px !important;
|
||||
border-bottom-right-radius: 0px !important;
|
||||
}
|
||||
@@ -156,7 +158,7 @@
|
||||
.cm-line {
|
||||
line-height: 34px !important;
|
||||
font-family: 'Space Mono', monospace !important;
|
||||
background-color: var(--l1-background) !important;
|
||||
background-color: var(--l2-background) !important;
|
||||
|
||||
::-moz-selection {
|
||||
background: var(--l3-background) !important;
|
||||
@@ -211,7 +213,7 @@
|
||||
|
||||
.invalid {
|
||||
background-color: color-mix(in srgb, var(--bg-cherry-400) 10%, transparent);
|
||||
color: var(--bg-cherry-500);
|
||||
color: var(--danger-background);
|
||||
}
|
||||
|
||||
.query-validation-status {
|
||||
@@ -232,7 +234,7 @@
|
||||
|
||||
font-size: 12px;
|
||||
font-family: 'Space Mono', monospace !important;
|
||||
color: var(--bg-cherry-500);
|
||||
color: var(--danger-background);
|
||||
padding: 8px;
|
||||
}
|
||||
}
|
||||
@@ -454,30 +456,3 @@
|
||||
margin-top: -6px !important;
|
||||
}
|
||||
}
|
||||
|
||||
.lightMode {
|
||||
.code-mirror-where-clause {
|
||||
.cm-editor {
|
||||
.cm-tooltip-autocomplete {
|
||||
background: var(--l1-background) !important;
|
||||
border: 1px solid var(--l1-border);
|
||||
backdrop-filter: blur(20px);
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1) !important;
|
||||
}
|
||||
|
||||
.cm-line {
|
||||
::-moz-selection {
|
||||
background: var(--bg-robin-200) !important;
|
||||
}
|
||||
|
||||
::selection {
|
||||
background: var(--bg-robin-200) !important;
|
||||
}
|
||||
}
|
||||
|
||||
.cm-selectionBackground {
|
||||
background: var(--bg-robin-200) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,8 +87,8 @@ jest.mock('hooks/useDarkMode', () => ({
|
||||
}));
|
||||
|
||||
jest.mock('providers/Dashboard/store/useDashboardStore', () => ({
|
||||
useDashboardStore: (): { selectedDashboard: undefined } => ({
|
||||
selectedDashboard: undefined,
|
||||
useDashboardStore: (): { dashboardData: undefined } => ({
|
||||
dashboardData: undefined,
|
||||
}),
|
||||
}));
|
||||
|
||||
|
||||
@@ -1,13 +1,10 @@
|
||||
import getSessionStorageApi from 'api/browser/sessionstorage/get';
|
||||
import removeSessionStorageApi from 'api/browser/sessionstorage/remove';
|
||||
import setSessionStorageApi from 'api/browser/sessionstorage/set';
|
||||
import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
|
||||
|
||||
export const PREVIOUS_QUERY_KEY = 'previousQuery';
|
||||
|
||||
function getPreviousQueryFromStore(): Record<string, IBuilderQuery> {
|
||||
try {
|
||||
const raw = getSessionStorageApi(PREVIOUS_QUERY_KEY);
|
||||
const raw = sessionStorage.getItem(PREVIOUS_QUERY_KEY);
|
||||
if (!raw) {
|
||||
return {};
|
||||
}
|
||||
@@ -20,7 +17,7 @@ function getPreviousQueryFromStore(): Record<string, IBuilderQuery> {
|
||||
|
||||
function writePreviousQueryToStore(store: Record<string, IBuilderQuery>): void {
|
||||
try {
|
||||
setSessionStorageApi(PREVIOUS_QUERY_KEY, JSON.stringify(store));
|
||||
sessionStorage.setItem(PREVIOUS_QUERY_KEY, JSON.stringify(store));
|
||||
} catch {
|
||||
// ignore quota or serialization errors
|
||||
}
|
||||
@@ -66,7 +63,7 @@ export const removeKeyFromPreviousQuery = (key: string): void => {
|
||||
|
||||
export const clearPreviousQuery = (): void => {
|
||||
try {
|
||||
removeSessionStorageApi(PREVIOUS_QUERY_KEY);
|
||||
sessionStorage.removeItem(PREVIOUS_QUERY_KEY);
|
||||
} catch {
|
||||
// no-op
|
||||
}
|
||||
|
||||
@@ -177,7 +177,7 @@
|
||||
width: 2px;
|
||||
height: 11px;
|
||||
border-radius: 2px;
|
||||
background: var(--bg-cherry-500);
|
||||
background: var(--danger-background);
|
||||
}
|
||||
|
||||
.label-true {
|
||||
|
||||
@@ -158,12 +158,12 @@
|
||||
|
||||
mask-image: radial-gradient(
|
||||
circle at 50% 0,
|
||||
color-mix(in srgb, var(--background) 10%, transparent) 0,
|
||||
color-mix(in srgb, var(--l1-background) 10%, transparent) 0,
|
||||
transparent 100%
|
||||
);
|
||||
-webkit-mask-image: radial-gradient(
|
||||
circle at 50% 0,
|
||||
color-mix(in srgb, var(--background) 10%, transparent) 0,
|
||||
color-mix(in srgb, var(--l1-background) 10%, transparent) 0,
|
||||
transparent 100%
|
||||
);
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ import {
|
||||
ComboboxItem,
|
||||
ComboboxList,
|
||||
ComboboxTrigger,
|
||||
} from '@signozhq/combobox';
|
||||
} from '@signozhq/ui';
|
||||
import { Skeleton, Switch, Tooltip, Typography } from 'antd';
|
||||
import getLocalStorageKey from 'api/browser/localstorage/get';
|
||||
import setLocalStorageKey from 'api/browser/localstorage/set';
|
||||
@@ -200,7 +200,6 @@ export default function QuickFilters(props: IQuickFiltersProps): JSX.Element {
|
||||
setOpen(false);
|
||||
}}
|
||||
isSelected={validQueryIndex === option.value}
|
||||
showCheck={false}
|
||||
>
|
||||
{option.label}
|
||||
</ComboboxItem>
|
||||
|
||||
@@ -11,6 +11,8 @@
|
||||
&__title {
|
||||
font-weight: 500;
|
||||
font-size: 15px;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
&__container {
|
||||
@@ -19,7 +21,7 @@
|
||||
background-color: var(--primary-background);
|
||||
color: var(--primary-foreground);
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
padding: 12px;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||||
z-index: 1000;
|
||||
}
|
||||
@@ -43,14 +45,23 @@
|
||||
&__footer {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
&__button {
|
||||
background: var(--secondary-background);
|
||||
color: var(--secondary-foreground);
|
||||
border: none;
|
||||
padding: 6px 12px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
// TODO: Need to override the button styles for this component due to container styles.
|
||||
// Fix - @aks07
|
||||
|
||||
&__button {
|
||||
margin-top: 12px;
|
||||
color: var(--base-black);
|
||||
background-color: var(--base-white);
|
||||
border: none;
|
||||
padding: 6px 12px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--base-white);
|
||||
color: var(--bg-robin-500);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useState } from 'react';
|
||||
import { Button, Typography } from 'antd';
|
||||
import { Button } from '@signozhq/ui';
|
||||
import classNames from 'classnames';
|
||||
import { X } from 'lucide-react';
|
||||
import { Check, X } from 'lucide-react';
|
||||
|
||||
import './AnnouncementTooltip.styles.scss';
|
||||
|
||||
@@ -46,13 +46,12 @@ function AnnouncementTooltip({
|
||||
className={classNames('announcement-tooltip__container', className)}
|
||||
style={{
|
||||
top: position.top,
|
||||
left: position.left + 30,
|
||||
left: position.left + 20,
|
||||
}}
|
||||
>
|
||||
<div className="announcement-tooltip__header">
|
||||
<Typography.Text className="announcement-tooltip__title">
|
||||
{title}
|
||||
</Typography.Text>
|
||||
<p className="announcement-tooltip__title">{title}</p>
|
||||
|
||||
<X
|
||||
size={18}
|
||||
onClick={closeTooltip}
|
||||
@@ -61,7 +60,13 @@ function AnnouncementTooltip({
|
||||
</div>
|
||||
<p className="announcement-tooltip__message">{message}</p>
|
||||
<div className="announcement-tooltip__footer">
|
||||
<Button onClick={closeTooltip} className="announcement-tooltip__button">
|
||||
<Button
|
||||
variant="solid"
|
||||
color="primary"
|
||||
onClick={closeTooltip}
|
||||
prefix={<Check size={16} />}
|
||||
className="announcement-tooltip__footer__button"
|
||||
>
|
||||
Okay
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -108,7 +108,8 @@ function DynamicColumnTable({
|
||||
// Update URL with new page number while preserving other params
|
||||
urlQuery.set('page', page.toString());
|
||||
|
||||
safeNavigate({ search: `?${urlQuery.toString()}` });
|
||||
const newUrl = `${window.location.pathname}?${urlQuery.toString()}`;
|
||||
safeNavigate(newUrl);
|
||||
|
||||
// Call original pagination handler if provided
|
||||
if (pagination?.onChange && !!pageSize) {
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
import getLocalStorageKey from 'api/browser/localstorage/get';
|
||||
import setLocalStorageKey from 'api/browser/localstorage/set';
|
||||
|
||||
import { DynamicColumnsKey } from './contants';
|
||||
import {
|
||||
GetNewColumnDataFunction,
|
||||
@@ -15,7 +12,7 @@ export const getVisibleColumns: GetVisibleColumnsFunction = ({
|
||||
}) => {
|
||||
let columnVisibilityData: { [key: string]: boolean };
|
||||
try {
|
||||
const storedData = getLocalStorageKey(tablesource);
|
||||
const storedData = localStorage.getItem(tablesource);
|
||||
if (typeof storedData === 'string' && dynamicColumns) {
|
||||
columnVisibilityData = JSON.parse(storedData);
|
||||
return dynamicColumns.filter((column) => {
|
||||
@@ -31,7 +28,7 @@ export const getVisibleColumns: GetVisibleColumnsFunction = ({
|
||||
initialColumnVisibility[key] = false;
|
||||
});
|
||||
|
||||
setLocalStorageKey(tablesource, JSON.stringify(initialColumnVisibility));
|
||||
localStorage.setItem(tablesource, JSON.stringify(initialColumnVisibility));
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
@@ -45,14 +42,14 @@ export const setVisibleColumns = ({
|
||||
dynamicColumns,
|
||||
}: SetVisibleColumnsProps): void => {
|
||||
try {
|
||||
const storedData = getLocalStorageKey(tablesource);
|
||||
const storedData = localStorage.getItem(tablesource);
|
||||
if (typeof storedData === 'string' && dynamicColumns) {
|
||||
const columnVisibilityData = JSON.parse(storedData);
|
||||
const { key } = dynamicColumns[index];
|
||||
if (key) {
|
||||
columnVisibilityData[key] = checked;
|
||||
}
|
||||
setLocalStorageKey(tablesource, JSON.stringify(columnVisibilityData));
|
||||
localStorage.setItem(tablesource, JSON.stringify(columnVisibilityData));
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
}
|
||||
|
||||
&__label {
|
||||
font-size: var(--font-size-sm);
|
||||
font-size: var(--font-size-xs);
|
||||
font-weight: var(--font-weight-normal);
|
||||
color: var(--foreground);
|
||||
line-height: var(--line-height-20);
|
||||
@@ -142,6 +142,10 @@
|
||||
gap: var(--spacing-2);
|
||||
}
|
||||
|
||||
&__callout-wrapper {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
&__expiry-label {
|
||||
font-size: var(--font-size-xs);
|
||||
font-weight: var(--font-weight-medium);
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
import { Button } from '@signozhq/button';
|
||||
import { Callout } from '@signozhq/callout';
|
||||
import { Check, Copy } from '@signozhq/icons';
|
||||
import { Badge } from '@signozhq/ui';
|
||||
import { Badge, Button, Callout } from '@signozhq/ui';
|
||||
import type { ServiceaccounttypesGettableFactorAPIKeyWithKeyDTO } from 'api/generated/services/sigNoz.schemas';
|
||||
|
||||
export interface KeyCreatedPhaseProps {
|
||||
@@ -40,11 +38,13 @@ function KeyCreatedPhase({
|
||||
<Badge color="vanilla">{expiryLabel}</Badge>
|
||||
</div>
|
||||
|
||||
<Callout
|
||||
type="info"
|
||||
showIcon
|
||||
message="Store the key securely. This is the only time it will be displayed."
|
||||
/>
|
||||
<div className="add-key-modal__callout-wrapper">
|
||||
<Callout
|
||||
type="info"
|
||||
showIcon
|
||||
title="Store the key securely. This is the only time it will be displayed."
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import type { Control, UseFormRegister } from 'react-hook-form';
|
||||
import { Controller } from 'react-hook-form';
|
||||
import { Button } from '@signozhq/button';
|
||||
import { Input } from '@signozhq/input';
|
||||
import { ToggleGroup, ToggleGroupItem } from '@signozhq/toggle-group';
|
||||
import { Button, Input, ToggleGroup, ToggleGroupItem } from '@signozhq/ui';
|
||||
import { DatePicker } from 'antd';
|
||||
import { popupContainer } from 'utils/selectPopupContainer';
|
||||
|
||||
@@ -56,11 +54,12 @@ function KeyFormPhase({
|
||||
<ToggleGroup
|
||||
type="single"
|
||||
value={field.value}
|
||||
onValueChange={(val): void => {
|
||||
onChange={(val): void => {
|
||||
if (val) {
|
||||
field.onChange(val);
|
||||
}
|
||||
}}
|
||||
size="sm"
|
||||
className="add-key-modal__expiry-toggle"
|
||||
>
|
||||
<ToggleGroupItem
|
||||
@@ -112,6 +111,7 @@ function KeyFormPhase({
|
||||
</Button>
|
||||
<Button
|
||||
type="submit"
|
||||
// @ts-expect-error -- form prop not in @signozhq/ui Button type - TODO: Fix this - @SagarRajput
|
||||
form={FORM_ID}
|
||||
variant="solid"
|
||||
color="primary"
|
||||
|
||||
@@ -2,8 +2,7 @@ import { useCallback, useEffect, useState } from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { useQueryClient } from 'react-query';
|
||||
import { useCopyToClipboard } from 'react-use';
|
||||
import { DialogWrapper } from '@signozhq/dialog';
|
||||
import { toast } from '@signozhq/ui';
|
||||
import { DialogWrapper, toast } from '@signozhq/ui';
|
||||
import { convertToApiError } from 'api/ErrorResponseHandlerForGeneratedAPIs';
|
||||
import {
|
||||
invalidateListServiceAccountKeys,
|
||||
@@ -118,12 +117,12 @@ function AddKeyModal(): JSX.Element {
|
||||
copyToClipboard(createdKey.key);
|
||||
setHasCopied(true);
|
||||
setTimeout(() => setHasCopied(false), 2000);
|
||||
toast.success('Key copied to clipboard', { richColors: true });
|
||||
toast.success('Key copied to clipboard');
|
||||
}, [copyToClipboard, createdKey?.key]);
|
||||
|
||||
useEffect(() => {
|
||||
if (copyState.error) {
|
||||
toast.error('Failed to copy key', { richColors: true });
|
||||
toast.error('Failed to copy key');
|
||||
}
|
||||
}, [copyState.error]);
|
||||
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import { useQueryClient } from 'react-query';
|
||||
import { Button } from '@signozhq/button';
|
||||
import { DialogFooter, DialogWrapper } from '@signozhq/dialog';
|
||||
import { Trash2, X } from '@signozhq/icons';
|
||||
import { toast } from '@signozhq/ui';
|
||||
import { Button, DialogWrapper, toast } from '@signozhq/ui';
|
||||
import { convertToApiError } from 'api/ErrorResponseHandlerForGeneratedAPIs';
|
||||
import {
|
||||
getGetServiceAccountQueryKey,
|
||||
@@ -42,7 +40,7 @@ function DeleteAccountModal(): JSX.Element {
|
||||
} = useDeleteServiceAccount({
|
||||
mutation: {
|
||||
onSuccess: async () => {
|
||||
toast.success('Service account deleted', { richColors: true });
|
||||
toast.success('Service account deleted');
|
||||
await setIsDeleteOpen(null);
|
||||
await setAccountId(null);
|
||||
await invalidateListServiceAccounts(queryClient);
|
||||
@@ -70,6 +68,32 @@ function DeleteAccountModal(): JSX.Element {
|
||||
setIsDeleteOpen(null);
|
||||
}
|
||||
|
||||
const content = (
|
||||
<p className="sa-delete-dialog__body">
|
||||
Are you sure you want to delete <strong>{accountName}</strong>? This action
|
||||
cannot be undone. All keys associated with this service account will be
|
||||
permanently removed.
|
||||
</p>
|
||||
);
|
||||
|
||||
const footer = (
|
||||
<div className="sa-delete-dialog__footer">
|
||||
<Button variant="solid" color="secondary" onClick={handleCancel}>
|
||||
<X size={12} />
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
variant="solid"
|
||||
color="destructive"
|
||||
loading={isDeleting}
|
||||
onClick={handleConfirm}
|
||||
>
|
||||
<Trash2 size={12} />
|
||||
Delete
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<DialogWrapper
|
||||
open={open}
|
||||
@@ -83,28 +107,9 @@ function DeleteAccountModal(): JSX.Element {
|
||||
className="alert-dialog sa-delete-dialog"
|
||||
showCloseButton={false}
|
||||
disableOutsideClick={isErrorModalVisible}
|
||||
footer={footer}
|
||||
>
|
||||
<p className="sa-delete-dialog__body">
|
||||
Are you sure you want to delete <strong>{accountName}</strong>? This action
|
||||
cannot be undone. All keys associated with this service account will be
|
||||
permanently removed.
|
||||
</p>
|
||||
<DialogFooter className="sa-delete-dialog__footer">
|
||||
<Button variant="solid" color="secondary" size="sm" onClick={handleCancel}>
|
||||
<X size={12} />
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
variant="solid"
|
||||
color="destructive"
|
||||
size="sm"
|
||||
loading={isDeleting}
|
||||
onClick={handleConfirm}
|
||||
>
|
||||
<Trash2 size={12} />
|
||||
Delete
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
{content}
|
||||
</DialogWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
import type { Control, UseFormRegister } from 'react-hook-form';
|
||||
import { Controller } from 'react-hook-form';
|
||||
import { Button } from '@signozhq/button';
|
||||
import { LockKeyhole, Trash2, X } from '@signozhq/icons';
|
||||
import { Input } from '@signozhq/input';
|
||||
import { ToggleGroup, ToggleGroupItem } from '@signozhq/toggle-group';
|
||||
import { Badge } from '@signozhq/ui';
|
||||
import {
|
||||
Badge,
|
||||
Button,
|
||||
Input,
|
||||
ToggleGroup,
|
||||
ToggleGroupItem,
|
||||
} from '@signozhq/ui';
|
||||
import { DatePicker } from 'antd';
|
||||
import type { ServiceaccounttypesGettableFactorAPIKeyDTO } from 'api/generated/services/sigNoz.schemas';
|
||||
import { popupContainer } from 'utils/selectPopupContainer';
|
||||
@@ -72,11 +75,12 @@ function EditKeyForm({
|
||||
<ToggleGroup
|
||||
type="single"
|
||||
value={field.value}
|
||||
onValueChange={(val): void => {
|
||||
onChange={(val): void => {
|
||||
if (val) {
|
||||
field.onChange(val);
|
||||
}
|
||||
}}
|
||||
size="sm"
|
||||
className="edit-key-modal__expiry-toggle"
|
||||
>
|
||||
<ToggleGroupItem
|
||||
@@ -132,25 +136,21 @@ function EditKeyForm({
|
||||
</form>
|
||||
|
||||
<div className="edit-key-modal__footer">
|
||||
<Button
|
||||
type="button"
|
||||
className="edit-key-modal__footer-danger"
|
||||
onClick={onRevokeClick}
|
||||
>
|
||||
<Button variant="ghost" color="destructive" onClick={onRevokeClick}>
|
||||
<Trash2 size={12} />
|
||||
Revoke Key
|
||||
</Button>
|
||||
<div className="edit-key-modal__footer-right">
|
||||
<Button variant="solid" color="secondary" size="sm" onClick={onClose}>
|
||||
<Button variant="solid" color="secondary" onClick={onClose}>
|
||||
<X size={12} />
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
type="submit"
|
||||
// @ts-expect-error -- form prop not in @signozhq/ui Button type - TODO: Fix this - @SagarRajput
|
||||
form={FORM_ID}
|
||||
variant="solid"
|
||||
color="primary"
|
||||
size="sm"
|
||||
loading={isSaving}
|
||||
disabled={!isDirty}
|
||||
>
|
||||
|
||||
@@ -138,7 +138,7 @@
|
||||
&__meta {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-2);
|
||||
gap: var(--spacing-4);
|
||||
}
|
||||
|
||||
&__meta-label {
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { useQueryClient } from 'react-query';
|
||||
import { DialogWrapper } from '@signozhq/dialog';
|
||||
import { toast } from '@signozhq/ui';
|
||||
import { DialogWrapper, toast } from '@signozhq/ui';
|
||||
import { convertToApiError } from 'api/ErrorResponseHandlerForGeneratedAPIs';
|
||||
import {
|
||||
invalidateListServiceAccountKeys,
|
||||
@@ -72,7 +71,7 @@ function EditKeyModal({ keyItem }: EditKeyModalProps): JSX.Element {
|
||||
const { mutate: updateKey, isLoading: isSaving } = useUpdateServiceAccountKey({
|
||||
mutation: {
|
||||
onSuccess: async () => {
|
||||
toast.success('Key updated successfully', { richColors: true });
|
||||
toast.success('Key updated successfully');
|
||||
await setEditKeyId(null);
|
||||
if (selectedAccountId) {
|
||||
await invalidateListServiceAccountKeys(queryClient, {
|
||||
@@ -96,7 +95,7 @@ function EditKeyModal({ keyItem }: EditKeyModalProps): JSX.Element {
|
||||
} = useRevokeServiceAccountKey({
|
||||
mutation: {
|
||||
onSuccess: async () => {
|
||||
toast.success('Key revoked successfully', { richColors: true });
|
||||
toast.success('Key revoked successfully');
|
||||
setIsRevokeConfirmOpen(false);
|
||||
await setEditKeyId(null);
|
||||
if (selectedAccountId) {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user