Compare commits

...

3 Commits

Author SHA1 Message Date
Yunus M
feea9e9b36 refactor: remove light mode styles from various components and update… (#11080)
Some checks are pending
build-staging / prepare (push) Waiting to run
build-staging / js-build (push) Blocked by required conditions
build-staging / go-build (push) Blocked by required conditions
build-staging / staging (push) Blocked by required conditions
Release Drafter / update_release_draft (push) Waiting to run
* refactor: remove light mode styles from various components and update color variables

* fix: remove hardcoded background in celery
2026-04-25 06:57:25 +00:00
Ashwin Bhatkal
e94767bda8 refactor(frontend): remove xstate and migrate to plain React state (#11059)
* refactor(frontend): remove xstate and migrate to plain React state

Replace xstate state machines with useState-based step tracking in the
three remaining consumers (labels form, dashboard search filter,
resource attribute provider). Drops xstate and @xstate/react from
dependencies and removes the corresponding no-restricted-imports
entries from oxlint config.

* refactor: clear list of dashboard

* chore: resolve comments
2026-04-25 05:11:39 +00:00
Vishal Sharma
bb10f51cc5 feat(settings): add SigNoz MCP Server setup page (#11025)
Some checks failed
build-staging / prepare (push) Has been cancelled
build-staging / js-build (push) Has been cancelled
build-staging / go-build (push) Has been cancelled
build-staging / staging (push) Has been cancelled
Release Drafter / update_release_draft (push) Has been cancelled
* feat(settings): add SigNoz MCP Server setup page

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

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

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

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

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

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

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

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

* feat(mcp-page): added test cases

* feat(mcp-page): formatting lint

* feat(mcp-page): code refactor

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

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

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

---------

Co-authored-by: SagarRajput-7 <sagar@signoz.io>
Co-authored-by: SagarRajput-7 <162284829+SagarRajput-7@users.noreply.github.com>
2026-04-24 21:25:00 +00:00
157 changed files with 1460 additions and 5001 deletions

View File

@@ -313,14 +313,6 @@
"name": "react-redux",
"message": "[State mgmt] react-redux is deprecated. Migrate to Zustand, nuqs, or react-query."
},
{
"name": "xstate",
"message": "[State mgmt] xstate is deprecated. Migrate to Zustand or react-query."
},
{
"name": "@xstate/react",
"message": "[State mgmt] @xstate/react is deprecated. Migrate to Zustand or react-query."
},
{
"name": "react",
"importNames": [

View File

@@ -63,7 +63,6 @@
"@visx/shape": "3.5.0",
"@visx/tooltip": "3.3.0",
"@vitejs/plugin-react": "5.1.4",
"@xstate/react": "^3.0.0",
"ansi-to-html": "0.7.2",
"antd": "5.11.0",
"antd-table-saveas-excel": "2.2.1",
@@ -146,7 +145,6 @@
"vite": "npm:rolldown-vite@7.3.1",
"vite-plugin-html": "3.2.2",
"web-vitals": "^0.2.4",
"xstate": "^4.31.0",
"zod": "4.3.6",
"zustand": "5.0.11"
},

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -74,7 +74,7 @@
width: 100%;
height: 100%;
background: radial-gradient(circle, #fff 10%, transparent 0);
background: radial-gradient(circle, var(--l1-foreground) 10%, transparent 0);
background-size: 12px 12px;
opacity: 1;

View File

@@ -99,36 +99,6 @@
}
}
.lightMode {
.auth-error-container {
.error-content {
&__error-code {
color: var(--l2-foreground);
}
&__error-message {
color: var(--l1-foreground);
}
&__message-badge-label-text {
color: var(--l2-foreground);
}
&__message-item {
color: var(--l1-foreground);
&::before {
background: var(--l3-background);
}
}
&__scroll-hint-text {
color: var(--l2-foreground);
}
}
}
}
@keyframes horizontal-shaking {
0% {
transform: translateX(0);

View File

@@ -87,23 +87,3 @@
background: var(--l3-background);
flex-shrink: 0;
}
.lightMode {
.auth-footer-content {
box-shadow: 0px 1px 4px rgba(0, 0, 0, 0.08);
}
.auth-footer-icon {
filter: brightness(0) saturate(100%) invert(25%) sepia(8%) saturate(518%)
hue-rotate(192deg) brightness(80%) contrast(95%);
opacity: 0.9;
}
.auth-footer-text {
color: var(--text-neutral-light-200);
}
.auth-footer-link-icon {
color: var(--text-neutral-light-100);
}
}

View File

@@ -143,28 +143,3 @@
}
}
}
.lightMode {
.bg-dot-pattern {
background: radial-gradient(
circle,
var(--l3-background) 1px,
transparent 1px
);
background-size: 12px 12px;
}
.auth-page-gradient {
background: radial-gradient(
ellipse at center top,
color-mix(in srgb, var(--primary-background) 12%, transparent) 0%,
transparent 60%
);
opacity: 0.8;
filter: blur(200px);
@media (min-width: 768px) {
filter: blur(300px);
}
}
}

View File

@@ -41,7 +41,6 @@
justify-content: center;
align-items: center;
border: 1px solid var(--l1-border);
background: linear-gradient(0deg, transparent 0%, transparent 100%), #0b0c0e;
.ant-card-body {
height: 100%;
@@ -238,18 +237,7 @@
height: 2px;
bottom: 0;
left: 0;
background-color: var(--l1-foreground);
}
}
.lightMode {
.celery-task-graph-grid-container {
.celery-task-graph-worker-count {
background: unset;
}
}
.configure-option-Info {
border: 1px dashed var(--bg-robin-400);
background-color: var(--l1-background);
color: var(--l1-foreground);
}
}

View File

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

View File

@@ -77,11 +77,11 @@
width: 280px;
&::placeholder {
color: white;
color: var(--l1-foreground);
}
&:focus::placeholder {
color: rgba($color: #ffffff, $alpha: 0.4);
color: rgba($color: var(--l1-foreground), $alpha: 0.4);
}
}
}
@@ -113,42 +113,6 @@
}
}
.lightMode {
.time-options-container {
.time-options-item {
&.active {
background-color: rgba($color: #ffffff, $alpha: 0.2);
&:hover {
cursor: pointer;
background-color: rgba($color: #ffffff, $alpha: 0.3);
}
}
&:hover {
cursor: pointer;
background-color: rgba($color: #ffffff, $alpha: 0.3);
}
}
}
.timeSelection-input {
display: flex;
gap: 8px;
align-items: center;
padding: 4px 8px;
padding-left: 0px !important;
input::placeholder {
color: var(---bg-ink-300);
}
input:focus::placeholder {
color: rgba($color: #000000, $alpha: 0.4);
}
}
}
.date-time-popover__footer {
border-top: 1px solid var(--l1-border);
padding: 8px 14px;
@@ -300,34 +264,3 @@
background: color-mix(in srgb, var(--bg-robin-200) 8%, transparent);
}
}
.lightMode {
.timezone-container {
.timezone {
background: rgb(179 179 179 / 15%);
&__icon {
stroke: var(--l1-foreground);
}
}
}
.custom-time-picker {
.timeSelection-input {
&:hover {
border-color: var(--l1-border) !important;
}
}
}
.timezone-badge {
background: rgb(179 179 179 / 15%);
}
.time-input-suffix-icon-badge {
background: rgb(179 179 179 / 15%);
&:hover {
background: rgb(179 179 179 / 20%);
}
}
}

View File

@@ -129,20 +129,3 @@ $item-spacing: 8px;
width: 15px;
}
}
.lightMode {
.timezone-picker {
&__search {
.search-icon {
stroke: var(--l1-foreground);
}
}
}
.timezone-name-wrapper {
&__selected-icon {
.check-icon {
stroke: var(--l1-foreground);
}
}
}
}

View File

@@ -1,9 +1,5 @@
.dropdown-button {
color: #fff;
}
.dropdown-button--dark {
color: #000;
color: var(--l1-foreground);
}
.dropdown-icon {

View File

@@ -1,7 +1,6 @@
import { useState } from 'react';
import { EllipsisOutlined } from '@ant-design/icons';
import { Button, Dropdown, MenuProps } from 'antd';
import { useIsDarkMode } from 'hooks/useDarkMode';
import './DropDown.styles.scss';
@@ -12,8 +11,6 @@ function DropDown({
element: JSX.Element[];
onDropDownItemClick?: MenuProps['onClick'];
}): JSX.Element {
const isDarkMode = useIsDarkMode();
const items: MenuProps['items'] = element.map(
(e: JSX.Element, index: number) => ({
label: e,
@@ -35,7 +32,7 @@ function DropDown({
>
<Button
type="link"
className={!isDarkMode ? 'dropdown-button--dark' : 'dropdown-button'}
className={`dropdown-button`}
onClick={(e): void => {
e.preventDefault();
setDdOpen(true);

View File

@@ -196,17 +196,3 @@
opacity: 0.5;
}
}
.lightMode {
.members-table {
.ant-table-tbody {
> tr.members-table-row--tinted > td {
background: rgba(0, 0, 0, 0.015);
}
> tr:hover > td {
background: rgba(0, 0, 0, 0.03) !important;
}
}
}
}

View File

@@ -167,22 +167,3 @@
padding-right: 8px;
}
}
.lightMode {
.config-btn {
&.missing-config-btn {
background: var(--bg-amber-100);
color: var(--bg-amber-500);
&:hover {
color: var(--bg-amber-600) !important;
}
}
.missing-config-btn {
.config-btn-content {
border-right: 1px solid var(--bg-amber-600);
}
}
}
}

View File

@@ -16,7 +16,7 @@ $custom-border-color: #2c3044;
}
.ant-select-selection-placeholder {
color: color-mix(in srgb, var(--border) 45%, transparent);
color: color-mix(in srgb, var(--l2-foreground) 45%, transparent);
}
// Base styles are for dark mode
@@ -48,10 +48,6 @@ $custom-border-color: #2c3044;
visibility: visible !important;
pointer-events: none;
z-index: 2;
.lightMode & {
color: rgba(0, 0, 0, 0.85) !important;
}
}
&.ant-select-focused .ant-select-selection-placeholder {
@@ -67,10 +63,6 @@ $custom-border-color: #2c3044;
color: var(--l2-foreground);
z-index: 1;
pointer-events: none;
.lightMode & {
color: rgba(0, 0, 0, 0.85);
}
}
.ant-select-selector {
@@ -114,7 +106,7 @@ $custom-border-color: #2c3044;
}
.ant-select-selection-placeholder {
color: color-mix(in srgb, var(--border) 45%, transparent);
color: color-mix(in srgb, var(--l2-foreground) 45%, transparent);
}
// Customize tags in multiselect (dark mode by default)
@@ -217,7 +209,7 @@ $custom-border-color: #2c3044;
.empty-message {
padding: 12px;
text-align: center;
color: color-mix(in srgb, var(--border) 45%, transparent);
color: color-mix(in srgb, var(--l1-foreground) 45%, transparent);
}
}
@@ -575,7 +567,7 @@ $custom-border-color: #2c3044;
.empty-message {
padding: 12px;
text-align: center;
color: color-mix(in srgb, var(--border) 45%, transparent);
color: color-mix(in srgb, var(--l1-foreground) 45%, transparent);
}
.status-message {
@@ -983,10 +975,6 @@ $custom-border-color: #2c3044;
transition:
opacity 0.2s ease,
visibility 0.2s ease;
.lightMode & {
color: rgba(0, 0, 0, 0.85);
}
}
&:focus-within .all-text {

View File

@@ -249,57 +249,6 @@
}
}
.lightMode {
.query-aggregation-container {
.aggregation-container {
.query-aggregation-select-container {
.query-aggregation-select-editor {
.cm-editor {
.cm-tooltip-autocomplete {
background: var(--l1-background) !important;
border: 1px solid var(--l1-border) !important;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1) !important;
backdrop-filter: none;
ul {
li {
&:hover,
&[aria-selected='true'] {
background: var(--l3-background) !important;
}
}
}
}
.cm-line {
::-moz-selection {
background: var(--l2-background) !important;
opacity: 0.5 !important;
}
::selection {
background: var(--l1-background) !important;
opacity: 0.5 !important;
}
.cm-function {
color: var(--primary-background) !important;
}
}
}
}
}
}
}
.query-aggregation-error-popover {
.ant-popover-inner {
background-color: var(--l1-background);
border: none;
box-shadow: 0px 4px 12px rgba(0, 0, 0, 0.1);
}
}
}
.query-aggregation-error-popover {
.ant-popover-inner {
background-color: var(--l1-border);

View File

@@ -121,44 +121,3 @@
}
}
}
.lightMode {
.qb-trace-operator {
&-arrow {
&::before {
background: repeating-linear-gradient(
to right,
var(--l3-background),
var(--l3-background) 4px,
transparent 4px,
transparent 8px
);
}
&::after {
background-color: var(--l3-background);
}
}
&.non-list-view {
&::before {
background: repeating-linear-gradient(
to bottom,
var(--l3-background),
var(--l3-background) 4px,
transparent 4px,
transparent 8px
);
}
}
&-label-with-input {
border: 1px solid var(--l1-border) !important;
background: var(--l1-background) !important;
.label {
color: var(--l1-foreground) !important;
border-right: 1px solid var(--l1-border) !important;
background: var(--l1-background) !important;
}
}
}
}

