mirror of
https://github.com/SigNoz/signoz.git
synced 2026-02-09 03:02:20 +00:00
Compare commits
17 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d5e0a26f55 | ||
|
|
48ebe91713 | ||
|
|
5bc3c074f8 | ||
|
|
f5b5a9a657 | ||
|
|
ac835c80e9 | ||
|
|
2cf0bb4fa5 | ||
|
|
0f44246795 | ||
|
|
64307f323f | ||
|
|
616b8e0a45 | ||
|
|
2c0690a8ee | ||
|
|
2f361de693 | ||
|
|
457380c065 | ||
|
|
96e3d00e74 | ||
|
|
d224e08145 | ||
|
|
13ced00a35 | ||
|
|
71f6b355c4 | ||
|
|
110b545454 |
@@ -146,7 +146,7 @@ services:
|
||||
condition: on-failure
|
||||
|
||||
query-service:
|
||||
image: signoz/query-service:0.37.1
|
||||
image: signoz/query-service:0.38.2
|
||||
command:
|
||||
[
|
||||
"-config=/root/config/prometheus.yml",
|
||||
@@ -186,7 +186,7 @@ services:
|
||||
<<: *db-depend
|
||||
|
||||
frontend:
|
||||
image: signoz/frontend:0.37.1
|
||||
image: signoz/frontend:0.38.2
|
||||
deploy:
|
||||
restart_policy:
|
||||
condition: on-failure
|
||||
@@ -199,7 +199,7 @@ services:
|
||||
- ../common/nginx-config.conf:/etc/nginx/conf.d/default.conf
|
||||
|
||||
otel-collector:
|
||||
image: signoz/signoz-otel-collector:0.88.8
|
||||
image: signoz/signoz-otel-collector:0.88.11
|
||||
command:
|
||||
[
|
||||
"--config=/etc/otel-collector-config.yaml",
|
||||
@@ -237,7 +237,7 @@ services:
|
||||
- query-service
|
||||
|
||||
otel-collector-migrator:
|
||||
image: signoz/signoz-schema-migrator:0.88.8
|
||||
image: signoz/signoz-schema-migrator:0.88.11
|
||||
deploy:
|
||||
restart_policy:
|
||||
condition: on-failure
|
||||
|
||||
@@ -66,7 +66,7 @@ services:
|
||||
- --storage.path=/data
|
||||
|
||||
otel-collector-migrator:
|
||||
image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-0.88.8}
|
||||
image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-0.88.11}
|
||||
container_name: otel-migrator
|
||||
command:
|
||||
- "--dsn=tcp://clickhouse:9000"
|
||||
@@ -81,7 +81,7 @@ services:
|
||||
# Notes for Maintainers/Contributors who will change Line Numbers of Frontend & Query-Section. Please Update Line Numbers in `./scripts/commentLinesForSetup.sh` & `./CONTRIBUTING.md`
|
||||
otel-collector:
|
||||
container_name: signoz-otel-collector
|
||||
image: signoz/signoz-otel-collector:0.88.8
|
||||
image: signoz/signoz-otel-collector:0.88.11
|
||||
command:
|
||||
[
|
||||
"--config=/etc/otel-collector-config.yaml",
|
||||
|
||||
@@ -164,7 +164,7 @@ services:
|
||||
# Notes for Maintainers/Contributors who will change Line Numbers of Frontend & Query-Section. Please Update Line Numbers in `./scripts/commentLinesForSetup.sh` & `./CONTRIBUTING.md`
|
||||
|
||||
query-service:
|
||||
image: signoz/query-service:${DOCKER_TAG:-0.37.1}
|
||||
image: signoz/query-service:${DOCKER_TAG:-0.38.2}
|
||||
container_name: signoz-query-service
|
||||
command:
|
||||
[
|
||||
@@ -203,7 +203,7 @@ services:
|
||||
<<: *db-depend
|
||||
|
||||
frontend:
|
||||
image: signoz/frontend:${DOCKER_TAG:-0.37.1}
|
||||
image: signoz/frontend:${DOCKER_TAG:-0.38.2}
|
||||
container_name: signoz-frontend
|
||||
restart: on-failure
|
||||
depends_on:
|
||||
@@ -215,7 +215,7 @@ services:
|
||||
- ../common/nginx-config.conf:/etc/nginx/conf.d/default.conf
|
||||
|
||||
otel-collector-migrator:
|
||||
image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-0.88.8}
|
||||
image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-0.88.11}
|
||||
container_name: otel-migrator
|
||||
command:
|
||||
- "--dsn=tcp://clickhouse:9000"
|
||||
@@ -229,7 +229,7 @@ services:
|
||||
|
||||
|
||||
otel-collector:
|
||||
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-0.88.8}
|
||||
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-0.88.11}
|
||||
container_name: signoz-otel-collector
|
||||
command:
|
||||
[
|
||||
|
||||
@@ -22,7 +22,7 @@ const config: Config.InitialOptions = {
|
||||
'^.+\\.(js|jsx)$': 'babel-jest',
|
||||
},
|
||||
transformIgnorePatterns: [
|
||||
'node_modules/(?!(lodash-es|react-dnd|core-dnd|@react-dnd|dnd-core|react-dnd-html5-backend|axios|@signozhq/design-tokens)/)',
|
||||
'node_modules/(?!(lodash-es|react-dnd|core-dnd|@react-dnd|dnd-core|react-dnd-html5-backend|axios)/)',
|
||||
],
|
||||
setupFilesAfterEnv: ['<rootDir>jest.setup.ts'],
|
||||
testPathIgnorePatterns: ['/node_modules/', '/public/'],
|
||||
|
||||
@@ -36,9 +36,7 @@
|
||||
"@mdx-js/loader": "2.3.0",
|
||||
"@mdx-js/react": "2.3.0",
|
||||
"@monaco-editor/react": "^4.3.1",
|
||||
"@radix-ui/react-tabs": "1.0.4",
|
||||
"@radix-ui/react-tooltip": "1.0.7",
|
||||
"@signozhq/design-tokens": "0.0.8",
|
||||
"@signozhq/design-tokens": "0.0.6",
|
||||
"@uiw/react-md-editor": "3.23.5",
|
||||
"@xstate/react": "^3.0.0",
|
||||
"ansi-to-html": "0.7.2",
|
||||
@@ -77,7 +75,7 @@
|
||||
"less": "^4.1.2",
|
||||
"less-loader": "^10.2.0",
|
||||
"lodash-es": "^4.17.21",
|
||||
"lucide-react": "0.321.0",
|
||||
"lucide-react": "0.288.0",
|
||||
"mini-css-extract-plugin": "2.4.5",
|
||||
"papaparse": "5.4.1",
|
||||
"react": "18.2.0",
|
||||
@@ -212,7 +210,8 @@
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.(js|jsx|ts|tsx)": [
|
||||
"eslint --fix"
|
||||
"eslint --fix",
|
||||
"sh scripts/typecheck-staged.sh"
|
||||
]
|
||||
},
|
||||
"resolutions": {
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
<svg width="32" height="33" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M15.91 28.675c-6.199 0-12.888-3.888-12.888-12.421S9.711 3.832 15.911 3.832c3.444 0 6.621 1.134 8.977 3.2 2.555 2.267 3.91 5.466 3.91 9.222 0 3.755-1.355 6.933-3.91 9.2-2.356 2.066-5.555 3.221-8.977 3.221z" fill="url(#prefix__paint0_radial_2122_6520)"/><path d="M26.552 8.87c1.185 1.91 1.803 4.186 1.803 6.717 0 3.756-1.356 6.933-3.911 9.2-2.356 2.066-5.556 3.222-8.978 3.222-4.013 0-8.221-1.634-10.706-5.098 2.391 3.924 6.889 5.764 11.15 5.764 3.423 0 6.623-1.155 8.978-3.222 2.555-2.266 3.911-5.444 3.911-9.2 0-2.83-.771-5.346-2.247-7.383z" fill="#EB8F00"/><path d="M20.123 22.905c0 1.685-1.846 2.667-4.124 2.667-2.277 0-4.124-.989-4.124-2.667 0-1.677 1.847-3.522 4.124-3.522 2.278 0 4.124 1.838 4.124 3.522zM12.06 14.852l1.88-1.748c.267-.331.307-.778.038-1.045-.353-.355-.98-.269-1.32.136-.018.033-.03.042-.049.075l-1.333 1.938-1.804-1.682c-.027-.03-.042-.034-.067-.062-.42-.32-1.05-.267-1.315.157-.207.32-.07.745.264 1.011l2.313 1.372-1.96 1.833c-.262.326-.31.77-.04 1.044.351.358.978.276 1.32-.127.018-.033.031-.042.051-.075l1.405-2.031 1.706 1.609c.027.029.043.035.067.064.418.322 1.049.273 1.318-.149.206-.32.07-.746-.26-1.013l-2.213-1.307zM20.61 14.852l-1.879-1.748c-.267-.331-.307-.778-.036-1.045.354-.355.978-.269 1.318.136.018.033.034.042.051.075l1.334 1.938 1.806-1.682c.025-.03.04-.034.065-.062.422-.32 1.05-.267 1.317.157.205.32.067.745-.266 1.011L22 15.004l1.96 1.833c.268.33.313.775.042 1.044-.349.358-.976.276-1.318-.127-.02-.033-.033-.042-.051-.075l-1.404-2.031-1.71 1.609c-.024.029-.04.035-.066.064-.418.322-1.046.273-1.315-.149-.21-.32-.074-.746.257-1.013l2.216-1.307zM11.911 8.696c.511.044.711-.645.178-.8a4.07 4.07 0 00-1.289-.133A4.596 4.596 0 007.689 9.14c-.378.4.156.89.556.6a5.829 5.829 0 013.666-1.044zM20.044 8.696a5.85 5.85 0 013.689 1.044c.4.29.933-.2.555-.6a4.645 4.645 0 00-3.11-1.377 4.07 4.07 0 00-1.29.133.408.408 0 00-.282.504c.053.194.24.318.438.296z" fill="#422B0D"/><defs><radialGradient id="prefix__paint0_radial_2122_6520" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(15.91 16.254) scale(12.657)"><stop offset=".5" stop-color="#FDE030"/><stop offset=".92" stop-color="#F7C02B"/><stop offset="1" stop-color="#F4A223"/></radialGradient></defs></svg>
|
||||
|
Before Width: | Height: | Size: 2.3 KiB |
File diff suppressed because one or more lines are too long
|
Before Width: | Height: | Size: 5.6 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 88 KiB |
@@ -1 +0,0 @@
|
||||
<svg width="32" height="33" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M14.309 13.108l-6.704-3.32s-.016-.317.284-.477c.302-.16 5.053-2.107 5.435-2.107.383 0 2.62.431 4.249.793 1.629.363 5.933 1.287 5.953 1.57.02.281-4.404 4.806-4.404 4.806l-4.813-1.265z" fill="#C3FECE"/><path d="M20.423 11.037s-2.811-.826-5.546-1.469c-1.274-.3-5.016-1.084-5.016-1.084s.398-.173.698-.3c.305-.127.547-.193.547-.193s2.44.486 4.253.873c2.453.522 5.886 1.547 5.966 1.709.082.16-.902.464-.902.464z" fill="#fff"/><path d="M14.98 10.26c-.598.415-.011.666 1.09.924 1.207.282 2.127.698 2.903.247.7-.405-1.014-.845-1.8-1.014-.6-.129-1.731-.478-2.193-.158z" fill="#ACB1B2"/><path d="M17.17 11.095c-.005 0 .02-4.869.02-5.049 0-.18-.203-.342.02-.724.222-.382.804-.342.804-.342s2.416-.702 3.38-.945c.964-.242 3.098-.804 3.098-.804l.142 1.22s-2.236.631-3.342.913c-1.107.282-2.616.745-2.616.745l-.222.202.064 4.757s-.206.231-.668.231c-.45-.002-.68-.204-.68-.204z" fill="#FFD816"/><path d="M24.095 3.855c.018.38.22.616.46.616.24 0 .404-.307.369-.707-.038-.398-.296-.58-.516-.506-.22.073-.327.32-.313.597zM18.46 6.422a.209.209 0 01-.123-.038l-1.153-.769a.225.225 0 01-.063-.309.222.222 0 01.31-.062l1.153.769a.224.224 0 01.062.309.228.228 0 01-.187.1z" fill="#FEB804"/><path d="M18.636 6.235a.225.225 0 01-.178-.089c-.295-.393-.633-.84-.693-.909a.225.225 0 01-.031-.284.222.222 0 01.309-.062c.04.027.062.042.771.986.073.098.007.238-.091.312-.04.03-.04.046-.087.046z" fill="#FEB804"/><path d="M18.365 6.609c-.01 0-.022 0-.035-.003l-1.111-.175a.221.221 0 11.069-.438l1.11.176c.12.02.225.042.205.164-.016.107-.129.276-.238.276z" fill="#FEB804"/><path d="M7.596 9.764c.353 0 3.188.744 4.65 1.013 1.463.27 5.878 1.314 6.027 1.342.149.03.12 1.94.12 1.94s2.089 10.8 2.029 11.309c-.06.506-1.431 4.415-1.431 4.415s-.807.12-2.865-.478c-2.057-.598-7.488-2.089-7.817-2.506-.329-.418-.12-5.938-.298-9.338-.182-3.402-.415-7.697-.415-7.697z" fill="#79DD8A"/><path d="M24.06 27.036c.113-.375-.518-4.402-.607-8.101-.089-3.698.229-9.324.076-9.369-.154-.042-5.256 2.553-5.256 2.553s-.022 3.671.04 7.133c.08 4.48.438 10.41.676 10.53.238.12 2.302-1.035 2.924-1.372 1.102-.598 2.058-1.074 2.147-1.374z" fill="#02AB46"/><path d="M20.408 13.82l.011-2.787.914-.45.026 3.056-.422.74-.529-.56z" fill="#DBDFE1"/><path d="M12.322 14.797c-1.973-.211-3.34 1.549-3.233 3.842.127 2.709 1.91 4.704 3.842 5.102 1.93.398 3.802-.44 3.842-3.402.044-3.087-2.669-5.353-4.451-5.542z" fill="#FEFEFD"/><path d="M13.637 17.27s-.4-1.344-1.602-.986c-1.202.357-1.853 2.973.187 4.15 1.96 1.131 3.764-.944 3.133-2.288-.574-1.227-1.718-.876-1.718-.876z" fill="#EF5B44"/><path d="M13.18 15.626c-.136.049-.243.602-.1 1.13.106.396.446.939.643.903.158-.029.278-.651.13-1.173-.174-.602-.516-.918-.674-.86z" fill="#B8CF17"/><path d="M13.15 18.746c-.564-.171-1.2 1.769-.057 2.977 1.26 1.331 2.73.158 2.69-.1-.057-.358-1.044-.615-1.53-1.215-.487-.605-.774-1.562-1.102-1.662z" fill="#FD8F01"/><path d="M11.346 18.417s.113-.849-.673-.802c-.76.046-.574.944-.574.944s-.633.076-.526.778c.08.53.64.524.64.524s-.616.242-.336.945c.249.624.822.373.822.373s-.21.609.287.93c.42.272.787.043.787.043s-.023.52.557.616c.703.115 1.007-.74.507-1.136-.38-.3-.724-.067-.724-.067s.07-.166.004-.357c-.045-.125-.116-.171-.116-.171s.616-.058.516-.758c-.1-.702-.716-.616-.716-.616s.358-.286.216-.802c-.14-.518-.671-.444-.671-.444z" fill="#A281D0"/><path d="M21.04 14.595c-.511 0-2.691-2.167-2.711-2.189a.222.222 0 01.024-.313.224.224 0 01.314.022c.14.155 1.806 1.702 2.286 2 .311-.465 1.322-2.498 2.191-4.333a.224.224 0 01.296-.107.223.223 0 01.106.296c-2.142 4.526-2.353 4.586-2.466 4.617-.013.007-.027.007-.04.007z" fill="#2D802D"/></svg>
|
||||
|
Before Width: | Height: | Size: 3.6 KiB |
@@ -1,19 +0,0 @@
|
||||
<svg width="32" height="33" viewBox="0 0 32 33" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M9.36806 25.9481C5.93935 25.9481 3.15283 21.7098 3.15283 16.5002C3.15283 11.2907 5.94157 7.05238 9.36806 7.05238C12.7945 7.05238 15.5833 11.2907 15.5833 16.5002C15.5833 21.7098 12.7945 25.9481 9.36806 25.9481Z" fill="#FAFAFA"/>
|
||||
<path d="M9.36815 7.49694C10.8414 7.49694 12.2524 8.38594 13.3391 10.0017C14.499 11.7241 15.139 14.0333 15.139 16.5003C15.139 18.9673 14.499 21.2764 13.3391 22.9989C12.2524 24.6146 10.8414 25.5036 9.36815 25.5036C7.89489 25.5036 6.48385 24.6146 5.39724 22.9989C4.23508 21.2764 3.59734 18.9673 3.59734 16.5003C3.59734 14.0333 4.23731 11.7241 5.39724 10.0017C6.48385 8.38594 7.89267 7.49694 9.36815 7.49694ZM9.36815 6.60794C5.69056 6.60794 2.7085 11.0374 2.7085 16.5003C2.7085 21.9632 5.69056 26.3926 9.36815 26.3926C13.0457 26.3926 16.0278 21.9632 16.0278 16.5003C16.0278 11.0374 13.0457 6.60794 9.36815 6.60794Z" fill="#B0BEC5"/>
|
||||
<path d="M7.47266 15.5762C6.87269 15.0118 7.00602 13.8919 7.77487 13.0741C7.81486 13.0319 7.85486 12.9919 7.89708 12.9541C7.55488 12.7608 7.17934 12.6519 6.78381 12.6519C5.18611 12.6519 3.89062 14.414 3.89062 16.585C3.89062 18.756 5.18611 20.5182 6.78381 20.5182C8.3815 20.5182 9.67699 18.756 9.67699 16.585C9.67699 16.1962 9.63477 15.8184 9.55699 15.4629C8.83703 15.9806 7.97708 16.0495 7.47266 15.5762Z" fill="url(#paint0_linear_2122_5062)"/>
|
||||
<path d="M22.6294 26.3932C26.3074 26.3932 29.289 21.9642 29.289 16.5008C29.289 11.0374 26.3074 6.60847 22.6294 6.60847C18.9514 6.60847 15.9697 11.0374 15.9697 16.5008C15.9697 21.9642 18.9514 26.3932 22.6294 26.3932Z" fill="#EEEEEE"/>
|
||||
<path d="M22.6283 25.9493C19.2018 25.9493 16.4131 21.711 16.4131 16.5014C16.4131 11.2919 19.2018 7.05357 22.6283 7.05357C26.0548 7.05357 28.8435 11.2919 28.8435 16.5014C28.8435 21.711 26.057 25.9493 22.6283 25.9493Z" fill="#FAFAFA"/>
|
||||
<path d="M22.6284 7.49816C24.1017 7.49816 25.5127 8.38716 26.5993 10.0029C27.7592 11.7254 28.3992 14.0345 28.3992 16.5015C28.3992 18.9685 27.7592 21.2777 26.5993 23.0001C25.5127 24.6159 24.1017 25.5049 22.6284 25.5049C21.1551 25.5049 19.7441 24.6159 18.6575 23.0001C17.4976 21.2777 16.8576 18.9685 16.8576 16.5015C16.8576 14.0345 17.4976 11.7254 18.6575 10.0029C19.7441 8.38716 21.1551 7.49816 22.6284 7.49816ZM22.6284 6.60916C18.9508 6.60916 15.9688 11.0386 15.9688 16.5015C15.9688 21.9644 18.9508 26.3939 22.6284 26.3939C26.306 26.3939 29.2881 21.9644 29.2881 16.5015C29.2881 11.0386 26.306 6.60916 22.6284 6.60916Z" fill="#B0BEC5"/>
|
||||
<path d="M20.7339 15.5767C20.1339 15.0123 20.2672 13.8924 21.0361 13.0746C21.0761 13.0324 21.1161 12.9924 21.1583 12.9546C20.8161 12.7613 20.4406 12.6524 20.045 12.6524C18.4473 12.6524 17.1519 14.4146 17.1519 16.5856C17.1519 18.7566 18.4473 20.5187 20.045 20.5187C21.6427 20.5187 22.9382 18.7566 22.9382 16.5856C22.9382 16.1967 22.896 15.8189 22.8182 15.4634C22.1005 15.9812 21.2383 16.05 20.7339 15.5767Z" fill="url(#paint1_linear_2122_5062)"/>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear_2122_5062" x1="6.78232" y1="12.651" x2="6.78232" y2="20.5188" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#424242"/>
|
||||
<stop offset="1" stop-color="#212121"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint1_linear_2122_5062" x1="20.0449" y1="12.6515" x2="20.0449" y2="20.5193" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#424242"/>
|
||||
<stop offset="1" stop-color="#212121"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 3.3 KiB |
@@ -3,7 +3,6 @@
|
||||
"alert_channels": "Alert Channels",
|
||||
"organization_settings": "Organization Settings",
|
||||
"ingestion_settings": "Ingestion Settings",
|
||||
"api_keys": "API Keys",
|
||||
"my_settings": "My Settings",
|
||||
"overview_metrics": "Overview Metrics",
|
||||
"dbcall_metrics": "Database Calls",
|
||||
|
||||
@@ -26,7 +26,6 @@
|
||||
"MY_SETTINGS": "SigNoz | My Settings",
|
||||
"ORG_SETTINGS": "SigNoz | Organization Settings",
|
||||
"INGESTION_SETTINGS": "SigNoz | Ingestion Settings",
|
||||
"API_KEYS": "SigNoz | API Keys",
|
||||
"SOMETHING_WENT_WRONG": "SigNoz | Something Went Wrong",
|
||||
"UN_AUTHORIZED": "SigNoz | Unauthorized",
|
||||
"NOT_FOUND": "SigNoz | Page Not Found",
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
{
|
||||
"delete_confirm_message": "Are you sure you want to delete {{keyName}} key? Deleting a key is irreversible and cannot be undone."
|
||||
}
|
||||
@@ -1,4 +1,3 @@
|
||||
{
|
||||
"name_of_the_view": "Name of the view",
|
||||
"delete_confirm_message": "Are you sure you want to delete {{viewName}} view? Deleting a view is irreversible and cannot be undone."
|
||||
"name_of_the_view": "Name of the view"
|
||||
}
|
||||
@@ -3,7 +3,6 @@
|
||||
"alert_channels": "Alert Channels",
|
||||
"organization_settings": "Organization Settings",
|
||||
"ingestion_settings": "Ingestion Settings",
|
||||
"api_keys": "API Keys",
|
||||
"my_settings": "My Settings",
|
||||
"overview_metrics": "Overview Metrics",
|
||||
"dbcall_metrics": "Database Calls",
|
||||
|
||||
@@ -26,7 +26,6 @@
|
||||
"MY_SETTINGS": "SigNoz | My Settings",
|
||||
"ORG_SETTINGS": "SigNoz | Organization Settings",
|
||||
"INGESTION_SETTINGS": "SigNoz | Ingestion Settings",
|
||||
"API_KEYS": "SigNoz | API Keys",
|
||||
"SOMETHING_WENT_WRONG": "SigNoz | Something Went Wrong",
|
||||
"UN_AUTHORIZED": "SigNoz | Unauthorized",
|
||||
"NOT_FOUND": "SigNoz | Page Not Found",
|
||||
@@ -40,7 +39,5 @@
|
||||
"LIST_LICENSES": "SigNoz | List of Licenses",
|
||||
"WORKSPACE_LOCKED": "SigNoz | Workspace Locked",
|
||||
"SUPPORT": "SigNoz | Support",
|
||||
"LOGS_SAVE_VIEWS": "SigNoz | Logs Save Views",
|
||||
"TRACES_SAVE_VIEWS": "SigNoz | Traces Save Views",
|
||||
"DEFAULT": "Open source Observability Platform | SigNoz"
|
||||
}
|
||||
|
||||
25
frontend/scripts/typecheck-staged.sh
Normal file
25
frontend/scripts/typecheck-staged.sh
Normal file
@@ -0,0 +1,25 @@
|
||||
files="";
|
||||
|
||||
# lint-staged will pass all files in $1 $2 $3 etc. iterate and concat.
|
||||
for var in "$@"
|
||||
do
|
||||
files="$files \"$var\","
|
||||
done
|
||||
|
||||
# create temporary tsconfig which includes only passed files
|
||||
str="{
|
||||
\"extends\": \"./tsconfig.json\",
|
||||
\"include\": [\"src/types/global.d.ts\",\"src/typings/window.ts\", $files]
|
||||
}"
|
||||
echo $str > tsconfig.tmp
|
||||
|
||||
# run typecheck using temp config
|
||||
tsc -p ./tsconfig.tmp
|
||||
|
||||
# capture exit code of tsc
|
||||
code=$?
|
||||
|
||||
# delete temp config
|
||||
rm ./tsconfig.tmp
|
||||
|
||||
exit $code
|
||||
@@ -98,7 +98,6 @@ function PrivateRoute({ children }: PrivateRouteProps): JSX.Element {
|
||||
|
||||
if (
|
||||
userResponse &&
|
||||
route &&
|
||||
route.find((e) => e === userResponse.payload.role) === undefined
|
||||
) {
|
||||
history.push(ROUTES.UN_AUTHORIZED);
|
||||
@@ -161,7 +160,7 @@ function PrivateRoute({ children }: PrivateRouteProps): JSX.Element {
|
||||
if (currentRoute) {
|
||||
const { isPrivate, key } = currentRoute;
|
||||
|
||||
if (isPrivate && key !== String(ROUTES.WORKSPACE_LOCKED)) {
|
||||
if (isPrivate && key !== ROUTES.WORKSPACE_LOCKED) {
|
||||
handlePrivateRoutes(key);
|
||||
} else {
|
||||
// no need to fetch the user and make user fetching false
|
||||
|
||||
@@ -15,20 +15,9 @@ export const ServiceMapPage = Loadable(
|
||||
() => import(/* webpackChunkName: "ServiceMapPage" */ 'modules/Servicemap'),
|
||||
);
|
||||
|
||||
export const LogsSaveViews = Loadable(
|
||||
() => import(/* webpackChunkName: "LogsSaveViews" */ 'pages/LogsModulePage'), // TODO: Add a wrapper so that the same component can be used in traces
|
||||
);
|
||||
|
||||
export const TracesExplorer = Loadable(
|
||||
() =>
|
||||
import(
|
||||
/* webpackChunkName: "Traces Explorer Page" */ 'pages/TracesModulePage'
|
||||
),
|
||||
);
|
||||
|
||||
export const TracesSaveViews = Loadable(
|
||||
() =>
|
||||
import(/* webpackChunkName: "Traces Save Views" */ 'pages/TracesModulePage'),
|
||||
import(/* webpackChunkName: "Traces Explorer Page" */ 'pages/TracesExplorer'),
|
||||
);
|
||||
|
||||
export const TraceFilter = Loadable(
|
||||
@@ -118,10 +107,6 @@ export const IngestionSettings = Loadable(
|
||||
() => import(/* webpackChunkName: "Ingestion Settings" */ 'pages/Settings'),
|
||||
);
|
||||
|
||||
export const APIKeys = Loadable(
|
||||
() => import(/* webpackChunkName: "All Settings" */ 'pages/Settings'),
|
||||
);
|
||||
|
||||
export const MySettings = Loadable(
|
||||
() => import(/* webpackChunkName: "All MySettings" */ 'pages/MySettings'),
|
||||
);
|
||||
|
||||
@@ -5,7 +5,6 @@ import { RouteProps } from 'react-router-dom';
|
||||
import {
|
||||
AllAlertChannels,
|
||||
AllErrors,
|
||||
APIKeys,
|
||||
BillingPage,
|
||||
CreateAlertChannelAlerts,
|
||||
CreateNewAlerts,
|
||||
@@ -22,7 +21,6 @@ import {
|
||||
Logs,
|
||||
LogsExplorer,
|
||||
LogsIndexToFields,
|
||||
LogsSaveViews,
|
||||
MySettings,
|
||||
NewDashboardPage,
|
||||
OldLogsExplorer,
|
||||
@@ -41,7 +39,6 @@ import {
|
||||
TraceDetail,
|
||||
TraceFilter,
|
||||
TracesExplorer,
|
||||
TracesSaveViews,
|
||||
UnAuthorized,
|
||||
UsageExplorerPage,
|
||||
} from './pageComponents';
|
||||
@@ -89,13 +86,6 @@ const routes: AppRoutes[] = [
|
||||
exact: true,
|
||||
key: 'SERVICE_MAP',
|
||||
},
|
||||
{
|
||||
path: ROUTES.LOGS_SAVE_VIEWS,
|
||||
component: LogsSaveViews,
|
||||
isPrivate: true,
|
||||
exact: true,
|
||||
key: 'LOGS_SAVE_VIEWS',
|
||||
},
|
||||
{
|
||||
path: ROUTES.TRACE_DETAIL,
|
||||
exact: true,
|
||||
@@ -173,13 +163,6 @@ const routes: AppRoutes[] = [
|
||||
isPrivate: true,
|
||||
key: 'TRACES_EXPLORER',
|
||||
},
|
||||
{
|
||||
path: ROUTES.TRACES_SAVE_VIEWS,
|
||||
exact: true,
|
||||
component: TracesSaveViews,
|
||||
isPrivate: true,
|
||||
key: 'TRACES_SAVE_VIEWS',
|
||||
},
|
||||
{
|
||||
path: ROUTES.CHANNELS_NEW,
|
||||
exact: true,
|
||||
@@ -236,13 +219,6 @@ const routes: AppRoutes[] = [
|
||||
isPrivate: true,
|
||||
key: 'INGESTION_SETTINGS',
|
||||
},
|
||||
{
|
||||
path: ROUTES.API_KEYS,
|
||||
exact: true,
|
||||
component: APIKeys,
|
||||
isPrivate: true,
|
||||
key: 'API_KEYS',
|
||||
},
|
||||
{
|
||||
path: ROUTES.MY_SETTINGS,
|
||||
exact: true,
|
||||
|
||||
@@ -33,7 +33,6 @@
|
||||
.timeSelection-input {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
height: 33px;
|
||||
align-items: center;
|
||||
padding: 4px 8px;
|
||||
padding-left: 0px !important;
|
||||
@@ -60,26 +59,6 @@
|
||||
font-weight: 400 !important;
|
||||
}
|
||||
|
||||
.info-text {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 4px;
|
||||
cursor: default;
|
||||
color: var(--bg-vanilla-400, #c0c1c3) !important;
|
||||
font-size: 14px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: normal;
|
||||
letter-spacing: 0.14px;
|
||||
}
|
||||
|
||||
.info-text:hover {
|
||||
&.ant-btn-text {
|
||||
background-color: unset !important;
|
||||
}
|
||||
}
|
||||
|
||||
.lightMode {
|
||||
.time-options-container {
|
||||
.time-options-item {
|
||||
@@ -114,8 +93,4 @@
|
||||
color: rgba($color: #000000, $alpha: 0.4);
|
||||
}
|
||||
}
|
||||
|
||||
.info-text {
|
||||
color: var(--bg-slate-400) !important;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,43 +4,22 @@ import './CustomTimePicker.styles.scss';
|
||||
|
||||
import { Input, Popover, Tooltip, Typography } from 'antd';
|
||||
import cx from 'classnames';
|
||||
import { DateTimeRangeType } from 'container/TopNav/CustomDateTimeModal';
|
||||
import { Options } from 'container/TopNav/DateTimeSelection/config';
|
||||
import {
|
||||
FixedDurationSuggestionOptions,
|
||||
RelativeDurationSuggestionOptions,
|
||||
} from 'container/TopNav/DateTimeSelectionV2/config';
|
||||
import dayjs from 'dayjs';
|
||||
import { defaultTo, noop } from 'lodash-es';
|
||||
import debounce from 'lodash-es/debounce';
|
||||
import { CheckCircle, ChevronDown, Clock } from 'lucide-react';
|
||||
import {
|
||||
ChangeEvent,
|
||||
Dispatch,
|
||||
SetStateAction,
|
||||
useEffect,
|
||||
useState,
|
||||
} from 'react';
|
||||
import { ChangeEvent, useEffect, useState } from 'react';
|
||||
import { popupContainer } from 'utils/selectPopupContainer';
|
||||
|
||||
import CustomTimePickerPopoverContent from './CustomTimePickerPopoverContent';
|
||||
|
||||
const maxAllowedMinTimeInMonths = 6;
|
||||
|
||||
interface CustomTimePickerProps {
|
||||
onSelect: (value: string) => void;
|
||||
onError: (value: boolean) => void;
|
||||
items: any[];
|
||||
selectedValue: string;
|
||||
selectedTime: string;
|
||||
onValidCustomDateChange: ([t1, t2]: any[]) => void;
|
||||
open: boolean;
|
||||
setOpen: Dispatch<SetStateAction<boolean>>;
|
||||
items: any[];
|
||||
newPopover?: boolean;
|
||||
customDateTimeVisible?: boolean;
|
||||
setCustomDTPickerVisible?: Dispatch<SetStateAction<boolean>>;
|
||||
onCustomDateHandler?: (dateTimeRange: DateTimeRangeType) => void;
|
||||
handleGoLive?: () => void;
|
||||
}
|
||||
|
||||
function CustomTimePicker({
|
||||
@@ -49,15 +28,9 @@ function CustomTimePicker({
|
||||
items,
|
||||
selectedValue,
|
||||
selectedTime,
|
||||
open,
|
||||
setOpen,
|
||||
onValidCustomDateChange,
|
||||
newPopover,
|
||||
customDateTimeVisible,
|
||||
setCustomDTPickerVisible,
|
||||
onCustomDateHandler,
|
||||
handleGoLive,
|
||||
}: CustomTimePickerProps): JSX.Element {
|
||||
const [open, setOpen] = useState(false);
|
||||
const [
|
||||
selectedTimePlaceholderValue,
|
||||
setSelectedTimePlaceholderValue,
|
||||
@@ -83,20 +56,6 @@ function CustomTimePicker({
|
||||
return Options[index].label;
|
||||
}
|
||||
}
|
||||
for (
|
||||
let index = 0;
|
||||
index < RelativeDurationSuggestionOptions.length;
|
||||
index++
|
||||
) {
|
||||
if (RelativeDurationSuggestionOptions[index].value === selectedTime) {
|
||||
return RelativeDurationSuggestionOptions[index].label;
|
||||
}
|
||||
}
|
||||
for (let index = 0; index < FixedDurationSuggestionOptions.length; index++) {
|
||||
if (FixedDurationSuggestionOptions[index].value === selectedTime) {
|
||||
return FixedDurationSuggestionOptions[index].label;
|
||||
}
|
||||
}
|
||||
|
||||
return '';
|
||||
};
|
||||
@@ -181,25 +140,19 @@ function CustomTimePicker({
|
||||
debouncedHandleInputChange(inputValue);
|
||||
};
|
||||
|
||||
const handleSelect = (label: string, value: string): void => {
|
||||
onSelect(value);
|
||||
setSelectedTimePlaceholderValue(label);
|
||||
setInputStatus('');
|
||||
onError(false);
|
||||
setInputErrorMessage(null);
|
||||
setInputValue('');
|
||||
if (value !== 'custom') {
|
||||
hide();
|
||||
}
|
||||
};
|
||||
|
||||
const content = (
|
||||
<div className="time-selection-dropdown-content">
|
||||
<div className="time-options-container">
|
||||
{items?.map(({ value, label }) => (
|
||||
{items.map(({ value, label }) => (
|
||||
<div
|
||||
onClick={(): void => {
|
||||
handleSelect(label, value);
|
||||
onSelect(value);
|
||||
setSelectedTimePlaceholderValue(label);
|
||||
setInputStatus('');
|
||||
onError(false);
|
||||
setInputErrorMessage(null);
|
||||
setInputValue('');
|
||||
hide();
|
||||
}}
|
||||
key={value}
|
||||
className={cx(
|
||||
@@ -231,27 +184,11 @@ function CustomTimePicker({
|
||||
)}
|
||||
placement="bottomRight"
|
||||
getPopupContainer={popupContainer}
|
||||
rootClassName="date-time-root"
|
||||
content={
|
||||
newPopover ? (
|
||||
<CustomTimePickerPopoverContent
|
||||
setIsOpen={setOpen}
|
||||
customDateTimeVisible={defaultTo(customDateTimeVisible, false)}
|
||||
setCustomDTPickerVisible={defaultTo(setCustomDTPickerVisible, noop)}
|
||||
onCustomDateHandler={defaultTo(onCustomDateHandler, noop)}
|
||||
onSelectHandler={handleSelect}
|
||||
handleGoLive={defaultTo(handleGoLive, noop)}
|
||||
options={items}
|
||||
selectedTime={selectedTime}
|
||||
/>
|
||||
) : (
|
||||
content
|
||||
)
|
||||
}
|
||||
content={content}
|
||||
arrow={false}
|
||||
trigger="hover"
|
||||
open={open}
|
||||
onOpenChange={handleOpenChange}
|
||||
trigger={['click']}
|
||||
style={{
|
||||
padding: 0,
|
||||
}}
|
||||
@@ -299,11 +236,3 @@ function CustomTimePicker({
|
||||
}
|
||||
|
||||
export default CustomTimePicker;
|
||||
|
||||
CustomTimePicker.defaultProps = {
|
||||
newPopover: false,
|
||||
customDateTimeVisible: false,
|
||||
setCustomDTPickerVisible: noop,
|
||||
onCustomDateHandler: noop,
|
||||
handleGoLive: noop,
|
||||
};
|
||||
|
||||
@@ -1,133 +0,0 @@
|
||||
import './CustomTimePicker.styles.scss';
|
||||
|
||||
import { Button, DatePicker } from 'antd';
|
||||
import cx from 'classnames';
|
||||
import ROUTES from 'constants/routes';
|
||||
import { DateTimeRangeType } from 'container/TopNav/CustomDateTimeModal';
|
||||
import {
|
||||
Option,
|
||||
RelativeDurationSuggestionOptions,
|
||||
} from 'container/TopNav/DateTimeSelectionV2/config';
|
||||
import dayjs, { Dayjs } from 'dayjs';
|
||||
import { Dispatch, SetStateAction, useMemo } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { AppState } from 'store/reducers';
|
||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
|
||||
interface CustomTimePickerPopoverContentProps {
|
||||
options: any[];
|
||||
setIsOpen: Dispatch<SetStateAction<boolean>>;
|
||||
customDateTimeVisible: boolean;
|
||||
setCustomDTPickerVisible: Dispatch<SetStateAction<boolean>>;
|
||||
onCustomDateHandler: (dateTimeRange: DateTimeRangeType) => void;
|
||||
onSelectHandler: (label: string, value: string) => void;
|
||||
handleGoLive: () => void;
|
||||
selectedTime: string;
|
||||
}
|
||||
|
||||
function CustomTimePickerPopoverContent({
|
||||
options,
|
||||
setIsOpen,
|
||||
customDateTimeVisible,
|
||||
setCustomDTPickerVisible,
|
||||
onCustomDateHandler,
|
||||
onSelectHandler,
|
||||
handleGoLive,
|
||||
selectedTime,
|
||||
}: CustomTimePickerPopoverContentProps): JSX.Element {
|
||||
const { RangePicker } = DatePicker;
|
||||
const { pathname } = useLocation();
|
||||
|
||||
const { maxTime, minTime } = useSelector<AppState, GlobalReducer>(
|
||||
(state) => state.globalTime,
|
||||
);
|
||||
|
||||
const isLogsExplorerPage = useMemo(() => pathname === ROUTES.LOGS_EXPLORER, [
|
||||
pathname,
|
||||
]);
|
||||
|
||||
const disabledDate = (current: Dayjs): boolean => {
|
||||
const currentDay = dayjs(current);
|
||||
return currentDay.isAfter(dayjs());
|
||||
};
|
||||
|
||||
const onPopoverClose = (visible: boolean): void => {
|
||||
if (!visible) {
|
||||
setCustomDTPickerVisible(false);
|
||||
}
|
||||
setIsOpen(visible);
|
||||
};
|
||||
|
||||
const onModalOkHandler = (date_time: any): void => {
|
||||
if (date_time?.[1]) {
|
||||
onPopoverClose(false);
|
||||
}
|
||||
onCustomDateHandler(date_time);
|
||||
};
|
||||
function getTimeChips(options: Option[]): JSX.Element {
|
||||
return (
|
||||
<div className="relative-date-time-section">
|
||||
{options.map((option) => (
|
||||
<Button
|
||||
type="text"
|
||||
className="time-btns"
|
||||
key={option.label + option.value}
|
||||
onClick={(): void => {
|
||||
onSelectHandler(option.label, option.value);
|
||||
}}
|
||||
>
|
||||
{option.label}
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="date-time-popover">
|
||||
<div className="date-time-options">
|
||||
{isLogsExplorerPage && (
|
||||
<Button className="data-time-live" type="text" onClick={handleGoLive}>
|
||||
Live
|
||||
</Button>
|
||||
)}
|
||||
{options.map((option) => (
|
||||
<Button
|
||||
type="text"
|
||||
key={option.label + option.value}
|
||||
onClick={(): void => {
|
||||
onSelectHandler(option.label, option.value);
|
||||
}}
|
||||
className={cx(
|
||||
'date-time-options-btn',
|
||||
selectedTime === option.value && 'active',
|
||||
)}
|
||||
>
|
||||
{option.label}
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
<div className="relative-date-time">
|
||||
{selectedTime === 'custom' || customDateTimeVisible ? (
|
||||
<RangePicker
|
||||
disabledDate={disabledDate}
|
||||
allowClear
|
||||
onCalendarChange={onModalOkHandler}
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
{...(selectedTime === 'custom' && {
|
||||
defaultValue: [dayjs(minTime / 1000000), dayjs(maxTime / 1000000)],
|
||||
})}
|
||||
/>
|
||||
) : (
|
||||
<div>
|
||||
<div className="time-heading">RELATIVE TIMES</div>
|
||||
<div>{getTimeChips(RelativeDurationSuggestionOptions)}</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default CustomTimePickerPopoverContent;
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
} from '@ant-design/icons';
|
||||
import {
|
||||
Button,
|
||||
Card,
|
||||
Col,
|
||||
Dropdown,
|
||||
MenuProps,
|
||||
@@ -151,100 +152,95 @@ function ExplorerCard({
|
||||
const saveButtonType = isQueryUpdated ? 'default' : 'primary';
|
||||
const saveButtonIcon = isQueryUpdated ? null : <SaveOutlined />;
|
||||
|
||||
const showSaveView = false;
|
||||
|
||||
return (
|
||||
<>
|
||||
{showSaveView && (
|
||||
<ExplorerCardHeadContainer size="small">
|
||||
<Row align="middle">
|
||||
<Col span={6}>
|
||||
<Space>
|
||||
<Typography>Query Builder</Typography>
|
||||
<TextToolTip
|
||||
url={ExploreHeaderToolTip.url}
|
||||
text={ExploreHeaderToolTip.text}
|
||||
useFilledIcon={false}
|
||||
/>
|
||||
</Space>
|
||||
</Col>
|
||||
<OffSetCol span={18}>
|
||||
<Space size="large">
|
||||
{viewsData?.data.data && viewsData?.data.data.length && (
|
||||
<Space>
|
||||
<Select
|
||||
getPopupContainer={popupContainer}
|
||||
loading={isLoading || isRefetching}
|
||||
showSearch
|
||||
placeholder="Select a view"
|
||||
dropdownStyle={DropDownOverlay}
|
||||
dropdownMatchSelectWidth={false}
|
||||
optionLabelProp="value"
|
||||
value={viewName || undefined}
|
||||
>
|
||||
{viewsData?.data.data.map((view) => (
|
||||
<Select.Option key={view.uuid} value={view.name}>
|
||||
<MenuItemGenerator
|
||||
viewName={view.name}
|
||||
viewKey={viewKey}
|
||||
createdBy={view.createdBy}
|
||||
uuid={view.uuid}
|
||||
refetchAllView={refetchAllView}
|
||||
viewData={viewsData.data.data}
|
||||
sourcePage={sourcepage}
|
||||
/>
|
||||
</Select.Option>
|
||||
))}
|
||||
</Select>
|
||||
</Space>
|
||||
)}
|
||||
{isQueryUpdated && (
|
||||
<Button
|
||||
type="primary"
|
||||
icon={<SaveOutlined />}
|
||||
onClick={onUpdateQueryHandler}
|
||||
<ExplorerCardHeadContainer size="small">
|
||||
<Row align="middle">
|
||||
<Col span={6}>
|
||||
<Space>
|
||||
<Typography>Query Builder</Typography>
|
||||
<TextToolTip
|
||||
url={ExploreHeaderToolTip.url}
|
||||
text={ExploreHeaderToolTip.text}
|
||||
useFilledIcon={false}
|
||||
/>
|
||||
</Space>
|
||||
</Col>
|
||||
<OffSetCol span={18}>
|
||||
<Space size="large">
|
||||
{viewsData?.data.data && viewsData?.data.data.length && (
|
||||
<Space>
|
||||
<Select
|
||||
getPopupContainer={popupContainer}
|
||||
loading={isLoading || isRefetching}
|
||||
showSearch
|
||||
placeholder="Select a view"
|
||||
dropdownStyle={DropDownOverlay}
|
||||
dropdownMatchSelectWidth={false}
|
||||
optionLabelProp="value"
|
||||
value={viewName || undefined}
|
||||
>
|
||||
Save changes
|
||||
</Button>
|
||||
)}
|
||||
<Popover
|
||||
getPopupContainer={popupContainer}
|
||||
placement="bottomLeft"
|
||||
trigger="click"
|
||||
content={
|
||||
<SaveViewWithName
|
||||
sourcePage={sourcepage}
|
||||
handlePopOverClose={handleOpenChange}
|
||||
refetchAllView={refetchAllView}
|
||||
/>
|
||||
}
|
||||
showArrow={false}
|
||||
open={isOpen}
|
||||
onOpenChange={handleOpenChange}
|
||||
{viewsData?.data.data.map((view) => (
|
||||
<Select.Option key={view.uuid} value={view.name}>
|
||||
<MenuItemGenerator
|
||||
viewName={view.name}
|
||||
viewKey={viewKey}
|
||||
createdBy={view.createdBy}
|
||||
uuid={view.uuid}
|
||||
refetchAllView={refetchAllView}
|
||||
viewData={viewsData.data.data}
|
||||
sourcePage={sourcepage}
|
||||
/>
|
||||
</Select.Option>
|
||||
))}
|
||||
</Select>
|
||||
</Space>
|
||||
)}
|
||||
{isQueryUpdated && (
|
||||
<Button
|
||||
type="primary"
|
||||
icon={<SaveOutlined />}
|
||||
onClick={onUpdateQueryHandler}
|
||||
>
|
||||
<Button
|
||||
type={saveButtonType}
|
||||
icon={saveButtonIcon}
|
||||
data-testid="traces-save-view-action"
|
||||
>
|
||||
{isQueryUpdated
|
||||
? SaveButtonText.SAVE_AS_NEW_VIEW
|
||||
: SaveButtonText.SAVE_VIEW}
|
||||
</Button>
|
||||
</Popover>
|
||||
<ShareAltOutlined onClick={onCopyUrlHandler} />
|
||||
{viewKey && (
|
||||
<Dropdown trigger={['click']} menu={moreOptionMenu}>
|
||||
<MoreOutlined />
|
||||
</Dropdown>
|
||||
)}
|
||||
</Space>
|
||||
</OffSetCol>
|
||||
</Row>
|
||||
</ExplorerCardHeadContainer>
|
||||
)}
|
||||
|
||||
<div>{children}</div>
|
||||
Save changes
|
||||
</Button>
|
||||
)}
|
||||
<Popover
|
||||
getPopupContainer={popupContainer}
|
||||
placement="bottomLeft"
|
||||
trigger="click"
|
||||
content={
|
||||
<SaveViewWithName
|
||||
sourcePage={sourcepage}
|
||||
handlePopOverClose={handleOpenChange}
|
||||
refetchAllView={refetchAllView}
|
||||
/>
|
||||
}
|
||||
showArrow={false}
|
||||
open={isOpen}
|
||||
onOpenChange={handleOpenChange}
|
||||
>
|
||||
<Button
|
||||
type={saveButtonType}
|
||||
icon={saveButtonIcon}
|
||||
data-testid="traces-save-view-action"
|
||||
>
|
||||
{isQueryUpdated
|
||||
? SaveButtonText.SAVE_AS_NEW_VIEW
|
||||
: SaveButtonText.SAVE_VIEW}
|
||||
</Button>
|
||||
</Popover>
|
||||
<ShareAltOutlined onClick={onCopyUrlHandler} />
|
||||
{viewKey && (
|
||||
<Dropdown trigger={['click']} menu={moreOptionMenu}>
|
||||
<MoreOutlined />
|
||||
</Dropdown>
|
||||
)}
|
||||
</Space>
|
||||
</OffSetCol>
|
||||
</Row>
|
||||
</ExplorerCardHeadContainer>
|
||||
<Card>{children}</Card>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ import styled, { CSSProperties } from 'styled-components';
|
||||
|
||||
export const ExplorerCardHeadContainer = styled(Card)`
|
||||
margin: 1rem 0;
|
||||
padding: 0;
|
||||
`;
|
||||
|
||||
export const OffSetCol = styled(Col)`
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { fireEvent, render, screen } from '@testing-library/react';
|
||||
import ROUTES from 'constants/routes';
|
||||
import MockQueryClientProvider from 'providers/test/MockQueryClientProvider';
|
||||
import { DataSource } from 'types/common/queryBuilder';
|
||||
@@ -46,7 +46,7 @@ describe('ExplorerCard', () => {
|
||||
<ExplorerCard sourcepage={DataSource.TRACES}>child</ExplorerCard>
|
||||
</MockQueryClientProvider>,
|
||||
);
|
||||
expect(screen.queryByText('Query Builder')).not.toBeInTheDocument();
|
||||
expect(screen.getByText('Query Builder')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders a save view button', () => {
|
||||
@@ -55,6 +55,19 @@ describe('ExplorerCard', () => {
|
||||
<ExplorerCard sourcepage={DataSource.TRACES}>child</ExplorerCard>
|
||||
</MockQueryClientProvider>,
|
||||
);
|
||||
expect(screen.queryByText('Save view')).not.toBeInTheDocument();
|
||||
expect(screen.getByText('Save view')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should see all the view listed in dropdown', async () => {
|
||||
const screen = render(
|
||||
<ExplorerCard sourcepage={DataSource.TRACES}>Mock Children</ExplorerCard>,
|
||||
);
|
||||
const selectPlaceholder = screen.getByText('Select a view');
|
||||
|
||||
fireEvent.mouseDown(selectPlaceholder);
|
||||
const viewNameText = await screen.getAllByText('View 1');
|
||||
viewNameText.forEach((element) => {
|
||||
expect(element).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -14,7 +14,7 @@ import {
|
||||
SaveViewHandlerProps,
|
||||
} from './types';
|
||||
|
||||
export const showErrorNotification = (
|
||||
const showErrorNotification = (
|
||||
notifications: NotificationInstance,
|
||||
err: Error,
|
||||
): void => {
|
||||
@@ -90,14 +90,6 @@ export const isQueryUpdatedInView = ({
|
||||
// Omitting id from aggregateAttribute and groupBy
|
||||
const updatedCurrentQuery = omitIdFromQuery(stagedQuery);
|
||||
|
||||
if (
|
||||
updatedCurrentQuery?.builder === undefined ||
|
||||
updatedCurrentQuery.clickhouse_sql === undefined ||
|
||||
updatedCurrentQuery.promql === undefined
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return (
|
||||
panelType !== currentPanelType ||
|
||||
!isEqual(query.builder, updatedCurrentQuery?.builder) ||
|
||||
|
||||
@@ -3,11 +3,8 @@ import { AddToQueryHOCProps } from 'components/Logs/AddToQueryHOC';
|
||||
import { ActionItemProps } from 'container/LogDetailedView/ActionItem';
|
||||
import { ILog } from 'types/api/logs/log';
|
||||
|
||||
import { VIEWS } from './constants';
|
||||
|
||||
export type LogDetailProps = {
|
||||
log: ILog | null;
|
||||
selectedTab: VIEWS;
|
||||
} & Pick<AddToQueryHOCProps, 'onAddToQuery'> &
|
||||
Partial<Pick<ActionItemProps, 'onClickActionItem'>> &
|
||||
Pick<ActionItemProps, 'onClickActionItem'> &
|
||||
Pick<DrawerProps, 'onClose'>;
|
||||
|
||||
@@ -1,224 +0,0 @@
|
||||
.log-detail-drawer {
|
||||
border-left: 1px solid var(--bg-slate-500);
|
||||
background: var(--bg-ink-400);
|
||||
box-shadow: -4px 10px 16px 2px rgba(0, 0, 0, 0.2);
|
||||
|
||||
.ant-drawer-header {
|
||||
padding: 8px 16px;
|
||||
border-bottom: none;
|
||||
|
||||
align-items: stretch;
|
||||
|
||||
border-bottom: 1px solid var(--bg-slate-500);
|
||||
background: var(--bg-ink-400);
|
||||
}
|
||||
|
||||
.ant-drawer-close {
|
||||
margin-inline-end: 0px;
|
||||
}
|
||||
|
||||
.ant-drawer-body {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.title {
|
||||
color: var(--text-vanilla-400);
|
||||
font-family: Inter;
|
||||
font-size: var(--font-size-sm);
|
||||
font-style: normal;
|
||||
font-weight: var(--font-weight-normal);
|
||||
line-height: 20px; /* 142.857% */
|
||||
letter-spacing: -0.07px;
|
||||
}
|
||||
|
||||
.radio-button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding-top: var(--padding-1);
|
||||
border: 1px solid var(--bg-slate-400);
|
||||
background: var(--bg-ink-300);
|
||||
box-shadow: 0px 0px 8px 0px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.log-detail-drawer__log {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
position: relative;
|
||||
|
||||
.log-body {
|
||||
font-family: 'SF Mono';
|
||||
font-family: 'Space Mono', monospace;
|
||||
|
||||
font-size: var(--font-size-sm);
|
||||
font-weight: var(--font-weight-normal);
|
||||
line-height: 18px;
|
||||
letter-spacing: -0.07px;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
color: var(--text-vanilla-400);
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.log-type-indicator {
|
||||
height: 24px;
|
||||
border: 2px solid var(--bg-slate-400);
|
||||
border-radius: 5px;
|
||||
margin-left: 0;
|
||||
|
||||
&.INFO {
|
||||
border-color: #1d212d;
|
||||
}
|
||||
|
||||
&.WARNING {
|
||||
border-color: #ffcd56;
|
||||
}
|
||||
|
||||
&.ERROR {
|
||||
border-color: #e5484d;
|
||||
}
|
||||
}
|
||||
|
||||
.log-overflow-shadow {
|
||||
background: linear-gradient(270deg, #121317 10.4%, rgba(18, 19, 23, 0) 100%);
|
||||
|
||||
width: 196px;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.tabs-and-search {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin: 16px 0;
|
||||
|
||||
.action-btn {
|
||||
border-radius: 2px;
|
||||
border: 1px solid var(--bg-slate-400);
|
||||
background: var(--bg-ink-300);
|
||||
box-shadow: 0px 0px 8px 0px rgba(0, 0, 0, 0.1);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.json-action-btn {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.views-tabs {
|
||||
color: var(--text-vanilla-400);
|
||||
|
||||
.view-title {
|
||||
display: flex;
|
||||
gap: var(--margin-2);
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: var(--font-size-xs);
|
||||
font-style: normal;
|
||||
font-weight: var(--font-weight-normal);
|
||||
}
|
||||
|
||||
.tab {
|
||||
border: 1px solid var(--bg-slate-400);
|
||||
width: 114px;
|
||||
}
|
||||
|
||||
.tab::before {
|
||||
background: var(--bg-slate-400);
|
||||
}
|
||||
|
||||
.selected_view {
|
||||
background: var(--bg-slate-300);
|
||||
color: var(--text-vanilla-100);
|
||||
border: 1px solid var(--bg-slate-400);
|
||||
}
|
||||
|
||||
.selected_view::before {
|
||||
background: var(--bg-slate-400);
|
||||
}
|
||||
}
|
||||
|
||||
.search-input {
|
||||
margin-top: var(--margin-2);
|
||||
border: 1px solid var(--bg-slate-400);
|
||||
height: 46px;
|
||||
padding: var(--padding-1) var(--padding-2);
|
||||
}
|
||||
}
|
||||
|
||||
.lightMode {
|
||||
.ant-drawer-header {
|
||||
border-bottom: 1px solid var(--bg-vanilla-400);
|
||||
background: var(--bg-vanilla-100);
|
||||
}
|
||||
|
||||
.log-detail-drawer {
|
||||
.title {
|
||||
color: var(--text-ink-300);
|
||||
}
|
||||
|
||||
.log-detail-drawer__log {
|
||||
.log-overflow-shadow {
|
||||
background: linear-gradient(
|
||||
270deg,
|
||||
var(--bg-vanilla-100) 10.4%,
|
||||
rgba(255, 255, 255, 0) 100%
|
||||
);
|
||||
}
|
||||
|
||||
.log-type-indicator {
|
||||
border: 2px solid var(--bg-vanilla-400);
|
||||
}
|
||||
|
||||
.ant-typography {
|
||||
color: var(--text-ink-300);
|
||||
background: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
.radio-button {
|
||||
border: 1px solid var(--bg-vanilla-400);
|
||||
background: var(--bg-vanilla-100);
|
||||
color: var(--text-ink-300);
|
||||
}
|
||||
|
||||
.views-tabs {
|
||||
.tab {
|
||||
background: var(--bg-vanilla-100);
|
||||
}
|
||||
|
||||
.selected_view {
|
||||
background: var(--bg-vanilla-300);
|
||||
border: 1px solid var(--bg-slate-300);
|
||||
color: var(--text-ink-400);
|
||||
}
|
||||
|
||||
.selected_view::before {
|
||||
background: var(--bg-vanilla-300);
|
||||
border-left: 1px solid var(--bg-slate-300);
|
||||
}
|
||||
}
|
||||
|
||||
.tabs-and-search {
|
||||
.action-btn {
|
||||
border: 1px solid var(--bg-vanilla-400);
|
||||
background: var(--bg-vanilla-100);
|
||||
color: var(--text-ink-300);
|
||||
}
|
||||
}
|
||||
|
||||
.search-input {
|
||||
border: 1px solid var(--bg-vanilla-200);
|
||||
background: var(--bg-vanilla-100);
|
||||
color: var(--text-ink-300);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
.query-builder-search-wrapper {
|
||||
margin-top: 10px;
|
||||
height: 46px;
|
||||
border: 1px solid var(--bg-slate-400);
|
||||
border-bottom: none;
|
||||
|
||||
.ant-select-selector {
|
||||
border: none !important;
|
||||
}
|
||||
}
|
||||
@@ -1,77 +0,0 @@
|
||||
import './QueryBuilderSearchWrapper.styles.scss';
|
||||
|
||||
import useInitialQuery from 'container/LogsExplorerContext/useInitialQuery';
|
||||
import QueryBuilderSearch from 'container/QueryBuilder/filters/QueryBuilderSearch';
|
||||
import { Dispatch, SetStateAction, useEffect } from 'react';
|
||||
import { ILog } from 'types/api/logs/log';
|
||||
import { Query, TagFilter } from 'types/api/queryBuilder/queryBuilderData';
|
||||
|
||||
function QueryBuilderSearchWrapper({
|
||||
log,
|
||||
filters,
|
||||
contextQuery,
|
||||
isEdit,
|
||||
suffixIcon,
|
||||
setFilters,
|
||||
setContextQuery,
|
||||
}: QueryBuilderSearchWraperProps): JSX.Element {
|
||||
const initialContextQuery = useInitialQuery(log);
|
||||
|
||||
useEffect(() => {
|
||||
setContextQuery(initialContextQuery);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
const handleSearch = (tagFilters: TagFilter): void => {
|
||||
const tagFiltersLength = tagFilters.items.length;
|
||||
|
||||
if (
|
||||
(!tagFiltersLength && (!filters || !filters.items.length)) ||
|
||||
tagFiltersLength === filters?.items.length ||
|
||||
!contextQuery
|
||||
)
|
||||
return;
|
||||
|
||||
const nextQuery: Query = {
|
||||
...contextQuery,
|
||||
builder: {
|
||||
...contextQuery.builder,
|
||||
queryData: contextQuery.builder.queryData.map((item) => ({
|
||||
...item,
|
||||
filters: tagFilters,
|
||||
})),
|
||||
},
|
||||
};
|
||||
|
||||
setFilters({ ...tagFilters });
|
||||
setContextQuery({ ...nextQuery });
|
||||
};
|
||||
|
||||
// eslint-disable-next-line react/jsx-no-useless-fragment
|
||||
if (!contextQuery || !isEdit) return <></>;
|
||||
|
||||
return (
|
||||
<QueryBuilderSearch
|
||||
query={contextQuery?.builder.queryData[0]}
|
||||
onChange={handleSearch}
|
||||
className="query-builder-search-wrapper"
|
||||
suffixIcon={suffixIcon}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
interface QueryBuilderSearchWraperProps {
|
||||
log: ILog;
|
||||
isEdit: boolean;
|
||||
contextQuery: Query | undefined;
|
||||
setContextQuery: Dispatch<SetStateAction<Query | undefined>>;
|
||||
filters: TagFilter | null;
|
||||
setFilters: Dispatch<SetStateAction<TagFilter | null>>;
|
||||
suffixIcon?: React.ReactNode;
|
||||
}
|
||||
|
||||
QueryBuilderSearchWrapper.defaultProps = {
|
||||
suffixIcon: undefined,
|
||||
};
|
||||
|
||||
export default QueryBuilderSearchWrapper;
|
||||
@@ -1,7 +0,0 @@
|
||||
export const VIEW_TYPES = {
|
||||
OVERVIEW: 'OVERVIEW',
|
||||
JSON: 'JSON',
|
||||
CONTEXT: 'CONTEXT',
|
||||
} as const;
|
||||
|
||||
export type VIEWS = typeof VIEW_TYPES[keyof typeof VIEW_TYPES];
|
||||
@@ -1,207 +1,50 @@
|
||||
/* eslint-disable sonarjs/cognitive-complexity */
|
||||
import './LogDetails.styles.scss';
|
||||
|
||||
import { Color, Spacing } from '@signozhq/design-tokens';
|
||||
import { Button, Divider, Drawer, Radio, Tooltip, Typography } from 'antd';
|
||||
import { RadioChangeEvent } from 'antd/lib';
|
||||
import cx from 'classnames';
|
||||
import { LogType } from 'components/Logs/LogStateIndicator/LogStateIndicator';
|
||||
import ContextView from 'container/LogDetailedView/ContextView/ContextView';
|
||||
import { Drawer, Tabs } from 'antd';
|
||||
import JSONView from 'container/LogDetailedView/JsonView';
|
||||
import Overview from 'container/LogDetailedView/Overview';
|
||||
import { aggregateAttributesResourcesToString } from 'container/LogDetailedView/utils';
|
||||
import { useIsDarkMode } from 'hooks/useDarkMode';
|
||||
import { useNotifications } from 'hooks/useNotifications';
|
||||
import {
|
||||
Braces,
|
||||
Copy,
|
||||
Filter,
|
||||
HardHat,
|
||||
Table,
|
||||
TextSelect,
|
||||
X,
|
||||
} from 'lucide-react';
|
||||
import { useState } from 'react';
|
||||
import { useCopyToClipboard } from 'react-use';
|
||||
import { Query, TagFilter } from 'types/api/queryBuilder/queryBuilderData';
|
||||
import TableView from 'container/LogDetailedView/TableView';
|
||||
import { useMemo } from 'react';
|
||||
|
||||
import { VIEW_TYPES, VIEWS } from './constants';
|
||||
import { LogDetailProps } from './LogDetail.interfaces';
|
||||
import QueryBuilderSearchWrapper from './QueryBuilderSearchWrapper';
|
||||
|
||||
function LogDetail({
|
||||
log,
|
||||
onClose,
|
||||
onAddToQuery,
|
||||
onClickActionItem,
|
||||
selectedTab,
|
||||
}: LogDetailProps): JSX.Element {
|
||||
const [, copyToClipboard] = useCopyToClipboard();
|
||||
const [selectedView, setSelectedView] = useState<VIEWS>(selectedTab);
|
||||
|
||||
const [isFilterVisibile, setIsFilterVisible] = useState<boolean>(false);
|
||||
|
||||
const [contextQuery, setContextQuery] = useState<Query | undefined>();
|
||||
const [filters, setFilters] = useState<TagFilter | null>(null);
|
||||
const [isEdit, setIsEdit] = useState<boolean>(false);
|
||||
|
||||
const isDarkMode = useIsDarkMode();
|
||||
|
||||
const { notifications } = useNotifications();
|
||||
|
||||
const LogJsonData = log ? aggregateAttributesResourcesToString(log) : '';
|
||||
|
||||
const handleModeChange = (e: RadioChangeEvent): void => {
|
||||
setSelectedView(e.target.value);
|
||||
setIsEdit(false);
|
||||
setIsFilterVisible(false);
|
||||
};
|
||||
|
||||
const handleFilterVisible = (): void => {
|
||||
setIsFilterVisible(!isFilterVisibile);
|
||||
setIsEdit(!isEdit);
|
||||
};
|
||||
|
||||
const drawerCloseHandler = (
|
||||
e: React.MouseEvent | React.KeyboardEvent,
|
||||
): void => {
|
||||
if (onClose) {
|
||||
onClose(e);
|
||||
}
|
||||
};
|
||||
|
||||
const handleJSONCopy = (): void => {
|
||||
copyToClipboard(LogJsonData);
|
||||
notifications.success({
|
||||
message: 'Copied to clipboard',
|
||||
});
|
||||
};
|
||||
|
||||
if (!log) {
|
||||
// eslint-disable-next-line react/jsx-no-useless-fragment
|
||||
return <></>;
|
||||
}
|
||||
|
||||
const logType = log?.attributes_string?.log_level || LogType.INFO;
|
||||
const items = useMemo(
|
||||
() => [
|
||||
{
|
||||
label: 'Table',
|
||||
key: '1',
|
||||
children: log && (
|
||||
<TableView
|
||||
logData={log}
|
||||
onAddToQuery={onAddToQuery}
|
||||
onClickActionItem={onClickActionItem}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
label: 'JSON',
|
||||
key: '2',
|
||||
children: log && <JSONView logData={log} />,
|
||||
},
|
||||
],
|
||||
[log, onAddToQuery, onClickActionItem],
|
||||
);
|
||||
|
||||
return (
|
||||
<Drawer
|
||||
width="60%"
|
||||
title={
|
||||
<>
|
||||
<Divider type="vertical" className={cx('log-type-indicator', LogType)} />
|
||||
<Typography.Text className="title">Log details</Typography.Text>
|
||||
</>
|
||||
}
|
||||
title="Log Details"
|
||||
placement="right"
|
||||
// closable
|
||||
onClose={drawerCloseHandler}
|
||||
closable
|
||||
onClose={onClose}
|
||||
open={log !== null}
|
||||
style={{
|
||||
overscrollBehavior: 'contain',
|
||||
background: isDarkMode ? Color.BG_INK_400 : Color.BG_VANILLA_100,
|
||||
}}
|
||||
className="log-detail-drawer"
|
||||
style={{ overscrollBehavior: 'contain' }}
|
||||
destroyOnClose
|
||||
closeIcon={<X size={16} style={{ marginTop: Spacing.MARGIN_1 }} />}
|
||||
>
|
||||
<div className="log-detail-drawer__log">
|
||||
<Divider type="vertical" className={cx('log-type-indicator', logType)} />
|
||||
<Tooltip title={log?.body} placement="left">
|
||||
<Typography.Text className="log-body">{log?.body}</Typography.Text>
|
||||
</Tooltip>
|
||||
|
||||
<div className="log-overflow-shadow"> </div>
|
||||
</div>
|
||||
|
||||
<div className="tabs-and-search">
|
||||
<Radio.Group
|
||||
className="views-tabs"
|
||||
onChange={handleModeChange}
|
||||
value={selectedView}
|
||||
>
|
||||
<Radio.Button
|
||||
className={
|
||||
// eslint-disable-next-line sonarjs/no-duplicate-string
|
||||
selectedView === VIEW_TYPES.OVERVIEW ? 'selected_view tab' : 'tab'
|
||||
}
|
||||
value={VIEW_TYPES.OVERVIEW}
|
||||
>
|
||||
<div className="view-title">
|
||||
<Table size={14} />
|
||||
Overview
|
||||
</div>
|
||||
</Radio.Button>
|
||||
<Radio.Button
|
||||
className={selectedView === VIEW_TYPES.JSON ? 'selected_view tab' : 'tab'}
|
||||
value={VIEW_TYPES.JSON}
|
||||
>
|
||||
<div className="view-title">
|
||||
<Braces size={14} />
|
||||
JSON
|
||||
</div>
|
||||
</Radio.Button>
|
||||
<Radio.Button
|
||||
className={
|
||||
selectedView === VIEW_TYPES.CONTEXT ? 'selected_view tab' : 'tab'
|
||||
}
|
||||
value={VIEW_TYPES.CONTEXT}
|
||||
>
|
||||
<div className="view-title">
|
||||
<TextSelect size={14} />
|
||||
Context
|
||||
</div>
|
||||
</Radio.Button>
|
||||
</Radio.Group>
|
||||
|
||||
{selectedView === VIEW_TYPES.JSON && (
|
||||
<div className="json-action-btn">
|
||||
<Button
|
||||
className="action-btn"
|
||||
icon={<Copy size={16} />}
|
||||
onClick={handleJSONCopy}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{selectedView === VIEW_TYPES.CONTEXT && (
|
||||
<Button
|
||||
className="action-btn"
|
||||
icon={<Filter size={16} />}
|
||||
onClick={handleFilterVisible}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<QueryBuilderSearchWrapper
|
||||
isEdit={isEdit}
|
||||
log={log}
|
||||
filters={filters}
|
||||
setContextQuery={setContextQuery}
|
||||
setFilters={setFilters}
|
||||
contextQuery={contextQuery}
|
||||
suffixIcon={
|
||||
<HardHat size={12} style={{ paddingRight: Spacing.PADDING_2 }} />
|
||||
}
|
||||
/>
|
||||
|
||||
{selectedView === VIEW_TYPES.OVERVIEW && (
|
||||
<Overview
|
||||
logData={log}
|
||||
onAddToQuery={onAddToQuery}
|
||||
onClickActionItem={onClickActionItem}
|
||||
/>
|
||||
)}
|
||||
{selectedView === VIEW_TYPES.JSON && <JSONView logData={log} />}
|
||||
|
||||
{selectedView === VIEW_TYPES.CONTEXT && (
|
||||
<ContextView
|
||||
log={log}
|
||||
filters={filters}
|
||||
contextQuery={contextQuery}
|
||||
isEdit={isEdit}
|
||||
/>
|
||||
)}
|
||||
<Tabs defaultActiveKey="1" items={items} />
|
||||
</Drawer>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,103 +0,0 @@
|
||||
.log-field-key {
|
||||
padding-right: 5px;
|
||||
color: var(--text-vanilla-400, #c0c1c3);
|
||||
font-size: 14px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 18px; /* 128.571% */
|
||||
letter-spacing: -0.07px;
|
||||
}
|
||||
.log-value {
|
||||
color: var(--text-vanilla-400, #c0c1c3);
|
||||
font-size: 14px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 18px; /* 128.571% */
|
||||
letter-spacing: -0.07px;
|
||||
}
|
||||
.log-line {
|
||||
display: flex;
|
||||
overflow: hidden;
|
||||
.log-state-indicator {
|
||||
padding-left: 0;
|
||||
}
|
||||
transition: background-color 0.2s ease-in;
|
||||
&:hover {
|
||||
background-color: rgba(171, 189, 255, 0.04) !important;
|
||||
}
|
||||
}
|
||||
|
||||
.log-selected-fields {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
align-items: center;
|
||||
|
||||
.selected-log-field-key {
|
||||
color: var(--bg-robin-400) !important;
|
||||
font-size: 14px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 18px; /* 128.571% */
|
||||
letter-spacing: -0.07px;
|
||||
}
|
||||
|
||||
.selected-log-value {
|
||||
color: var(--bg-sienna-500);
|
||||
border-radius: 2px;
|
||||
background: rgba(173, 127, 88, 0.08);
|
||||
padding: 0px 2px;
|
||||
margin-left: 7px;
|
||||
font-weight: 400;
|
||||
line-height: 18px;
|
||||
letter-spacing: -0.07px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.selected-log-kv {
|
||||
min-height: 24px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
|
||||
.log-action-buttons {
|
||||
position: absolute;
|
||||
transform: translate(-50%, -50%);
|
||||
top: 50%;
|
||||
right: 0;
|
||||
cursor: pointer;
|
||||
height: 32px;
|
||||
width: 68px;
|
||||
border-radius: 2px;
|
||||
border: 1px solid var(--bg-slate-400, #1d212d);
|
||||
background: var(--bg-ink-400, #121317);
|
||||
box-shadow: 0px 0px 8px 0px rgba(0, 0, 0, 0.1);
|
||||
|
||||
.context-btn {
|
||||
width: 50% !important;
|
||||
}
|
||||
.copy-link-btn {
|
||||
width: 50% !important;
|
||||
border-left: 1px solid var(--bg-slate-400, #1d212d) !important;
|
||||
}
|
||||
|
||||
.ant-btn-default {
|
||||
border: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
|
||||
.lightMode {
|
||||
.log-field-key {
|
||||
color: var(--text-slate-400);
|
||||
}
|
||||
.log-value {
|
||||
color: var(--text-slate-400);
|
||||
}
|
||||
.log-line {
|
||||
&:hover {
|
||||
background-color: var(--text-vanilla-200) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,32 +1,35 @@
|
||||
import './ListLogView.styles.scss';
|
||||
|
||||
import { blue } from '@ant-design/colors';
|
||||
import { blue, grey, orange } from '@ant-design/colors';
|
||||
import {
|
||||
CopyFilled,
|
||||
ExpandAltOutlined,
|
||||
LinkOutlined,
|
||||
MonitorOutlined,
|
||||
} from '@ant-design/icons';
|
||||
import Convert from 'ansi-to-html';
|
||||
import { Typography } from 'antd';
|
||||
import LogDetail from 'components/LogDetail';
|
||||
import { VIEW_TYPES } from 'components/LogDetail/constants';
|
||||
import { Button, Divider, Row, Typography } from 'antd';
|
||||
import LogsExplorerContext from 'container/LogsExplorerContext';
|
||||
import dayjs from 'dayjs';
|
||||
import dompurify from 'dompurify';
|
||||
import { useActiveLog } from 'hooks/logs/useActiveLog';
|
||||
import { useCopyLogLink } from 'hooks/logs/useCopyLogLink';
|
||||
import { useNotifications } from 'hooks/useNotifications';
|
||||
// utils
|
||||
import { FlatLogData } from 'lib/logs/flatLogData';
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { useCopyToClipboard } from 'react-use';
|
||||
// interfaces
|
||||
import { IField } from 'types/api/logs/fields';
|
||||
import { ILog } from 'types/api/logs/log';
|
||||
|
||||
// components
|
||||
import AddToQueryHOC, { AddToQueryHOCProps } from '../AddToQueryHOC';
|
||||
import LogLinesActionButtons from '../LogLinesActionButtons/LogLinesActionButtons';
|
||||
import LogStateIndicator, {
|
||||
LogType,
|
||||
} from '../LogStateIndicator/LogStateIndicator';
|
||||
import CopyClipboardHOC from '../CopyClipboardHOC';
|
||||
// styles
|
||||
import {
|
||||
Container,
|
||||
LogContainer,
|
||||
LogText,
|
||||
SelectedLog,
|
||||
Text,
|
||||
TextContainer,
|
||||
} from './styles';
|
||||
@@ -52,10 +55,12 @@ function LogGeneralField({ fieldKey, fieldValue }: LogFieldProps): JSX.Element {
|
||||
|
||||
return (
|
||||
<TextContainer>
|
||||
<Text ellipsis type="secondary" className="log-field-key">
|
||||
{`${fieldKey} : `}
|
||||
<Text ellipsis type="secondary">
|
||||
{`${fieldKey}: `}
|
||||
</Text>
|
||||
<LogText dangerouslySetInnerHTML={html} className="log-value" />
|
||||
<CopyClipboardHOC textToCopy={fieldValue}>
|
||||
<LogText dangerouslySetInnerHTML={html} />
|
||||
</CopyClipboardHOC>
|
||||
</TextContainer>
|
||||
);
|
||||
}
|
||||
@@ -66,23 +71,23 @@ function LogSelectedField({
|
||||
onAddToQuery,
|
||||
}: LogSelectedFieldProps): JSX.Element {
|
||||
return (
|
||||
<div className="log-selected-fields">
|
||||
<SelectedLog>
|
||||
<AddToQueryHOC
|
||||
fieldKey={fieldKey}
|
||||
fieldValue={fieldValue}
|
||||
onAddToQuery={onAddToQuery}
|
||||
>
|
||||
<Typography.Text>
|
||||
<span style={{ color: blue[4] }} className="selected-log-field-key">
|
||||
{fieldKey}
|
||||
</span>
|
||||
<span style={{ color: blue[4] }}>{fieldKey}</span>
|
||||
</Typography.Text>
|
||||
</AddToQueryHOC>
|
||||
<Typography.Text ellipsis className="selected-log-kv">
|
||||
<span className="selected-log-field-key">{': '}</span>
|
||||
<span className="selected-log-value">{fieldValue || "''"}</span>
|
||||
</Typography.Text>
|
||||
</div>
|
||||
<CopyClipboardHOC textToCopy={fieldValue}>
|
||||
<Typography.Text ellipsis>
|
||||
<span>{': '}</span>
|
||||
<span style={{ color: orange[6] }}>{fieldValue || "''"}</span>
|
||||
</Typography.Text>
|
||||
</CopyClipboardHOC>
|
||||
</SelectedLog>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -91,7 +96,6 @@ type ListLogViewProps = {
|
||||
selectedFields: IField[];
|
||||
onSetActiveLog: (log: ILog) => void;
|
||||
onAddToQuery: AddToQueryHOCProps['onAddToQuery'];
|
||||
activeLog?: ILog | null;
|
||||
};
|
||||
|
||||
function ListLogView({
|
||||
@@ -99,42 +103,34 @@ function ListLogView({
|
||||
selectedFields,
|
||||
onSetActiveLog,
|
||||
onAddToQuery,
|
||||
activeLog,
|
||||
}: ListLogViewProps): JSX.Element {
|
||||
const flattenLogData = useMemo(() => FlatLogData(logData), [logData]);
|
||||
|
||||
const [hasActionButtons, setHasActionButtons] = useState<boolean>(false);
|
||||
const [, setCopy] = useCopyToClipboard();
|
||||
const { notifications } = useNotifications();
|
||||
const { isHighlighted, isLogsExplorerPage, onLogCopy } = useCopyLogLink(
|
||||
logData.id,
|
||||
);
|
||||
const {
|
||||
activeLog: activeContextLog,
|
||||
onAddToQuery: handleAddToQuery,
|
||||
onSetActiveLog: handleSetActiveContextLog,
|
||||
onClearActiveLog: handleClearActiveContextLog,
|
||||
} = useActiveLog();
|
||||
|
||||
const handlerClearActiveContextLog = useCallback(
|
||||
(event: React.MouseEvent | React.KeyboardEvent) => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
handleClearActiveContextLog();
|
||||
},
|
||||
[handleClearActiveContextLog],
|
||||
);
|
||||
|
||||
const handleDetailedView = useCallback(() => {
|
||||
onSetActiveLog(logData);
|
||||
}, [logData, onSetActiveLog]);
|
||||
|
||||
const handleShowContext = useCallback(
|
||||
(event: React.MouseEvent) => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
handleSetActiveContextLog(logData);
|
||||
},
|
||||
[logData, handleSetActiveContextLog],
|
||||
);
|
||||
const handleShowContext = useCallback(() => {
|
||||
handleSetActiveContextLog(logData);
|
||||
}, [logData, handleSetActiveContextLog]);
|
||||
|
||||
const handleCopyJSON = (): void => {
|
||||
setCopy(JSON.stringify(logData, null, 2));
|
||||
notifications.success({
|
||||
message: 'Copied to clipboard',
|
||||
});
|
||||
};
|
||||
|
||||
const updatedSelecedFields = useMemo(
|
||||
() => selectedFields.filter((e) => e.name !== 'id'),
|
||||
@@ -149,74 +145,84 @@ function ListLogView({
|
||||
[flattenLogData.timestamp],
|
||||
);
|
||||
|
||||
const logType = logData?.attributes_string?.log_level || LogType.INFO;
|
||||
|
||||
const handleMouseEnter = (): void => {
|
||||
setHasActionButtons(true);
|
||||
};
|
||||
|
||||
const handleMouseLeave = (): void => {
|
||||
setHasActionButtons(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Container
|
||||
$isActiveLog={isHighlighted}
|
||||
onMouseEnter={handleMouseEnter}
|
||||
onMouseLeave={handleMouseLeave}
|
||||
onClick={handleDetailedView}
|
||||
>
|
||||
<div className="log-line">
|
||||
<LogStateIndicator
|
||||
type={logType}
|
||||
isActive={
|
||||
activeLog?.id === logData.id || activeContextLog?.id === logData.id
|
||||
}
|
||||
/>
|
||||
<div>
|
||||
<LogContainer>
|
||||
<LogGeneralField fieldKey="Log" fieldValue={flattenLogData.body} />
|
||||
{flattenLogData.stream && (
|
||||
<LogGeneralField fieldKey="Stream" fieldValue={flattenLogData.stream} />
|
||||
)}
|
||||
<LogGeneralField fieldKey="Timestamp" fieldValue={timestampValue} />
|
||||
|
||||
{updatedSelecedFields.map((field) =>
|
||||
isValidLogField(flattenLogData[field.name] as never) ? (
|
||||
<LogSelectedField
|
||||
key={field.name}
|
||||
fieldKey={field.name}
|
||||
fieldValue={flattenLogData[field.name] as never}
|
||||
onAddToQuery={onAddToQuery}
|
||||
/>
|
||||
) : null,
|
||||
)}
|
||||
</LogContainer>
|
||||
</div>
|
||||
<Container $isActiveLog={isHighlighted}>
|
||||
<div>
|
||||
<LogContainer>
|
||||
<>
|
||||
<LogGeneralField fieldKey="log" fieldValue={flattenLogData.body} />
|
||||
{flattenLogData.stream && (
|
||||
<LogGeneralField fieldKey="stream" fieldValue={flattenLogData.stream} />
|
||||
)}
|
||||
<LogGeneralField fieldKey="timestamp" fieldValue={timestampValue} />
|
||||
</>
|
||||
</LogContainer>
|
||||
<div>
|
||||
{updatedSelecedFields.map((field) =>
|
||||
isValidLogField(flattenLogData[field.name] as never) ? (
|
||||
<LogSelectedField
|
||||
key={field.name}
|
||||
fieldKey={field.name}
|
||||
fieldValue={flattenLogData[field.name] as never}
|
||||
onAddToQuery={onAddToQuery}
|
||||
/>
|
||||
) : null,
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<Divider style={{ padding: 0, margin: '0.4rem 0', opacity: 0.5 }} />
|
||||
<Row>
|
||||
<Button
|
||||
size="small"
|
||||
type="text"
|
||||
onClick={handleDetailedView}
|
||||
style={{ color: blue[5] }}
|
||||
icon={<ExpandAltOutlined />}
|
||||
>
|
||||
View Details
|
||||
</Button>
|
||||
<Button
|
||||
size="small"
|
||||
type="text"
|
||||
onClick={handleCopyJSON}
|
||||
style={{ color: grey[1] }}
|
||||
icon={<CopyFilled />}
|
||||
>
|
||||
Copy JSON
|
||||
</Button>
|
||||
|
||||
{hasActionButtons && isLogsExplorerPage && (
|
||||
<LogLinesActionButtons
|
||||
handleShowContext={handleShowContext}
|
||||
onLogCopy={onLogCopy}
|
||||
{isLogsExplorerPage && (
|
||||
<>
|
||||
<Button
|
||||
size="small"
|
||||
type="text"
|
||||
onClick={handleShowContext}
|
||||
style={{ color: grey[1] }}
|
||||
icon={<MonitorOutlined />}
|
||||
>
|
||||
Show in Context
|
||||
</Button>
|
||||
<Button
|
||||
size="small"
|
||||
type="text"
|
||||
onClick={onLogCopy}
|
||||
style={{ color: grey[1] }}
|
||||
icon={<LinkOutlined />}
|
||||
>
|
||||
Copy Link
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
|
||||
{activeContextLog && (
|
||||
<LogsExplorerContext
|
||||
log={activeContextLog}
|
||||
onClose={handleClearActiveContextLog}
|
||||
/>
|
||||
)}
|
||||
</Container>
|
||||
{activeContextLog && (
|
||||
<LogDetail
|
||||
log={activeContextLog}
|
||||
onAddToQuery={handleAddToQuery}
|
||||
selectedTab={VIEW_TYPES.CONTEXT}
|
||||
onClose={handlerClearActiveContextLog}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
</Row>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
ListLogView.defaultProps = {
|
||||
activeLog: null,
|
||||
};
|
||||
|
||||
export default ListLogView;
|
||||
|
||||
@@ -7,7 +7,6 @@ export const Container = styled(Card)<{
|
||||
}>`
|
||||
width: 100% !important;
|
||||
margin-bottom: 0.3rem;
|
||||
cursor: pointer;
|
||||
.ant-card-body {
|
||||
padding: 0.3rem 0.6rem;
|
||||
}
|
||||
@@ -30,13 +29,11 @@ export const TextContainer = styled.div`
|
||||
|
||||
export const LogContainer = styled.div`
|
||||
margin-left: 0.5rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
`;
|
||||
|
||||
export const LogText = styled.div`
|
||||
display: inline-block;
|
||||
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
.log-line-action-buttons {
|
||||
display: flex;
|
||||
position: absolute;
|
||||
transform: translate(-50%, -50%);
|
||||
top: 50%;
|
||||
right: 0;
|
||||
cursor: pointer;
|
||||
border-radius: 2px;
|
||||
border: 1px solid var(--bg-slate-400);
|
||||
background: var(--bg-ink-400);
|
||||
|
||||
.ant-btn-default {
|
||||
border: none;
|
||||
box-shadow: none;
|
||||
padding: 9px;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
display: flex;
|
||||
|
||||
&.active-tab {
|
||||
background-color: var(--bg-slate-400);
|
||||
}
|
||||
}
|
||||
|
||||
.copy-log-btn {
|
||||
border-left: 1px solid var(--bg-slate-400);
|
||||
border-color: var(--bg-slate-400) !important;
|
||||
}
|
||||
}
|
||||
|
||||
.lightMode {
|
||||
.log-line-action-buttons {
|
||||
border: 1px solid var(--bg-vanilla-400);
|
||||
background: var(--bg-vanilla-400);
|
||||
|
||||
.ant-btn-default {
|
||||
}
|
||||
|
||||
.copy-log-btn {
|
||||
border-left: 1px solid var(--bg-vanilla-400);
|
||||
border-color: var(--bg-vanilla-400) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
import './LogLinesActionButtons.styles.scss';
|
||||
|
||||
import { LinkOutlined } from '@ant-design/icons';
|
||||
import { Button, Tooltip } from 'antd';
|
||||
import { TextSelect } from 'lucide-react';
|
||||
import { MouseEventHandler } from 'react';
|
||||
|
||||
export interface LogLinesActionButtonsProps {
|
||||
handleShowContext: MouseEventHandler<HTMLElement>;
|
||||
onLogCopy: MouseEventHandler<HTMLElement>;
|
||||
customClassName?: string;
|
||||
}
|
||||
export default function LogLinesActionButtons({
|
||||
handleShowContext,
|
||||
onLogCopy,
|
||||
customClassName = '',
|
||||
}: LogLinesActionButtonsProps): JSX.Element {
|
||||
return (
|
||||
<div className={`log-line-action-buttons ${customClassName}`}>
|
||||
<Tooltip title="Show Context">
|
||||
<Button
|
||||
size="small"
|
||||
icon={<TextSelect size={14} />}
|
||||
className="show-context-btn"
|
||||
onClick={handleShowContext}
|
||||
/>
|
||||
</Tooltip>
|
||||
<Tooltip title="Copy Link">
|
||||
<Button
|
||||
size="small"
|
||||
icon={<LinkOutlined size={14} />}
|
||||
onClick={onLogCopy}
|
||||
className="copy-log-btn"
|
||||
/>
|
||||
</Tooltip>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
LogLinesActionButtons.defaultProps = {
|
||||
customClassName: '',
|
||||
};
|
||||
@@ -1,30 +0,0 @@
|
||||
.log-state-indicator {
|
||||
padding-left: 8px;
|
||||
|
||||
.line {
|
||||
margin: 0 8px;
|
||||
min-height: 24px;
|
||||
height: 100%;
|
||||
width: 3px;
|
||||
border-radius: 50px;
|
||||
background-color: transparent;
|
||||
|
||||
&.INFO {
|
||||
background-color: #1d212d;
|
||||
}
|
||||
|
||||
&.WARNING {
|
||||
background-color: #ffcd56;
|
||||
}
|
||||
|
||||
&.ERROR {
|
||||
background-color: #e5484d;
|
||||
}
|
||||
}
|
||||
|
||||
&.isActive {
|
||||
.line {
|
||||
background-color: var(--bg-robin-400, #7190f9);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
import './LogStateIndicator.styles.scss';
|
||||
|
||||
import cx from 'classnames';
|
||||
|
||||
export const LogType = {
|
||||
INFO: 'INFO',
|
||||
WARNING: 'WARNING',
|
||||
ERROR: 'ERROR',
|
||||
};
|
||||
function LogStateIndicator({
|
||||
type,
|
||||
isActive,
|
||||
}: {
|
||||
type: string;
|
||||
isActive?: boolean;
|
||||
}): JSX.Element {
|
||||
return (
|
||||
<div className={cx('log-state-indicator', isActive ? 'isActive' : '')}>
|
||||
<div className={cx('line', type)}> </div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
LogStateIndicator.defaultProps = {
|
||||
isActive: false,
|
||||
};
|
||||
|
||||
export default LogStateIndicator;
|
||||
@@ -1,9 +1,11 @@
|
||||
import './RawLogView.styles.scss';
|
||||
|
||||
import {
|
||||
ExpandAltOutlined,
|
||||
LinkOutlined,
|
||||
MonitorOutlined,
|
||||
} from '@ant-design/icons';
|
||||
import Convert from 'ansi-to-html';
|
||||
import { DrawerProps } from 'antd';
|
||||
import { Button, DrawerProps, Tooltip } from 'antd';
|
||||
import LogDetail from 'components/LogDetail';
|
||||
import { VIEW_TYPES, VIEWS } from 'components/LogDetail/constants';
|
||||
import LogsExplorerContext from 'container/LogsExplorerContext';
|
||||
import dayjs from 'dayjs';
|
||||
import dompurify from 'dompurify';
|
||||
@@ -22,12 +24,13 @@ import {
|
||||
useState,
|
||||
} from 'react';
|
||||
|
||||
import LogLinesActionButtons from '../LogLinesActionButtons/LogLinesActionButtons';
|
||||
import LogStateIndicator, {
|
||||
LogType,
|
||||
} from '../LogStateIndicator/LogStateIndicator';
|
||||
// styles
|
||||
import { RawLogContent, RawLogViewContainer } from './styles';
|
||||
import {
|
||||
ActionButtonsWrapper,
|
||||
ExpandIconWrapper,
|
||||
RawLogContent,
|
||||
RawLogViewContainer,
|
||||
} from './styles';
|
||||
import { RawLogViewProps } from './types';
|
||||
|
||||
const convert = new Convert();
|
||||
@@ -47,6 +50,7 @@ function RawLogView({
|
||||
|
||||
const {
|
||||
activeLog: activeContextLog,
|
||||
onSetActiveLog: handleSetActiveContextLog,
|
||||
onClearActiveLog: handleClearActiveContextLog,
|
||||
} = useActiveLog();
|
||||
const {
|
||||
@@ -57,15 +61,12 @@ function RawLogView({
|
||||
} = useActiveLog();
|
||||
|
||||
const [hasActionButtons, setHasActionButtons] = useState<boolean>(false);
|
||||
const [selectedTab, setSelectedTab] = useState<VIEWS | undefined>();
|
||||
|
||||
const isDarkMode = useIsDarkMode();
|
||||
const isReadOnlyLog = !isLogsExplorerPage || isReadOnly;
|
||||
|
||||
const severityText = data.severity_text ? `${data.severity_text} |` : '';
|
||||
|
||||
const logType = data?.attributes_string?.log_level || LogType.INFO;
|
||||
|
||||
const updatedSelecedFields = useMemo(
|
||||
() => selectedFields.filter((e) => e.name !== 'id'),
|
||||
[selectedFields],
|
||||
@@ -97,7 +98,6 @@ function RawLogView({
|
||||
if (activeContextLog || isReadOnly) return;
|
||||
|
||||
onSetActiveLog(data);
|
||||
setSelectedTab(VIEW_TYPES.OVERVIEW);
|
||||
}, [activeContextLog, isReadOnly, data, onSetActiveLog]);
|
||||
|
||||
const handleCloseLogDetail: DrawerProps['onClose'] = useCallback(
|
||||
@@ -108,7 +108,6 @@ function RawLogView({
|
||||
event.stopPropagation();
|
||||
|
||||
onClearActiveLog();
|
||||
setSelectedTab(undefined);
|
||||
},
|
||||
[onClearActiveLog],
|
||||
);
|
||||
@@ -129,11 +128,9 @@ function RawLogView({
|
||||
(event) => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
// handleSetActiveContextLog(data);
|
||||
setSelectedTab(VIEW_TYPES.CONTEXT);
|
||||
onSetActiveLog(data);
|
||||
handleSetActiveContextLog(data);
|
||||
},
|
||||
[data, onSetActiveLog],
|
||||
[data, handleSetActiveContextLog],
|
||||
);
|
||||
|
||||
const html = useMemo(
|
||||
@@ -150,30 +147,37 @@ function RawLogView({
|
||||
align="middle"
|
||||
$isDarkMode={isDarkMode}
|
||||
$isReadOnly={isReadOnly}
|
||||
$isHightlightedLog={isHighlighted}
|
||||
$isActiveLog={isActiveLog}
|
||||
$isActiveLog={isHighlighted}
|
||||
onMouseEnter={handleMouseEnter}
|
||||
onMouseLeave={handleMouseLeave}
|
||||
>
|
||||
<LogStateIndicator
|
||||
type={logType}
|
||||
isActive={activeLog?.id === data.id || activeContextLog?.id === data.id}
|
||||
/>
|
||||
{!isReadOnly && (
|
||||
<ExpandIconWrapper flex="30px">
|
||||
<ExpandAltOutlined />
|
||||
</ExpandIconWrapper>
|
||||
)}
|
||||
|
||||
<RawLogContent
|
||||
$isReadOnly={isReadOnly}
|
||||
$isActiveLog={isActiveLog}
|
||||
$isDarkMode={isDarkMode}
|
||||
$isTextOverflowEllipsisDisabled={isTextOverflowEllipsisDisabled}
|
||||
linesPerRow={linesPerRow}
|
||||
dangerouslySetInnerHTML={html}
|
||||
/>
|
||||
|
||||
{hasActionButtons && (
|
||||
<LogLinesActionButtons
|
||||
handleShowContext={handleShowContext}
|
||||
onLogCopy={onLogCopy}
|
||||
/>
|
||||
<ActionButtonsWrapper>
|
||||
<Tooltip title="Show Context">
|
||||
<Button
|
||||
size="small"
|
||||
icon={<MonitorOutlined />}
|
||||
onClick={handleShowContext}
|
||||
/>
|
||||
</Tooltip>
|
||||
<Tooltip title="Copy Link">
|
||||
<Button size="small" icon={<LinkOutlined />} onClick={onLogCopy} />
|
||||
</Tooltip>
|
||||
</ActionButtonsWrapper>
|
||||
)}
|
||||
|
||||
{activeContextLog && (
|
||||
@@ -182,15 +186,12 @@ function RawLogView({
|
||||
onClose={handleClearActiveContextLog}
|
||||
/>
|
||||
)}
|
||||
{selectedTab && (
|
||||
<LogDetail
|
||||
selectedTab={selectedTab}
|
||||
log={activeLog}
|
||||
onClose={handleCloseLogDetail}
|
||||
onAddToQuery={onAddToQuery}
|
||||
onClickActionItem={onAddToQuery}
|
||||
/>
|
||||
)}
|
||||
<LogDetail
|
||||
log={activeLog}
|
||||
onClose={handleCloseLogDetail}
|
||||
onAddToQuery={onAddToQuery}
|
||||
onClickActionItem={onAddToQuery}
|
||||
/>
|
||||
</RawLogViewContainer>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { blue } from '@ant-design/colors';
|
||||
import { Color } from '@signozhq/design-tokens';
|
||||
import { Col, Row, Space } from 'antd';
|
||||
import styled from 'styled-components';
|
||||
import { getActiveLogBackground, getDefaultLogBackground } from 'utils/logs';
|
||||
@@ -10,25 +9,20 @@ export const RawLogViewContainer = styled(Row)<{
|
||||
$isDarkMode: boolean;
|
||||
$isReadOnly?: boolean;
|
||||
$isActiveLog?: boolean;
|
||||
$isHightlightedLog: boolean;
|
||||
}>`
|
||||
position: relative;
|
||||
width: 100%;
|
||||
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
font-weight: 700;
|
||||
font-size: 0.625rem;
|
||||
line-height: 1.25rem;
|
||||
|
||||
transition: background-color 0.2s ease-in;
|
||||
|
||||
.log-state-indicator {
|
||||
margin: 4px 0;
|
||||
}
|
||||
|
||||
${({ $isActiveLog }): string => getActiveLogBackground($isActiveLog)}
|
||||
|
||||
${({ $isReadOnly, $isActiveLog, $isDarkMode }): string =>
|
||||
${({ $isReadOnly, $isDarkMode, $isActiveLog }): string =>
|
||||
$isActiveLog
|
||||
? getActiveLogBackground($isActiveLog, $isDarkMode)
|
||||
? getActiveLogBackground()
|
||||
: getDefaultLogBackground($isReadOnly, $isDarkMode)}
|
||||
`;
|
||||
|
||||
@@ -36,17 +30,13 @@ export const ExpandIconWrapper = styled(Col)`
|
||||
color: ${blue[6]};
|
||||
padding: 0.25rem 0.375rem;
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
`;
|
||||
|
||||
export const RawLogContent = styled.div<RawLogContentProps>`
|
||||
margin-bottom: 0;
|
||||
font-family: 'SF Mono', monospace;
|
||||
font-family: 'Space Mono', monospace;
|
||||
font-size: 13px;
|
||||
font-weight: 400;
|
||||
text-align: left;
|
||||
color: ${({ $isDarkMode }): string =>
|
||||
$isDarkMode ? Color.BG_VANILLA_400 : Color.BG_INK_400};
|
||||
font-family: Fira Code, monospace;
|
||||
font-weight: 300;
|
||||
|
||||
${({ $isTextOverflowEllipsisDisabled, linesPerRow }): string =>
|
||||
$isTextOverflowEllipsisDisabled
|
||||
@@ -58,12 +48,15 @@ export const RawLogContent = styled.div<RawLogContentProps>`
|
||||
line-clamp: ${linesPerRow};
|
||||
-webkit-box-orient: vertical;`};
|
||||
|
||||
font-size: 12px;
|
||||
line-height: 24px;
|
||||
letter-spacing: -0.07px;
|
||||
padding: 4px;
|
||||
|
||||
cursor: ${({ $isActiveLog, $isReadOnly }): string =>
|
||||
$isActiveLog || $isReadOnly ? 'initial' : 'pointer'};
|
||||
|
||||
${({ $isActiveLog, $isReadOnly }): string =>
|
||||
$isReadOnly && $isActiveLog ? 'padding: 0 1.5rem;' : ''}
|
||||
`;
|
||||
|
||||
export const ActionButtonsWrapper = styled(Space)`
|
||||
|
||||
@@ -14,6 +14,5 @@ export interface RawLogContentProps {
|
||||
linesPerRow: number;
|
||||
$isReadOnly?: boolean;
|
||||
$isActiveLog?: boolean;
|
||||
$isDarkMode?: boolean;
|
||||
$isTextOverflowEllipsisDisabled?: boolean;
|
||||
}
|
||||
|
||||
@@ -1,21 +1,12 @@
|
||||
import { TableProps } from 'antd';
|
||||
import { CSSProperties } from 'react';
|
||||
|
||||
export function getDefaultCellStyle(isDarkMode?: boolean): CSSProperties {
|
||||
return {
|
||||
paddingTop: 4,
|
||||
paddingBottom: 6,
|
||||
paddingRight: 8,
|
||||
paddingLeft: 8,
|
||||
color: isDarkMode ? 'var(--bg-vanilla-400)' : 'var(--bg-slate-400)',
|
||||
fontSize: '14px',
|
||||
fontStyle: 'normal',
|
||||
fontWeight: 400,
|
||||
lineHeight: '18px',
|
||||
letterSpacing: '-0.07px',
|
||||
marginBottom: '0px',
|
||||
};
|
||||
}
|
||||
export const defaultCellStyle: CSSProperties = {
|
||||
paddingTop: 4,
|
||||
paddingBottom: 6,
|
||||
paddingRight: 8,
|
||||
paddingLeft: 8,
|
||||
};
|
||||
|
||||
export const defaultTableStyle: CSSProperties = {
|
||||
minWidth: '40rem',
|
||||
|
||||
@@ -2,22 +2,18 @@ import styled from 'styled-components';
|
||||
|
||||
interface TableBodyContentProps {
|
||||
linesPerRow: number;
|
||||
isDarkMode?: boolean;
|
||||
}
|
||||
|
||||
export const TableBodyContent = styled.div<TableBodyContentProps>`
|
||||
margin-bottom: 0;
|
||||
color: ${(props): string =>
|
||||
props.isDarkMode ? 'var(--bg-vanilla-400, #c0c1c3)' : 'var(--bg-slate-400)'};
|
||||
font-size: 14px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 18px; /* 128.571% */
|
||||
letter-spacing: -0.07px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: ${(props): number => props.linesPerRow};
|
||||
line-clamp: ${(props): number => props.linesPerRow};
|
||||
-webkit-box-orient: vertical;
|
||||
|
||||
font-size: 0.875rem;
|
||||
|
||||
line-height: 2rem;
|
||||
`;
|
||||
|
||||
@@ -22,8 +22,6 @@ export type UseTableViewProps = {
|
||||
appendTo?: 'center' | 'end';
|
||||
onOpenLogsContext?: (log: ILog) => void;
|
||||
onClickExpand?: (log: ILog) => void;
|
||||
activeLog?: ILog | null;
|
||||
activeContextLog?: ILog | null;
|
||||
} & LogsTableViewProps;
|
||||
|
||||
export type ActionsColumnProps = {
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
.text {
|
||||
color: var(--bg-vanilla-400);
|
||||
font-size: 14px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 18px; /* 128.571% */
|
||||
letter-spacing: -0.07px;
|
||||
}
|
||||
|
||||
.table-timestamp {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.ant-typography {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.log-state-indicator {
|
||||
padding: 0px;
|
||||
}
|
||||
}
|
||||
|
||||
.lightMode {
|
||||
.text {
|
||||
color: var(--bg-slate-400);
|
||||
}
|
||||
}
|
||||
@@ -1,21 +1,22 @@
|
||||
import './useTableView.styles.scss';
|
||||
|
||||
import {
|
||||
ExpandAltOutlined,
|
||||
LinkOutlined,
|
||||
MonitorOutlined,
|
||||
} from '@ant-design/icons';
|
||||
import Convert from 'ansi-to-html';
|
||||
import { Typography } from 'antd';
|
||||
import { Button, Space, Typography } from 'antd';
|
||||
import { ColumnsType } from 'antd/es/table';
|
||||
import dayjs from 'dayjs';
|
||||
import dompurify from 'dompurify';
|
||||
import { useIsDarkMode } from 'hooks/useDarkMode';
|
||||
import { useCopyLogLink } from 'hooks/logs/useCopyLogLink';
|
||||
import { FlatLogData } from 'lib/logs/flatLogData';
|
||||
import { defaultTo } from 'lodash-es';
|
||||
import { useMemo } from 'react';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
|
||||
import LogStateIndicator, {
|
||||
LogType,
|
||||
} from '../LogStateIndicator/LogStateIndicator';
|
||||
import { defaultTableStyle, getDefaultCellStyle } from './config';
|
||||
import { ExpandIconWrapper } from '../RawLogView/styles';
|
||||
import { defaultCellStyle, defaultTableStyle } from './config';
|
||||
import { TableBodyContent } from './styles';
|
||||
import {
|
||||
ActionsColumnProps,
|
||||
ColumnTypeRender,
|
||||
UseTableViewProps,
|
||||
UseTableViewResult,
|
||||
@@ -23,22 +24,60 @@ import {
|
||||
|
||||
const convert = new Convert();
|
||||
|
||||
function ActionsColumn({
|
||||
logId,
|
||||
logs,
|
||||
onOpenLogsContext,
|
||||
}: ActionsColumnProps): JSX.Element {
|
||||
const currentLog = useMemo(() => logs.find(({ id }) => id === logId), [
|
||||
logs,
|
||||
logId,
|
||||
]);
|
||||
|
||||
const { onLogCopy } = useCopyLogLink(currentLog?.id);
|
||||
|
||||
const handleShowContext = useCallback(() => {
|
||||
if (!onOpenLogsContext || !currentLog) return;
|
||||
|
||||
onOpenLogsContext(currentLog);
|
||||
}, [currentLog, onOpenLogsContext]);
|
||||
|
||||
return (
|
||||
<Space>
|
||||
<Button
|
||||
size="small"
|
||||
onClick={handleShowContext}
|
||||
icon={<MonitorOutlined />}
|
||||
/>
|
||||
<Button size="small" onClick={onLogCopy} icon={<LinkOutlined />} />
|
||||
</Space>
|
||||
);
|
||||
}
|
||||
|
||||
export const useTableView = (props: UseTableViewProps): UseTableViewResult => {
|
||||
const {
|
||||
logs,
|
||||
fields,
|
||||
linesPerRow,
|
||||
appendTo = 'center',
|
||||
activeContextLog,
|
||||
activeLog,
|
||||
onOpenLogsContext,
|
||||
onClickExpand,
|
||||
} = props;
|
||||
|
||||
const isDarkMode = useIsDarkMode();
|
||||
const { isLogsExplorerPage } = useCopyLogLink();
|
||||
|
||||
const flattenLogData = useMemo(() => logs.map((log) => FlatLogData(log)), [
|
||||
logs,
|
||||
]);
|
||||
|
||||
const handleClickExpand = useCallback(
|
||||
(index: number): void => {
|
||||
if (!onClickExpand) return;
|
||||
|
||||
onClickExpand(logs[index]);
|
||||
},
|
||||
[logs, onClickExpand],
|
||||
);
|
||||
|
||||
const columns: ColumnsType<Record<string, unknown>> = useMemo(() => {
|
||||
const fieldColumns: ColumnsType<Record<string, unknown>> = fields
|
||||
.filter((e) => e.name !== 'id')
|
||||
@@ -48,7 +87,7 @@ export const useTableView = (props: UseTableViewProps): UseTableViewResult => {
|
||||
key: name,
|
||||
render: (field): ColumnTypeRender<Record<string, unknown>> => ({
|
||||
props: {
|
||||
style: getDefaultCellStyle(isDarkMode),
|
||||
style: defaultCellStyle,
|
||||
},
|
||||
children: (
|
||||
<Typography.Paragraph ellipsis={{ rows: linesPerRow }}>
|
||||
@@ -59,30 +98,38 @@ export const useTableView = (props: UseTableViewProps): UseTableViewResult => {
|
||||
}));
|
||||
|
||||
return [
|
||||
{
|
||||
title: '',
|
||||
dataIndex: 'id',
|
||||
key: 'expand',
|
||||
// https://github.com/ant-design/ant-design/discussions/36886
|
||||
render: (_, item, index): ColumnTypeRender<Record<string, unknown>> => ({
|
||||
props: {
|
||||
style: defaultCellStyle,
|
||||
},
|
||||
children: (
|
||||
<ExpandIconWrapper
|
||||
onClick={(): void => {
|
||||
handleClickExpand(index);
|
||||
}}
|
||||
>
|
||||
<ExpandAltOutlined />
|
||||
</ExpandIconWrapper>
|
||||
),
|
||||
}),
|
||||
},
|
||||
{
|
||||
title: 'timestamp',
|
||||
dataIndex: 'timestamp',
|
||||
key: 'timestamp',
|
||||
// https://github.com/ant-design/ant-design/discussions/36886
|
||||
render: (field, item): ColumnTypeRender<Record<string, unknown>> => {
|
||||
render: (field): ColumnTypeRender<Record<string, unknown>> => {
|
||||
const date =
|
||||
typeof field === 'string'
|
||||
? dayjs(field).format()
|
||||
: dayjs(field / 1e6).format();
|
||||
return {
|
||||
children: (
|
||||
<div className="table-timestamp">
|
||||
<LogStateIndicator
|
||||
type={defaultTo(item.log_level, LogType.INFO) as string}
|
||||
isActive={
|
||||
activeLog?.id === item.id || activeContextLog?.id === item.id
|
||||
}
|
||||
/>
|
||||
<Typography.Paragraph ellipsis className="text">
|
||||
{date}
|
||||
</Typography.Paragraph>
|
||||
</div>
|
||||
),
|
||||
children: <Typography.Paragraph ellipsis>{date}</Typography.Paragraph>,
|
||||
};
|
||||
},
|
||||
},
|
||||
@@ -101,20 +148,38 @@ export const useTableView = (props: UseTableViewProps): UseTableViewResult => {
|
||||
__html: convert.toHtml(dompurify.sanitize(field)),
|
||||
}}
|
||||
linesPerRow={linesPerRow}
|
||||
isDarkMode={isDarkMode}
|
||||
/>
|
||||
),
|
||||
}),
|
||||
},
|
||||
...(appendTo === 'end' ? fieldColumns : []),
|
||||
...(isLogsExplorerPage
|
||||
? ([
|
||||
{
|
||||
title: 'actions',
|
||||
dataIndex: 'actions',
|
||||
key: 'actions',
|
||||
render: (_, log): ColumnTypeRender<Record<string, unknown>> => ({
|
||||
children: (
|
||||
<ActionsColumn
|
||||
logId={(log.id as unknown) as string}
|
||||
logs={logs}
|
||||
onOpenLogsContext={onOpenLogsContext}
|
||||
/>
|
||||
),
|
||||
}),
|
||||
},
|
||||
] as ColumnsType<Record<string, unknown>>)
|
||||
: []),
|
||||
];
|
||||
}, [
|
||||
logs,
|
||||
fields,
|
||||
appendTo,
|
||||
isDarkMode,
|
||||
linesPerRow,
|
||||
activeLog?.id,
|
||||
activeContextLog?.id,
|
||||
isLogsExplorerPage,
|
||||
handleClickExpand,
|
||||
onOpenLogsContext,
|
||||
]);
|
||||
|
||||
return { columns, dataSource: flattenLogData };
|
||||
|
||||
@@ -1,394 +0,0 @@
|
||||
.nested-menu-container {
|
||||
z-index: 2;
|
||||
position: absolute;
|
||||
right: -2px;
|
||||
margin: 6px 0;
|
||||
width: 160px;
|
||||
|
||||
border-radius: 4px;
|
||||
|
||||
border: 1px solid var(--bg-slate-400, #1d212d);
|
||||
background: linear-gradient(
|
||||
139deg,
|
||||
rgba(18, 19, 23, 0.8) 0%,
|
||||
rgba(18, 19, 23, 0.9) 98.68%
|
||||
);
|
||||
|
||||
box-shadow: 4px 10px 16px 2px rgba(0, 0, 0, 0.2);
|
||||
backdrop-filter: blur(20px);
|
||||
|
||||
.menu-container {
|
||||
padding: 12px;
|
||||
|
||||
.title {
|
||||
font-family: Inter;
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
line-height: 18px;
|
||||
letter-spacing: 0.08em;
|
||||
text-align: left;
|
||||
color: var(--bg-slate-200, #52575c);
|
||||
}
|
||||
|
||||
.menu-items {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
flex-direction: column;
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
.item {
|
||||
font-family: Inter;
|
||||
font-size: 13px;
|
||||
font-weight: 400;
|
||||
line-height: 17px;
|
||||
letter-spacing: 0.01em;
|
||||
text-align: left;
|
||||
|
||||
.item-label {
|
||||
display: flex;
|
||||
color: var(--bg-vanilla-400, #c0c1c3);
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.horizontal-line {
|
||||
height: 1px;
|
||||
background: #1d212d;
|
||||
}
|
||||
|
||||
.max-lines-per-row {
|
||||
padding: 12px;
|
||||
|
||||
.title {
|
||||
color: var(--bg-slate-200, #52575c);
|
||||
font-family: Inter;
|
||||
font-size: 11px;
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
line-height: 18px; /* 163.636% */
|
||||
letter-spacing: 0.88px;
|
||||
text-transform: uppercase;
|
||||
|
||||
margin-bottom: 12px;
|
||||
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
.lucide {
|
||||
color: var(--bg-vanilla-400, #c0c1c3);
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.max-lines-per-row-input {
|
||||
display: flex;
|
||||
|
||||
border: 1px solid var(--bg-slate-400, #1d212d);
|
||||
|
||||
.ant-input-number-handler-wrap {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.ant-input-number {
|
||||
min-width: 36px;
|
||||
width: auto;
|
||||
border: 0px;
|
||||
text-align: center;
|
||||
height: 26px;
|
||||
border-radius: 0;
|
||||
|
||||
&:active,
|
||||
&:focus {
|
||||
border: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
|
||||
.ant-input-number-focused {
|
||||
box-shadow: none !important;
|
||||
}
|
||||
|
||||
.ant-input-number-input-wrap {
|
||||
input {
|
||||
text-align: center;
|
||||
font-size: 13px;
|
||||
|
||||
&:active,
|
||||
&:focus {
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
|
||||
&:active,
|
||||
&:focus {
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
|
||||
.periscope-btn {
|
||||
box-shadow: none;
|
||||
padding: 6px 12px;
|
||||
height: 26px;
|
||||
|
||||
border-radius: 0px 1px 1px 0px;
|
||||
background: var(--bg-ink-300, #16181d);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.selected-item-content-container {
|
||||
.add-new-column-header {
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.title {
|
||||
color: var(--bg-slate-200, #52575c);
|
||||
font-family: Inter;
|
||||
font-size: 11px;
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
line-height: 18px; /* 163.636% */
|
||||
letter-spacing: 0.88px;
|
||||
text-transform: uppercase;
|
||||
|
||||
margin-bottom: 12px;
|
||||
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
.lucide {
|
||||
color: var(--bg-vanilla-400, #c0c1c3);
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.horizontal-line {
|
||||
height: 1px;
|
||||
background: #1d212d;
|
||||
}
|
||||
|
||||
.loading-container {
|
||||
margin: 12px 0;
|
||||
}
|
||||
|
||||
.item-content {
|
||||
padding: 12px;
|
||||
|
||||
.column-format,
|
||||
.column-format-new-options {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
flex-direction: column;
|
||||
margin-top: 12px;
|
||||
|
||||
.column-name {
|
||||
color: var(--bg-vanilla-400, #c0c1c3);
|
||||
font-family: Inter;
|
||||
font-size: 13px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 20px; /* 142.857% */
|
||||
letter-spacing: -0.07px;
|
||||
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
|
||||
cursor: pointer;
|
||||
|
||||
.name {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.delete-btn {
|
||||
display: none;
|
||||
flex: 0 0 16px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
.delete-btn {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
overflow-x: hidden;
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
height: 1rem;
|
||||
width: 0.2rem;
|
||||
}
|
||||
}
|
||||
|
||||
.column-format {
|
||||
max-height: 150px;
|
||||
overflow: auto;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.column-format-new-options {
|
||||
max-height: 150px;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.column-divider {
|
||||
margin: 12px 0;
|
||||
border-top: 2px solid var(--bg-slate-400);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.active {
|
||||
.nested-menu-container {
|
||||
backdrop-filter: blur(18px);
|
||||
|
||||
.item {
|
||||
.item-label {
|
||||
color: var(--bg-vanilla-400);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.selected-item-content-container {
|
||||
width: 110%;
|
||||
margin-left: -5%;
|
||||
|
||||
border-radius: 4px;
|
||||
border: 1px solid var(--bg-slate-400);
|
||||
background: linear-gradient(
|
||||
139deg,
|
||||
rgba(18, 19, 23, 0.8) 0%,
|
||||
rgba(18, 19, 23, 0.9) 98.68%
|
||||
);
|
||||
|
||||
box-shadow: 4px 10px 16px 2px rgba(0, 0, 0, 0.2);
|
||||
backdrop-filter: blur(20px);
|
||||
|
||||
.column-format {
|
||||
margin-top: 0px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.lightMode {
|
||||
.nested-menu-container {
|
||||
border: 1px solid var(--bg-vanilla-300);
|
||||
background: linear-gradient(
|
||||
139deg,
|
||||
rgba(255, 255, 255, 0.8) 0%,
|
||||
rgba(255, 255, 255, 0.9) 98.68%
|
||||
);
|
||||
|
||||
box-shadow: 4px 10px 16px 2px rgba(255, 255, 255, 0.2);
|
||||
|
||||
.horizontal-line {
|
||||
background: var(--bg-vanilla-300);
|
||||
}
|
||||
|
||||
.item-content {
|
||||
.column-divider {
|
||||
border-top: 2px solid var(--bg-vanilla-300);
|
||||
}
|
||||
}
|
||||
|
||||
.max-lines-per-row {
|
||||
.title {
|
||||
color: var(--bg-ink-200);
|
||||
|
||||
.lucide {
|
||||
color: var(--bg-ink-300);
|
||||
}
|
||||
}
|
||||
|
||||
.max-lines-per-row-input {
|
||||
border: 1px solid var(--bg-vanilla-300);
|
||||
|
||||
.periscope-btn {
|
||||
background: var(--bg-vanilla-300);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.menu-container {
|
||||
.title {
|
||||
color: var(--bg-ink-200);
|
||||
}
|
||||
|
||||
.item {
|
||||
.item-label {
|
||||
color: var(--bg-ink-400);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.selected-item-content-container {
|
||||
.title {
|
||||
color: var(--bg-ink-200);
|
||||
|
||||
.lucide {
|
||||
color: var(--bg-ink-300);
|
||||
}
|
||||
}
|
||||
|
||||
.horizontal-line {
|
||||
background: var(--bg-vanilla-300);
|
||||
}
|
||||
|
||||
.item-content {
|
||||
.max-lines-per-row-input {
|
||||
border: 1px solid var(--bg-vanilla-300);
|
||||
|
||||
.periscope-btn {
|
||||
background: var(--bg-vanilla-300);
|
||||
}
|
||||
}
|
||||
|
||||
.column-format,
|
||||
.column-format-new-options {
|
||||
.column-name {
|
||||
color: var(--bg-ink-300);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.active {
|
||||
.nested-menu-container {
|
||||
backdrop-filter: blur(18px);
|
||||
|
||||
.item {
|
||||
.item-label {
|
||||
color: var(--bg-ink-300);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.selected-item-content-container {
|
||||
border: 1px solid var(--bg-vanilla-300);
|
||||
background: linear-gradient(
|
||||
139deg,
|
||||
rgba(255, 255, 255, 0.8) 0%,
|
||||
rgba(255, 255, 255, 0.9) 98.68%
|
||||
);
|
||||
|
||||
box-shadow: 4px 10px 16px 2px rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,248 +0,0 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
/* eslint-disable jsx-a11y/no-static-element-interactions */
|
||||
/* eslint-disable jsx-a11y/click-events-have-key-events */
|
||||
import './LogsFormatOptionsMenu.styles.scss';
|
||||
|
||||
import { Divider, Input, InputNumber, Tooltip } from 'antd';
|
||||
import cx from 'classnames';
|
||||
import { LogViewMode } from 'container/LogsTable';
|
||||
import { OptionsMenuConfig } from 'container/OptionsMenu/types';
|
||||
import useDebouncedFn from 'hooks/useDebouncedFunction';
|
||||
import { Check, Minus, Plus, X } from 'lucide-react';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
|
||||
interface LogsFormatOptionsMenuProps {
|
||||
title: string;
|
||||
items: any;
|
||||
selectedOptionFormat: any;
|
||||
config: OptionsMenuConfig;
|
||||
}
|
||||
|
||||
export default function LogsFormatOptionsMenu({
|
||||
title,
|
||||
items,
|
||||
selectedOptionFormat,
|
||||
config,
|
||||
}: LogsFormatOptionsMenuProps): JSX.Element {
|
||||
const { maxLines, format, addColumn } = config;
|
||||
const [selectedItem, setSelectedItem] = useState(selectedOptionFormat);
|
||||
const maxLinesNumber = (maxLines?.value as number) || 1;
|
||||
const [maxLinesPerRow, setMaxLinesPerRow] = useState<number>(maxLinesNumber);
|
||||
|
||||
const [addNewColumn, setAddNewColumn] = useState(false);
|
||||
|
||||
const onChange = useCallback(
|
||||
(key: LogViewMode) => {
|
||||
if (!format) return;
|
||||
|
||||
format.onChange(key);
|
||||
},
|
||||
[format],
|
||||
);
|
||||
|
||||
const handleMenuItemClick = (key: LogViewMode): void => {
|
||||
setSelectedItem(key);
|
||||
onChange(key);
|
||||
setAddNewColumn(false);
|
||||
};
|
||||
|
||||
const incrementMaxLinesPerRow = (): void => {
|
||||
if (maxLinesPerRow < 10) {
|
||||
setMaxLinesPerRow(maxLinesPerRow + 1);
|
||||
}
|
||||
};
|
||||
|
||||
const decrementMaxLinesPerRow = (): void => {
|
||||
if (maxLinesPerRow > 1) {
|
||||
setMaxLinesPerRow(maxLinesPerRow - 1);
|
||||
}
|
||||
};
|
||||
|
||||
const handleSearchValueChange = useDebouncedFn((event): void => {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
const value = event?.target?.value || '';
|
||||
|
||||
if (addColumn && addColumn?.onSearch) {
|
||||
addColumn?.onSearch(value);
|
||||
}
|
||||
}, 300);
|
||||
|
||||
const handleToggleAddNewColumn = (): void => {
|
||||
setAddNewColumn(!addNewColumn);
|
||||
};
|
||||
|
||||
// console.log('optionsMenuConfig', config);
|
||||
|
||||
const handleLinesPerRowChange = (maxLinesPerRow: number | null): void => {
|
||||
if (
|
||||
maxLinesPerRow &&
|
||||
Number.isInteger(maxLinesNumber) &&
|
||||
maxLinesPerRow > 1
|
||||
) {
|
||||
setMaxLinesPerRow(maxLinesPerRow);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (maxLinesPerRow && config && config.maxLines?.onChange) {
|
||||
config.maxLines.onChange(maxLinesPerRow);
|
||||
}
|
||||
}, [maxLinesPerRow]);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cx('nested-menu-container', addNewColumn ? 'active' : '')}
|
||||
onClick={(event): void => {
|
||||
// this is to restrict click events to propogate to parent
|
||||
event.stopPropagation();
|
||||
}}
|
||||
>
|
||||
<div className="menu-container">
|
||||
<div className="title"> {title} </div>
|
||||
|
||||
<div className="menu-items">
|
||||
{items.map(
|
||||
(item: any): JSX.Element => (
|
||||
<div
|
||||
className="item"
|
||||
key={item.label}
|
||||
onClick={(): void => handleMenuItemClick(item.key)}
|
||||
>
|
||||
<div className={cx('item-label')}>
|
||||
{item.label}
|
||||
|
||||
{selectedItem === item.key && <Check size={12} />}
|
||||
</div>
|
||||
</div>
|
||||
),
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{selectedItem && (
|
||||
<>
|
||||
{selectedItem === 'raw' && (
|
||||
<>
|
||||
<div className="horizontal-line" />
|
||||
<div className="max-lines-per-row">
|
||||
<div className="title"> max lines per row </div>
|
||||
<div className="raw-format max-lines-per-row-input">
|
||||
<button
|
||||
type="button"
|
||||
className="periscope-btn"
|
||||
onClick={decrementMaxLinesPerRow}
|
||||
>
|
||||
{' '}
|
||||
<Minus size={12} />{' '}
|
||||
</button>
|
||||
<InputNumber
|
||||
min={1}
|
||||
max={10}
|
||||
value={maxLinesPerRow}
|
||||
onChange={handleLinesPerRowChange}
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
className="periscope-btn"
|
||||
onClick={incrementMaxLinesPerRow}
|
||||
>
|
||||
{' '}
|
||||
<Plus size={12} />{' '}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
<div className="selected-item-content-container active">
|
||||
{!addNewColumn && <div className="horizontal-line" />}
|
||||
|
||||
{addNewColumn && (
|
||||
<div className="add-new-column-header">
|
||||
<div className="title">
|
||||
{' '}
|
||||
columns
|
||||
<X size={14} onClick={handleToggleAddNewColumn} />{' '}
|
||||
</div>
|
||||
|
||||
<Input
|
||||
tabIndex={0}
|
||||
type="text"
|
||||
autoFocus
|
||||
onFocus={addColumn?.onFocus}
|
||||
onChange={handleSearchValueChange}
|
||||
placeholder="Search..."
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="item-content">
|
||||
{!addNewColumn && (
|
||||
<div className="title">
|
||||
columns
|
||||
<Plus size={14} onClick={handleToggleAddNewColumn} />{' '}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="column-format">
|
||||
{addColumn?.value?.map(({ key, id }) => (
|
||||
<div className="column-name" key={id}>
|
||||
<div className="name">
|
||||
<Tooltip placement="left" title={key}>
|
||||
{key}
|
||||
</Tooltip>
|
||||
</div>
|
||||
<X
|
||||
className="delete-btn"
|
||||
size={14}
|
||||
onClick={(): void => addColumn.onRemove(id as string)}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{addColumn?.isFetching && (
|
||||
<div className="loading-container"> Loading ... </div>
|
||||
)}
|
||||
|
||||
{addNewColumn &&
|
||||
addColumn &&
|
||||
addColumn.value.length > 0 &&
|
||||
addColumn.options &&
|
||||
addColumn?.options?.length > 0 && (
|
||||
<Divider className="column-divider" />
|
||||
)}
|
||||
|
||||
{addNewColumn && (
|
||||
<div className="column-format-new-options">
|
||||
{addColumn?.options?.map(({ label, value }) => (
|
||||
<div
|
||||
className="column-name"
|
||||
key={value}
|
||||
onClick={(eve): void => {
|
||||
console.log('coluimn name', label, value);
|
||||
|
||||
eve.stopPropagation();
|
||||
|
||||
if (addColumn && addColumn?.onSelect) {
|
||||
addColumn?.onSelect(value, { label, disabled: false });
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div className="name">
|
||||
<Tooltip placement="left" title={label}>
|
||||
{label}
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -25,12 +25,11 @@ const allComponentMap: ComponentMapType[] = [
|
||||
if (!path) {
|
||||
return false;
|
||||
}
|
||||
const allowedPaths: string[] = [
|
||||
const allowedPaths = [
|
||||
ROUTES.LIST_ALL_ALERT,
|
||||
ROUTES.APPLICATION,
|
||||
ROUTES.ALL_DASHBOARD,
|
||||
];
|
||||
|
||||
return (
|
||||
userFlags?.ReleaseNote0120Hide !== 'Y' &&
|
||||
allowedPaths.includes(path) &&
|
||||
|
||||
@@ -73,21 +73,12 @@ function ResizeTable({
|
||||
}
|
||||
}, [columns]);
|
||||
|
||||
const paginationConfig = tableParams.pagination
|
||||
? {
|
||||
hideOnSinglePage: true,
|
||||
showTotal: (total: number, range: number[]): string =>
|
||||
`${range[0]}-${range[1]} of ${total} items`,
|
||||
...tableParams.pagination,
|
||||
}
|
||||
: tableParams.pagination;
|
||||
|
||||
return onDragColumn ? (
|
||||
<ReactDragListView.DragColumn {...dragColumnParams} onDragEnd={onDragColumn}>
|
||||
<Table {...tableParams} pagination={paginationConfig} />
|
||||
<Table {...tableParams} />
|
||||
</ReactDragListView.DragColumn>
|
||||
) : (
|
||||
<Table {...tableParams} pagination={paginationConfig} />
|
||||
<Table {...tableParams} />
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,15 +1,13 @@
|
||||
import { TabsProps } from 'antd';
|
||||
import { History } from 'history';
|
||||
|
||||
export type TabRoutes = {
|
||||
name: React.ReactNode;
|
||||
route: string;
|
||||
Component: () => JSX.Element;
|
||||
key: string;
|
||||
};
|
||||
|
||||
export interface RouteTabProps {
|
||||
routes: TabRoutes[];
|
||||
routes: {
|
||||
name: React.ReactNode;
|
||||
route: string;
|
||||
Component: () => JSX.Element;
|
||||
key: string;
|
||||
}[];
|
||||
activeKey: TabsProps['activeKey'];
|
||||
onChangeHandler?: VoidFunction;
|
||||
history: History<unknown>;
|
||||
|
||||
@@ -24,7 +24,6 @@ const ROUTES = {
|
||||
MY_SETTINGS: '/my-settings',
|
||||
SETTINGS: '/settings',
|
||||
ORG_SETTINGS: '/settings/org-settings',
|
||||
API_KEYS: '/settings/api-keys',
|
||||
INGESTION_SETTINGS: '/settings/ingestion-settings',
|
||||
SOMETHING_WENT_WRONG: '/something-went-wrong',
|
||||
UN_AUTHORIZED: '/un-authorized',
|
||||
@@ -42,9 +41,7 @@ const ROUTES = {
|
||||
TRACE_EXPLORER: '/trace-explorer',
|
||||
BILLING: '/billing',
|
||||
SUPPORT: '/support',
|
||||
LOGS_SAVE_VIEWS: '/logs-save-views',
|
||||
TRACES_SAVE_VIEWS: '/traces-save-views',
|
||||
WORKSPACE_LOCKED: '/workspace-locked',
|
||||
} as const;
|
||||
};
|
||||
|
||||
export default ROUTES;
|
||||
|
||||
@@ -1,447 +0,0 @@
|
||||
.api-key-container {
|
||||
margin-top: 32px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
|
||||
.api-key-content {
|
||||
width: calc(100% - 30px);
|
||||
max-width: 736px;
|
||||
|
||||
.title {
|
||||
color: var(--bg-vanilla-100);
|
||||
font-size: var(--font-size-lg);
|
||||
font-style: normal;
|
||||
font-weight: var(--font-weight-normal);
|
||||
line-height: 28px; /* 155.556% */
|
||||
letter-spacing: -0.09px;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
color: var(---bg-vanilla-400);
|
||||
font-size: var(--font-size-sm);
|
||||
font-style: normal;
|
||||
font-weight: var(--font-weight-normal);
|
||||
line-height: 20px; /* 142.857% */
|
||||
letter-spacing: -0.07px;
|
||||
}
|
||||
|
||||
.api-keys-search-add-new {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
|
||||
padding: 16px 0;
|
||||
|
||||
.add-new-api-key-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.ant-table-row {
|
||||
.ant-table-cell {
|
||||
padding: 0;
|
||||
border: none;
|
||||
background: var(--bg-ink-500);
|
||||
}
|
||||
.column-render {
|
||||
margin: 8px 0 !important;
|
||||
border-radius: 6px;
|
||||
border: 1px solid var(--bg-slate-500);
|
||||
background: var(--bg-ink-400);
|
||||
|
||||
.title-with-action {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
||||
align-items: center;
|
||||
padding: 8px;
|
||||
|
||||
.api-key-data {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
|
||||
.api-key-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
|
||||
.ant-typography {
|
||||
color: var(--bg-vanilla-400);
|
||||
font-size: var(--font-size-sm);
|
||||
font-style: normal;
|
||||
font-weight: var(--font-weight-medium);
|
||||
line-height: 20px;
|
||||
letter-spacing: -0.07px;
|
||||
}
|
||||
}
|
||||
|
||||
.api-key-value {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
|
||||
border-radius: 20px;
|
||||
padding: 0px 12px;
|
||||
|
||||
background: var(--bg-ink-200, #23262e);
|
||||
|
||||
.ant-typography {
|
||||
color: var(--bg-vanilla-400);
|
||||
font-size: var(--font-size-xs);
|
||||
font-family: 'Space Mono', monospace;
|
||||
font-style: normal;
|
||||
font-weight: var(--font-weight-medium);
|
||||
line-height: 20px;
|
||||
letter-spacing: -0.07px;
|
||||
}
|
||||
|
||||
.copy-key-btn {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.action-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 20px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.visibility-btn {
|
||||
border: 1px solid rgba(113, 144, 249, 0.2);
|
||||
background: rgba(113, 144, 249, 0.1);
|
||||
}
|
||||
}
|
||||
.api-key-details {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border-top: 1px solid var(--bg-slate-500, #161922);
|
||||
padding: 8px;
|
||||
|
||||
.api-key-tag {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
border-radius: 50px;
|
||||
background: var(--bg-slate-300);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
.tag-text {
|
||||
color: var(--bg-vanilla-400);
|
||||
leading-trim: both;
|
||||
text-edge: cap;
|
||||
font-size: 10px;
|
||||
font-style: normal;
|
||||
font-weight: var(--font-weight-normal);
|
||||
line-height: normal;
|
||||
letter-spacing: -0.05px;
|
||||
}
|
||||
}
|
||||
|
||||
.api-key-created-by {
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
.api-key-created-at {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
|
||||
.ant-typography {
|
||||
margin-left: 6px;
|
||||
color: var(--bg-vanilla-400);
|
||||
font-size: var(--font-size-sm);
|
||||
font-style: normal;
|
||||
font-weight: var(--font-weight-normal);
|
||||
line-height: 18px; /* 128.571% */
|
||||
letter-spacing: -0.07px;
|
||||
font-variant-numeric: lining-nums tabular-nums stacked-fractions
|
||||
slashed-zero;
|
||||
font-feature-settings: 'dlig' on, 'salt' on, 'cpsp' on, 'case' on;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.ant-pagination-item {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
> a {
|
||||
color: var(--bg-vanilla-400);
|
||||
font-variant-numeric: lining-nums tabular-nums slashed-zero;
|
||||
font-feature-settings: 'dlig' on, 'salt' on, 'case' on, 'cpsp' on;
|
||||
font-size: var(--font-size-sm);
|
||||
font-style: normal;
|
||||
font-weight: var(--font-weight-normal);
|
||||
line-height: 20px; /* 142.857% */
|
||||
}
|
||||
}
|
||||
|
||||
.ant-pagination-item-active {
|
||||
background-color: var(--bg-robin-500);
|
||||
> a {
|
||||
color: var(--bg-ink-500) !important;
|
||||
font-size: var(--font-size-sm);
|
||||
font-style: normal;
|
||||
font-weight: var(--font-weight-medium);
|
||||
line-height: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.api-key-modal {
|
||||
.ant-modal-content {
|
||||
border-radius: 4px;
|
||||
border: 1px solid var(--Slate-500, #161922);
|
||||
background: var(--Ink-400, #121317);
|
||||
box-shadow: 0px -4px 16px 2px rgba(0, 0, 0, 0.2);
|
||||
padding: 0;
|
||||
|
||||
.ant-modal-header {
|
||||
background: none;
|
||||
border-bottom: 1px solid var(--Slate-500, #161922);
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.ant-modal-close-x {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.ant-modal-body {
|
||||
padding: 12px 16px;
|
||||
}
|
||||
|
||||
.ant-modal-footer {
|
||||
padding: 16px;
|
||||
margin-top: 0;
|
||||
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.api-key-access-role {
|
||||
.ant-radio-button-wrapper {
|
||||
font-size: 12px;
|
||||
text-transform: capitalize;
|
||||
}
|
||||
|
||||
.tab {
|
||||
border: 1px solid var(--bg-slate-400);
|
||||
|
||||
&::before {
|
||||
background: var(--bg-slate-400);
|
||||
}
|
||||
|
||||
&.selected {
|
||||
background: var(--Slate-400, #1d212d);
|
||||
}
|
||||
}
|
||||
|
||||
.role {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.delete-api-key-modal {
|
||||
width: calc(100% - 30px) !important; /* Adjust the 20px as needed */
|
||||
max-width: 384px;
|
||||
.ant-modal-content {
|
||||
padding: 0;
|
||||
border-radius: 4px;
|
||||
border: 1px solid var(--bg-slate-500);
|
||||
background: var(--bg-ink-400);
|
||||
box-shadow: 0px -4px 16px 2px rgba(0, 0, 0, 0.2);
|
||||
|
||||
.ant-modal-header {
|
||||
padding: 16px;
|
||||
background: var(--bg-ink-400);
|
||||
}
|
||||
|
||||
.ant-modal-body {
|
||||
padding: 0px 16px 28px 16px;
|
||||
|
||||
.ant-typography {
|
||||
color: var(--bg-vanilla-400);
|
||||
font-size: var(--font-size-sm);
|
||||
font-style: normal;
|
||||
font-weight: var(--font-weight-normal);
|
||||
line-height: 20px;
|
||||
letter-spacing: -0.07px;
|
||||
}
|
||||
|
||||
.api-key-input {
|
||||
margin-top: 8px;
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.ant-color-picker-trigger {
|
||||
padding: 6px;
|
||||
border-radius: 2px;
|
||||
border: 1px solid var(--bg-slate-400);
|
||||
background: var(--bg-ink-300);
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
|
||||
.ant-color-picker-color-block {
|
||||
border-radius: 50px;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
flex-shrink: 0;
|
||||
|
||||
.ant-color-picker-color-block-inner {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.ant-modal-footer {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
padding: 16px 16px;
|
||||
margin: 0;
|
||||
|
||||
.cancel-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border: none;
|
||||
border-radius: 2px;
|
||||
background: var(--bg-slate-500);
|
||||
}
|
||||
|
||||
.delete-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border: none;
|
||||
border-radius: 2px;
|
||||
background: var(--bg-cherry-500);
|
||||
margin-left: 12px;
|
||||
}
|
||||
|
||||
.delete-btn:hover {
|
||||
color: var(--bg-vanilla-100);
|
||||
background: var(--bg-cherry-600);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.title {
|
||||
color: var(--bg-vanilla-100);
|
||||
font-size: var(--font-size-sm);
|
||||
font-style: normal;
|
||||
font-weight: var(--font-weight-medium);
|
||||
line-height: 20px; /* 142.857% */
|
||||
}
|
||||
}
|
||||
|
||||
.lightMode {
|
||||
.api-key-container {
|
||||
.api-key-content {
|
||||
.title {
|
||||
color: var(--bg-ink-500);
|
||||
}
|
||||
|
||||
.ant-table-row {
|
||||
.ant-table-cell {
|
||||
background: var(--bg-vanilla-200);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
.ant-table-cell {
|
||||
background: var(--bg-vanilla-200) !important;
|
||||
}
|
||||
}
|
||||
|
||||
.column-render {
|
||||
border: 1px solid var(--bg-vanilla-200);
|
||||
background: var(--bg-vanilla-100);
|
||||
|
||||
.title-with-action {
|
||||
.api-key-title {
|
||||
.ant-typography {
|
||||
color: var(--bg-ink-500);
|
||||
}
|
||||
}
|
||||
|
||||
.action-btn {
|
||||
.ant-typography {
|
||||
color: var(--bg-ink-500);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.api-key-details {
|
||||
.api-key-tag {
|
||||
background: var(--bg-vanilla-200);
|
||||
.tag-text {
|
||||
color: var(--bg-ink-500);
|
||||
}
|
||||
}
|
||||
|
||||
.api-key-created-by {
|
||||
color: var(--bg-ink-500);
|
||||
}
|
||||
|
||||
.api-key-created-at {
|
||||
.ant-typography {
|
||||
color: var(--bg-ink-500);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.delete-api-key-modal {
|
||||
.ant-modal-content {
|
||||
border: 1px solid var(--bg-vanilla-200);
|
||||
background: var(--bg-vanilla-100);
|
||||
|
||||
.ant-modal-header {
|
||||
background: var(--bg-vanilla-100);
|
||||
|
||||
.title {
|
||||
color: var(--bg-ink-500);
|
||||
}
|
||||
}
|
||||
|
||||
.ant-modal-body {
|
||||
.ant-typography {
|
||||
color: var(--bg-ink-500);
|
||||
}
|
||||
|
||||
.api-key-input {
|
||||
.ant-input {
|
||||
background: var(--bg-vanilla-200);
|
||||
color: var(--bg-ink-500);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.ant-modal-footer {
|
||||
.cancel-btn {
|
||||
background: var(--bg-vanilla-300);
|
||||
color: var(--bg-ink-400);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,412 +0,0 @@
|
||||
import './APIKeys.styles.scss';
|
||||
|
||||
import { Color } from '@signozhq/design-tokens';
|
||||
import {
|
||||
Button,
|
||||
Flex,
|
||||
Form,
|
||||
Input,
|
||||
InputNumber,
|
||||
Modal,
|
||||
Radio,
|
||||
Table,
|
||||
TableProps,
|
||||
Typography,
|
||||
} from 'antd';
|
||||
import cx from 'classnames';
|
||||
import { getRandomColor } from 'container/ExplorerOptions/utils';
|
||||
import { useGetAllViews } from 'hooks/saveViews/useGetAllViews';
|
||||
import useErrorNotification from 'hooks/useErrorNotification';
|
||||
import {
|
||||
CalendarClock,
|
||||
Check,
|
||||
ClipboardEdit,
|
||||
Contact2,
|
||||
Copy,
|
||||
Eye,
|
||||
PenLine,
|
||||
Plus,
|
||||
Search,
|
||||
Trash2,
|
||||
X,
|
||||
} from 'lucide-react';
|
||||
import { ChangeEvent, useEffect, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { ICompositeMetricQuery } from 'types/api/alerts/compositeQuery';
|
||||
import { ViewProps } from 'types/api/saveViews/types';
|
||||
import { DataSource } from 'types/common/queryBuilder';
|
||||
import { USER_ROLES } from 'types/roles';
|
||||
|
||||
function APIKeys(): JSX.Element {
|
||||
const sourcepage = 'traces';
|
||||
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
|
||||
const [activeViewKey, setActiveViewKey] = useState<string>('');
|
||||
const [newViewName, setNewViewName] = useState<string>('');
|
||||
const [color, setColor] = useState(Color.BG_SIENNA_500);
|
||||
|
||||
const [isAddModalOpen, setIsAddModalOpen] = useState(false);
|
||||
|
||||
const [isEditModalOpen, setIsEditModalOpen] = useState(false);
|
||||
const [activeViewName, setActiveViewName] = useState<string>('');
|
||||
const [
|
||||
activeCompositeQuery,
|
||||
setActiveCompositeQuery,
|
||||
] = useState<ICompositeMetricQuery | null>(null);
|
||||
const [searchValue, setSearchValue] = useState<string>('');
|
||||
const [dataSource, setDataSource] = useState<ViewProps[]>([]);
|
||||
const { t } = useTranslation(['apiKeys']);
|
||||
|
||||
const [form] = Form.useForm();
|
||||
|
||||
const hideDeleteViewModal = (): void => {
|
||||
setIsDeleteModalOpen(false);
|
||||
};
|
||||
|
||||
const handleDeleteModelOpen = (uuid: string, name: string): void => {
|
||||
setActiveViewKey(uuid);
|
||||
setActiveViewName(name);
|
||||
setIsDeleteModalOpen(true);
|
||||
};
|
||||
|
||||
const hideEditViewModal = (): void => {
|
||||
setIsEditModalOpen(false);
|
||||
};
|
||||
|
||||
const hideAddViewModal = (): void => {
|
||||
setIsAddModalOpen(false);
|
||||
};
|
||||
|
||||
const handleEditModelOpen = (view: ViewProps, color: string): void => {
|
||||
setActiveViewKey(view.uuid);
|
||||
setColor(color);
|
||||
setActiveViewName(view.name);
|
||||
setNewViewName(view.name);
|
||||
setActiveCompositeQuery(view.compositeQuery);
|
||||
setIsEditModalOpen(true);
|
||||
};
|
||||
|
||||
const handleAddModelOpen = (): void => {
|
||||
setIsAddModalOpen(true);
|
||||
};
|
||||
|
||||
const { data: viewsData, isLoading, error, isRefetching } = useGetAllViews(
|
||||
sourcepage as DataSource,
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
setDataSource(viewsData?.data.data || []);
|
||||
}, [viewsData?.data.data]);
|
||||
|
||||
useErrorNotification(error);
|
||||
|
||||
const handleSearch = (e: ChangeEvent<HTMLInputElement>): void => {
|
||||
setSearchValue(e.target.value);
|
||||
const filteredData = viewsData?.data.data.filter((view) =>
|
||||
view.name.toLowerCase().includes(e.target.value.toLowerCase()),
|
||||
);
|
||||
setDataSource(filteredData || []);
|
||||
};
|
||||
|
||||
const clearSearch = (): void => {
|
||||
setSearchValue('');
|
||||
};
|
||||
|
||||
const onDeleteHandler = (): void => {
|
||||
console.log('on delete handler');
|
||||
clearSearch();
|
||||
};
|
||||
|
||||
const onUpdateApiKey = (): void => {
|
||||
console.log('update key');
|
||||
};
|
||||
|
||||
const columns: TableProps<ViewProps>['columns'] = [
|
||||
{
|
||||
title: 'API Key',
|
||||
key: 'api-key',
|
||||
render: (view: ViewProps): JSX.Element => {
|
||||
const extraData = view.extraData !== '' ? JSON.parse(view.extraData) : '';
|
||||
let bgColor = getRandomColor();
|
||||
if (extraData !== '') {
|
||||
bgColor = extraData.color;
|
||||
}
|
||||
|
||||
const timeOptions: Intl.DateTimeFormatOptions = {
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
second: '2-digit',
|
||||
hour12: false,
|
||||
};
|
||||
const formattedTime = new Date(view.createdAt).toLocaleTimeString(
|
||||
'en-US',
|
||||
timeOptions,
|
||||
);
|
||||
|
||||
const dateOptions: Intl.DateTimeFormatOptions = {
|
||||
month: 'short',
|
||||
day: 'numeric',
|
||||
year: 'numeric',
|
||||
};
|
||||
|
||||
const formattedDate = new Date(view.createdAt).toLocaleDateString(
|
||||
'en-US',
|
||||
dateOptions,
|
||||
);
|
||||
|
||||
// Combine time and date
|
||||
const formattedDateAndTime = `${formattedDate} ${formattedTime} `;
|
||||
return (
|
||||
<div className="column-render">
|
||||
<div className="title-with-action">
|
||||
<div className="api-key-data">
|
||||
<div className="api-key-title">
|
||||
<Typography.Text>{view.name}</Typography.Text>
|
||||
</div>
|
||||
|
||||
<div className="api-key-value">
|
||||
<Typography.Text>
|
||||
{view.name.substring(0, 2)}********
|
||||
{view.name.substring(view.name.length - 2).trim()}
|
||||
</Typography.Text>
|
||||
|
||||
<Copy className="copy-key-btn" size={12} />
|
||||
</div>
|
||||
|
||||
<Button
|
||||
size="small"
|
||||
className="periscope-btn primary visibility-btn"
|
||||
shape="circle"
|
||||
icon={<Eye size={12} color={Color.BG_ROBIN_400} />}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="action-btn">
|
||||
<PenLine
|
||||
size={14}
|
||||
onClick={(): void => handleEditModelOpen(view, bgColor)}
|
||||
/>
|
||||
|
||||
<Trash2
|
||||
size={14}
|
||||
color={Color.BG_CHERRY_500}
|
||||
onClick={(): void => handleDeleteModelOpen(view.uuid, view.name)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="api-key-details">
|
||||
<div className="api-key-created-at">
|
||||
<CalendarClock size={14} />
|
||||
Last used
|
||||
<Typography.Text>{formattedDateAndTime}</Typography.Text>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="api-key-container">
|
||||
<div className="api-key-content">
|
||||
<header>
|
||||
<Typography.Title className="title">API Keys</Typography.Title>
|
||||
<Typography.Text className="subtitle">
|
||||
Create and manage access keys for the SigNoz API
|
||||
</Typography.Text>
|
||||
</header>
|
||||
|
||||
<div className="api-keys-search-add-new">
|
||||
<Input
|
||||
placeholder="Search for keys..."
|
||||
prefix={<Search size={12} color={Color.BG_VANILLA_400} />}
|
||||
value={searchValue}
|
||||
onChange={handleSearch}
|
||||
/>
|
||||
|
||||
<Button
|
||||
className="add-new-api-key-btn"
|
||||
type="primary"
|
||||
onClick={handleAddModelOpen}
|
||||
>
|
||||
{' '}
|
||||
<Plus size={14} /> New Key{' '}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<Table
|
||||
columns={columns}
|
||||
dataSource={dataSource}
|
||||
loading={isLoading || isRefetching}
|
||||
showHeader={false}
|
||||
pagination={{ pageSize: 5 }}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Modal
|
||||
className="delete-api-key-modal"
|
||||
title={<span className="title">Delete key</span>}
|
||||
open={isDeleteModalOpen}
|
||||
closable={false}
|
||||
onCancel={hideDeleteViewModal}
|
||||
footer={[
|
||||
<Button
|
||||
key="cancel"
|
||||
onClick={hideDeleteViewModal}
|
||||
className="cancel-btn"
|
||||
icon={<X size={16} />}
|
||||
>
|
||||
Cancel
|
||||
</Button>,
|
||||
<Button
|
||||
key="submit"
|
||||
icon={<Trash2 size={16} />}
|
||||
onClick={onDeleteHandler}
|
||||
className="delete-btn"
|
||||
>
|
||||
Delete key
|
||||
</Button>,
|
||||
]}
|
||||
>
|
||||
<Typography.Text className="delete-text">
|
||||
{t('delete_confirm_message', {
|
||||
keyName: activeViewName,
|
||||
})}
|
||||
</Typography.Text>
|
||||
</Modal>
|
||||
|
||||
<Modal
|
||||
className="api-key-modal"
|
||||
title="Edit key"
|
||||
open={isEditModalOpen}
|
||||
closable
|
||||
onCancel={hideEditViewModal}
|
||||
footer={[
|
||||
<Button
|
||||
className="periscope-btn primary"
|
||||
key="submit"
|
||||
type="primary"
|
||||
icon={<Check size={14} />}
|
||||
onClick={onUpdateApiKey}
|
||||
>
|
||||
Update key
|
||||
</Button>,
|
||||
]}
|
||||
>
|
||||
<Form form={form} layout="vertical" autoComplete="off">
|
||||
<Form.Item
|
||||
name="label"
|
||||
label="Label"
|
||||
rules={[
|
||||
{ required: true },
|
||||
{ type: 'url', warningOnly: true },
|
||||
{ type: 'string', min: 6 },
|
||||
]}
|
||||
>
|
||||
<Input placeholder="Top Secret" />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item name="role" label="Role">
|
||||
<Flex vertical gap="middle">
|
||||
<Radio.Group buttonStyle="solid" className="api-key-access-role">
|
||||
<Radio.Button value={USER_ROLES.ADMIN} className="tab">
|
||||
<div className="role">
|
||||
<Contact2 size={14} /> Admin
|
||||
</div>
|
||||
</Radio.Button>
|
||||
<Radio.Button value={USER_ROLES.EDITOR} className="tab selected">
|
||||
<div className="role">
|
||||
{' '}
|
||||
<ClipboardEdit size={14} /> Editor
|
||||
</div>
|
||||
</Radio.Button>
|
||||
<Radio.Button value={USER_ROLES.EDITOR} className="tab">
|
||||
<div className="role">
|
||||
{' '}
|
||||
<Eye size={14} /> Viewer
|
||||
</div>
|
||||
</Radio.Button>
|
||||
</Radio.Group>
|
||||
</Flex>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Modal>
|
||||
|
||||
<Modal
|
||||
className="api-key-modal"
|
||||
title="Create new key"
|
||||
open={isAddModalOpen}
|
||||
closable
|
||||
onCancel={hideAddViewModal}
|
||||
footer={[
|
||||
<Button
|
||||
className="periscope-btn primary"
|
||||
key="submit"
|
||||
type="primary"
|
||||
icon={<Check size={14} />}
|
||||
onClick={onUpdateApiKey}
|
||||
>
|
||||
Create new key
|
||||
</Button>,
|
||||
]}
|
||||
>
|
||||
<Form
|
||||
form={form}
|
||||
initialValues={{
|
||||
role: USER_ROLES.ADMIN,
|
||||
}}
|
||||
layout="vertical"
|
||||
autoComplete="off"
|
||||
>
|
||||
<Form.Item
|
||||
name="label"
|
||||
label="Label"
|
||||
rules={[{ required: true }, { type: 'string', min: 6 }]}
|
||||
>
|
||||
<Input placeholder="Top Secret" />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item name="role" label="Role">
|
||||
<Flex vertical gap="middle">
|
||||
<Radio.Group buttonStyle="solid" className="api-key-access-role">
|
||||
<Radio.Button
|
||||
value={USER_ROLES.ADMIN}
|
||||
className={cx(
|
||||
'tab',
|
||||
form.getFieldValue('role') === USER_ROLES.ADMIN ? 'selected' : '',
|
||||
)}
|
||||
>
|
||||
<div className="role">
|
||||
<Contact2 size={14} /> Admin
|
||||
</div>
|
||||
</Radio.Button>
|
||||
<Radio.Button value={USER_ROLES.VIEWER} className="tab">
|
||||
<div className="role">
|
||||
{' '}
|
||||
<ClipboardEdit size={14} /> Editor
|
||||
</div>
|
||||
</Radio.Button>
|
||||
<Radio.Button value={USER_ROLES.EDITOR} className="tab">
|
||||
<div className="role">
|
||||
{' '}
|
||||
<Eye size={14} /> Viewer
|
||||
</div>
|
||||
</Radio.Button>
|
||||
</Radio.Group>
|
||||
</Flex>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="expiration"
|
||||
label="Expiration"
|
||||
rules={[{ required: true }]}
|
||||
>
|
||||
<InputNumber min={1} max={100} defaultValue={30} />
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Modal>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default APIKeys;
|
||||
@@ -1,20 +1,12 @@
|
||||
@import '@signozhq/design-tokens';
|
||||
|
||||
.app-layout {
|
||||
position: relative;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
|
||||
.app-content {
|
||||
width: 100%;
|
||||
overflow: auto;
|
||||
|
||||
.content-container {
|
||||
position: relative;
|
||||
margin: 0 1rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,12 +4,10 @@
|
||||
import './AppLayout.styles.scss';
|
||||
|
||||
import { Flex } from 'antd';
|
||||
import getLocalStorageKey from 'api/browser/localstorage/get';
|
||||
import getDynamicConfigs from 'api/dynamicConfigs/getDynamicConfigs';
|
||||
import getUserLatestVersion from 'api/user/getLatestVersion';
|
||||
import getUserVersion from 'api/user/getVersion';
|
||||
import cx from 'classnames';
|
||||
import { IS_SIDEBAR_COLLAPSED } from 'constants/app';
|
||||
import ROUTES from 'constants/routes';
|
||||
import SideNav from 'container/SideNav';
|
||||
import TopNav from 'container/TopNav';
|
||||
@@ -18,15 +16,7 @@ import useLicense from 'hooks/useLicense';
|
||||
import { useNotifications } from 'hooks/useNotifications';
|
||||
import history from 'lib/history';
|
||||
import ErrorBoundaryFallback from 'pages/ErrorBoundaryFallback/ErrorBoundaryFallback';
|
||||
import {
|
||||
ReactNode,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useLayoutEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react';
|
||||
import { ReactNode, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { ErrorBoundary } from 'react-error-boundary';
|
||||
import { Helmet } from 'react-helmet-async';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
@@ -34,7 +24,6 @@ import { useQueries } from 'react-query';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { Dispatch } from 'redux';
|
||||
import { sideBarCollapse } from 'store/actions';
|
||||
import { AppState } from 'store/reducers';
|
||||
import AppActions from 'types/actions';
|
||||
import {
|
||||
@@ -55,10 +44,6 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
|
||||
(state) => state.app,
|
||||
);
|
||||
|
||||
const [collapsed, setCollapsed] = useState<boolean>(
|
||||
getLocalStorageKey(IS_SIDEBAR_COLLAPSED) === 'true',
|
||||
);
|
||||
|
||||
const isDarkMode = useIsDarkMode();
|
||||
|
||||
const { data: licenseData, isFetching } = useLicense();
|
||||
@@ -107,7 +92,7 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
|
||||
|
||||
const { children } = props;
|
||||
|
||||
const dispatch = useDispatch<Dispatch<AppActions | any>>();
|
||||
const dispatch = useDispatch<Dispatch<AppActions>>();
|
||||
|
||||
const latestCurrentCounter = useRef(0);
|
||||
const latestVersionCounter = useRef(0);
|
||||
@@ -115,14 +100,6 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
|
||||
|
||||
const { notifications } = useNotifications();
|
||||
|
||||
const onCollapse = useCallback(() => {
|
||||
setCollapsed((collapsed) => !collapsed);
|
||||
}, []);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
dispatch(sideBarCollapse(collapsed));
|
||||
}, [collapsed, dispatch]);
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
getUserLatestVersionResponse.isFetched &&
|
||||
@@ -253,34 +230,8 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
|
||||
}
|
||||
};
|
||||
|
||||
const isLogsView = (): boolean =>
|
||||
routeKey === 'LOGS' ||
|
||||
routeKey === 'LOGS_EXPLORER' ||
|
||||
routeKey === 'LOGS_PIPELINES' ||
|
||||
routeKey === 'LOGS_SAVE_VIEWS';
|
||||
|
||||
const isTracesView = (): boolean =>
|
||||
routeKey === 'TRACES_EXPLORER' || routeKey === 'TRACES_SAVE_VIEWS';
|
||||
|
||||
useEffect(() => {
|
||||
if (isDarkMode) {
|
||||
document.body.classList.remove('lightMode');
|
||||
document.body.classList.add('darkMode');
|
||||
} else {
|
||||
document.body.classList.add('lightMode');
|
||||
document.body.classList.remove('darkMode');
|
||||
}
|
||||
}, [isDarkMode]);
|
||||
|
||||
const isSideNavCollapsed = getLocalStorageKey(IS_SIDEBAR_COLLAPSED);
|
||||
|
||||
return (
|
||||
<Layout
|
||||
className={cx(
|
||||
isDarkMode ? 'darkMode' : 'lightMode',
|
||||
isSideNavCollapsed ? 'sidebarCollapsed' : '',
|
||||
)}
|
||||
>
|
||||
<Layout className={isDarkMode ? 'darkMode' : 'lightMode'}>
|
||||
<Helmet>
|
||||
<title>{pageTitle}</title>
|
||||
</Helmet>
|
||||
@@ -308,21 +259,12 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
|
||||
|
||||
<Flex className={cx('app-layout', isDarkMode ? 'darkMode' : 'lightMode')}>
|
||||
{isToDisplayLayout && !renderFullScreen && (
|
||||
<SideNav
|
||||
licenseData={licenseData}
|
||||
isFetching={isFetching}
|
||||
onCollapse={onCollapse}
|
||||
collapsed={collapsed}
|
||||
/>
|
||||
<SideNav licenseData={licenseData} isFetching={isFetching} />
|
||||
)}
|
||||
<div className={cx('app-content', collapsed ? 'collapsed' : '')}>
|
||||
<div className="app-content">
|
||||
<ErrorBoundary FallbackComponent={ErrorBoundaryFallback}>
|
||||
<LayoutContent>
|
||||
<ChildrenContainer
|
||||
style={{
|
||||
margin: isLogsView() || isTracesView() ? 0 : ' 0 1rem',
|
||||
}}
|
||||
>
|
||||
<ChildrenContainer>
|
||||
{isToDisplayLayout && !renderFullScreen && <TopNav />}
|
||||
{children}
|
||||
</ChildrenContainer>
|
||||
|
||||
@@ -18,6 +18,7 @@ export const LayoutContent = styled(LayoutComponent.Content)`
|
||||
`;
|
||||
|
||||
export const ChildrenContainer = styled.div`
|
||||
margin: 0 1rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
|
||||
@@ -7,18 +7,16 @@ function EditRules({ initialValue, ruleId }: EditRulesProps): JSX.Element {
|
||||
const [formInstance] = Form.useForm();
|
||||
|
||||
return (
|
||||
<div style={{ marginTop: '1rem' }}>
|
||||
<FormAlertRules
|
||||
alertType={
|
||||
initialValue.alertType
|
||||
? (initialValue.alertType as AlertTypes)
|
||||
: AlertTypes.METRICS_BASED_ALERT
|
||||
}
|
||||
formInstance={formInstance}
|
||||
initialValue={initialValue}
|
||||
ruleId={ruleId}
|
||||
/>
|
||||
</div>
|
||||
<FormAlertRules
|
||||
alertType={
|
||||
initialValue.alertType
|
||||
? (initialValue.alertType as AlertTypes)
|
||||
: AlertTypes.METRICS_BASED_ALERT
|
||||
}
|
||||
formInstance={formInstance}
|
||||
initialValue={initialValue}
|
||||
ruleId={ruleId}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
.empty-logs-search-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 240px;
|
||||
|
||||
.empty-logs-search-container-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
|
||||
color: var(--text-vanilla-400);
|
||||
font-family: Inter;
|
||||
font-size: 14px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 18px; /* 128.571% */
|
||||
letter-spacing: -0.07px;
|
||||
|
||||
.empty-state-svg {
|
||||
height: 50px;
|
||||
width: 50px;
|
||||
}
|
||||
|
||||
.sub-text {
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
import './EmptyLogsSearch.styles.scss';
|
||||
|
||||
import { Typography } from 'antd';
|
||||
|
||||
export default function EmptyLogsSearch(): JSX.Element {
|
||||
return (
|
||||
<div className="empty-logs-search-container">
|
||||
<div className="empty-logs-search-container-content">
|
||||
<img
|
||||
src="/Icons/emptyState.svg"
|
||||
alt="thinking-emoji"
|
||||
className="empty-state-svg"
|
||||
/>
|
||||
<Typography.Text>
|
||||
<span className="sub-text">This query had no results. </span>
|
||||
Edit your query and try again!
|
||||
</Typography.Text>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,307 +0,0 @@
|
||||
.explorer-update {
|
||||
position: fixed;
|
||||
bottom: 16px;
|
||||
left: calc(50% - 225px);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 10px 12px;
|
||||
border-radius: 50px;
|
||||
border: 1px solid var(--bg-slate-400);
|
||||
background: rgba(22, 24, 29, 0.6);
|
||||
box-shadow: 4px 4px 16px 4px rgba(0, 0, 0, 0.25);
|
||||
backdrop-filter: blur(20px);
|
||||
|
||||
.action-icon {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 8px;
|
||||
border-radius: 50px;
|
||||
border: 1px solid var(--bg-slate-400);
|
||||
background: var(--bg-slate-500);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.ant-divider {
|
||||
margin: 0;
|
||||
height: 28px;
|
||||
border: 1px solid var(--bg-slate-400);
|
||||
}
|
||||
}
|
||||
|
||||
.explorer-options {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
padding: 10px 12px;
|
||||
border-radius: 50px;
|
||||
border: 1px solid var(--bg-slate-400);
|
||||
background: rgba(22, 24, 29, 0.6);
|
||||
box-shadow: 4px 4px 16px 4px rgba(0, 0, 0, 0.25);
|
||||
backdrop-filter: blur(20px);
|
||||
position: fixed;
|
||||
bottom: 16px;
|
||||
left: calc(50% + 240px);
|
||||
transform: translate(calc(-50% - 120px), 0);
|
||||
transition: left 0.2s linear;
|
||||
|
||||
.ant-select-selector {
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
hr {
|
||||
border-color: #1d212d;
|
||||
}
|
||||
|
||||
.view-options,
|
||||
.actions {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
|
||||
button {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
|
||||
border: 1px solid #1d2023;
|
||||
color: #c0c1c3;
|
||||
background-color: #161922;
|
||||
|
||||
box-shadow: none !important;
|
||||
|
||||
&.ant-btn-round {
|
||||
padding-inline-start: 10px;
|
||||
padding-inline-end: 8px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
&.ant-btn-round:disabled {
|
||||
background-color: rgba(209, 209, 209, 0.074);
|
||||
color: #5f5f5f;
|
||||
}
|
||||
}
|
||||
|
||||
.ant-select-focused {
|
||||
border-color: transparent !important;
|
||||
|
||||
.ant-select-selector {
|
||||
border-color: transparent !important;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
.ant-select-selector {
|
||||
border: transparent !important;
|
||||
background-color: transparent !important;
|
||||
|
||||
.ant-select-selection-placeholder {
|
||||
margin-left: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.app-content {
|
||||
&.collapsed {
|
||||
.explorer-options {
|
||||
left: calc(50% + 72px);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.render-options {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 0 2px;
|
||||
color: var(--bg-vanilla-400);
|
||||
font-family: Inter;
|
||||
font-size: 12px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 20px; /* 142.857% */
|
||||
letter-spacing: -0.07px;
|
||||
|
||||
.dot {
|
||||
margin-left: 6px;
|
||||
min-height: 6px;
|
||||
min-width: 6px;
|
||||
border-radius: 50%;
|
||||
backdrop-filter: blur(20px);
|
||||
}
|
||||
}
|
||||
|
||||
.save-view-modal {
|
||||
width: 384px !important;
|
||||
.ant-modal-content {
|
||||
padding: 0;
|
||||
border-radius: 4px;
|
||||
border: 1px solid var(--bg-slate-500);
|
||||
background: var(--bg-ink-400);
|
||||
box-shadow: 0px -4px 16px 2px rgba(0, 0, 0, 0.2);
|
||||
|
||||
.ant-modal-header {
|
||||
padding: 16px;
|
||||
background: var(--bg-ink-400);
|
||||
border-bottom: 1px solid var(--bg-slate-500);
|
||||
}
|
||||
|
||||
.ant-modal-body {
|
||||
padding: 12px 16px 0px 16px;
|
||||
|
||||
.ant-typography {
|
||||
color: var(--bg-vanilla-100);
|
||||
font-family: Inter;
|
||||
font-size: 14px;
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
line-height: 20px; /* 142.857% */
|
||||
}
|
||||
|
||||
.save-view-input {
|
||||
margin-top: 8px;
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.ant-color-picker-trigger {
|
||||
padding: 6px;
|
||||
border-radius: 2px;
|
||||
border: 1px solid var(--bg-slate-400);
|
||||
background: var(--bg-ink-300);
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
|
||||
.ant-color-picker-color-block {
|
||||
border-radius: 50px;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
flex-shrink: 0;
|
||||
|
||||
.ant-color-picker-color-block-inner {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.ant-modal-footer {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
padding: 16px 16px;
|
||||
margin: 0;
|
||||
|
||||
> button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border-radius: 2px;
|
||||
background-color: var(--bg-robin-500) !important;
|
||||
color: var(--bg-vanilla-100) !important;
|
||||
font-family: Inter;
|
||||
font-size: 12px;
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
line-height: 24px;
|
||||
}
|
||||
}
|
||||
}
|
||||
.title {
|
||||
color: var(--bg-vanilla-100);
|
||||
font-family: Inter;
|
||||
font-size: 14px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.lightMode {
|
||||
.explorer-options {
|
||||
border: 1px solid var(--bg-vanilla-300);
|
||||
background: rgba(255, 255, 255, 0.8);
|
||||
box-shadow: 4px 4px 16px 4px rgba(255, 255, 255, 0.55);
|
||||
backdrop-filter: blur(20px);
|
||||
|
||||
hr {
|
||||
border-color: var(--bg-vanilla-300);
|
||||
}
|
||||
|
||||
.view-options,
|
||||
.actions {
|
||||
button {
|
||||
border: 1px solid var(--bg-vanilla-300);
|
||||
color: var(--bg-ink-200);
|
||||
background-color: var(--bg-vanilla-300);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.render-options {
|
||||
color: var(--bg-ink-200);
|
||||
}
|
||||
|
||||
.explorer-update {
|
||||
border: 1px solid var(--bg-vanilla-300);
|
||||
background: transparent;
|
||||
box-shadow: 4px 4px 16px 4px rgba(255, 255, 255, 0.55);
|
||||
backdrop-filter: blur(20px);
|
||||
|
||||
.action-icon {
|
||||
border: 1px solid var(--bg-vanilla-300);
|
||||
background: var(--bg-vanilla-300);
|
||||
}
|
||||
|
||||
.ant-divider {
|
||||
border-color: var(--bg-vanilla-300);
|
||||
}
|
||||
}
|
||||
|
||||
.ant-tooltip-arrow {
|
||||
border-top-color: var(--bg-vanilla-300) !important;
|
||||
}
|
||||
|
||||
.ant-tooltip-inner {
|
||||
background-color: var(--bg-vanilla-300);
|
||||
color: var(--bg-ink-200);
|
||||
}
|
||||
|
||||
.save-view-modal {
|
||||
.ant-modal-content {
|
||||
background: var(--bg-vanilla-200);
|
||||
border-color: var(--bg-vanilla-300);
|
||||
|
||||
.ant-modal-header {
|
||||
background: var(--bg-vanilla-200);
|
||||
border-bottom: 1px solid var(--bg-vanilla-300);
|
||||
}
|
||||
|
||||
.ant-modal-body {
|
||||
.ant-typography {
|
||||
color: var(--bg-ink-200);
|
||||
}
|
||||
|
||||
.ant-color-picker-trigger {
|
||||
border: 1px solid var(--bg-vanilla-300);
|
||||
background: var(--bg-vanilla-100);
|
||||
|
||||
.ant-color-picker-color-block {
|
||||
.ant-color-picker-color-block-inner {
|
||||
svg {
|
||||
fill: var(--bg-ink-200);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.title {
|
||||
color: var(--bg-ink-200);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,403 +0,0 @@
|
||||
import './ExplorerOptions.styles.scss';
|
||||
|
||||
import { Color } from '@signozhq/design-tokens';
|
||||
import {
|
||||
Button,
|
||||
ColorPicker,
|
||||
Divider,
|
||||
Input,
|
||||
Modal,
|
||||
RefSelectProps,
|
||||
Select,
|
||||
Tooltip,
|
||||
Typography,
|
||||
} from 'antd';
|
||||
import axios from 'axios';
|
||||
import { getViewDetailsUsingViewKey } from 'components/ExplorerCard/utils';
|
||||
import { SOMETHING_WENT_WRONG } from 'constants/api';
|
||||
import { QueryParams } from 'constants/query';
|
||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import ROUTES from 'constants/routes';
|
||||
import ExportPanelContainer from 'container/ExportPanel/ExportPanelContainer';
|
||||
import { useGetSearchQueryParam } from 'hooks/queryBuilder/useGetSearchQueryParam';
|
||||
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
||||
import { useGetAllViews } from 'hooks/saveViews/useGetAllViews';
|
||||
import { useSaveView } from 'hooks/saveViews/useSaveView';
|
||||
import { useUpdateView } from 'hooks/saveViews/useUpdateView';
|
||||
import { useIsDarkMode } from 'hooks/useDarkMode';
|
||||
import useErrorNotification from 'hooks/useErrorNotification';
|
||||
import { useHandleExplorerTabChange } from 'hooks/useHandleExplorerTabChange';
|
||||
import { useNotifications } from 'hooks/useNotifications';
|
||||
import { mapCompositeQueryFromQuery } from 'lib/newQueryBuilder/queryBuilderMappers/mapCompositeQueryFromQuery';
|
||||
import { Check, ConciergeBell, Disc3, Plus, X } from 'lucide-react';
|
||||
import { CSSProperties, useCallback, useMemo, useRef, useState } from 'react';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import { Dashboard } from 'types/api/dashboard/getAll';
|
||||
import { Query } from 'types/api/queryBuilder/queryBuilderData';
|
||||
import { DataSource } from 'types/common/queryBuilder';
|
||||
|
||||
import {
|
||||
DATASOURCE_VS_ROUTES,
|
||||
generateRGBAFromHex,
|
||||
getRandomColor,
|
||||
saveNewViewHandler,
|
||||
} from './utils';
|
||||
|
||||
function ExplorerOptions({
|
||||
disabled,
|
||||
isLoading,
|
||||
onExport,
|
||||
query,
|
||||
sourcepage,
|
||||
}: ExplorerOptionsProps): JSX.Element {
|
||||
const [isExport, setIsExport] = useState<boolean>(false);
|
||||
const [isSaveModalOpen, setIsSaveModalOpen] = useState(false);
|
||||
const [newViewName, setNewViewName] = useState<string>('');
|
||||
const [color, setColor] = useState(Color.BG_SIENNA_500);
|
||||
const { notifications } = useNotifications();
|
||||
const history = useHistory();
|
||||
const ref = useRef<RefSelectProps>(null);
|
||||
const isDarkMode = useIsDarkMode();
|
||||
|
||||
const onModalToggle = useCallback((value: boolean) => {
|
||||
setIsExport(value);
|
||||
}, []);
|
||||
|
||||
const handleSaveViewModalToggle = (): void => {
|
||||
setIsSaveModalOpen(!isSaveModalOpen);
|
||||
};
|
||||
|
||||
const hideSaveViewModal = (): void => {
|
||||
setIsSaveModalOpen(false);
|
||||
};
|
||||
|
||||
const onCreateAlertsHandler = useCallback(() => {
|
||||
history.push(
|
||||
`${ROUTES.ALERTS_NEW}?${QueryParams.compositeQuery}=${encodeURIComponent(
|
||||
JSON.stringify(query),
|
||||
)}`,
|
||||
);
|
||||
}, [history, query]);
|
||||
|
||||
const onCancel = (value: boolean) => (): void => {
|
||||
onModalToggle(value);
|
||||
};
|
||||
|
||||
const onAddToDashboard = (): void => {
|
||||
setIsExport(true);
|
||||
};
|
||||
|
||||
const {
|
||||
data: viewsData,
|
||||
isLoading: viewsIsLoading,
|
||||
error,
|
||||
isRefetching,
|
||||
refetch: refetchAllView,
|
||||
} = useGetAllViews(sourcepage);
|
||||
|
||||
const {
|
||||
currentQuery,
|
||||
panelType,
|
||||
isStagedQueryUpdated,
|
||||
redirectWithQueryBuilderData,
|
||||
} = useQueryBuilder();
|
||||
|
||||
const compositeQuery = mapCompositeQueryFromQuery(currentQuery, panelType);
|
||||
|
||||
const viewName = useGetSearchQueryParam(QueryParams.viewName) || '';
|
||||
const viewKey = useGetSearchQueryParam(QueryParams.viewKey) || '';
|
||||
|
||||
const extraData = viewsData?.data.data.find((view) => view.uuid === viewKey)
|
||||
?.extraData;
|
||||
|
||||
const extraDataColor = extraData ? JSON.parse(extraData).color : '';
|
||||
const rgbaColor = generateRGBAFromHex(
|
||||
extraDataColor || Color.BG_SIENNA_500,
|
||||
0.08,
|
||||
);
|
||||
|
||||
const {
|
||||
mutateAsync: updateViewAsync,
|
||||
isLoading: isViewUpdating,
|
||||
} = useUpdateView({
|
||||
compositeQuery,
|
||||
viewKey,
|
||||
extraData: extraData || JSON.stringify({ color: Color.BG_SIENNA_500 }),
|
||||
sourcePage: sourcepage,
|
||||
viewName,
|
||||
});
|
||||
|
||||
const showErrorNotification = (err: Error): void => {
|
||||
notifications.error({
|
||||
message: axios.isAxiosError(err) ? err.message : SOMETHING_WENT_WRONG,
|
||||
});
|
||||
};
|
||||
|
||||
const onUpdateQueryHandler = (): void => {
|
||||
const extraData = viewsData?.data.data.find((view) => view.uuid === viewKey)
|
||||
?.extraData;
|
||||
updateViewAsync(
|
||||
{
|
||||
compositeQuery: mapCompositeQueryFromQuery(currentQuery, panelType),
|
||||
viewKey,
|
||||
extraData: extraData || JSON.stringify({ color: Color.BG_SIENNA_500 }),
|
||||
sourcePage: sourcepage,
|
||||
viewName,
|
||||
},
|
||||
{
|
||||
onSuccess: () => {
|
||||
notifications.success({
|
||||
message: 'View Updated Successfully',
|
||||
});
|
||||
refetchAllView();
|
||||
},
|
||||
onError: (err) => {
|
||||
showErrorNotification(err);
|
||||
},
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
useErrorNotification(error);
|
||||
|
||||
const { handleExplorerTabChange } = useHandleExplorerTabChange();
|
||||
|
||||
const onMenuItemSelectHandler = useCallback(
|
||||
({ key }: { key: string }): void => {
|
||||
const currentViewDetails = getViewDetailsUsingViewKey(
|
||||
key,
|
||||
viewsData?.data.data,
|
||||
);
|
||||
if (!currentViewDetails) return;
|
||||
const {
|
||||
query,
|
||||
name,
|
||||
uuid,
|
||||
panelType: currentPanelType,
|
||||
} = currentViewDetails;
|
||||
|
||||
handleExplorerTabChange(currentPanelType, {
|
||||
query,
|
||||
name,
|
||||
uuid,
|
||||
});
|
||||
},
|
||||
[viewsData, handleExplorerTabChange],
|
||||
);
|
||||
|
||||
const handleSelect = (
|
||||
value: string,
|
||||
option: { key: string; value: string },
|
||||
): void => {
|
||||
onMenuItemSelectHandler({
|
||||
key: option.key,
|
||||
});
|
||||
if (ref.current) {
|
||||
ref.current.blur();
|
||||
}
|
||||
};
|
||||
|
||||
const handleClearSelect = (): void => {
|
||||
history.replace(DATASOURCE_VS_ROUTES[sourcepage]);
|
||||
};
|
||||
|
||||
const isQueryUpdated = isStagedQueryUpdated(viewsData?.data?.data, viewKey);
|
||||
|
||||
const {
|
||||
isLoading: isSaveViewLoading,
|
||||
mutateAsync: saveViewAsync,
|
||||
} = useSaveView({
|
||||
viewName: newViewName || '',
|
||||
compositeQuery,
|
||||
sourcePage: sourcepage,
|
||||
extraData: JSON.stringify({ color }),
|
||||
});
|
||||
|
||||
const onSaveHandler = (): void => {
|
||||
saveNewViewHandler({
|
||||
compositeQuery,
|
||||
handlePopOverClose: hideSaveViewModal,
|
||||
extraData: JSON.stringify({ color }),
|
||||
notifications,
|
||||
panelType: panelType || PANEL_TYPES.LIST,
|
||||
redirectWithQueryBuilderData,
|
||||
refetchAllView,
|
||||
saveViewAsync,
|
||||
sourcePage: sourcepage,
|
||||
viewName: newViewName,
|
||||
setNewViewName,
|
||||
});
|
||||
};
|
||||
|
||||
// TODO: Remove this and move this to scss file
|
||||
const dropdownStyle: CSSProperties = useMemo(
|
||||
() => ({
|
||||
borderRadius: '4px',
|
||||
border: isDarkMode
|
||||
? `1px solid ${Color.BG_SLATE_400}`
|
||||
: `1px solid ${Color.BG_VANILLA_300}`,
|
||||
background: isDarkMode
|
||||
? 'linear-gradient(139deg, rgba(18, 19, 23, 0.80) 0%, rgba(18, 19, 23, 0.90) 98.68%)'
|
||||
: 'linear-gradient(139deg, rgba(241, 241, 241, 0.8) 0%, rgba(241, 241, 241, 0.9) 98.68%)',
|
||||
boxShadow: '4px 10px 16px 2px rgba(0, 0, 0, 0.20)',
|
||||
backdropFilter: 'blur(20px)',
|
||||
bottom: '74px',
|
||||
width: '191px',
|
||||
}),
|
||||
[isDarkMode],
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
{isQueryUpdated && (
|
||||
<div className="explorer-update">
|
||||
<Tooltip title="Clear this view" placement="top">
|
||||
<Button
|
||||
className="action-icon"
|
||||
onClick={handleClearSelect}
|
||||
icon={<X size={14} />}
|
||||
/>
|
||||
</Tooltip>
|
||||
<Divider type="vertical" />
|
||||
<Tooltip title="Update this view" placement="top">
|
||||
<Button
|
||||
className="action-icon"
|
||||
disabled={isViewUpdating}
|
||||
onClick={onUpdateQueryHandler}
|
||||
icon={<Disc3 size={14} />}
|
||||
/>
|
||||
</Tooltip>
|
||||
</div>
|
||||
)}
|
||||
<div
|
||||
className="explorer-options"
|
||||
style={{
|
||||
background: extraData
|
||||
? `linear-gradient(90deg, rgba(0,0,0,0) -5%, ${rgbaColor} 9%, rgba(0,0,0,0) 30%)`
|
||||
: 'transparent',
|
||||
backdropFilter: 'blur(20px)',
|
||||
}}
|
||||
>
|
||||
<div className="view-options">
|
||||
<Select<string, { key: string; value: string }>
|
||||
showSearch
|
||||
placeholder="Select a view"
|
||||
loading={viewsIsLoading || isRefetching}
|
||||
value={viewName || undefined}
|
||||
onSelect={handleSelect}
|
||||
style={{
|
||||
minWidth: 170,
|
||||
}}
|
||||
dropdownStyle={dropdownStyle}
|
||||
className="views-dropdown"
|
||||
allowClear
|
||||
onClear={handleClearSelect}
|
||||
ref={ref}
|
||||
>
|
||||
{viewsData?.data.data.map((view) => {
|
||||
const extraData =
|
||||
view.extraData !== '' ? JSON.parse(view.extraData) : '';
|
||||
let bgColor = getRandomColor();
|
||||
if (extraData !== '') {
|
||||
bgColor = extraData.color;
|
||||
}
|
||||
return (
|
||||
<Select.Option key={view.uuid} value={view.name}>
|
||||
<div className="render-options">
|
||||
<span
|
||||
className="dot"
|
||||
style={{
|
||||
background: bgColor,
|
||||
boxShadow: `0px 0px 6px 0px ${bgColor}`,
|
||||
}}
|
||||
/>{' '}
|
||||
{view.name}
|
||||
</div>
|
||||
</Select.Option>
|
||||
);
|
||||
})}
|
||||
</Select>
|
||||
|
||||
<Button
|
||||
shape="round"
|
||||
onClick={handleSaveViewModalToggle}
|
||||
disabled={viewsIsLoading || isRefetching}
|
||||
>
|
||||
<Disc3 size={16} /> Save this view
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<hr />
|
||||
|
||||
<div className="actions">
|
||||
<Button disabled={disabled} shape="circle" onClick={onCreateAlertsHandler}>
|
||||
<ConciergeBell size={16} />
|
||||
</Button>
|
||||
|
||||
<Button disabled={disabled} shape="circle" onClick={onAddToDashboard}>
|
||||
<Plus size={16} />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Modal
|
||||
className="save-view-modal"
|
||||
title={<span className="title">Save this view</span>}
|
||||
open={isSaveModalOpen}
|
||||
closable
|
||||
onCancel={hideSaveViewModal}
|
||||
footer={[
|
||||
<Button
|
||||
key="submit"
|
||||
type="primary"
|
||||
icon={<Check size={16} />}
|
||||
onClick={onSaveHandler}
|
||||
disabled={isSaveViewLoading}
|
||||
>
|
||||
Save this view
|
||||
</Button>,
|
||||
]}
|
||||
>
|
||||
<Typography.Text>Label</Typography.Text>
|
||||
<div className="save-view-input">
|
||||
<ColorPicker
|
||||
value={color}
|
||||
onChange={(value, hex): void => setColor(hex)}
|
||||
/>
|
||||
<Input
|
||||
placeholder="e.g. External http method view"
|
||||
value={newViewName}
|
||||
onChange={(e): void => setNewViewName(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
</Modal>
|
||||
|
||||
<Modal
|
||||
footer={null}
|
||||
onOk={onCancel(false)}
|
||||
onCancel={onCancel(false)}
|
||||
open={isExport}
|
||||
centered
|
||||
destroyOnClose
|
||||
>
|
||||
<ExportPanelContainer
|
||||
query={query}
|
||||
isLoading={isLoading}
|
||||
onExport={onExport}
|
||||
/>
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export interface ExplorerOptionsProps {
|
||||
isLoading?: boolean;
|
||||
onExport: (dashboard: Dashboard | null) => void;
|
||||
query: Query | null;
|
||||
disabled: boolean;
|
||||
sourcepage: DataSource;
|
||||
}
|
||||
|
||||
ExplorerOptions.defaultProps = { isLoading: false };
|
||||
|
||||
export default ExplorerOptions;
|
||||
@@ -1,28 +0,0 @@
|
||||
import { NotificationInstance } from 'antd/es/notification/interface';
|
||||
import { AxiosResponse } from 'axios';
|
||||
import { SaveViewWithNameProps } from 'components/ExplorerCard/types';
|
||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import { Dispatch, SetStateAction } from 'react';
|
||||
import { UseMutateAsyncFunction } from 'react-query';
|
||||
import { ICompositeMetricQuery } from 'types/api/alerts/compositeQuery';
|
||||
import { SaveViewPayloadProps, SaveViewProps } from 'types/api/saveViews/types';
|
||||
import { DataSource, QueryBuilderContextType } from 'types/common/queryBuilder';
|
||||
|
||||
export interface SaveNewViewHandlerProps {
|
||||
viewName: string;
|
||||
compositeQuery: ICompositeMetricQuery;
|
||||
sourcePage: DataSource;
|
||||
extraData: SaveViewProps['extraData'];
|
||||
panelType: PANEL_TYPES | null;
|
||||
notifications: NotificationInstance;
|
||||
refetchAllView: SaveViewWithNameProps['refetchAllView'];
|
||||
saveViewAsync: UseMutateAsyncFunction<
|
||||
AxiosResponse<SaveViewPayloadProps>,
|
||||
Error,
|
||||
SaveViewProps,
|
||||
SaveViewPayloadProps
|
||||
>;
|
||||
handlePopOverClose: SaveViewWithNameProps['handlePopOverClose'];
|
||||
redirectWithQueryBuilderData: QueryBuilderContextType['redirectWithQueryBuilderData'];
|
||||
setNewViewName: Dispatch<SetStateAction<string>>;
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
import { Color } from '@signozhq/design-tokens';
|
||||
import { showErrorNotification } from 'components/ExplorerCard/utils';
|
||||
import { QueryParams } from 'constants/query';
|
||||
import ROUTES from 'constants/routes';
|
||||
import { mapQueryDataFromApi } from 'lib/newQueryBuilder/queryBuilderMappers/mapQueryDataFromApi';
|
||||
import { DataSource } from 'types/common/queryBuilder';
|
||||
|
||||
import { SaveNewViewHandlerProps } from './types';
|
||||
|
||||
export const getRandomColor = (): Color => {
|
||||
const colorKeys = Object.keys(Color) as (keyof typeof Color)[];
|
||||
const randomKey = colorKeys[Math.floor(Math.random() * colorKeys.length)];
|
||||
return Color[randomKey];
|
||||
};
|
||||
|
||||
export const DATASOURCE_VS_ROUTES: Record<DataSource, string> = {
|
||||
[DataSource.METRICS]: '',
|
||||
[DataSource.TRACES]: ROUTES.TRACES_EXPLORER,
|
||||
[DataSource.LOGS]: ROUTES.LOGS_EXPLORER,
|
||||
};
|
||||
|
||||
export const saveNewViewHandler = ({
|
||||
saveViewAsync,
|
||||
refetchAllView,
|
||||
notifications,
|
||||
handlePopOverClose,
|
||||
viewName,
|
||||
compositeQuery,
|
||||
sourcePage,
|
||||
extraData,
|
||||
redirectWithQueryBuilderData,
|
||||
panelType,
|
||||
setNewViewName,
|
||||
}: SaveNewViewHandlerProps): void => {
|
||||
saveViewAsync(
|
||||
{
|
||||
viewName,
|
||||
compositeQuery,
|
||||
sourcePage,
|
||||
extraData,
|
||||
},
|
||||
{
|
||||
onSuccess: (data) => {
|
||||
refetchAllView();
|
||||
redirectWithQueryBuilderData(mapQueryDataFromApi(compositeQuery), {
|
||||
[QueryParams.panelTypes]: panelType,
|
||||
[QueryParams.viewName]: viewName,
|
||||
[QueryParams.viewKey]: data.data.data,
|
||||
});
|
||||
notifications.success({
|
||||
message: 'View Saved Successfully',
|
||||
});
|
||||
},
|
||||
onError: (err) => {
|
||||
showErrorNotification(notifications, err);
|
||||
},
|
||||
onSettled: () => {
|
||||
handlePopOverClose();
|
||||
setNewViewName('');
|
||||
},
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
export const generateRGBAFromHex = (hex: string, opacity: number): string =>
|
||||
`rgba(${parseInt(hex.slice(1, 3), 16)}, ${parseInt(
|
||||
hex.slice(3, 5),
|
||||
16,
|
||||
)}, ${parseInt(hex.slice(5, 7), 16)}, ${opacity})`;
|
||||
@@ -16,10 +16,7 @@ import {
|
||||
} from './styles';
|
||||
import { filterOptions, getSelectOptions } from './utils';
|
||||
|
||||
function ExportPanelContainer({
|
||||
isLoading,
|
||||
onExport,
|
||||
}: ExportPanelProps): JSX.Element {
|
||||
function ExportPanel({ isLoading, onExport }: ExportPanelProps): JSX.Element {
|
||||
const { t } = useTranslation(['dashboard']);
|
||||
|
||||
const [selectedDashboardId, setSelectedDashboardId] = useState<string | null>(
|
||||
@@ -121,4 +118,4 @@ function ExportPanelContainer({
|
||||
);
|
||||
}
|
||||
|
||||
export default ExportPanelContainer;
|
||||
export default ExportPanel;
|
||||
@@ -1,9 +1,13 @@
|
||||
import { Modal } from 'antd';
|
||||
import { AlertOutlined, AreaChartOutlined } from '@ant-design/icons';
|
||||
import { Button, Modal, Space } from 'antd';
|
||||
import { QueryParams } from 'constants/query';
|
||||
import ROUTES from 'constants/routes';
|
||||
import history from 'lib/history';
|
||||
import { useCallback, useState } from 'react';
|
||||
import { Dashboard } from 'types/api/dashboard/getAll';
|
||||
import { Query } from 'types/api/queryBuilder/queryBuilderData';
|
||||
|
||||
import ExportPanelContainer from './ExportPanelContainer';
|
||||
import ExportPanelContainer from './ExportPanel';
|
||||
|
||||
function ExportPanel({
|
||||
isLoading,
|
||||
@@ -16,25 +20,53 @@ function ExportPanel({
|
||||
setIsExport(value);
|
||||
}, []);
|
||||
|
||||
const onCreateAlertsHandler = useCallback(() => {
|
||||
history.push(
|
||||
`${ROUTES.ALERTS_NEW}?${QueryParams.compositeQuery}=${encodeURIComponent(
|
||||
JSON.stringify(query),
|
||||
)}`,
|
||||
);
|
||||
}, [query]);
|
||||
|
||||
const onCancel = (value: boolean) => (): void => {
|
||||
onModalToggle(value);
|
||||
};
|
||||
|
||||
const onAddToDashboard = (): void => {
|
||||
setIsExport(true);
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal
|
||||
footer={null}
|
||||
onOk={onCancel(false)}
|
||||
onCancel={onCancel(false)}
|
||||
open={isExport}
|
||||
centered
|
||||
destroyOnClose
|
||||
>
|
||||
<ExportPanelContainer
|
||||
query={query}
|
||||
isLoading={isLoading}
|
||||
onExport={onExport}
|
||||
/>
|
||||
</Modal>
|
||||
<>
|
||||
<Space size={24}>
|
||||
<Button
|
||||
icon={<AreaChartOutlined />}
|
||||
onClick={onAddToDashboard}
|
||||
type="primary"
|
||||
>
|
||||
Add to Dashboard
|
||||
</Button>
|
||||
|
||||
<Button onClick={onCreateAlertsHandler} icon={<AlertOutlined />}>
|
||||
Setup Alerts
|
||||
</Button>
|
||||
</Space>
|
||||
|
||||
<Modal
|
||||
footer={null}
|
||||
onOk={onCancel(false)}
|
||||
onCancel={onCancel(false)}
|
||||
open={isExport}
|
||||
centered
|
||||
destroyOnClose
|
||||
>
|
||||
<ExportPanelContainer
|
||||
query={query}
|
||||
isLoading={isLoading}
|
||||
onExport={onExport}
|
||||
/>
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,6 @@ import GridPanelSwitch from 'container/GridPanelSwitch';
|
||||
import { getFormatNameByOptionId } from 'container/NewWidget/RightContainer/alertFomatCategories';
|
||||
import { timePreferenceType } from 'container/NewWidget/RightContainer/timeItems';
|
||||
import { Time } from 'container/TopNav/DateTimeSelection/config';
|
||||
import { Time as TimeV2 } from 'container/TopNav/DateTimeSelectionV2/config';
|
||||
import { useGetQueryRange } from 'hooks/queryBuilder/useGetQueryRange';
|
||||
import { useIsDarkMode } from 'hooks/useDarkMode';
|
||||
import { useResizeObserver } from 'hooks/useDimensions';
|
||||
@@ -29,7 +28,7 @@ export interface ChartPreviewProps {
|
||||
query: Query | null;
|
||||
graphType?: PANEL_TYPES;
|
||||
selectedTime?: timePreferenceType;
|
||||
selectedInterval?: Time | TimeV2;
|
||||
selectedInterval?: Time;
|
||||
headline?: JSX.Element;
|
||||
alertDef?: AlertDef;
|
||||
userQueryKey?: string;
|
||||
|
||||
@@ -1,56 +0,0 @@
|
||||
.alert-tabs {
|
||||
.ant-tabs-tab {
|
||||
border: none !important;
|
||||
margin-left: 0px !important;
|
||||
padding: 0px !important;
|
||||
|
||||
.nav-btns {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.ant-btn-default {
|
||||
border-color: transparent;
|
||||
}
|
||||
}
|
||||
.ant-tabs-tab-active {
|
||||
.nav-btns {
|
||||
background: var(--bg-slate-400) !important;
|
||||
}
|
||||
}
|
||||
|
||||
.ant-tabs-nav {
|
||||
margin: 0px;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
.ant-tabs-nav::before {
|
||||
border-bottom: none !important;
|
||||
}
|
||||
.ant-tabs-nav-list {
|
||||
border: 1px solid var(--bg-slate-200);
|
||||
}
|
||||
.ant-tabs-tab + .ant-tabs-tab {
|
||||
border-left: 1px solid var(--bg-slate-200) !important;
|
||||
}
|
||||
|
||||
.stage-run-query {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
|
||||
.lightMode {
|
||||
.alert-tabs {
|
||||
.ant-tabs-nav-list {
|
||||
border: 1px solid var(--bg-vanilla-300);
|
||||
}
|
||||
.ant-tabs-tab + .ant-tabs-tab {
|
||||
border-left: 1px solid var(--bg-vanilla-200) !important;
|
||||
}
|
||||
.ant-tabs-tab-active {
|
||||
.nav-btns {
|
||||
background: var(--bg-vanilla-300) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,8 @@
|
||||
import './QuerySection.styles.scss';
|
||||
|
||||
import { Button, Tabs } from 'antd';
|
||||
import { ALERTS_DATA_SOURCE_MAP } from 'constants/alerts';
|
||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import { QueryBuilder } from 'container/QueryBuilder';
|
||||
import { Atom, LucideAccessibility, Play, Terminal } from 'lucide-react';
|
||||
import { useMemo, useState } from 'react';
|
||||
import { useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { AppState } from 'store/reducers';
|
||||
@@ -25,7 +22,6 @@ function QuerySection({
|
||||
}: QuerySectionProps): JSX.Element {
|
||||
// init namespace for translations
|
||||
const { t } = useTranslation('alerts');
|
||||
const [currentTab, setCurrentTab] = useState(queryCategory);
|
||||
|
||||
const { featureResponse } = useSelector<AppState, AppReducer>(
|
||||
(state) => state.app,
|
||||
@@ -35,7 +31,6 @@ function QuerySection({
|
||||
featureResponse.refetch().then(() => {
|
||||
setQueryCategory(queryType as EQueryType);
|
||||
});
|
||||
setCurrentTab(queryType as EQueryType);
|
||||
};
|
||||
|
||||
const renderPromqlUI = (): JSX.Element => <PromqlSection />;
|
||||
@@ -54,51 +49,22 @@ function QuerySection({
|
||||
|
||||
const tabs = [
|
||||
{
|
||||
label: (
|
||||
<Button className="nav-btns">
|
||||
<Atom size={14} />
|
||||
</Button>
|
||||
),
|
||||
label: t('tab_qb'),
|
||||
key: EQueryType.QUERY_BUILDER,
|
||||
},
|
||||
{
|
||||
label: (
|
||||
<Button className="nav-btns">
|
||||
<Terminal size={14} />
|
||||
</Button>
|
||||
),
|
||||
label: t('tab_chquery'),
|
||||
key: EQueryType.CLICKHOUSE,
|
||||
},
|
||||
];
|
||||
|
||||
const items = useMemo(
|
||||
() => [
|
||||
{
|
||||
label: (
|
||||
<Button className="nav-btns">
|
||||
<Atom size={14} />
|
||||
</Button>
|
||||
),
|
||||
key: EQueryType.QUERY_BUILDER,
|
||||
},
|
||||
{
|
||||
label: (
|
||||
<Button className="nav-btns">
|
||||
<Terminal size={14} />
|
||||
</Button>
|
||||
),
|
||||
key: EQueryType.CLICKHOUSE,
|
||||
},
|
||||
{
|
||||
label: (
|
||||
<Button className="nav-btns">
|
||||
<LucideAccessibility size={14} />
|
||||
</Button>
|
||||
),
|
||||
key: EQueryType.PROM,
|
||||
},
|
||||
{ label: t('tab_qb'), key: EQueryType.QUERY_BUILDER },
|
||||
{ label: t('tab_chquery'), key: EQueryType.CLICKHOUSE },
|
||||
{ label: t('tab_promql'), key: EQueryType.PROM },
|
||||
],
|
||||
[],
|
||||
[t],
|
||||
);
|
||||
|
||||
const renderTabs = (typ: AlertTypes): JSX.Element | null => {
|
||||
@@ -107,54 +73,40 @@ function QuerySection({
|
||||
case AlertTypes.LOGS_BASED_ALERT:
|
||||
case AlertTypes.EXCEPTIONS_BASED_ALERT:
|
||||
return (
|
||||
<div className="alert-tabs">
|
||||
<Tabs
|
||||
type="card"
|
||||
style={{ width: '100%' }}
|
||||
defaultActiveKey={currentTab}
|
||||
activeKey={currentTab}
|
||||
onChange={handleQueryCategoryChange}
|
||||
tabBarExtraContent={
|
||||
<span style={{ display: 'flex', gap: '1rem', alignItems: 'center' }}>
|
||||
<Button
|
||||
type="primary"
|
||||
onClick={runQuery}
|
||||
className="stage-run-query"
|
||||
icon={<Play size={14} />}
|
||||
>
|
||||
Stage & Run Query
|
||||
</Button>
|
||||
</span>
|
||||
}
|
||||
items={tabs}
|
||||
/>
|
||||
</div>
|
||||
<Tabs
|
||||
type="card"
|
||||
style={{ width: '100%' }}
|
||||
defaultActiveKey={EQueryType.QUERY_BUILDER}
|
||||
activeKey={queryCategory}
|
||||
onChange={handleQueryCategoryChange}
|
||||
tabBarExtraContent={
|
||||
<span style={{ display: 'flex', gap: '1rem', alignItems: 'center' }}>
|
||||
<Button type="primary" onClick={runQuery}>
|
||||
Run Query
|
||||
</Button>
|
||||
</span>
|
||||
}
|
||||
items={tabs}
|
||||
/>
|
||||
);
|
||||
case AlertTypes.METRICS_BASED_ALERT:
|
||||
default:
|
||||
return (
|
||||
<div className="alert-tabs">
|
||||
<Tabs
|
||||
type="card"
|
||||
style={{ width: '100%' }}
|
||||
defaultActiveKey={currentTab}
|
||||
activeKey={currentTab}
|
||||
onChange={handleQueryCategoryChange}
|
||||
tabBarExtraContent={
|
||||
<span style={{ display: 'flex', gap: '1rem', alignItems: 'center' }}>
|
||||
<Button
|
||||
type="primary"
|
||||
onClick={runQuery}
|
||||
className="stage-run-query"
|
||||
icon={<Play size={14} />}
|
||||
>
|
||||
Stage & Run Query
|
||||
</Button>
|
||||
</span>
|
||||
}
|
||||
items={items}
|
||||
/>
|
||||
</div>
|
||||
<Tabs
|
||||
type="card"
|
||||
style={{ width: '100%' }}
|
||||
defaultActiveKey={EQueryType.QUERY_BUILDER}
|
||||
activeKey={queryCategory}
|
||||
onChange={handleQueryCategoryChange}
|
||||
tabBarExtraContent={
|
||||
<span style={{ display: 'flex', gap: '1rem', alignItems: 'center' }}>
|
||||
<Button type="primary" onClick={runQuery}>
|
||||
Run Query
|
||||
</Button>
|
||||
</span>
|
||||
}
|
||||
items={items}
|
||||
/>
|
||||
);
|
||||
}
|
||||
};
|
||||
@@ -174,8 +126,8 @@ function QuerySection({
|
||||
<>
|
||||
<StepHeading> {t('alert_form_step1')}</StepHeading>
|
||||
<FormContainer>
|
||||
<div>{renderTabs(alertType)}</div>
|
||||
{renderQuerySection(currentTab)}
|
||||
<div style={{ display: 'flex' }}>{renderTabs(alertType)}</div>
|
||||
{renderQuerySection(queryCategory)}
|
||||
</FormContainer>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -142,10 +142,6 @@ function FormAlertRules({
|
||||
// onQueryCategoryChange handles changes to query category
|
||||
// in state as well as sets additional defaults
|
||||
const onQueryCategoryChange = (val: EQueryType): void => {
|
||||
const element = document.getElementById('top');
|
||||
if (element) {
|
||||
element.scrollIntoView({ behavior: 'smooth' });
|
||||
}
|
||||
if (val === EQueryType.PROM) {
|
||||
setAlertDef({
|
||||
...alertDef,
|
||||
@@ -469,7 +465,7 @@ function FormAlertRules({
|
||||
<>
|
||||
{Element}
|
||||
|
||||
<PanelContainer id="top">
|
||||
<PanelContainer>
|
||||
<StyledLeftContainer flex="5 1 600px" md={18}>
|
||||
<MainFormContainer
|
||||
initialValues={initialValue}
|
||||
|
||||
@@ -76,10 +76,6 @@ export const FormContainer = styled(Card)`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border-radius: 4px;
|
||||
|
||||
.ant-card-body {
|
||||
padding: 12px;
|
||||
}
|
||||
`;
|
||||
|
||||
export const TextareaMedium = styled(TextArea)`
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
.general-settings-container {
|
||||
.ant-card-body {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import './GeneralSettingsCloud.styles.scss';
|
||||
|
||||
import { Card, Typography } from 'antd';
|
||||
import { Info } from 'lucide-react';
|
||||
|
||||
export default function GeneralSettingsCloud(): JSX.Element {
|
||||
return (
|
||||
<Card className="general-settings-container">
|
||||
<Info size={16} />
|
||||
<Typography.Text>
|
||||
Please <a href="mailto:cloud-support@signoz.io"> email us </a> or connect
|
||||
with us via intercom support to change the retention period.
|
||||
</Typography.Text>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
3
frontend/src/container/GeneralSettingsCloud/index.tsx
Normal file
3
frontend/src/container/GeneralSettingsCloud/index.tsx
Normal file
@@ -0,0 +1,3 @@
|
||||
import GeneralSettingsCloud from './GeneralSettingsCloud';
|
||||
|
||||
export default GeneralSettingsCloud;
|
||||
@@ -51,7 +51,7 @@ function HeaderContainer(): JSX.Element {
|
||||
const isDarkMode = useIsDarkMode();
|
||||
const { toggleTheme } = useThemeMode();
|
||||
const [showTrialExpiryBanner, setShowTrialExpiryBanner] = useState(false);
|
||||
const [homeRoute, setHomeRoute] = useState<string>(ROUTES.APPLICATION);
|
||||
const [homeRoute, setHomeRoute] = useState(ROUTES.APPLICATION);
|
||||
|
||||
const [isUserDropDownOpen, setIsUserDropDownOpen] = useState<boolean>(false);
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { Card, Typography } from 'antd';
|
||||
import LogDetail from 'components/LogDetail';
|
||||
import { VIEW_TYPES } from 'components/LogDetail/constants';
|
||||
import ListLogView from 'components/Logs/ListLogView';
|
||||
import RawLogView from 'components/Logs/RawLogView';
|
||||
import Spinner from 'components/Spinner';
|
||||
@@ -14,6 +13,7 @@ import { Heading } from 'container/LogsTable/styles';
|
||||
import { useOptionsMenu } from 'container/OptionsMenu';
|
||||
import { useActiveLog } from 'hooks/logs/useActiveLog';
|
||||
import { useCopyLogLink } from 'hooks/logs/useCopyLogLink';
|
||||
import useFontFaceObserver from 'hooks/useFontObserver';
|
||||
import { useEventSource } from 'providers/EventSource';
|
||||
import { memo, useCallback, useEffect, useMemo, useRef } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
@@ -51,6 +51,19 @@ function LiveLogsList({ logs }: LiveLogsListProps): JSX.Element {
|
||||
[logs, activeLogId],
|
||||
);
|
||||
|
||||
useFontFaceObserver(
|
||||
[
|
||||
{
|
||||
family: 'Fira Code',
|
||||
weight: '300',
|
||||
},
|
||||
],
|
||||
options.format === 'raw',
|
||||
{
|
||||
timeout: 5000,
|
||||
},
|
||||
);
|
||||
|
||||
const selectedFields = convertKeysToColumnFields(options.selectColumns);
|
||||
|
||||
const getItemContent = useCallback(
|
||||
@@ -137,7 +150,6 @@ function LiveLogsList({ logs }: LiveLogsListProps): JSX.Element {
|
||||
</InfinityWrapperStyled>
|
||||
)}
|
||||
<LogDetail
|
||||
selectedTab={VIEW_TYPES.OVERVIEW}
|
||||
log={activeLog}
|
||||
onClose={onClearActiveLog}
|
||||
onAddToQuery={onAddToQuery}
|
||||
|
||||
@@ -29,11 +29,11 @@ function ActionItem({
|
||||
() => (
|
||||
<Col>
|
||||
<Button type="text" size="small" onClick={onClickHandler(OPERATORS.IN)}>
|
||||
<PlusCircleOutlined size={12} /> Filter for value
|
||||
<PlusCircleOutlined /> Filter for value
|
||||
</Button>
|
||||
<br />
|
||||
<Button type="text" size="small" onClick={onClickHandler(OPERATORS.NIN)}>
|
||||
<MinusCircleOutlined size={12} /> Filter out value
|
||||
<MinusCircleOutlined /> Filter out value
|
||||
</Button>
|
||||
</Col>
|
||||
),
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
.log-context-container {
|
||||
border: 1px solid var(--bg-slate-400);
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
import './ContextView.styles.scss';
|
||||
|
||||
import RawLogView from 'components/Logs/RawLogView';
|
||||
import LogsContextList from 'container/LogsContextList';
|
||||
import { ORDERBY_FILTERS } from 'container/QueryBuilder/filters/OrderByFilter/config';
|
||||
import { ILog } from 'types/api/logs/log';
|
||||
import { Query, TagFilter } from 'types/api/queryBuilder/queryBuilderData';
|
||||
|
||||
interface LogContextProps {
|
||||
log: ILog;
|
||||
contextQuery: Query | undefined;
|
||||
filters: TagFilter | null;
|
||||
isEdit: boolean;
|
||||
}
|
||||
|
||||
function ContextView({
|
||||
log,
|
||||
filters,
|
||||
contextQuery,
|
||||
isEdit,
|
||||
}: LogContextProps): JSX.Element {
|
||||
// eslint-disable-next-line react/jsx-no-useless-fragment
|
||||
if (!contextQuery) return <></>;
|
||||
|
||||
return (
|
||||
<div className="log-context-container">
|
||||
<LogsContextList
|
||||
className="logs-context-list-asc"
|
||||
order={ORDERBY_FILTERS.ASC}
|
||||
filters={filters}
|
||||
isEdit={isEdit}
|
||||
log={log}
|
||||
query={contextQuery}
|
||||
/>
|
||||
<RawLogView
|
||||
isActiveLog
|
||||
isReadOnly
|
||||
isTextOverflowEllipsisDisabled={false}
|
||||
data={log}
|
||||
linesPerRow={1}
|
||||
/>
|
||||
<LogsContextList
|
||||
className="logs-context-list-desc"
|
||||
order={ORDERBY_FILTERS.DESC}
|
||||
filters={filters}
|
||||
isEdit={isEdit}
|
||||
log={log}
|
||||
query={contextQuery}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default ContextView;
|
||||
@@ -1,22 +0,0 @@
|
||||
.field-renderer-container {
|
||||
display: flex !important;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
.label {
|
||||
color: var(--text-robin-400);
|
||||
font-family: SF Mono;
|
||||
font-family: 'Space Mono', monospace;
|
||||
font-size: 13px;
|
||||
font-weight: var(--font-weight-normal);
|
||||
line-height: 18px;
|
||||
letter-spacing: -0.005em;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.tags {
|
||||
display: flex;
|
||||
gap: 8;
|
||||
}
|
||||
}
|
||||
@@ -3,23 +3,18 @@ import styled from 'styled-components';
|
||||
|
||||
export const TagContainer = styled(Tag)`
|
||||
&&& {
|
||||
border-color: var(--bg-slate-400);
|
||||
border-radius: 0.25rem;
|
||||
padding: 0.063rem 0.5rem;
|
||||
font-weight: 600;
|
||||
font-size: var(--font-size-xs);
|
||||
font-size: 0.75rem;
|
||||
line-height: 1.25rem;
|
||||
}
|
||||
`;
|
||||
|
||||
export const TagLabel = styled.span`
|
||||
font-weight: 400;
|
||||
font-size: 12px;
|
||||
`;
|
||||
|
||||
export const TagValue = styled.span`
|
||||
color: var(--text-sakura-400);
|
||||
/* background-color: var(--bg-slate-400); */
|
||||
text-transform: capitalize;
|
||||
font-size: var(--font-size-xs);
|
||||
font-weight: 400;
|
||||
`;
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
import './FieldRenderer.styles.scss';
|
||||
|
||||
import { Divider } from 'antd';
|
||||
import { blue } from '@ant-design/colors';
|
||||
|
||||
import { TagContainer, TagLabel, TagValue } from './FieldRenderer.styles';
|
||||
import { FieldRendererProps } from './LogDetailedView.types';
|
||||
@@ -10,29 +8,21 @@ function FieldRenderer({ field }: FieldRendererProps): JSX.Element {
|
||||
const { dataType, newField, logType } = getFieldAttributes(field);
|
||||
|
||||
return (
|
||||
<span className="field-renderer-container">
|
||||
<span>
|
||||
{dataType && newField && logType ? (
|
||||
<>
|
||||
<div className="label">{newField} </div>
|
||||
|
||||
<div className="tags">
|
||||
<TagContainer>
|
||||
<TagLabel>
|
||||
type
|
||||
<Divider type="vertical" />{' '}
|
||||
</TagLabel>
|
||||
<TagValue>{logType}</TagValue>
|
||||
</TagContainer>
|
||||
<TagContainer>
|
||||
<TagLabel>
|
||||
data type <Divider type="vertical" />{' '}
|
||||
</TagLabel>
|
||||
<TagValue>{dataType}</TagValue>
|
||||
</TagContainer>
|
||||
</div>
|
||||
<span style={{ color: blue[4] }}>{newField} </span>
|
||||
<TagContainer>
|
||||
<TagLabel>Type: </TagLabel>
|
||||
<TagValue>{logType}</TagValue>
|
||||
</TagContainer>
|
||||
<TagContainer>
|
||||
<TagLabel>Data type: </TagLabel>
|
||||
<TagValue>{dataType}</TagValue>
|
||||
</TagContainer>
|
||||
</>
|
||||
) : (
|
||||
<span className="label">{field}</span>
|
||||
<span style={{ color: blue[4] }}>{field}</span>
|
||||
)}
|
||||
</span>
|
||||
);
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
.json-view-container {
|
||||
border-radius: 3px;
|
||||
border: 1px solid var(--bg-slate-500);
|
||||
padding-top: 16px;
|
||||
|
||||
.json-view-footer {
|
||||
height: 36px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border-top: 1px solid var(--bg-slate-500);
|
||||
}
|
||||
|
||||
.log-switch {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
.wrap-word-switch {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
margin-left: var(--margin-3);
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.log-switch-btn {
|
||||
border: 1px solid var(--bg-slate-500);
|
||||
background-color: var(--bg-slate-500);
|
||||
width: 40px;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.lightMode {
|
||||
.json-view-container {
|
||||
.log-switch {
|
||||
.log-switch-btn {
|
||||
background: var(--bg-vanilla-200);
|
||||
border: 1px solid var(--bg-vanilla-400);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,90 +1,44 @@
|
||||
import './JsonView.styles.scss';
|
||||
|
||||
import MEditor, { EditorProps, Monaco } from '@monaco-editor/react';
|
||||
import { Color } from '@signozhq/design-tokens';
|
||||
import { Switch, Typography } from 'antd';
|
||||
import { useIsDarkMode } from 'hooks/useDarkMode';
|
||||
import { useMemo, useState } from 'react';
|
||||
import { blue } from '@ant-design/colors';
|
||||
import { CopyFilled } from '@ant-design/icons';
|
||||
import { Button, Row } from 'antd';
|
||||
import Editor from 'components/Editor';
|
||||
import { useMemo } from 'react';
|
||||
import { useCopyToClipboard } from 'react-use';
|
||||
|
||||
import { JSONViewProps } from './LogDetailedView.types';
|
||||
import { aggregateAttributesResourcesToString } from './utils';
|
||||
|
||||
function JSONView({ logData }: JSONViewProps): JSX.Element {
|
||||
const [isWrapWord, setIsWrapWord] = useState<boolean>(false);
|
||||
const [, copyToClipboard] = useCopyToClipboard();
|
||||
|
||||
const LogJsonData = useMemo(
|
||||
() => aggregateAttributesResourcesToString(logData),
|
||||
[logData],
|
||||
);
|
||||
|
||||
const isDarkMode = useIsDarkMode();
|
||||
|
||||
const options: EditorProps['options'] = {
|
||||
automaticLayout: true,
|
||||
readOnly: true,
|
||||
wordWrap: 'on',
|
||||
minimap: {
|
||||
enabled: false,
|
||||
},
|
||||
fontWeight: 400,
|
||||
// fontFamily: 'SF Mono',
|
||||
fontFamily: 'Space Mono',
|
||||
fontSize: 13,
|
||||
lineHeight: '18px',
|
||||
colorDecorators: true,
|
||||
scrollBeyondLastLine: false,
|
||||
decorationsOverviewRuler: false,
|
||||
scrollbar: {
|
||||
vertical: 'hidden',
|
||||
horizontal: 'hidden',
|
||||
},
|
||||
folding: false,
|
||||
};
|
||||
|
||||
const handleWrapWord = (checked: boolean): void => {
|
||||
setIsWrapWord(checked);
|
||||
};
|
||||
|
||||
function setEditorTheme(monaco: Monaco): void {
|
||||
monaco.editor.defineTheme('my-theme', {
|
||||
base: 'vs-dark',
|
||||
inherit: true,
|
||||
rules: [
|
||||
{ token: 'string.key.json', foreground: Color.BG_VANILLA_400 },
|
||||
{ token: 'string.value.json', foreground: Color.BG_ROBIN_400 },
|
||||
],
|
||||
colors: {
|
||||
'editor.background': Color.BG_INK_400,
|
||||
},
|
||||
// fontFamily: 'SF Mono',
|
||||
fontFamily: 'Space Mono',
|
||||
fontSize: 12,
|
||||
fontWeight: 'normal',
|
||||
lineHeight: 18,
|
||||
letterSpacing: -0.06,
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="json-view-container">
|
||||
<MEditor
|
||||
value={isWrapWord ? JSON.stringify(LogJsonData) : LogJsonData}
|
||||
language="json"
|
||||
options={options}
|
||||
onChange={(): void => {}}
|
||||
height="68vh"
|
||||
theme={isDarkMode ? 'my-theme' : 'light'}
|
||||
// eslint-disable-next-line react/jsx-no-bind
|
||||
beforeMount={setEditorTheme}
|
||||
/>
|
||||
|
||||
<div className="json-view-footer">
|
||||
<div className="log-switch">
|
||||
<div className="wrap-word-switch">
|
||||
<Typography.Text>Wrap text</Typography.Text>
|
||||
<Switch checked={isWrapWord} onChange={handleWrapWord} size="small" />
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<Row
|
||||
style={{
|
||||
justifyContent: 'flex-end',
|
||||
margin: '0.5rem 0',
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
size="small"
|
||||
type="text"
|
||||
onClick={(): void => copyToClipboard(LogJsonData)}
|
||||
>
|
||||
<CopyFilled /> <span style={{ color: blue[5] }}>Copy to Clipboard</span>
|
||||
</Button>
|
||||
</Row>
|
||||
<div style={{ marginTop: '0.5rem' }}>
|
||||
<Editor
|
||||
value={LogJsonData}
|
||||
language="json"
|
||||
height="70vh"
|
||||
readOnly
|
||||
onChange={(): void => {}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
.log-context-container {
|
||||
border: 1px solid var(--bg-slate-400);
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
import './LogContext.styles.scss';
|
||||
|
||||
import RawLogView from 'components/Logs/RawLogView';
|
||||
import LogsContextList from 'container/LogsContextList';
|
||||
import { ORDERBY_FILTERS } from 'container/QueryBuilder/filters/OrderByFilter/config';
|
||||
import { ILog } from 'types/api/logs/log';
|
||||
import { Query, TagFilter } from 'types/api/queryBuilder/queryBuilderData';
|
||||
|
||||
interface LogContextProps {
|
||||
log: ILog;
|
||||
contextQuery: Query | undefined;
|
||||
filters: TagFilter | null;
|
||||
isEdit: boolean;
|
||||
}
|
||||
|
||||
function LogContext({
|
||||
log,
|
||||
filters,
|
||||
contextQuery,
|
||||
isEdit,
|
||||
}: LogContextProps): JSX.Element {
|
||||
// eslint-disable-next-line react/jsx-no-useless-fragment
|
||||
if (!contextQuery) return <></>;
|
||||
|
||||
return (
|
||||
<div className="log-context-container">
|
||||
<LogsContextList
|
||||
order={ORDERBY_FILTERS.ASC}
|
||||
filters={filters}
|
||||
isEdit={isEdit}
|
||||
log={log}
|
||||
query={contextQuery}
|
||||
/>
|
||||
<RawLogView
|
||||
isActiveLog
|
||||
isReadOnly
|
||||
isTextOverflowEllipsisDisabled={false}
|
||||
data={log}
|
||||
linesPerRow={1}
|
||||
/>
|
||||
<LogsContextList
|
||||
order={ORDERBY_FILTERS.DESC}
|
||||
filters={filters}
|
||||
isEdit={isEdit}
|
||||
log={log}
|
||||
query={contextQuery}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default LogContext;
|
||||
@@ -1,131 +0,0 @@
|
||||
.overview-container {
|
||||
.tag {
|
||||
border-radius: 20px;
|
||||
border: 1px solid rgba(173, 127, 88, 0.2);
|
||||
background: rgba(173, 127, 88, 0.1);
|
||||
padding: var(--padding-1) var(--padding-2);
|
||||
font-family: 'Inter';
|
||||
font-size: var(--font-size-xs);
|
||||
font-weight: var(--font-weight-medium);
|
||||
line-height: 16px;
|
||||
letter-spacing: -0.005em;
|
||||
text-align: center;
|
||||
color: var(--text-sienna-400);
|
||||
}
|
||||
|
||||
.log-switch {
|
||||
height: 36px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border-top: 1px solid var(--bg-slate-500);
|
||||
|
||||
.wrap-word-switch {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
margin-left: var(--margin-3);
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.log-switch-btn {
|
||||
border: 1px solid var(--bg-slate-500);
|
||||
background-color: var(--bg-slate-500);
|
||||
width: 40px;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
|
||||
.attribute-table {
|
||||
margin-top: var(--margin-1);
|
||||
}
|
||||
|
||||
.ant-collapse {
|
||||
border: 1px solid var(--bg-slate-400);
|
||||
}
|
||||
|
||||
.collapse-content {
|
||||
border-bottom: 1px solid var(--bg-slate-500);
|
||||
|
||||
.ant-collapse-header {
|
||||
align-items: center;
|
||||
border-radius: 2px;
|
||||
background: rgba(171, 189, 255, 0.04);
|
||||
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.ant-collapse-content {
|
||||
padding: 0;
|
||||
background: var(--bg-ink-400);
|
||||
border-top: 1px solid var(--bg-slate-500);
|
||||
|
||||
.ant-collapse-content-box {
|
||||
padding: var(--padding-2) 0 0 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.logs-body-content {
|
||||
padding-top: 12px;
|
||||
}
|
||||
|
||||
.ant-tag-borderless {
|
||||
border-radius: 2px;
|
||||
background: rgba(113, 144, 249, 0.08);
|
||||
}
|
||||
|
||||
.attribute-collapse {
|
||||
.ant-collapse-content {
|
||||
.ant-collapse-content-box {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.ant-table-wrapper .ant-table-cell {
|
||||
padding: 14px 14px;
|
||||
}
|
||||
|
||||
.attribute-tab-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
.action-btn {
|
||||
border-radius: 2px;
|
||||
border: 1px solid var(--bg-slate-400);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
background: none;
|
||||
border: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.lightMode {
|
||||
.overview-container {
|
||||
.ant-collapse {
|
||||
border: 1px solid var(--bg-vanilla-200);
|
||||
}
|
||||
|
||||
.collapse-content {
|
||||
border-bottom: 1px solid var(--bg-vanilla-200);
|
||||
.ant-collapse-content {
|
||||
background: var(--bg-vanilla-100);
|
||||
border-top: 1px solid var(--bg-vanilla-200);
|
||||
}
|
||||
}
|
||||
|
||||
.log-switch {
|
||||
.log-switch-btn {
|
||||
background: var(--bg-vanilla-200);
|
||||
border: 1px solid var(--bg-vanilla-400);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,205 +0,0 @@
|
||||
import './Overview.styles.scss';
|
||||
|
||||
import MEditor, { EditorProps, Monaco } from '@monaco-editor/react';
|
||||
import { Color } from '@signozhq/design-tokens';
|
||||
import {
|
||||
Button,
|
||||
Collapse,
|
||||
Divider,
|
||||
Input,
|
||||
Switch,
|
||||
Tag,
|
||||
Typography,
|
||||
} from 'antd';
|
||||
import { AddToQueryHOCProps } from 'components/Logs/AddToQueryHOC';
|
||||
import { useIsDarkMode } from 'hooks/useDarkMode';
|
||||
import { Search } from 'lucide-react';
|
||||
import { useState } from 'react';
|
||||
import { ILog } from 'types/api/logs/log';
|
||||
|
||||
import { ActionItemProps } from './ActionItem';
|
||||
import TableView from './TableView';
|
||||
|
||||
interface OverviewProps {
|
||||
logData: ILog;
|
||||
}
|
||||
|
||||
type Props = OverviewProps &
|
||||
Partial<Pick<ActionItemProps, 'onClickActionItem'>> &
|
||||
Pick<AddToQueryHOCProps, 'onAddToQuery'>;
|
||||
|
||||
function Overview({
|
||||
logData,
|
||||
onAddToQuery,
|
||||
onClickActionItem,
|
||||
}: Props): JSX.Element {
|
||||
const [isWrapWord, setIsWrapWord] = useState<boolean>(false);
|
||||
const [isSearchVisible, setIsSearchVisible] = useState<boolean>(false);
|
||||
const [isAttributesExpanded, setIsAttributesExpanded] = useState<boolean>(
|
||||
true,
|
||||
);
|
||||
const [fieldSearchInput, setFieldSearchInput] = useState<string>('');
|
||||
|
||||
const isDarkMode = useIsDarkMode();
|
||||
|
||||
const options: EditorProps['options'] = {
|
||||
automaticLayout: true,
|
||||
readOnly: true,
|
||||
height: '40vh',
|
||||
wordWrap: 'on',
|
||||
minimap: {
|
||||
enabled: false,
|
||||
},
|
||||
fontWeight: 400,
|
||||
// fontFamily: 'SF Mono',
|
||||
fontFamily: 'Space Mono',
|
||||
fontSize: 13,
|
||||
lineHeight: '18px',
|
||||
colorDecorators: true,
|
||||
scrollBeyondLastLine: false,
|
||||
scrollbar: {
|
||||
vertical: 'hidden',
|
||||
horizontal: 'hidden',
|
||||
},
|
||||
};
|
||||
|
||||
const handleWrapWord = (checked: boolean): void => {
|
||||
setIsWrapWord(checked);
|
||||
};
|
||||
|
||||
function setEditorTheme(monaco: Monaco): void {
|
||||
monaco.editor.defineTheme('my-theme', {
|
||||
base: 'vs-dark',
|
||||
inherit: true,
|
||||
rules: [
|
||||
{ token: 'string.key.json', foreground: Color.BG_VANILLA_400 },
|
||||
{ token: 'string.value.json', foreground: Color.BG_ROBIN_400 },
|
||||
],
|
||||
colors: {
|
||||
'editor.background': Color.BG_INK_400,
|
||||
},
|
||||
// fontFamily: 'SF Mono',
|
||||
fontFamily: 'Space Mono',
|
||||
fontSize: 12,
|
||||
fontWeight: 'normal',
|
||||
lineHeight: 18,
|
||||
letterSpacing: -0.06,
|
||||
});
|
||||
}
|
||||
|
||||
const handleSearchVisible = (): void => {
|
||||
setIsSearchVisible(!isSearchVisible);
|
||||
};
|
||||
|
||||
const toogleAttributePanelOpenState = (): void => {
|
||||
setIsAttributesExpanded(!isAttributesExpanded);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="overview-container">
|
||||
<Collapse
|
||||
defaultActiveKey={['1']}
|
||||
items={[
|
||||
{
|
||||
key: '1',
|
||||
label: (
|
||||
<Tag bordered={false}>
|
||||
<Typography.Text style={{ color: Color.BG_ROBIN_400 }}>
|
||||
body
|
||||
</Typography.Text>
|
||||
</Tag>
|
||||
),
|
||||
children: (
|
||||
<div className="logs-body-content">
|
||||
<MEditor
|
||||
value={isWrapWord ? JSON.stringify(logData.body) : logData.body}
|
||||
language={isWrapWord ? 'placetext' : 'json'}
|
||||
options={options}
|
||||
onChange={(): void => {}}
|
||||
height="20vh"
|
||||
theme={isDarkMode ? 'my-theme' : 'light'}
|
||||
// eslint-disable-next-line react/jsx-no-bind
|
||||
beforeMount={setEditorTheme}
|
||||
/>
|
||||
<Divider
|
||||
style={{
|
||||
margin: 0,
|
||||
border: isDarkMode
|
||||
? `1px solid ${Color.BG_SLATE_500}`
|
||||
: `1px solid ${Color.BG_VANILLA_200}`,
|
||||
}}
|
||||
/>
|
||||
<div className="log-switch">
|
||||
<div className="wrap-word-switch">
|
||||
<Typography.Text>Wrap text</Typography.Text>
|
||||
<Switch checked={isWrapWord} onChange={handleWrapWord} size="small" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
),
|
||||
extra: <Tag className="tag">{isWrapWord ? 'Raw' : 'JSON'}</Tag>,
|
||||
className: 'collapse-content',
|
||||
},
|
||||
]}
|
||||
/>
|
||||
|
||||
<Collapse
|
||||
className="attribute-table"
|
||||
defaultActiveKey={['1']}
|
||||
bordered={false}
|
||||
items={[
|
||||
{
|
||||
key: '1',
|
||||
label: (
|
||||
// eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions
|
||||
<div
|
||||
className="attribute-tab-header"
|
||||
onClick={toogleAttributePanelOpenState}
|
||||
>
|
||||
<Tag bordered={false}>
|
||||
<Typography.Text style={{ color: Color.BG_ROBIN_400 }}>
|
||||
Attributes
|
||||
</Typography.Text>
|
||||
</Tag>
|
||||
|
||||
{isAttributesExpanded && (
|
||||
<Button
|
||||
className="action-btn"
|
||||
icon={<Search size={12} />}
|
||||
onClick={(e): void => {
|
||||
e.stopPropagation();
|
||||
handleSearchVisible();
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
),
|
||||
children: (
|
||||
<>
|
||||
{isSearchVisible && (
|
||||
<Input
|
||||
autoFocus
|
||||
placeholder="Search for a field..."
|
||||
className="search-input"
|
||||
value={fieldSearchInput}
|
||||
onChange={(e): void => setFieldSearchInput(e.target.value)}
|
||||
/>
|
||||
)}
|
||||
|
||||
<TableView
|
||||
logData={logData}
|
||||
onAddToQuery={onAddToQuery}
|
||||
fieldSearchInput={fieldSearchInput}
|
||||
onClickActionItem={onClickActionItem}
|
||||
/>
|
||||
</>
|
||||
),
|
||||
className: 'collapse-content attribute-collapse',
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default Overview;
|
||||
@@ -1,80 +0,0 @@
|
||||
.attribute-table-container {
|
||||
.ant-table {
|
||||
background: var(--bg-ink-400);
|
||||
|
||||
.ant-table-row:hover {
|
||||
.ant-table-cell {
|
||||
.value-field {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
.action-btn {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.ant-table-cell {
|
||||
border: 1px solid var(--bg-slate-500);
|
||||
}
|
||||
|
||||
.attribute-name {
|
||||
.ant-btn {
|
||||
&:hover {
|
||||
background-color: none !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.value-field-container {
|
||||
background: rgba(22, 25, 34, 0.4);
|
||||
|
||||
.value-field {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.action-btn {
|
||||
display: none;
|
||||
width: max-content;
|
||||
position: absolute;
|
||||
padding: 0 16px;
|
||||
right: 0;
|
||||
|
||||
.filter-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border: none;
|
||||
box-shadow: none;
|
||||
border-radius: 2px;
|
||||
background: var(--bg-slate-400);
|
||||
|
||||
height: 24px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.lightMode {
|
||||
.attribute-table-container {
|
||||
.ant-table {
|
||||
background: var(--bg-vanilla-100);
|
||||
}
|
||||
|
||||
.ant-table-cell {
|
||||
border: 1px solid var(--bg-vanilla-200);
|
||||
}
|
||||
|
||||
.value-field-container {
|
||||
background: var(--bg-vanilla-300);
|
||||
|
||||
.action-btn {
|
||||
.filter-btn {
|
||||
background: var(--bg-vanilla-300);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,21 +1,16 @@
|
||||
import './TableView.styles.scss';
|
||||
|
||||
import { orange } from '@ant-design/colors';
|
||||
import { LinkOutlined } from '@ant-design/icons';
|
||||
import { Color } from '@signozhq/design-tokens';
|
||||
import { Button, Space, Spin, Tooltip, Tree } from 'antd';
|
||||
import { Input, Space, Tooltip, Tree } from 'antd';
|
||||
import { ColumnsType } from 'antd/es/table';
|
||||
import AddToQueryHOC, {
|
||||
AddToQueryHOCProps,
|
||||
} from 'components/Logs/AddToQueryHOC';
|
||||
import CopyClipboardHOC from 'components/Logs/CopyClipboardHOC';
|
||||
import { ResizeTable } from 'components/ResizeTable';
|
||||
import { OPERATORS } from 'constants/queryBuilder';
|
||||
import ROUTES from 'constants/routes';
|
||||
import history from 'lib/history';
|
||||
import { fieldSearchFilter } from 'lib/logs/fieldSearch';
|
||||
import { removeJSONStringifyQuotes } from 'lib/removeJSONStringifyQuotes';
|
||||
import { isEmpty } from 'lodash-es';
|
||||
import { ArrowDownToDot, ArrowUpFromDot } from 'lucide-react';
|
||||
import { useMemo, useState } from 'react';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { generatePath } from 'react-router-dom';
|
||||
@@ -24,7 +19,7 @@ import AppActions from 'types/actions';
|
||||
import { SET_DETAILED_LOG_DATA } from 'types/actions/logs';
|
||||
import { ILog } from 'types/api/logs/log';
|
||||
|
||||
import { ActionItemProps } from './ActionItem';
|
||||
import ActionItem, { ActionItemProps } from './ActionItem';
|
||||
import FieldRenderer from './FieldRenderer';
|
||||
import {
|
||||
filterKeyForField,
|
||||
@@ -39,53 +34,25 @@ const RESTRICTED_FIELDS = ['timestamp'];
|
||||
|
||||
interface TableViewProps {
|
||||
logData: ILog;
|
||||
fieldSearchInput: string;
|
||||
}
|
||||
|
||||
type Props = TableViewProps &
|
||||
Partial<Pick<ActionItemProps, 'onClickActionItem'>> &
|
||||
Pick<AddToQueryHOCProps, 'onAddToQuery'>;
|
||||
Pick<AddToQueryHOCProps, 'onAddToQuery'> &
|
||||
Pick<ActionItemProps, 'onClickActionItem'>;
|
||||
|
||||
function TableView({
|
||||
logData,
|
||||
fieldSearchInput,
|
||||
onAddToQuery,
|
||||
onClickActionItem,
|
||||
}: Props): JSX.Element | null {
|
||||
const [fieldSearchInput, setFieldSearchInput] = useState<string>('');
|
||||
|
||||
const dispatch = useDispatch<Dispatch<AppActions>>();
|
||||
const [isfilterInLoading, setIsFilterInLoading] = useState<boolean>(false);
|
||||
const [isfilterOutLoading, setIsFilterOutLoading] = useState<boolean>(false);
|
||||
|
||||
const flattenLogData: Record<string, string> | null = useMemo(
|
||||
() => (logData ? flattenObject(logData) : null),
|
||||
[logData],
|
||||
);
|
||||
|
||||
const handleClick = (
|
||||
operator: string,
|
||||
fieldKey: string,
|
||||
fieldValue: string,
|
||||
): void => {
|
||||
const validatedFieldValue = removeJSONStringifyQuotes(fieldValue);
|
||||
if (onClickActionItem) {
|
||||
onClickActionItem(fieldKey, validatedFieldValue, operator);
|
||||
}
|
||||
};
|
||||
|
||||
const onClickHandler = (
|
||||
operator: string,
|
||||
fieldKey: string,
|
||||
fieldValue: string,
|
||||
) => (): void => {
|
||||
handleClick(operator, fieldKey, fieldValue);
|
||||
if (operator === OPERATORS.IN) {
|
||||
setIsFilterInLoading(true);
|
||||
}
|
||||
if (operator === OPERATORS.NIN) {
|
||||
setIsFilterOutLoading(true);
|
||||
}
|
||||
};
|
||||
|
||||
if (logData === null) {
|
||||
return null;
|
||||
}
|
||||
@@ -128,6 +95,24 @@ function TableView({
|
||||
}
|
||||
|
||||
const columns: ColumnsType<DataType> = [
|
||||
{
|
||||
title: 'Action',
|
||||
width: 11,
|
||||
render: (fieldData: Record<string, string>): JSX.Element | null => {
|
||||
const fieldFilterKey = filterKeyForField(fieldData.field);
|
||||
|
||||
if (!RESTRICTED_FIELDS.includes(fieldFilterKey)) {
|
||||
return (
|
||||
<ActionItem
|
||||
fieldKey={fieldFilterKey}
|
||||
fieldValue={fieldData.value}
|
||||
onClickActionItem={onClickActionItem}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'Field',
|
||||
dataIndex: 'field',
|
||||
@@ -135,7 +120,6 @@ function TableView({
|
||||
width: 50,
|
||||
align: 'left',
|
||||
ellipsis: true,
|
||||
className: 'attribute-name',
|
||||
render: (field: string, record): JSX.Element => {
|
||||
const renderedField = <FieldRenderer field={field} />;
|
||||
|
||||
@@ -143,7 +127,7 @@ function TableView({
|
||||
const traceId = flattenLogData[record.field];
|
||||
|
||||
return (
|
||||
<Space size="middle" className="log-attribute">
|
||||
<Space size="middle">
|
||||
{renderedField}
|
||||
|
||||
{traceId && (
|
||||
@@ -182,15 +166,15 @@ function TableView({
|
||||
},
|
||||
{
|
||||
title: 'Value',
|
||||
dataIndex: 'value',
|
||||
key: 'value',
|
||||
width: 70,
|
||||
ellipsis: false,
|
||||
className: 'value-field-container attribute-value',
|
||||
render: (fieldData: Record<string, string>, record): JSX.Element => {
|
||||
const textToCopy = fieldData.value.slice(1, -1);
|
||||
render: (field, record): JSX.Element => {
|
||||
const textToCopy = field.slice(1, -1);
|
||||
|
||||
if (record.field === 'body') {
|
||||
const parsedBody = recursiveParseJSON(fieldData.value);
|
||||
const parsedBody = recursiveParseJSON(field);
|
||||
if (!isEmpty(parsedBody)) {
|
||||
return (
|
||||
<Tree defaultExpandAll showLine treeData={jsonToDataNodes(parsedBody)} />
|
||||
@@ -198,62 +182,30 @@ function TableView({
|
||||
}
|
||||
}
|
||||
|
||||
const fieldFilterKey = filterKeyForField(fieldData.field);
|
||||
|
||||
return (
|
||||
<div className="value-field">
|
||||
<CopyClipboardHOC textToCopy={textToCopy}>
|
||||
<span style={{ color: Color.BG_SIENNA_400 }}>
|
||||
{removeEscapeCharacters(fieldData.value)}
|
||||
</span>
|
||||
</CopyClipboardHOC>
|
||||
<span className="action-btn">
|
||||
<Tooltip title="Filter for value">
|
||||
<Button
|
||||
className="filter-btn periscope-btn"
|
||||
icon={
|
||||
isfilterInLoading ? (
|
||||
<Spin size="small" />
|
||||
) : (
|
||||
<ArrowDownToDot size={14} style={{ transform: 'rotate(90deg)' }} />
|
||||
)
|
||||
}
|
||||
onClick={onClickHandler(OPERATORS.IN, fieldFilterKey, fieldData.value)}
|
||||
/>
|
||||
</Tooltip>
|
||||
<Tooltip title="Filter out value">
|
||||
<Button
|
||||
className="filter-btn periscope-btn"
|
||||
icon={
|
||||
isfilterOutLoading ? (
|
||||
<Spin size="small" />
|
||||
) : (
|
||||
<ArrowUpFromDot size={14} style={{ transform: 'rotate(90deg)' }} />
|
||||
)
|
||||
}
|
||||
onClick={onClickHandler(
|
||||
OPERATORS.NIN,
|
||||
fieldFilterKey,
|
||||
fieldData.value,
|
||||
)}
|
||||
/>
|
||||
</Tooltip>
|
||||
</span>
|
||||
</div>
|
||||
<CopyClipboardHOC textToCopy={textToCopy}>
|
||||
<span style={{ color: orange[6] }}>{removeEscapeCharacters(field)}</span>
|
||||
</CopyClipboardHOC>
|
||||
);
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<ResizeTable
|
||||
columns={columns}
|
||||
tableLayout="fixed"
|
||||
dataSource={dataSource}
|
||||
pagination={false}
|
||||
showHeader={false}
|
||||
className="attribute-table-container"
|
||||
/>
|
||||
<>
|
||||
<Input
|
||||
placeholder="Search field names"
|
||||
size="large"
|
||||
value={fieldSearchInput}
|
||||
onChange={(e): void => setFieldSearchInput(e.target.value)}
|
||||
/>
|
||||
<ResizeTable
|
||||
columns={columns}
|
||||
tableLayout="fixed"
|
||||
dataSource={dataSource}
|
||||
pagination={false}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import LogDetail from 'components/LogDetail';
|
||||
import { VIEW_TYPES } from 'components/LogDetail/constants';
|
||||
import ROUTES from 'constants/routes';
|
||||
import { getGeneratedFilterQueryString } from 'lib/getGeneratedFilterQueryString';
|
||||
import getStep from 'lib/getStep';
|
||||
@@ -137,7 +136,6 @@ function LogDetailedView({
|
||||
|
||||
return (
|
||||
<LogDetail
|
||||
selectedTab={VIEW_TYPES.OVERVIEW}
|
||||
log={detailedLog}
|
||||
onClose={onDrawerClose}
|
||||
onAddToQuery={handleAddToQuery}
|
||||
|
||||
@@ -262,7 +262,3 @@ export const removeEscapeCharacters = (str: string): string =>
|
||||
};
|
||||
return escapeMap[char as keyof typeof escapeMap];
|
||||
});
|
||||
|
||||
export function removeExtraSpaces(input: string): string {
|
||||
return input.replace(/\s+/g, ' ').trim();
|
||||
}
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
.qb-search-view-container {
|
||||
padding: 8px 16px;
|
||||
border-top: 1px solid var(--bg-slate-400, #1d212d);
|
||||
border-bottom: 1px solid var(--bg-slate-400, #1d212d);
|
||||
|
||||
.ant-select-selector {
|
||||
border-radius: 2px;
|
||||
border: 1px solid var(--bg-slate-400) !important;
|
||||
background-color: var(--bg-ink-300) !important;
|
||||
|
||||
input {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.ant-tag .ant-typography {
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.lightMode {
|
||||
.qb-search-view-container {
|
||||
border-top: 1px solid var(--bg-vanilla-300);
|
||||
border-bottom: 1px solid var(--bg-vanilla-300);
|
||||
|
||||
.ant-select-selector {
|
||||
border-color: var(--bg-vanilla-300) !important;
|
||||
background-color: var(--bg-vanilla-100) !important;
|
||||
color: var(--bg-ink-200);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
import './LogsExplorerQuerySection.styles.scss';
|
||||
|
||||
import { Button } from 'antd';
|
||||
import {
|
||||
initialQueriesMap,
|
||||
OPERATORS,
|
||||
@@ -8,26 +7,17 @@ import {
|
||||
import ExplorerOrderBy from 'container/ExplorerOrderBy';
|
||||
import { QueryBuilder } from 'container/QueryBuilder';
|
||||
import { OrderByFilterProps } from 'container/QueryBuilder/filters/OrderByFilter/OrderByFilter.interfaces';
|
||||
import QueryBuilderSearch from 'container/QueryBuilder/filters/QueryBuilderSearch';
|
||||
import { QueryBuilderProps } from 'container/QueryBuilder/QueryBuilder.interfaces';
|
||||
import { useGetPanelTypesQueryParam } from 'hooks/queryBuilder/useGetPanelTypesQueryParam';
|
||||
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
||||
import { useQueryOperations } from 'hooks/queryBuilder/useQueryBuilderOperations';
|
||||
import { useShareBuilderUrl } from 'hooks/queryBuilder/useShareBuilderUrl';
|
||||
import { ButtonWrapperStyled } from 'pages/LogsExplorer/styles';
|
||||
import { prepareQueryWithDefaultTimestamp } from 'pages/LogsExplorer/utils';
|
||||
import { memo, useCallback, useMemo } from 'react';
|
||||
import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
|
||||
import { DataSource } from 'types/common/queryBuilder';
|
||||
|
||||
function LogExplorerQuerySection({
|
||||
selectedView,
|
||||
}: {
|
||||
selectedView: string;
|
||||
}): JSX.Element {
|
||||
const { currentQuery, updateAllQueriesOperators } = useQueryBuilder();
|
||||
|
||||
const query = currentQuery?.builder?.queryData[0] || null;
|
||||
|
||||
function LogExplorerQuerySection(): JSX.Element {
|
||||
const { handleRunQuery, updateAllQueriesOperators } = useQueryBuilder();
|
||||
const panelTypes = useGetPanelTypesQueryParam(PANEL_TYPES.LIST);
|
||||
const defaultValue = useMemo(() => {
|
||||
const updatedQuery = updateAllQueriesOperators(
|
||||
@@ -55,12 +45,6 @@ function LogExplorerQuerySection({
|
||||
return config;
|
||||
}, [panelTypes]);
|
||||
|
||||
const { handleChangeQueryData } = useQueryOperations({
|
||||
index: 0,
|
||||
query,
|
||||
filterConfigs,
|
||||
});
|
||||
|
||||
const renderOrderBy = useCallback(
|
||||
({ query, onChange }: OrderByFilterProps): JSX.Element => (
|
||||
<ExplorerOrderBy query={query} onChange={onChange} />
|
||||
@@ -75,34 +59,20 @@ function LogExplorerQuerySection({
|
||||
[panelTypes, renderOrderBy],
|
||||
);
|
||||
|
||||
const handleChangeTagFilters = useCallback(
|
||||
(value: IBuilderQuery['filters']) => {
|
||||
handleChangeQueryData('filters', value);
|
||||
},
|
||||
[handleChangeQueryData],
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
{selectedView === 'search' && (
|
||||
<div className="qb-search-view-container">
|
||||
<QueryBuilderSearch
|
||||
query={query}
|
||||
onChange={handleChangeTagFilters}
|
||||
whereClauseConfig={filterConfigs?.filters}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{selectedView === 'query-builder' && (
|
||||
<QueryBuilder
|
||||
panelType={panelTypes}
|
||||
config={{ initialDataSource: DataSource.LOGS, queryVariant: 'static' }}
|
||||
filterConfigs={filterConfigs}
|
||||
queryComponents={queryComponents}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
<QueryBuilder
|
||||
panelType={panelTypes}
|
||||
config={{ initialDataSource: DataSource.LOGS, queryVariant: 'static' }}
|
||||
filterConfigs={filterConfigs}
|
||||
queryComponents={queryComponents}
|
||||
actions={
|
||||
<ButtonWrapperStyled>
|
||||
<Button type="primary" onClick={handleRunQuery}>
|
||||
Run Query
|
||||
</Button>
|
||||
</ButtonWrapperStyled>
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
.context-logs-list {
|
||||
position: relative;
|
||||
|
||||
.show-more-button {
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
opacity: 1;
|
||||
|
||||
&.up {
|
||||
top: 0;
|
||||
}
|
||||
|
||||
&.down {
|
||||
bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.virtuoso-list {
|
||||
&::-webkit-scrollbar {
|
||||
width: 0.1rem;
|
||||
height: 0.1rem;
|
||||
}
|
||||
}
|
||||
|
||||
&.logs-context-list-asc {
|
||||
.virtuoso-list {
|
||||
padding-top: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
&.logs-context-list-desc {
|
||||
.virtuoso-list {
|
||||
padding-bottom: 16px;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
.show-more-button {
|
||||
background-color: var(--bg-slate-400);
|
||||
color: var(--bg-vanilla-100);
|
||||
display: flex;
|
||||
padding: 4px 8px;
|
||||
align-items: center;
|
||||
gap: 3px;
|
||||
border: none;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.show-more-button {
|
||||
&.disabled {
|
||||
background-color: var(--bg-slate-200);
|
||||
color: var(--bg-vanilla-400);
|
||||
}
|
||||
}
|
||||
|
||||
.lightMode {
|
||||
.show-more-button {
|
||||
background-color: var(--bg-vanilla-300);
|
||||
color: var(--bg-slate-400);
|
||||
}
|
||||
|
||||
.show-more-button {
|
||||
&.disabled {
|
||||
background-color: var(--bg-vanilla-300);
|
||||
color: var(--bg-vanilla-400);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,7 @@
|
||||
import './ShowButton.styles.scss';
|
||||
|
||||
import { Color } from '@signozhq/design-tokens';
|
||||
import { Button } from 'antd';
|
||||
import cx from 'classnames';
|
||||
import { Button, Typography } from 'antd';
|
||||
import { ORDERBY_FILTERS } from 'container/QueryBuilder/filters/OrderByFilter/config';
|
||||
import { ArrowDown, ArrowUp, Ban } from 'lucide-react';
|
||||
|
||||
import { ShowButtonWrapper } from './styles';
|
||||
|
||||
interface ShowButtonProps {
|
||||
isLoading: boolean;
|
||||
@@ -19,35 +16,20 @@ function ShowButton({
|
||||
order,
|
||||
onClick,
|
||||
}: ShowButtonProps): JSX.Element {
|
||||
const getIcons = (): JSX.Element => {
|
||||
if (order === ORDERBY_FILTERS.ASC) {
|
||||
return isDisabled ? (
|
||||
<Ban size={14} style={{ color: Color.BG_VANILLA_400 }} />
|
||||
) : (
|
||||
<ArrowUp size={14} />
|
||||
);
|
||||
}
|
||||
return isDisabled ? (
|
||||
<Ban size={14} style={{ color: Color.BG_VANILLA_400 }} />
|
||||
) : (
|
||||
<ArrowDown size={14} />
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<Button
|
||||
disabled={isLoading || isDisabled}
|
||||
loading={isLoading}
|
||||
onClick={onClick}
|
||||
icon={getIcons()}
|
||||
className={cx(
|
||||
'show-more-button',
|
||||
order === ORDERBY_FILTERS.ASC ? 'up' : 'down',
|
||||
isDisabled && 'disabled',
|
||||
)}
|
||||
>
|
||||
Load more
|
||||
</Button>
|
||||
<ShowButtonWrapper>
|
||||
<Typography>
|
||||
Showing 10 lines {order === ORDERBY_FILTERS.ASC ? 'after' : 'before'} match
|
||||
</Typography>
|
||||
<Button
|
||||
size="small"
|
||||
disabled={isLoading || isDisabled}
|
||||
loading={isLoading}
|
||||
onClick={onClick}
|
||||
>
|
||||
Show 10 more lines
|
||||
</Button>
|
||||
</ShowButtonWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user