mirror of
https://github.com/SigNoz/signoz.git
synced 2026-04-25 13:20:24 +01:00
Compare commits
3 Commits
chore/mino
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
feea9e9b36 | ||
|
|
e94767bda8 | ||
|
|
bb10f51cc5 |
@@ -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": [
|
||||
|
||||
@@ -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"
|
||||
},
|
||||
|
||||
@@ -16,5 +16,6 @@
|
||||
"roles": "Roles",
|
||||
"role_details": "Role Details",
|
||||
"members": "Members",
|
||||
"service_accounts": "Service Accounts"
|
||||
"service_accounts": "Service Accounts",
|
||||
"mcp_server": "MCP Server"
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
@@ -16,5 +16,6 @@
|
||||
"roles": "Roles",
|
||||
"role_details": "Role Details",
|
||||
"members": "Members",
|
||||
"service_accounts": "Service Accounts"
|
||||
"service_accounts": "Service Accounts",
|
||||
"mcp_server": "MCP Server"
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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%);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
.dropdown-button {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.dropdown-button--dark {
|
||||
color: #000;
|
||||
color: var(--l1-foreground);
|
||||
}
|
||||
|
||||
.dropdown-icon {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -12,6 +12,12 @@
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
&__layout {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
&__tab-group {
|
||||
[data-slot='toggle-group'] {
|
||||
border-radius: 2px;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -157,9 +157,3 @@
|
||||
.view-all-drawer {
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.lightMode {
|
||||
.ant-table {
|
||||
background: inherit;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,11 +61,3 @@
|
||||
width: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
.lightMode {
|
||||
.dashboard-breadcrumbs {
|
||||
.dashboard-btn {
|
||||
color: var(--l1-foreground);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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',
|
||||
});
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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 />}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -176,15 +176,3 @@
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.lightMode {
|
||||
.info-text {
|
||||
color: var(--bg-robin-600) !important;
|
||||
}
|
||||
|
||||
.info-link-container {
|
||||
.anticon {
|
||||
color: var(--bg-robin-400);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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',
|
||||
});
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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]);
|
||||
});
|
||||
});
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
`;
|
||||
@@ -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[] | [];
|
||||
}
|
||||
@@ -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)
|
||||
);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
@@ -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;
|
||||
@@ -0,0 +1,5 @@
|
||||
.mcp-copy-btn {
|
||||
&:hover {
|
||||
background-color: var(--l3-background-hover) !important;
|
||||
}
|
||||
}
|
||||
40
frontend/src/container/MCPServerSettings/CopyIconButton.tsx
Normal file
40
frontend/src/container/MCPServerSettings/CopyIconButton.tsx
Normal 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;
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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',
|
||||
);
|
||||
});
|
||||
});
|
||||
144
frontend/src/container/MCPServerSettings/MCPServerSettings.tsx
Normal file
144
frontend/src/container/MCPServerSettings/MCPServerSettings.tsx
Normal 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;
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
108
frontend/src/container/MCPServerSettings/clients.ts
Normal file
108
frontend/src/container/MCPServerSettings/clients.ts
Normal 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/`;
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,11 +38,3 @@
|
||||
gap: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.lightMode {
|
||||
.column-unit-selector {
|
||||
.heading {
|
||||
color: var(--l2-background);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user