View File

@@ -152,7 +152,7 @@
width: 100%;
height: 100%;
background: radial-gradient(circle, #fff 10%, transparent 0);
background: radial-gradient(circle, var(--l1-foreground) 10%, transparent 0);
background-size: 12px 12px;
opacity: 1;

View File

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

View File

@@ -197,17 +197,3 @@
background-color: var(--l1-border);
}
}
.lightMode {
.sa-table {
.ant-table-tbody {
> tr.sa-table-row--tinted > td {
background: rgba(0, 0, 0, 0.015);
}
> tr:hover > td {
background: rgba(0, 0, 0, 0.03) !important;
}
}
}
}

View File

@@ -147,12 +147,12 @@
left: 50%;
width: 4px;
transform: translateX(-50%);
background: var(--l2-background);
opacity: 1;
pointer-events: none;
transition:
background 120ms ease,
width 120ms ease;
background: transparent;
}
.cursorColResize:hover .tanstackResizeHandleLine {

View File

@@ -186,29 +186,3 @@
letter-spacing: -0.06px;
}
}
.lightMode {
.warning-content {
&__warning-code {
color: var(--l2-foreground);
}
&__warning-message {
color: var(--l1-foreground);
}
&__message-item {
color: var(--l1-foreground);
}
&__message-badge {
&-label-text {
color: var(--l1-foreground);
}
.key-value-label__value {
color: var(--l1-foreground);
}
}
&__docs-button {
background: var(--l1-background);
color: var(--l2-foreground);
}
}
}

View File

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

View File

@@ -157,9 +157,3 @@
.view-all-drawer {
border-radius: 4px;
}
.lightMode {
.ant-table {
background: inherit;
}
}

View File

@@ -226,54 +226,3 @@
}
}
}
.lightMode {
.api-quick-filter-left-section {
.api-quick-filters-header {
border-bottom: 1px solid var(--bg-vanilla-300);
}
}
.api-module-right-section {
.toolbar {
border-bottom: 1px solid var(--bg-vanilla-300);
}
}
.no-filtered-domains-message-container {
.no-filtered-domains-message-content {
.no-filtered-domains-message {
.no-domain-title {
color: var(--l1-foreground);
}
.no-domain-subtitle {
color: var(--l2-foreground);
.attribute {
font-family: 'Space Mono';
}
}
}
}
}
.api-monitoring-domain-list-table {
.ant-table {
.ant-table-cell {
color: var(--l1-foreground);
}
.table-row-light {
background: none;
}
.table-row-dark {
background: none;
}
.round-metric-tag {
color: var(--l1-foreground);
}
}
}
}

View File

@@ -28,7 +28,7 @@
}
.dashboard-title {
color: #fff;
color: var(--l1-foreground);
font-family: Inter;
font-size: 16px;
font-style: normal;
@@ -463,135 +463,3 @@
}
}
}
.lightMode {
.dashboard-description-container {
color: var(--l1-foreground);
.dashboard-details {
.left-section {
.dashboard-title {
color: var(--l1-foreground);
}
}
.right-section {
.icons {
border: 1px solid var(--l1-border);
background: var(--l1-background);
color: var(--l1-foreground);
}
.configure-button {
border: 1px solid var(--l1-border);
background: var(--l1-background);
color: var(--l1-foreground);
}
}
}
.dashboard-description-section {
color: var(--l1-foreground);
}
}
.dashboard-settings {
.ant-popover-inner {
border: 1px solid var(--l1-border);
background: var(--l1-background) !important;
}
.menu-content {
display: flex;
flex-direction: column;
.section-1 {
border-bottom: 1px solid var(--l1-border);
.ant-btn {
color: var(--l1-foreground);
}
}
.section-2 {
border-bottom: 1px solid var(--l1-border);
.ant-btn {
color: var(--l1-foreground);
}
}
}
}
.rename-dashboard {
.ant-modal-content {
border: 1px solid var(--l1-border);
background: var(--l1-background);
.ant-modal-header {
background: var(--l1-background);
border-bottom: 1px solid var(--l1-border);
.ant-modal-title {
color: var(--l1-foreground);
}
}
.ant-modal-body {
.dashboard-content {
.name-text {
color: var(--l1-foreground);
}
.dashboard-name-input {
border: 1px solid var(--l1-border);
background: var(--l1-background);
}
}
}
.ant-modal-footer {
.dashboard-rename {
.cancel-btn {
background: var(--l3-background);
}
}
}
}
}
.section-naming {
.ant-modal-content {
border: 1px solid var(--l1-border);
background: var(--l1-background);
.ant-modal-header {
background: var(--l1-background);
border-bottom: 1px solid var(--l1-border);
.ant-modal-title {
color: var(--l1-foreground);
}
}
.ant-modal-body {
.section-naming-content {
.name-text {
color: var(--l1-foreground);
}
.section-name-input {
border: 1px solid var(--l1-border);
background: var(--l1-background);
}
}
}
.ant-modal-footer {
.dashboard-rename {
.cancel-btn {
background: var(--l3-background);
}
}
}
}
}
}

View File

@@ -141,58 +141,3 @@
}
}
}
.lightMode {
.overview-content {
.overview-settings {
border: 1px solid var(--l1-border);
.name-icon-input {
.dashboard-image-input {
.ant-select-selector {
border: 1px solid var(--l1-border);
background: var(--l3-background);
}
}
.dashboard-name-input {
border: 1px solid var(--l1-border);
background: var(--l3-background);
}
}
.dashboard-name {
color: var(--l1-foreground);
}
.description-text-area {
border: 1px solid var(--l1-border);
background: var(--l3-background);
}
}
.overview-settings-footer {
border-top: 1px solid var(--l1-border);
background: var(--l1-background);
.unsaved {
.unsaved-dot {
background: var(--primary-background);
}
.unsaved-changes {
color: var(--bg-robin-400);
}
}
.footer-action-btns {
.discard-btn {
color: var(--l1-foreground);
background-color: var(--l3-background);
}
.save-btn {
color: var(--l3-background);
}
}
}
}
}

View File

@@ -100,27 +100,6 @@
max-width: 500px;
}
.lightMode {
.variable-item {
.variable-name {
border: 1px solid var(--l1-border);
background: var(--l1-background);
color: var(--bg-robin-300);
}
.variable-value {
border: 1px solid var(--l1-border);
background: var(--l1-background);
color: var(--l1-foreground);
&:hover,
&:focus-within {
outline: 1px solid var(--bg-robin-400);
}
}
}
}
.cycle-error-alert {
margin-bottom: 12px;
padding: 4px 12px;

View File

@@ -116,50 +116,3 @@
}
}
}
.lightMode {
.panel-type-selection-modal {
.ant-modal-content {
border: 1px solid var(--l1-border);
background: var(--l1-background);
.ant-modal-header {
background: var(--l1-background);
border-bottom: 1px solid var(--l1-border);
.ant-modal-title {
color: var(--l1-foreground);
}
}
.ant-modal-body {
.panel-selection {
.selected {
background: var(--l2-background);
}
.ant-card {
border: 1px solid var(--l1-border);
.ant-card-body {
.ant-typography {
color: var(--l2-foreground);
}
}
}
}
}
.ant-modal-footer {
border-top: 1px solid var(--l1-border);
background: var(--l1-background);
.ant-btn {
color: var(--l1-foreground);
background: var(--primary-background);
}
}
}
}
}

View File

@@ -61,11 +61,3 @@
width: 14px;
}
}
.lightMode {
.dashboard-breadcrumbs {
.dashboard-btn {
color: var(--l1-foreground);
}
}
}

View File

@@ -57,32 +57,3 @@
}
}
}
.lightMode {
.download-logs-popover {
.ant-popover-inner {
border: 1px solid var(--l1-border);
background: linear-gradient(
139deg,
color-mix(in srgb, var(--card) 80%, transparent) 0%,
color-mix(in srgb, var(--card) 90%, transparent) 98.68%
);
box-shadow: 4px 10px 16px 2px rgba(255, 255, 255, 0.2);
.download-logs-content {
.action-btns {
color: var(--l1-foreground);
}
.action-btns:hover {
&.ant-btn-text {
background-color: var(--l3-background) !important;
}
}
.export-heading {
color: var(--l2-foreground);
}
}
}
}
}

View File

@@ -119,41 +119,6 @@
}
}
.lightMode {
.main-container {
.plot-tag {
background: var(--l3-background);
}
}
.ant-modal-content {
background-color: var(--l1-foreground);
.ant-modal-confirm-title {
color: var(--l1-foreground);
}
.ant-modal-confirm-content {
.ant-typography {
color: var(--l1-foreground);
}
}
.ant-modal-confirm-btns {
button:nth-of-type(1) {
background-color: var(--l3-background);
border: none;
color: var(--l1-foreground);
}
}
}
.info-help-btns {
.doc-redirection-btn {
color: var(--bg-aqua-600) !important;
border-color: var(--bg-aqua-600) !important;
}
}
}
.create-notification-btn {
box-shadow: none;
}

View File

@@ -1,50 +0,0 @@
// eslint-disable-next-line no-restricted-imports
import { createMachine } from 'xstate';
export const ResourceAttributesFilterMachine =
/** @xstate-layout N4IgpgJg5mDOIC5QBECGsAWAjA9qgThAAQDKYBAxhkQIIB2xAYgJYA2ALmPgHQAqqUANJgAngGIAcgFEAGr0SgADjljN2zHHQUgAHogAcAFgAM3AOz6ATAEYAzJdsA2Y4cOWAnABoQIxAFpDR2tuQ319AFYTcKdbFycAX3jvNExcAmIySmp6JjZOHn4hUTFNACFWAFd8bWVVdU1tPQQzY1MXY2tDdzNHM3dHd0NvXwR7biMTa313S0i+63DE5PRsPEJScnwqWgYiFg4uPgFhcQAlKRIpeSQQWrUNLRumx3Czbg8TR0sbS31jfUcw38fW47gBHmm4XCVms3SWIBSq3SGyyO1yBx4AHlFFxUOwcPhJLJrkoVPcGk9ENYFuF3i5YR0wtEHECEAEgiEmV8zH1DLYzHZ4Yi0utMltsrt9vluNjcfjCWVKtUbnd6o9QE1rMYBtxbGFvsZ3NrZj1WdYOfotUZLX0XEFHEKViKMpttjk9nlDrL8HiCWJzpcSbcyWrGoh3NCQj0zK53P1ph1WeFLLqnJZ2s5vmZLA6kginWsXaj3VLDoUAGqoSpgEp0cpVGohh5hhDWDy0sz8zruakzamWVm-Qyg362V5-AZOayO1KFlHitEejFHKCV6v+i5XRt1ZuU1s52zjNOOaZfdOWIY+RDZ0Hc6ZmKEXqyLPPCudit2Sz08ACSEFYNbSHI27kuquiIOEjiONwjJgrM3RWJYZisgEIJgnYPTmuEdi2OaiR5nQOAQHA2hvsiH4Sui0qFCcIGhnuLSmP0YJuJ2xjJsmKELG8XZTK0tjdHG06vgW5GupRS7St6vrKqSO4UhqVL8TBWp8o4eqdl0A5Xmy3G6gK56-B4uERDOSKiuJi6lgUAhrhUYB0buimtrEKZBDYrxaS0OZca8+ltheybOI4hivGZzrzp+VGHH+AGOQp4EIHy+ghNYnawtG4TsbYvk8QKfHGAJfQ9uF76WSW37xWBTSGJ0qXpd0vRZdEKGPqC2YeO2-zfO4+HxEAA */
createMachine({
tsTypes: {} as import('./Labels.machine.typegen').Typegen0,
initial: 'Idle',
states: {
LabelKey: {
on: {
NEXT: {
actions: 'onSelectLabelValue',
target: 'LabelValue',
},
onBlur: {
actions: 'onSelectLabelValue',
target: 'LabelValue',
},
RESET: {
target: 'Idle',
},
},
},
LabelValue: {
on: {
NEXT: {
actions: ['onValidateQuery'],
},
onBlur: {
actions: ['onValidateQuery'],
// target: 'Idle',
},
RESET: {
target: 'Idle',
},
},
},
Idle: {
on: {
NEXT: {
actions: 'onSelectLabelKey',
description: 'Enter a label key',
target: 'LabelKey',
},
},
},
},
id: 'Label Key Values',
});

View File

@@ -1,25 +0,0 @@
// This file was automatically generated. Edits will be overwritten
export interface Typegen0 {
'@@xstate/typegen': true;
eventsCausingActions: {
onSelectLabelValue: 'NEXT' | 'onBlur';
onValidateQuery: 'NEXT' | 'onBlur';
onSelectLabelKey: 'NEXT';
};
internalEvents: {
'xstate.init': { type: 'xstate.init' };
};
invokeSrcNameMap: {};
missingImplementations: {
actions: 'onSelectLabelValue' | 'onValidateQuery' | 'onSelectLabelKey';
services: never;
guards: never;
delays: never;
};
eventsCausingServices: {};
eventsCausingGuards: {};
eventsCausingDelays: {};
matchesStates: 'LabelKey' | 'LabelValue' | 'Idle';
tags: never;
}

View File

@@ -4,20 +4,20 @@ import {
CloseCircleFilled,
ExclamationCircleOutlined,
} from '@ant-design/icons';
// eslint-disable-next-line no-restricted-imports
import { useMachine } from '@xstate/react';
import { Button, Input, message, Modal } from 'antd';
import { useIsDarkMode } from 'hooks/useDarkMode';
import { map } from 'lodash-es';
import { Labels } from 'types/api/alerts/def';
import { v4 as uuid } from 'uuid';
import { ResourceAttributesFilterMachine } from './Labels.machine';
import QueryChip from './QueryChip';
import { QueryChipItem, SearchContainer } from './styles';
import { ILabelRecord } from './types';
import { createQuery, flattenLabels, prepareLabels } from './utils';
type LabelStep = 'Idle' | 'LabelKey' | 'LabelValue';
type LabelEvent = 'NEXT' | 'onBlur' | 'RESET';
interface LabelSelectProps {
onSetLabels: (q: Labels) => void;
initialValues: Labels | undefined;
@@ -35,42 +35,65 @@ function LabelSelect({
const [queries, setQueries] = useState<ILabelRecord[]>(
initialValues ? flattenLabels(initialValues) : [],
);
const [step, setStep] = useState<LabelStep>('Idle');
const dispatchChanges = (updatedRecs: ILabelRecord[]): void => {
onSetLabels(prepareLabels(updatedRecs, initialValues));
setQueries(updatedRecs);
};
const [state, send] = useMachine(ResourceAttributesFilterMachine, {
actions: {
onSelectLabelKey: () => {},
onSelectLabelValue: () => {
if (currentVal !== '') {
setStaging((prevState) => [...prevState, currentVal]);
} else {
return;
}
setCurrentVal('');
},
onValidateQuery: (): void => {
if (currentVal === '') {
return;
}
const onSelectLabelValue = (): void => {
if (currentVal !== '') {
setStaging((prevState) => [...prevState, currentVal]);
} else {
return;
}
setCurrentVal('');
};
const generatedQuery = createQuery([...staging, currentVal]);
const onValidateQuery = (): void => {
if (currentVal === '') {
return;
}
if (generatedQuery) {
dispatchChanges([...queries, generatedQuery]);
setStaging([]);
setCurrentVal('');
send('RESET');
}
},
},
});
const generatedQuery = createQuery([...staging, currentVal]);
if (generatedQuery) {
dispatchChanges([...queries, generatedQuery]);
setStaging([]);
setCurrentVal('');
setStep('Idle');
}
};
const send = (event: LabelEvent): void => {
if (event === 'RESET') {
setStep('Idle');
return;
}
if (event === 'NEXT') {
if (step === 'Idle') {
setStep('LabelKey');
} else if (step === 'LabelKey') {
onSelectLabelValue();
setStep('LabelValue');
} else if (step === 'LabelValue') {
onValidateQuery();
}
return;
}
if (event === 'onBlur') {
if (step === 'LabelKey') {
onSelectLabelValue();
setStep('LabelValue');
} else if (step === 'LabelValue') {
onValidateQuery();
}
}
};
const handleFocus = (): void => {
if (state.value === 'Idle') {
if (step === 'Idle') {
send('NEXT');
}
};
@@ -79,7 +102,7 @@ function LabelSelect({
if (staging.length === 1 && staging[0] !== undefined) {
send('onBlur');
}
}, [send, staging]);
}, [staging]);
useEffect(() => {
handleBlur();
@@ -115,14 +138,14 @@ function LabelSelect({
});
};
const renderPlaceholder = useCallback((): string => {
if (state.value === 'LabelKey') {
if (step === 'LabelKey') {
return 'Enter a label key then press ENTER.';
}
if (state.value === 'LabelValue') {
if (step === 'LabelValue') {
return `Enter a value for label key(${staging[0]}) then press ENTER.`;
}
return t('placeholder_label_key_pair');
}, [t, state, staging]);
}, [t, step, staging]);
return (
<SearchContainer isDarkMode={isDarkMode} disabled={false}>
<div style={{ display: 'inline-flex', flexWrap: 'wrap' }}>
@@ -148,7 +171,7 @@ function LabelSelect({
if (e.key === 'Enter' || e.code === 'Enter' || e.key === ':') {
send('NEXT');
}
if (state.value === 'Idle') {
if (step === 'Idle') {
send('NEXT');
}
}}
@@ -159,7 +182,7 @@ function LabelSelect({
onBlur={handleBlur}
/>
{queries.length || staging.length || currentVal ? (
{queries.length > 0 || staging.length > 0 || currentVal ? (
<Button
onClick={handleClearAll}
icon={<CloseCircleFilled />}

View File

@@ -174,23 +174,3 @@
}
}
}
.lightMode {
.dashboard-empty-state {
.dashboard-content {
.heading {
.icons {
color: var(--l1-foreground);
}
.welcome {
color: var(--l1-foreground);
}
.welcome-info {
color: var(--l1-foreground);
}
}
}
}
}

View File

@@ -333,100 +333,3 @@
}
}
}
.lightMode {
.fullscreen-grid-container {
.react-grid-layout {
.row-panel {
background: var(--l2-background);
.settings-icon {
color: var(--l1-foreground);
}
.row-icon {
color: var(--l1-foreground);
}
.section-title {
color: var(--l1-foreground);
}
}
}
}
.widget-full-view {
.ant-modal-content {
background-color: var(--l1-foreground);
.ant-modal-header {
background-color: var(--l1-foreground);
}
}
}
.row-settings {
.ant-popover-inner {
border: 1px solid var(--l1-border);
background: var(--l1-background);
.menu-content {
.section-1 {
.rename-btn {
color: var(--l1-foreground);
}
}
.section-2 {
border-top: 1px solid var(--l1-border);
.remove-section {
color: var(--bg-cherry-400);
}
}
}
}
}
.rename-section {
.ant-modal-content {
border: 1px solid var(--l1-border);
background: var(--l1-background);
.ant-modal-header {
background: var(--l1-background);
border-bottom: 1px solid var(--l1-border);
.ant-modal-title {
color: var(--l1-foreground);
}
}
.ant-modal-body {
.typography {
color: var(--l2-foreground);
}
.ant-form-item {
.action-btns {
.cancel-btn {
color: var(--l1-foreground);
background: var(--l3-background);
}
}
}
}
}
}
.view-onclick-show-button {
background: var(--l2-background);
border-color: var(--l1-foreground);
color: var(--l1-foreground);
.menu-item {
&:hover {
background-color: var(--l2-foreground);
}
}
}
}

View File

@@ -55,14 +55,6 @@
padding-right: 0.25rem;
}
.lightMode {
.widget-header-container {
.ant-input-group-addon {
background-color: inherit;
}
}
}
.long-tooltip {
.ant-tooltip-content {
max-height: 500px;

View File

@@ -482,10 +482,10 @@
border-radius: 2px;
border: 1px solid var(--l1-border);
background: var(--l2-background);
box-shadow: 0px 0px 8px 0px rgba(0, 0, 0, 0.1);
&.selected {
background: var(--l2-background);
background: var(--l3-background);
color: var(--primary);
}
}
}

View File

@@ -21,9 +21,9 @@ import { DataSource } from 'types/common/queryBuilder';
import { USER_ROLES } from 'types/roles';
import floppyDiscUrl from '@/assets/Icons/floppy-disc.svg';
import logsUrl from '@/assets/Icons/logs.svg';
import { getItemIcon } from '../constants';
import { ScrollText } from '@signozhq/icons';
export default function SavedViews({
onUpdateChecklistDoneItem,
@@ -351,7 +351,7 @@ export default function SavedViews({
className={selectedEntity === 'logs' ? 'selected tab' : 'tab'}
onClick={(): void => handleTabChange('logs')}
>
<img src={logsUrl} alt="logs-icon" className="logs-icon" />
<ScrollText size={14} />
Logs
</Button>
<Button

View File

@@ -148,50 +148,3 @@
}
}
}
.lightMode {
.entity-metric-traces-header {
.filter-section {
border-top: 1px solid var(--l1-border);
border-bottom: 1px solid var(--l1-border);
.ant-select-selector {
border-color: var(--l1-border) !important;
color: var(--l2-foreground);
}
}
}
.entity-metric-traces-table {
.ant-table {
border-radius: 3px;
border: 1px solid var(--l1-border);
.ant-table-thead > tr > th {
background: var(--l1-foreground);
color: var(--l2-foreground);
}
.ant-table-thead > tr > th:has(.entityname-column-header) {
background: var(--l1-foreground);
}
.ant-table-cell {
background: var(--l1-foreground);
color: var(--l1-foreground);
}
.ant-table-cell:has(.entityname-column-value) {
background: var(--l1-foreground);
}
.entityname-column-value {
color: var(--l1-foreground);
}
.ant-table-tbody > tr:hover > td {
background: rgba(0, 0, 0, 0.04);
}
}
}
}

View File

@@ -192,63 +192,6 @@
}
}
.lightMode {
.ant-drawer-header {
border-bottom: 1px solid var(--l1-border);
background: var(--l1-foreground);
}
.entity-detail-drawer {
.title {
color: var(--l2-foreground);
}
.entity-detail-drawer__entity {
.ant-typography {
color: var(--l2-foreground);
background: transparent;
}
}
.radio-button {
border: 1px solid var(--l1-border);
background: var(--l1-foreground);
color: var(--l2-foreground);
}
.views-tabs {
.tab {
background: var(--l1-foreground);
}
.selected_view {
background: var(--l3-background);
border: 1px solid var(--l1-border);
color: var(--l2-foreground);
}
.selected_view::before {
background: var(--l3-background);
border-left: 1px solid var(--l1-border);
}
}
.compass-button {
border: 1px solid var(--l1-border);
background: var(--l1-foreground);
box-shadow: 0px 0px 8px 0px rgba(0, 0, 0, 0.1);
}
.tabs-and-search {
.action-btn {
border: 1px solid var(--l1-border);
background: var(--l1-foreground);
color: var(--l2-foreground);
}
}
}
}
.entity-metric-traces {
margin-top: 1rem;
@@ -399,53 +342,6 @@
}
}
.lightMode {
.entity-metric-traces-header {
.filter-section {
border-top: 1px solid var(--l1-border);
border-bottom: 1px solid var(--l1-border);
.ant-select-selector {
border-color: var(--l1-border) !important;
color: var(--l2-foreground);
}
}
}
.entity-metric-traces-table {
.ant-table {
border-radius: 3px;
border: 1px solid var(--l1-border);
.ant-table-thead > tr > th {
background: var(--l1-foreground);
color: var(--l2-foreground);
}
.ant-table-thead > tr > th:has(.entityname-column-header) {
background: var(--l1-foreground);
}
.ant-table-cell {
background: var(--l1-foreground);
color: var(--l1-foreground);
}
.ant-table-cell:has(.entityname-column-value) {
background: var(--l1-foreground);
}
.entityname-column-value {
color: var(--l3-background);
}
.ant-table-tbody > tr:hover > td {
background: rgba(0, 0, 0, 0.04);
}
}
}
}
.entity-metrics-logs-container {
margin-top: 1rem;

View File

@@ -773,206 +773,6 @@
box-shadow: 0px -4px 16px 2px rgba(0, 0, 0, 0.2);
}
.lightMode {
.ingestion-key-container {
.ingestion-key-content {
.title {
color: var(--l1-foreground);
}
.ant-table-row {
.ant-table-cell {
background: var(--l1-background);
}
&:hover {
.ant-table-cell {
background: var(--l1-background) !important;
}
}
.column-render {
border: 1px solid var(--l1-border);
background: var(--l1-background);
.ant-collapse {
border: none;
.ant-collapse-header {
background: var(--l1-background);
}
.ant-collapse-content {
border-top: 1px solid var(--l1-border);
}
}
.title-with-action {
.ingestion-key-title {
.ant-typography {
color: var(--l1-foreground);
}
}
.ingestion-key-value {
background: var(--l3-background);
.ant-typography {
color: var(--l2-foreground);
}
.copy-key-btn {
cursor: pointer;
}
}
.action-btn {
.ant-typography {
color: var(--l2-foreground);
}
}
}
.ingestion-key-details {
border-top: 1px solid var(--l1-border);
.ingestion-key-tag {
background: var(--l3-background);
.tag-text {
color: var(--l1-foreground);
}
}
.ingestion-key-created-by {
color: var(--l2-foreground);
}
.ingestion-key-last-used-at {
.ant-typography {
color: var(--l2-foreground);
}
}
}
}
}
}
}
.delete-ingestion-key-modal {
.ant-modal-content {
border: 1px solid var(--l1-border);
background: var(--l1-background);
.ant-modal-header {
background: var(--l1-background);
.title {
color: var(--l1-foreground);
}
}
.ant-modal-body {
.ant-typography {
color: var(--l2-foreground);
}
.ingestion-key-input {
.ant-input {
background: var(--l3-background);
color: var(--l1-foreground);
}
}
}
.ant-modal-footer {
.cancel-btn {
background: var(--l3-background);
color: var(--l1-foreground);
}
}
}
}
.ingestion-key-info-container {
.user-email {
background: var(--l3-background);
}
.limits-data {
border: 1px solid var(--l1-border);
}
}
.ingestion-key-modal {
.ant-modal-content {
border-radius: 4px;
border: 1px solid var(--l1-border);
background: var(--l1-background);
box-shadow: 0px -4px 16px 2px rgba(0, 0, 0, 0.2);
padding: 0;
.ant-modal-header {
background: none;
border-bottom: 1px solid var(--l1-border);
padding: 16px;
}
}
}
.ingestion-key-access-role {
.ant-radio-button-wrapper {
&.ant-radio-button-wrapper-checked {
color: var(--l1-foreground);
background: var(--l3-background);
border-color: var(--l1-border);
&:hover {
color: var(--l1-foreground);
background: var(--l3-background);
border-color: var(--l1-border);
&::before {
background-color: var(--l3-background);
}
}
&:focus {
color: var(--l1-foreground);
background: var(--l3-background);
border-color: var(--l1-border);
}
}
}
.tab {
border: 1px solid var(--l1-border);
&::before {
background: var(--l3-background);
}
&.selected {
background: var(--l3-background);
}
}
}
.copyable-text {
background: var(--l3-background);
}
.ingestion-key-expires-at {
border: 1px solid var(--l1-border);
background: var(--l1-background);
box-shadow: 0px -4px 16px 2px rgba(0, 0, 0, 0.2);
}
.expires-at .ant-picker {
border-color: var(--l1-border) !important;
}
}
.mt-8 {
margin-top: 8px;
}
@@ -1085,26 +885,3 @@
color: var(--l2-foreground);
}
}
.lightMode {
.ingestion-setup-details-links {
background: color-mix(in srgb, var(--primary-background) 10%, transparent);
color: var(--accent-primary);
.learn-more {
color: var(--accent-primary);
}
}
.ingestion-url-error-tooltip {
.ingestion-url-error-content {
.ingestion-url-error-code {
color: var(--bg-amber-500);
}
}
.ingestion-url-error-message {
color: var(--l1-foreground);
}
}
}

View File

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

View File

@@ -9,6 +9,8 @@
.licenses-page-header-title {
color: var(--l1-foreground);
background: var(--l1-background);
border-right: 1px solid var(--l1-border);
text-align: center;
font-family: Inter;
font-size: 13px;
@@ -54,38 +56,3 @@
}
}
}
.lightMode {
.licenses-page {
.licenses-page-header {
border-bottom: 1px solid var(--l1-border);
background: var(--card);
backdrop-filter: blur(20px);
.licenses-page-header-title {
color: var(--l1-foreground);
background: var(--l1-background);
border-right: 1px solid var(--l1-border);
}
}
.licenses-page-content-container {
.licenses-page-content {
background: var(--l1-background);
&::-webkit-scrollbar-track {
background: transparent;
}
&::-webkit-scrollbar-thumb {
background: var(--l3-background);
}
&::-webkit-scrollbar-thumb:hover {
background: var(--l3-background);
}
}
}
}
}

View File

@@ -176,15 +176,3 @@
cursor: pointer;
}
}
.lightMode {
.info-text {
color: var(--bg-robin-600) !important;
}
.info-link-container {
.anticon {
color: var(--bg-robin-400);
}
}
}

View File

@@ -1114,305 +1114,6 @@
}
}
.lightMode {
.dashboards-list-container {
.dashboards-list-view-content {
.title {
color: var(--l1-foreground);
}
.subtitle {
color: var(--muted-foreground);
}
.ant-table-row {
.ant-table-cell {
background: var(--l1-background);
}
&:hover {
.ant-table-cell {
background: var(--l1-background) !important;
}
}
.dashboard-list-item {
border: 1px solid var(--l1-border);
background: var(--card);
.dashboard-title {
color: var(--l2-foreground);
.title {
color: var(--l1-foreground);
}
}
.title-with-action {
.dashboard-title {
.ant-typography {
color: var(--l1-foreground);
}
}
.action-btn {
.ant-typography {
color: var(--l1-foreground);
}
}
}
.dashboard-details {
.dashboard-tag {
background: var(--l3-background);
.tag-text {
color: var(--l1-foreground);
}
}
.created-by {
.dashboard-tag {
background: var(--l3-background);
.tag-text {
color: var(--l1-foreground);
}
}
.dashboard-created-by {
color: var(--l2-foreground);
}
}
.updated-by {
.text {
color: var(--l2-foreground);
}
.dashboard-tag {
background: var(--l3-background);
.tag-text {
color: var(--l1-foreground);
}
}
.dashboard-created-by {
color: var(--l2-foreground);
}
}
.dashboard-created-by {
color: var(--l1-foreground);
}
.dashboard-created-at {
.ant-typography {
color: var(--l1-foreground);
}
}
}
}
}
}
.no-search {
.text {
color: var(--muted-foreground);
}
}
.all-dashboards-header {
border: 1px solid var(--l1-border);
background: var(--l2-background);
.typography {
color: var(--l2-foreground);
}
.right-actions {
color: var(--l2-foreground);
}
}
.dashboard-empty-state {
.text {
.no-dashboard {
color: var(--l2-foreground);
}
.info {
color: var(--l2-foreground);
}
}
}
.dashboard-error-state {
.error-text {
color: var(--muted-foreground);
}
.action-btns {
.retry-btn {
background: var(--l3-background);
color: var(--l1-foreground);
}
}
}
}
.delete-view-modal {
.ant-modal-content {
border: 1px solid var(--l1-border);
background: var(--card);
.ant-modal-header {
background: var(--card);
.title {
color: var(--l1-foreground);
}
}
.ant-modal-body {
.ant-typography {
color: var(--l1-foreground);
}
.save-view-input {
.ant-input {
background: var(--l2-background);
color: var(--l1-foreground);
}
}
}
.ant-modal-footer {
.cancel-btn {
background: var(--l3-background);
color: var(--l2-foreground);
}
}
}
}
.dashboard-actions {
.ant-popover-inner {
border: 1px solid var(--l1-border);
background: var(--card);
.dashboard-action-content {
.section-1 {
.action-btn {
color: var(--l2-foreground);
}
}
.section-2 {
border-top: 1px solid var(--l1-border);
}
}
}
}
.sort-dashboards {
.ant-popover-inner {
border: 1px solid var(--l1-border);
background: var(--card);
.sort-content {
.sort-heading {
color: var(--l2-foreground);
}
.sort-btns {
color: var(--l2-foreground);
}
}
}
}
.configure-group {
.ant-popover-inner {
border: 1px solid var(--l1-border);
background: var(--card);
.configure-content {
.configure-btn {
color: var(--l2-foreground);
}
}
}
}
.configure-metadata-root {
.ant-modal-content {
border: 1px solid var(--l1-border);
background: var(--card);
.ant-modal-header {
background: var(--card);
border-bottom: 1px solid var(--l1-border);
}
.ant-modal-body {
.configure-content {
.configure-preview {
border: 0.915px solid var(--l1-border);
background: var(--l2-background);
.header {
.title {
color: var(--l2-foreground);
}
}
.details {
.createdAt {
.formatted-time {
color: var(--l2-foreground);
}
.user {
.user-tag {
color: var(--l2-foreground);
background-color: var(--l3-background);
}
.dashboard-created-by {
color: var(--l2-foreground);
}
}
}
.updatedAt {
.formatted-time {
color: var(--l2-foreground);
}
.user {
.user-tag {
color: var(--l2-foreground);
background-color: var(--l3-background);
}
.dashboard-created-by {
color: var(--l2-foreground);
}
}
}
}
}
.metadata-action {
.connection-line {
border: 1px dashed var(--l1-border);
}
}
}
}
.ant-modal-footer {
.save-changes {
border: 1px solid var(--l1-border);
background: var(--l2-background);
}
}
}
}
}
.title-toolip {
.ant-tooltip-content {
.ant-tooltip-inner {

View File

@@ -194,76 +194,3 @@
border-top: 1px solid var(--l1-border);
}
}
.lightMode {
.new-dashboard-templates-modal {
.ant-modal-content {
border: 1px solid var(--l1-border);
background: var(--l1-foreground);
}
.new-dashboard-templates-content-header {
border-bottom: 1px solid var(--l1-border);
}
.new-dashboard-templates-content {
.new-dashboard-templates-list {
border-right: 1px solid var(--l1-border);
.templates-list {
.template-list-item {
.template-name {
color: var(--l3-background);
}
&:hover {
background: color-mix(in srgb, var(--bg-robin-200) 8%, transparent);
}
&.active {
background: color-mix(in srgb, var(--bg-robin-200) 8%, transparent);
}
}
}
}
.new-dashboard-template-preview {
.template-preview-header {
.template-preview-title {
.template-preview-icon {
border: 1px solid var(--l1-border);
background: var(--l1-foreground);
}
.template-info {
.template-name {
color: var(--l3-background);
}
.template-description {
color: var(--l2-foreground);
}
}
}
.create-dashboard-btn {
.ant-btn {
box-shadow: none;
}
}
}
.template-preview-image {
img {
border: 1px solid var(--l1-border);
background: var(--l1-foreground);
}
}
}
}
.ant-modal-footer {
border-top: 1px solid var(--l1-border);
}
}
}

View File

@@ -1,51 +0,0 @@
// eslint-disable-next-line no-restricted-imports
import { createMachine } from 'xstate';
export const DashboardSearchAndFilter = createMachine({
tsTypes: {} as import('./Dashboard.machine.typegen').Typegen0,
initial: 'Idle',
states: {
Category: {
on: {
NEXT: {
actions: 'onSelectOperator',
target: 'Operator',
},
onBlur: {
actions: 'onBlurPurge',
target: 'Idle',
},
},
},
Operator: {
on: {
NEXT: {
actions: 'onSelectValue',
target: 'Value',
},
onBlur: {
actions: 'onBlurPurge',
target: 'Idle',
},
},
},
Value: {
on: {
onBlur: {
actions: ['onValidateQuery', 'onBlurPurge'],
target: 'Idle',
},
},
},
Idle: {
on: {
NEXT: {
actions: 'onSelectCategory',
description: 'Select Category',
target: 'Category',
},
},
},
},
id: 'Dashboard Search And Filter',
});

View File

@@ -1,32 +0,0 @@
// This file was automatically generated. Edits will be overwritten
export interface Typegen0 {
'@@xstate/typegen': true;
eventsCausingActions: {
onSelectOperator: 'NEXT';
onBlurPurge: 'onBlur';
onSelectValue: 'NEXT';
onValidateQuery: 'onBlur';
onSelectCategory: 'NEXT';
};
internalEvents: {
'xstate.init': { type: 'xstate.init' };
};
invokeSrcNameMap: {};
missingImplementations: {
actions:
| 'onSelectOperator'
| 'onBlurPurge'
| 'onSelectValue'
| 'onValidateQuery'
| 'onSelectCategory';
services: never;
guards: never;
delays: never;
};
eventsCausingServices: {};
eventsCausingGuards: {};
eventsCausingDelays: {};
matchesStates: 'Category' | 'Operator' | 'Value' | 'Idle';
tags: never;
}

View File

@@ -1,21 +0,0 @@
import { QueryChipContainer, QueryChipItem } from './styles';
import { IQueryStructure } from './types';
export default function QueryChip({
queryData,
onRemove,
}: {
queryData: IQueryStructure;
onRemove: (id: string) => void;
}): JSX.Element {
const { category, operator, value, id } = queryData;
return (
<QueryChipContainer>
<QueryChipItem>{category}</QueryChipItem>
<QueryChipItem>{operator}</QueryChipItem>
<QueryChipItem closable onClose={(): void => onRemove(id)}>
{Array.isArray(value) ? value.join(', ') : null}
</QueryChipItem>
</QueryChipContainer>
);
}

View File

@@ -1,64 +0,0 @@
import { Dashboard } from 'types/api/dashboard/getAll';
import { v4 as uuid } from 'uuid';
import { TOperator } from '../types';
import { executeSearchQueries } from '../utils';
describe('executeSearchQueries', () => {
const firstDashboard: Dashboard = {
id: uuid(),
createdAt: '',
updatedAt: '',
createdBy: '',
updatedBy: '',
data: {
title: 'first dashboard',
variables: {},
},
};
const secondDashboard: Dashboard = {
id: uuid(),
createdAt: '',
updatedAt: '',
createdBy: '',
updatedBy: '',
data: {
title: 'second dashboard',
variables: {},
},
};
const thirdDashboard: Dashboard = {
id: uuid(),
createdAt: '',
updatedAt: '',
createdBy: '',
updatedBy: '',
data: {
title: 'third dashboard (with special characters +?\\)',
variables: {},
},
};
const dashboards = [firstDashboard, secondDashboard, thirdDashboard];
it('should filter dashboards based on title', () => {
const query = {
category: 'title',
id: 'someid',
operator: '=' as TOperator,
value: 'first dashboard',
};
expect(executeSearchQueries([query], dashboards)).toEqual([firstDashboard]);
});
it('should filter dashboards with special characters', () => {
const query = {
category: 'title',
id: 'someid',
operator: '=' as TOperator,
value: 'third dashboard (with special characters +?\\)',
};
expect(executeSearchQueries([query], dashboards)).toEqual([thirdDashboard]);
});
});

View File

@@ -1,212 +0,0 @@
import {
MutableRefObject,
useCallback,
useEffect,
useRef,
useState,
} from 'react';
import { CloseCircleFilled } from '@ant-design/icons';
// eslint-disable-next-line no-restricted-imports
import { useMachine } from '@xstate/react';
import { Button, RefSelectProps, Select } from 'antd';
import history from 'lib/history';
import { filter, map } from 'lodash-es';
import { Dashboard } from 'types/api/dashboard/getAll';
import { v4 as uuidv4 } from 'uuid';
import { DashboardSearchAndFilter } from './Dashboard.machine';
import QueryChip from './QueryChip';
import { QueryChipItem, SearchContainer } from './styles';
import { IOptionsData, IQueryStructure, TCategory, TOperator } from './types';
import {
convertQueriesToURLQuery,
convertURLQueryStringToQuery,
executeSearchQueries,
OptionsSchemas,
OptionsValueResolution,
} from './utils';
function SearchFilter({
searchData,
filterDashboards,
}: {
searchData: Dashboard[];
filterDashboards: (filteredDashboards: Dashboard[]) => void;
}): JSX.Element {
const [category, setCategory] = useState<TCategory>();
const [optionsData, setOptionsData] = useState<IOptionsData>(
OptionsSchemas.attribute,
);
const selectRef = useRef() as MutableRefObject<RefSelectProps>;
const [selectedValues, setSelectedValues] = useState<string[]>([]);
const [staging, setStaging] = useState<string[] | string[][] | unknown[]>([]);
const [queries, setQueries] = useState<IQueryStructure[]>([]);
useEffect(() => {
const searchQueryString = new URLSearchParams(history.location.search).get(
'search',
);
if (searchQueryString) {
setQueries(convertURLQueryStringToQuery(searchQueryString) || []);
}
}, []);
useEffect(() => {
filterDashboards(executeSearchQueries(queries, searchData));
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [queries, searchData]);
const updateURLWithQuery = useCallback(
(inputQueries?: IQueryStructure[]): void => {
history.push({
pathname: history.location.pathname,
search:
inputQueries || queries
? `?search=${convertQueriesToURLQuery(inputQueries || queries)}`
: '',
});
},
[queries],
);
useEffect(() => {
if (Array.isArray(queries) && queries.length > 0) {
updateURLWithQuery();
}
}, [queries, updateURLWithQuery]);
const [state, send] = useMachine(DashboardSearchAndFilter, {
actions: {
onSelectCategory: () => {
setOptionsData(OptionsSchemas.attribute);
},
onSelectOperator: () => {
setOptionsData(OptionsSchemas.operator);
},
onSelectValue: () => {
setOptionsData(
OptionsValueResolution(category as TCategory, searchData) as IOptionsData,
);
},
onBlurPurge: () => {
setSelectedValues([]);
setStaging([]);
},
onValidateQuery: () => {
if (staging.length <= 2 && selectedValues.length === 0) {
return;
}
setQueries([
...queries,
{
id: uuidv4(),
category: staging[0] as string,
operator: staging[1] as TOperator,
value: selectedValues,
},
]);
},
},
});
const nextState = (): void => {
send('NEXT');
};
const removeQueryById = (queryId: string): void => {
setQueries((queries) => {
const updatedQueries = filter(queries, ({ id }) => id !== queryId);
updateURLWithQuery(updatedQueries);
return updatedQueries;
});
};
const handleChange = (value: never | string[]): void => {
if (!value) {
return;
}
if (optionsData.mode) {
setSelectedValues(value.filter(Boolean));
return;
}
setStaging([...staging, value]);
if (state.value === 'Category') {
setCategory(`${value}`.toLowerCase() as TCategory);
}
nextState();
setSelectedValues([]);
};
const handleFocus = (): void => {
if (state.value === 'Idle') {
send('NEXT');
selectRef.current?.focus();
}
};
const handleBlur = (): void => {
send('onBlur');
selectRef?.current?.blur();
};
const clearQueries = (): void => {
setQueries([]);
history.push({
pathname: history.location.pathname,
search: ``,
});
};
return (
<SearchContainer>
<div>
{map(queries, (query) => (
<QueryChip key={query.id} queryData={query} onRemove={removeQueryById} />
))}
{map(staging, (value) => (
<QueryChipItem key={JSON.stringify(value)}>
{value as string}
</QueryChipItem>
))}
</div>
{optionsData && (
<Select
placeholder={
!queries.length &&
!staging.length &&
!selectedValues.length &&
'Search or Filter results'
}
size="small"
ref={selectRef}
mode={optionsData.mode as 'tags' | 'multiple'}
style={{ flex: 1 }}
onChange={handleChange}
bordered={false}
suffixIcon={null}
value={selectedValues}
onFocus={handleFocus}
onBlur={handleBlur}
showSearch
>
{optionsData.options &&
Array.isArray(optionsData.options) &&
optionsData.options.map(
(optionItem): JSX.Element => (
<Select.Option
key={(optionItem.value as string) || (optionItem.name as string)}
value={optionItem.value || optionItem.name}
>
{optionItem.name}
</Select.Option>
),
)}
</Select>
)}
{queries && queries.length > 0 && (
<Button icon={<CloseCircleFilled />} type="text" onClick={clearQueries} />
)}
</SearchContainer>
);
}
export default SearchFilter;

View File

@@ -1,27 +0,0 @@
import { grey } from '@ant-design/colors';
import { Tag } from 'antd';
import styled from 'styled-components';
export const SearchContainer = styled.div`
width: 100%;
display: flex;
align-items: center;
gap: 0.2rem;
padding: 0.2rem 0;
margin: 1rem 0;
border: 1px solid #ccc5;
`;
export const QueryChipContainer = styled.span`
display: flex;
align-items: center;
margin-right: 0.5rem;
&:hover {
& > * {
background: ${grey.primary}44;
}
}
`;
export const QueryChipItem = styled(Tag)`
margin-right: 0.1rem;
`;

View File

@@ -1,18 +0,0 @@
export type TOperator = '=' | '!=';
export type TCategory = 'title' | 'description' | 'tags';
export interface IQueryStructure {
category: string;
id: string;
operator: TOperator;
value: string | string[];
}
interface IOptions {
name: string;
value?: string;
}
export interface IOptionsData {
mode: undefined | 'tags' | 'multiple';
options: IOptions[] | [];
}

View File

@@ -1,153 +0,0 @@
/* eslint-disable sonarjs/cognitive-complexity */
import { decode, encode } from 'js-base64';
import { flattenDeep, map, uniqWith } from 'lodash-es';
import { Dashboard } from 'types/api/dashboard/getAll';
import { IOptionsData, IQueryStructure, TCategory, TOperator } from './types';
export const convertQueriesToURLQuery = (
queries: IQueryStructure[],
): string => {
if (!queries || !queries.length) {
return '';
}
return encode(JSON.stringify(queries));
};
export const convertURLQueryStringToQuery = (
queryString: string,
): IQueryStructure[] => JSON.parse(decode(queryString));
export const resolveOperator = (
result: unknown,
operator: TOperator,
): boolean => {
if (operator === '!=') {
return !result;
}
if (operator === '=') {
return !!result;
}
return !!result;
};
export const executeSearchQueries = (
queries: IQueryStructure[] = [],
searchData: Dashboard[] = [],
): Dashboard[] => {
if (!searchData.length || !queries.length) {
return searchData;
}
const escapeRegExp = (regExp: string): string =>
regExp.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
queries.forEach((query: IQueryStructure) => {
const { operator } = query;
let { value } = query;
const categoryLowercase: TCategory = `${query.category}`.toLowerCase() as
| 'title'
| 'description';
value = flattenDeep([value]);
searchData = searchData.filter(({ data: searchPayload }: Dashboard) => {
try {
const searchSpace =
flattenDeep([searchPayload[categoryLowercase]]).filter(Boolean) || null;
if (!searchSpace || !searchSpace.length) {
return resolveOperator(false, operator);
}
for (const searchSpaceItem of searchSpace) {
if (searchSpaceItem) {
for (const queryValue of value) {
if (searchSpaceItem.match(escapeRegExp(queryValue))) {
return resolveOperator(true, operator);
}
}
}
}
} catch (error) {
console.error(error);
}
return resolveOperator(false, operator);
});
});
return searchData;
};
export const OptionsSchemas = {
attribute: {
mode: undefined,
options: [
{
name: 'Title',
},
{
name: 'Description',
},
{
name: 'Tags',
},
],
},
operator: {
mode: undefined,
options: [
{
value: '=',
name: 'Equal',
},
{
name: 'Not Equal',
value: '!=',
},
],
},
};
export function OptionsValueResolution(
category: TCategory,
searchData: Dashboard[],
): Record<string, unknown> | IOptionsData {
const OptionsValueSchema = {
title: {
mode: 'tags',
options: uniqWith(
map(searchData, (searchItem) => ({ name: searchItem.data.title })),
(prev, next) => prev.name === next.name,
),
},
description: {
mode: 'tags',
options: uniqWith(
map(searchData, (searchItem) =>
searchItem.data.description
? {
name: searchItem.data.description,
value: searchItem.data.description,
}
: null,
).filter(Boolean),
(prev, next) => prev?.name === next?.name,
),
},
tags: {
mode: 'tags',
options: uniqWith(
map(
flattenDeep(
// @ts-ignore
map(searchData, (searchItem) => searchItem.data.tags).filter(Boolean),
),
(tag) => ({ name: tag }),
),
(prev, next) => prev.name === next.name,
),
},
};
return (
OptionsValueSchema[category] ||
({ mode: undefined, options: [] } as IOptionsData)
);
}

View File

@@ -7,9 +7,3 @@
.delete-btn:hover {
background-color: color-mix(in srgb, var(--l1-foreground) 12%, transparent);
}
.lightMode {
.delete-btn:hover {
background-color: rgba(0, 0, 0, 0.06) !important;
}
}

View File

@@ -58,14 +58,3 @@
}
}
}
.lightMode {
.table-view-actions-content {
.ant-popover-inner {
border: 1px solid var(--l1-border);
background: var(--l1-background) !important;
backdrop-filter: none;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
}
}
}

View File

@@ -160,55 +160,6 @@
}
}
.lightMode {
.login-form-card {
background: var(--l2-background);
}
.login-error-container {
.error-content {
background: color-mix(in srgb, var(--danger-background) 10%, transparent);
border-color: color-mix(in srgb, var(--danger-background) 20%, transparent);
&__error-code {
color: var(--l2-foreground);
}
&__error-message {
color: var(--l1-foreground);
}
&__docs-button {
color: var(--l1-foreground);
border-color: var(--l1-border);
background: transparent;
&:hover {
color: var(--l2-foreground);
border-color: var(--l1-border);
background: transparent;
}
}
&__message-badge-label-text {
color: var(--l2-foreground);
}
&__message-item {
color: var(--l1-foreground);
&::before {
background: var(--l3-background);
}
}
&__scroll-hint-text {
color: var(--l2-foreground);
}
}
}
}
.password-label-container {
display: flex;
justify-content: space-between;

View File

@@ -10,10 +10,6 @@ export const Label = styled.label`
color: var(--l1-foreground);
margin-bottom: 12px;
display: block;
.lightMode & {
color: var(--text-ink-500);
}
`;
export const FormContainer = styled(Form)`
@@ -50,20 +46,10 @@ export const FormContainer = styled(Form)`
background: var(--l3-background) !important;
border-color: var(--l1-border) !important;
color: var(--l1-foreground) !important;
.lightMode & {
background: var(--bg-vanilla-200) !important;
border-color: var(--bg-vanilla-300) !important;
color: var(--text-ink-500) !important;
}
}
& .ant-input::placeholder {
color: var(--l3-foreground) !important;
.lightMode & {
color: var(--text-neutral-light-200) !important;
}
}
& .ant-input:focus,

View File

@@ -323,50 +323,3 @@
}
}
}
.lightMode {
.logs-list-view-container {
.logs-list-table-view-container {
table {
thead {
tr {
th {
color: var(--l1-foreground) !important;
}
border-bottom: 1px solid var(--l1-border) !important;
}
border-bottom: 1px solid var(--l1-border) !important;
background-color: var(--l1-background) !important;
tr {
&:hover {
background-color: var(--l3-background) !important;
}
}
}
tbody {
tr {
&:hover {
background-color: var(--l3-background) !important;
}
border-bottom: 1px solid var(--l1-border) !important;
}
}
}
.sticky-header-table-container {
&::-webkit-scrollbar-thumb {
background: var(--l3-background);
}
&::-webkit-scrollbar-thumb:hover {
background: var(--l1-background);
}
}
}
}
}

View File

@@ -263,24 +263,6 @@
}
}
.lightMode {
.logs-explorer-views-container {
.views-tabs-container {
.views-tabs {
.selected_view {
background: white;
color: var(--text-robin-400);
border: 1px solid var(--bg-robin-400);
}
.selected_view::before {
background: var(--bg-robin-400);
}
}
}
}
}
.order-by-container {
display: flex;
align-items: center;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -179,16 +179,6 @@
}
}
.lightMode {
.metrics-explorer-explore-container {
.explore-tabs {
.selected-view {
background: var(--l3-background);
}
}
}
}
.dashboards-and-alerts-popover-container {
display: flex;
gap: 16px;

View File

@@ -382,81 +382,6 @@
}
}
.lightMode {
.metric-details-header {
.ant-btn {
background-color: var(--bg-white-400);
}
}
.metric-details-content {
.metrics-accordion {
.metrics-accordion-header {
.action-menu {
.action-button {
.ant-typography {
color: var(--l1-border);
}
}
}
}
.metrics-accordion-content {
.metric-metadata-key {
.field-renderer-container {
.label {
color: var(--l2-foreground);
}
}
.all-attributes-key {
.all-attributes-contribution {
color: var(--l1-border);
}
}
}
.metric-metadata-value {
background-color: var(--l3-background);
.all-attributes-value {
.ant-btn {
background-color: var(--l2-foreground);
.ant-typography {
color: var(--l1-border);
}
&:hover {
background-color: var(--l1-foreground);
}
}
}
.field-renderer-container {
.label {
color: var(--l1-border);
}
}
}
}
}
}
.top-attributes-content {
.top-attributes-item-progress {
.top-attributes-item-progress-bar {
background-color: var(--bg-robin-400);
}
.top-attributes-item-count {
background-color: var(--bg-robin-300);
}
.top-attributes-item-key,
.top-attributes-item-count {
color: var(--l1-border);
}
}
}
}
.metric-metadata-value {
.y-axis-unit-selector-component {
.ant-select {
@@ -585,24 +510,6 @@
color: var(--bg-robin-400) !important;
}
.lightMode {
.attribute-key-popover-overlay,
.all-values-popover-overlay {
.ant-popover-inner {
border: 1px solid var(--l2-foreground);
background: var(--l1-foreground) !important;
}
}
.all-values-popover {
.all-values-item {
&:hover {
background: rgba(0, 0, 0, 0.04);
}
}
}
}
@keyframes fade-in-out {
0% {
opacity: 0;

View File

@@ -230,29 +230,6 @@
}
}
.lightMode {
.metrics-table-container {
.ant-table {
.ant-table-tbody > tr:hover > td {
background: rgba(0, 0, 0, 0.04);
}
}
.ant-pagination {
.ant-pagination-item {
&-active {
background: var(--primary-background);
border-color: var(--bg-robin-500);
a {
color: var(--l1-foreground) !important;
}
}
}
}
}
}
.metric-type-renderer {
border-radius: 50px;
max-height: 24px;

View File

@@ -57,28 +57,3 @@
}
}
}
.lightMode {
.license-section {
.license-section-header {
.license-section-title {
color: var(--l1-foreground);
}
}
.license-section-content {
.license-section-content-item {
border: 1px solid var(--l1-border);
background: var(--l1-background);
.license-section-content-item-title-action {
color: var(--l1-foreground);
}
.license-section-content-item-description {
color: var(--l1-foreground);
}
}
}
}
}

View File

@@ -153,62 +153,3 @@
border-radius: 3px;
}
.lightMode {
.user-info-section {
.user-info-section-header {
.user-info-section-title {
color: var(--l1-foreground);
}
.user-info-section-subtitle {
color: var(--l1-foreground);
}
}
}
.user-preference-section {
.user-preference-section-header {
.user-preference-section-title {
color: var(--l1-foreground);
}
.user-preference-section-subtitle {
color: var(--l1-foreground);
}
}
.user-preference-section-content {
.user-preference-section-content-item {
border: 1px solid var(--l1-border);
background: var(--l1-background);
.user-preference-section-content-item-title-action {
color: var(--l1-foreground);
}
.user-preference-section-content-item-description {
color: var(--l1-foreground);
}
.auto-theme-info {
background: var(--l2-background);
border: 1px solid var(--l1-border);
.auto-theme-status {
color: var(--l1-foreground);
strong {
color: var(--accent-primary);
}
}
}
}
}
}
.reset-password-card {
border: 1px solid var(--l1-border);
background: var(--l1-background);
}
}

View File

@@ -12,7 +12,7 @@
align-items: center;
justify-content: center;
gap: 4px;
color: #fff;
color: var(--l1-foreground);
font-family: Inter;
font-size: 12px;
font-style: normal;

View File

@@ -32,17 +32,3 @@
justify-content: end;
}
}
.lightMode {
.widget-graph {
background-color: var(--l1-background);
background-image: radial-gradient(var(--l2-foreground) 1px, transparent 0);
background-size: 20px 20px;
.header {
.plot-tag {
background: var(--l3-background);
}
}
}
}

View File

@@ -38,11 +38,3 @@
gap: 12px;
}
}
.lightMode {
.column-unit-selector {
.heading {
color: var(--l2-background);
}
}
}

View File

@@ -68,25 +68,3 @@
border-top: 1px solid var(--l1-border);
}
}
.lightMode {
.context-link-form-container {
.url-parameters-section {
.parameter-row {
.delete-parameter-btn {
color: var(--l1-border);
&:hover {
color: var(--danger-background) !important;
border-color: var(--danger-background) !important;
background-color: var(--bg-cherry-100);
}
}
}
}
.context-link-footer {
border-top-color: var(--l1-border);
}
}
}

View File

@@ -111,35 +111,3 @@
.add-context-link-button {
width: 100%;
}
.lightMode {
.context-links-text {
color: var(--l2-background);
}
.context-link-item {
&:hover {
background-color: var(--l2-background);
}
.context-link-label {
color: var(--l1-border);
}
.context-link-url {
color: var(--l1-border);
}
.drag-handle-icon {
color: var(--l1-border);
}
.delete-context-link-btn {
&:hover {
color: var(--danger-background);
border-color: var(--danger-background);
background-color: var(--bg-cherry-100);
}
}
}
}

View File

@@ -142,28 +142,3 @@
gap: 8px;
}
}
.lightMode {
.legend-colors-container {
.legend-colors-content {
.legend-items {
&::-webkit-scrollbar-thumb {
background: var(--l2-foreground);
}
&::-webkit-scrollbar-thumb:hover {
background: var(--l3-background);
}
scrollbar-color: var(--l2-foreground) transparent;
}
.legend-item {
&:hover {
background-color: var(--l3-background);
border-color: var(--l1-border);
}
}
}
}
}

View File

@@ -222,95 +222,3 @@
line-height: 16px; /* 133.333% */
}
}
.lightMode {
.right-container {
background-color: var(--l1-background);
.section-heading,
.section-heading-small {
color: var(--l2-foreground);
}
.header {
.header-text {
color: var(--l2-foreground);
}
}
.panel-config {
.panel-type-select {
.ant-select-selector {
border: 1px solid var(--l1-border);
background: var(--l2-background);
}
.select-option {
.display {
color: var(--l2-foreground);
}
}
}
.y-axis-unit-selector-v2 {
.y-axis-unit-selector-component {
.ant-select {
.ant-select-selector {
border: 1px solid var(--l1-border);
background: var(--l2-background);
}
}
}
}
.toggle-card {
border: 1px solid var(--l1-border);
background: var(--l2-background);
.fill-gaps-text {
color: var(--l2-foreground);
}
.toggle-card-description {
color: var(--l2-foreground);
}
}
.panel-time-text {
color: var(--l2-foreground);
}
.y-axis-unit-selector,
.y-axis-unit-selector-v2 {
.heading {
color: var(--l2-foreground);
}
.input {
border: 1px solid var(--l1-border);
background: var(--l2-background);
.ant-input {
background: var(--l2-background);
}
}
}
.soft-min-max {
.container {
border: 1px solid var(--l1-border);
background: var(--l2-background);
.text {
color: var(--l2-foreground);
}
.input {
border-left: 1px solid var(--l1-border);
}
}
}
}
}
.select-option {
.display {
color: var(--l2-foreground);
}
}
}

View File

@@ -36,20 +36,3 @@
}
}
}
.lightMode {
.histogram-settings__bucket-config {
.histogram-settings__merge-label {
color: var(--l2-background);
}
.histogram-settings__bucket-input {
border: 1px solid var(--l1-border);
background: var(--l2-background);
.ant-input {
background: var(--l2-background);
}
}
}
}

View File

@@ -8,7 +8,7 @@
.color-selector-button {
border: none;
width: 100%;
background-color: var(--l1-border);
background-color: var(--l1-background);
.ant-btn {
box-shadow: none;

View File

@@ -3,17 +3,8 @@
gap: 8px;
align-items: center;
.custom-color-typography-dark {
color: #fff !important;
font-family: 'Space Mono';
font-size: 12px;
font-style: normal;
font-weight: 400;
line-height: 16px; /* 133.333% */
}
.custom-color-typography-light {
color: #000 !important;
.custom-color-text {
color: var(--l1-foreground) !important;
font-family: 'Space Mono';
font-size: 12px;
font-style: normal;

View File

@@ -1,24 +1,14 @@
import { Typography } from 'antd';
import { useIsDarkMode } from 'hooks/useDarkMode';
import { CustomColorProps } from './types';
import './CustomColor.styles.scss';
function CustomColor({ color }: CustomColorProps): JSX.Element {
const isDarkMode = useIsDarkMode();
return (
<div className="custom-color-container">
<div className="custom-color-tag" style={{ background: color }} />
<Typography.Text
className={
isDarkMode
? `custom-color-typography-dark`
: `custom-color-typograph-light`
}
>
{color}
</Typography.Text>
<Typography.Text className={`custom-color-text`}>{color}</Typography.Text>
</div>
);
}

View File

@@ -20,7 +20,7 @@
flex-shrink: 0;
padding: 12px;
border-radius: 2px;
border: 1px solid var(--l1-border);
border: 1px solid var(--l2-border);
background: var(--l2-background);
.edit-action-btns {
@@ -38,11 +38,11 @@
align-items: center;
gap: 10px;
flex-shrink: 0;
background: var(--l1-border);
background: var(--l2-background);
}
.ant-btn + .ant-btn {
border-left: 1px solid var(--l1-border);
border-left: 1px solid var(--l2-border);
}
.edit-btn {
@@ -57,7 +57,7 @@
.time-series-alerts {
display: flex;
border-radius: 2px;
border: 1px solid var(--l1-border);
border: 1px solid var(--l2-border);
background: var(--l2-background);
box-shadow: 0px 0px 8px 0px rgba(0, 0, 0, 0.1);
width: 100%;
@@ -81,7 +81,7 @@
width: 80%;
flex-shrink: 0;
border-radius: 2px;
border-left: 1px solid var(--l1-border);
border-left: 1px solid var(--l2-border);
background: var(--l2-background);
box-shadow: 0px 0px 8px 0px rgba(0, 0, 0, 0.1);
color: var(--l1-foreground);
@@ -139,7 +139,7 @@
.threshold-units-selector {
display: flex;
border-radius: 2px 2px 0px 0px;
border: 1px solid var(--l1-border);
border: 1px solid var(--l2-border);
background: var(--l2-background);
box-shadow: 0px 0px 8px 0px rgba(0, 0, 0, 0.1);
height: 32px;
@@ -161,7 +161,7 @@
.unit-selection {
width: 50%;
border: none;
border-left: 1px solid var(--l1-border);
border-left: 1px solid var(--l2-border);
color: var(--l2-foreground);
font-family: 'Space Mono';
font-size: 12px;
@@ -188,14 +188,14 @@
.unit-selection-prev {
width: 50%;
border: none;
border-left: 1px solid var(--l1-border);
border-left: 1px solid var(--l2-border);
color: var(--l2-foreground);
font-family: 'Space Mono';
font-size: 12px;
font-style: normal;
font-weight: 400;
line-height: 16px; /* 133.333% */
background: var(--l2-background);
background: var(--l1-background);
}
}
@@ -203,9 +203,9 @@
display: flex;
align-items: center;
border-radius: 0px 0px 2px 2px;
border: 1px solid var(--l1-border);
border: 1px solid var(--l2-border);
border-top: none;
background: var(--l2-background);
background: var(--l1-background);
box-shadow: 0px 0px 8px 0px rgba(0, 0, 0, 0.1);
height: 32px;
flex-shrink: 0;
@@ -220,7 +220,7 @@
font-style: normal;
font-weight: 400;
line-height: 16px; /* 133.333% */
background: var(--l2-background);
background: var(--l1-background);
.ant-btn {
box-shadow: none;
@@ -230,8 +230,9 @@
.color-format {
width: 50%;
border: none;
border-left: 1px solid var(--l1-border);
border-left: 1px solid var(--l2-border);
color: var(--l1-foreground);
background: var(--l1-background);
font-family: 'Space Mono';
font-size: 12px;
font-style: normal;
@@ -247,21 +248,21 @@
font-style: normal;
font-weight: 400;
line-height: 16px; /* 133.333% */
background-color: unset;
background: var(--l1-background);
}
}
.color-format-prev {
width: 50%;
border: none;
border-left: 1px solid var(--l1-border);
border-left: 1px solid var(--l2-border);
color: var(--l1-foreground);
font-family: 'Space Mono';
font-size: 12px;
font-style: normal;
font-weight: 400;
line-height: 16px; /* 133.333% */
background: var(--l2-background);
background: var(--l1-background);
}
}
@@ -281,8 +282,8 @@
gap: 6px;
flex: 1 0 0;
border-radius: 2px;
border: 1px solid var(--l1-border);
background: var(--l1-border);
border: 1px solid var(--l2-border);
background: var(--l2-background);
box-shadow: none;
}
@@ -319,123 +320,3 @@
}
}
}
.lightMode {
.operator-input-root {
color: var(--l2-background);
}
.threshold-container {
.threshold-card-container {
border: 1px solid var(--l1-border);
background: var(--l1-foreground);
.edit-action-btns {
.ant-btn {
background: var(--l3-background);
}
.ant-btn + .ant-btn {
border-left: 1px solid var(--l1-foreground);
}
}
.time-series-alerts {
border: 1px solid var(--l1-border);
background: var(--l1-foreground);
.label {
color: var(--l2-background);
}
.label-input {
border-left: 1px solid var(--l1-border);
background: var(--l3-background);
color: var(--l3-background);
border: 1px solid var(--l1-border);
box-shadow: none;
}
}
.value-table-alerts {
.operator-input {
.ant-select-selector {
background-color: var(--l3-background) !important;
color: var(--l3-background);
}
}
.typography {
color: var(--l2-background);
}
.typography-preview {
color: var(--l2-background);
border: 1px solid var(--l1-border);
background-color: var(--l3-background) !important;
}
}
.threshold-units-selector {
border: 1px solid var(--l1-border);
background: var(--l3-background);
.unit-input {
color: var(--l2-background);
background: var(--l3-background);
}
.unit-selection {
border-left: 1px solid var(--l1-foreground);
color: var(--l2-background);
.ant-select-selector {
background-color: var(--l3-background);
color: var(--l2-background);
}
}
.unit-selection-prev {
border-left: 1px solid var(--l1-foreground);
color: var(--l2-background);
background: var(--l3-background);
}
}
.thresholds-color-selector {
border: 1px solid var(--l1-border);
border-top: 1px solid var(--l1-foreground);
background: var(--l3-background);
.color-selector {
color: var(--l3-background);
background: var(--l3-background);
border: 1px solid var(--l1-border);
border-top: 1px solid var(--l1-foreground);
}
.color-format {
border-left: 1px solid var(--l1-foreground);
color: var(--l3-background);
.ant-select-selector {
color: var(--l2-background);
}
}
.color-format-prev {
border-left: 1px solid var(--l1-foreground);
color: var(--l3-background);
background: var(--l3-background);
}
}
.threshold-action-button {
.discard-btn {
border: 1px solid var(--l1-border);
background: var(--l3-background);
}
.save-changes {
color: var(--l1-foreground);
}
}
}
}
}

Some files were not shown because too many files have changed in this diff Show More