feat: dashboard revamp according to the latest designs (#4868)

* feat: dashboard list view

* feat: update sort menu items

* feat: wire up add / import dashboard functionss

* feat: update import json styles

* feat: new dashboard templates modal

* feat: add template filter logic

* feat: revamp the overview settings modal (#4894)

* feat: revamp the overview settings modal

* feat: dashboard settings variable landing page

* feat: dashboard add variable button settings

* feat: add variable modal changes

* feat: handle the unsaved changes for general settings

* feat: follow ups for side panel section for dashboards  (#4906)

* feat: changes for tags input

* feat: side panel header styles

* feat: changes for textbox variable

* feat: handle changes for custom type variable

* feat: overflow preview vales

* feat: overflow preview vales

* feat: setup for new dashboard landing page (#4921)

* feat: setup for new dashboard landing page

* feat: added empty state widgets

* feat: added functionality to the configure and the add panel button

* feat: tag variables changes

* feat: dashboard revamp changes follow ups (#4929)

* feat: changes for new panel type modal

* fix: added missing / in the breadcrumbs

* feat: added dashboard settings menu items

* feat: added dashboard rename modal

* feat: move full screen handle a few components up

* feat: handle rename and copy export changes

* feat: minor cleanup

* feat: delete button changes

* feat: dashboard widget edit page design revamp (#4946)

* feat: dashboard edit page base setup

* feat: right container design revamp

* feat: alerts and thresholds changes right container

* feat: right container

* feat: fix graph styles

* fix: some edits for dashboard edit page

* feat: threshold preview changes (#4990)

* feat: threshold preview changes

* feat: threshold preview changes

* feat: threshold discard handler

* fix: remove the horizontal scroll from the dashboards landing page

* fix: added margin to dashboard widgets (#4991)

* fix: rebase conflicts

* feat: dashboard panel grouping change for new designs (#4992)

* feat: dashboard panel groping base cleanups

* feat: move add panel code from inner component to parent component

* feat: new dashboard section panel naming modal

* feat: dashboard panel groping base cleanups

* feat: grip changes

* feat: dashboard list page revamp and functionality additions (#4994)

* feat: fix types and code structure for list page

* feat: dashboard actions

* feat: design changes for tags

* feat: design changes for tags

* feat: update import json styles

* feat: added all dashboards row

* feat: added configure metadata linking

* feat: added local storage changes for dynamic columns

* feat: added user metadata display for metadata modal

* feat: configure metadata final changes

* feat: handle dashboard list loading state

* feat: sort and pagination changes for dashboard list view designs (#4996)

* feat: minor list view css changes

* feat: added sort functionality to the dashboards list

* feat: added sort functionality to the dashboards list

* feat: added name dropdown in the settings drawer and image as base64 format (#5000)

* feat: added name input in settings drawer

* feat: discard handler

* feat: implemented the name dropdown

* feat: added dashboard list page header

* fix: margin of dashboard list container

* feat: dashboard empty state (#5005)

* feat: light mode changes for new dashboard revamp (#5006)

* feat: light mode changes for dahsboards list page

* feat: dashboard description landing page changes

* feat: variable panels landing page light theme changes

* feat: dashboard edit panel light mode

* feat: added dashboard list error state (#5011)

* feat: added missing light mode designs

* fix: usability / customer issues  (#5014)

* fix: [GH-4986]: preview values not getting updated when the query result is empty

* fix: [GH-4985]: fix the usability of dahsboards variables drawer

* fix: light mode design for component slider

* fix: code cleanup

* fix: 0 being added in case of no tags

* fix: minor styling fixes

* fix: handle silent error for dashboard edit mutation (#5022)

* fix: handle silent error for dashboard edit mutation

* fix: handle silent error for dashboard edit mutation

* fix: rbac changes

* fix: grip icon color

* fix: new dashboards feedback from testing (#5030)

* fix: hide create new dashboards from viewer roles

* fix: move the elipsis button right of date time picker and make it a button

* fix: remove duplicate button from actions for now

* fix: last updated by and created by difference

* fix: hide intercom for modals

* fix: actions popover not closing

* fix: temp remove templates modal from normal flow

* fix: delete button event propagation

* fix: minor UI fixes (#5032)

* fix: update dashboards landing page icons

* fix: added lock icon for locked dashboards

* fix: updated dashboards list page styles

* fix: comment out filters code for phase 2 (#5034)

* fix: dashboard revamp ui fixes  (#5037)

* fix: increase the width of the graph section

* fix: remove select and next from component slider

* Dashboard vqa 1 (#5090)

* fix: dashboard VQA pt 1

* fix: dashboard VQA pt 2

* fix: dashboard VQA pt 3

* fix: dashboard VQA pt 4

* fix: dashboard VQA pt 5

* fix: dashboard VQA pt 6

* fix: dashboard VQA pt 7

* fix: added dashboard locked footer and base64 icons (#5091)

* fix: added dashboard locked footer

* fix: update base64 images

* fix: dashboard delete hover and row actions refactor

* fix: dashboard vqa pt 2 (#5098)

* fix: review comments

* fix: alerts plot tag spacing

* fix: css variables update

---------

Co-authored-by: Vikrant Gupta <vikrant.thomso@gmail.com>
This commit is contained in:
Yunus M 2024-05-28 19:09:04 +05:30 committed by GitHub
parent 3538815331
commit 932d892d9e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
113 changed files with 9669 additions and 1773 deletions

View File

@ -85,7 +85,7 @@
"less": "^4.1.2",
"less-loader": "^10.2.0",
"lodash-es": "^4.17.21",
"lucide-react": "0.321.0",
"lucide-react": "0.379.0",
"mini-css-extract-plugin": "2.4.5",
"papaparse": "5.4.1",
"react": "18.2.0",

View File

@ -0,0 +1 @@
<svg width="32" height="32" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M13.72 12.839l-9.054.92s.05.649.236.798c.178.142 5.617.066 11.048.088 5.433.023 10.82.125 10.944.072.249-.107.249-.992.249-.992l-13.424-.886zM16.55 7.787l-12.623-.32s.275.61.637.813c.523.29 3.71.889 11.518.918 7.808.028 10.635-.4 11.317-.678.58-.238 1.215-1.576 1.215-1.576l-12.064.843z" fill="#8A1E0C"/><path d="M21.95 8.658v1.335l2.176-.087V8.542l-2.176.116z" fill="#8A1E0C"/><path d="M21.948 9.566h2.177v16.797l-2.206.294.029-17.09z" fill="#EB2901"/><path d="M21.355 26.19c-.111.193-.111 2.297-.007 2.444.105.147 3.242.104 3.326 0 .085-.104.063-2.38 0-2.464-.062-.085-3.235-.125-3.32.02z" fill="#474C4F"/><path d="M8.462 9.85V8.488l2.042.125v1.22l-2.042.017z" fill="#8A1E0C"/><path d="M8.462 9.55l-.038 17.051 2.08-.207V9.566l-2.042-.015z" fill="#EB2901"/><path d="M7.804 25.919c-.073.073-.147 2.36-.02 2.464.125.104 3.14.129 3.244.024.105-.104.085-2.304.023-2.43-.063-.127-3.142-.163-3.247-.058z" fill="#474C4F"/><path d="M14.788 8.107v4.876l2.393-.33V8.108h-2.393z" fill="#EB2901"/><path d="M27.067 11.978c-.115-.16-.482-.138-.482-.138l-1.137-.013c.002-.398-.01-.913-.078-.996-.116-.137-4.542-.09-4.702.047-.091.078-.11.527-.107.898-2.738-.027-5.99-.058-8.83-.076 0-.384-.012-.849-.078-.915-.116-.116-4.22-.185-4.38-.07-.113.083-.136.647-.138.97-1.384.002-2.275.013-2.34.04-.322.137-.137 2.042-.137 2.042l22.476.16c.002.002.049-1.787-.067-1.95z" fill="#EB2901"/><path d="M3.93 6.942s-.646-.34-1.377-1.573c-.509-.858-.595-1.658-.387-1.778.21-.12 2.154 1.08 5.745 1.616a60.81 60.81 0 008.173.644c2.884.027 5.717-.135 8.397-.644 3.62-.689 4.906-1.436 5.264-1.316.36.12-.109 1.227-.369 1.78-.178.376-.944 1.77-1.515 1.87-.411.072-19.953-.09-19.953-.09l-3.977-.509z" fill="#474C4F"/><path d="M3.31 5.724c-.108.137-.057.457.212 1.06.107.237.415.782.529.917 0 0 2.982.756 11.977.7 8.995-.055 12.108-.62 12.108-.62s.911-1.277.745-1.32c-.096-.024-4.847.98-12.909.898C7.911 7.277 3.311 5.724 3.311 5.724z" fill="#EB2901"/></svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 5.2 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 6.1 KiB

View File

@ -0,0 +1 @@
<svg width="14" height="14" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M1.305 13.063c.74.739 1.637.482 2.156-.109.53-.604.813-.956.813-.956.66-.973 3.392-4.227 5.724-6.568a2.638 2.638 0 002.74-.434 2.648 2.648 0 00.922-2.041.155.155 0 00-.23-.132l-1.607.927a1.64 1.64 0 01-1.076-1.864l1.6-.923a.153.153 0 00.077-.134.153.153 0 00-.077-.133 2.65 2.65 0 00-3.66 3.563C6.11 6.826 2.966 9.604 2.15 10.223c0 0-.492.356-.962.84-.464.476-.636 1.245.117 1.999zm.542-1.137a.592.592 0 111.184 0 .592.592 0 01-1.184 0z" fill="#82AEC0"/><path d="M8.334 4.61l.353-.35a2.63 2.63 0 01-.212-2.039c.073-.12.189-.249.262-.171-.03.946.245 1.931.902 2.611.327.338.752.582 1.207.696.224.057.458.082.69.069.137-.008.519-.149.596-.044v.004a2.656 2.656 0 01-2.135.043 38.176 38.176 0 00-1.903 2.05c.262-.495 1.034-1.408 1.241-1.757a.412.412 0 00-.036-.464c-.207-.255-.633-.493-.965-.649z" fill="#2F7889"/><path d="M5.186 8.529c.06-.062.004-.167-.08-.148-.158.035-.386.125-.657.345-.531.43-1.934 1.595-2.107 1.825-.173.23.522-.003.767-.047.2-.036 1.602-1.48 2.077-1.975zM10.048 1.104c-.296.212-.563.465-.84.701-.072.061-.177.122-.25.065-.08-.064-.03-.191.03-.274C9.512.874 10.493.358 11.442.563c-.5.161-.95.223-1.395.541z" fill="#B9E4EA"/><path d="M12.408 3.583a2.1 2.1 0 01-.371.19c-.112.031-.43-.092-.522-.166l1.183-.772c.043-.028.087-.056.137-.072a.546.546 0 01.185-.014c.087.004.51-.01.56.064.05.075-.126.149-.183.183-.33.197-.66.391-.99.587zM7.867 7.687L6.624 6.254c-.45.423-.895.835-1.321 1.225l.362-.078a.482.482 0 01.439.13l.58.65c.122.122.142.334.096.5l-.065.308c.367-.423.755-.862 1.152-1.302z" fill="#2F7889"/><g><path d="M13.378 12.86l-.744.643a.686.686 0 01-.968-.072L2.84 2.779l1.135-.853 9.459 9.976a.668.668 0 01-.057.957z" fill="#A06841"/><path d="M3.648 3.752l2.1 2.535c.328-.493.494-1.084.629-1.83l-2.028-2.14a1.838 1.838 0 00-.414.48 2.17 2.17 0 00-.287.955z" fill="#7D5133"/><path d="M7.81.438C5.885.416 5.17.588 4.098 1.515l-.966.835c-.35.302-.815.566-.742 1.089.027.19.086.384.05.573-.034.179-.242.268-.39.166-.139-.096-.292-.214-.463-.234a.588.588 0 00-.45.14l-.747.664s-.107.434.729 1.38c.835.946 1.373.878 1.373.878l.702-.618a.53.53 0 00.176-.412c-.003-.184-.11-.326-.174-.49-.013-.031-.083-.143.04-.244.109-.094.333-.062.46-.027.129.034.25.088.38.122.25.065.369-.051.543-.201L6.013 3.93c.619-.536-.325-1.474-.325-1.474C5.244 1.953 7.941.687 7.941.687c.198-.069.138-.246-.13-.249z" fill="#82AEC0"/><path d="M4.076 5.338a.504.504 0 00.14.016v-.02c-.011-.12-.077-.23-.144-.33A7.18 7.18 0 002.545 3.33a1.683 1.683 0 00-.154-.111.726.726 0 00-.002.22c.027.19.086.384.05.573-.038.196-.242.25-.399.177a3.27 3.27 0 011.011 1.027c.035.056.07.115.11.168a.2.2 0 01.075-.14c.109-.095.333-.063.46-.029.13.034.25.088.38.123zM1.778 5.573c.585.613.914 1.247.734 1.42-.179.17-.799-.186-1.384-.797C.542 5.584.21 4.92.388 4.748c.18-.171.804.213 1.39.825z" fill="#2F7889"/><path d="M4.057 2.41c.465-.198.88-.623 1.422-1.09A2.53 2.53 0 016.03.964c.076-.035.048-.149-.036-.148-.278.005-.527.09-.772.196-.342.149-.644.374-.935.608-.2.16-.67.555-.965.805-.055.047-.012.12.06.12.208.002.325.014.674-.135zM1.124 4.352c-.196.221.055.281.496.646.311.257.642.018.645-.223.003-.216-.052-.333-.366-.53-.315-.199-.597-.093-.775.107z" fill="#B9E4EA"/></g></svg>

After

Width:  |  Height:  |  Size: 3.2 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 204 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 1.7 MiB

View File

@ -1,6 +1,6 @@
{
"create_dashboard": "Create Dashboard",
"import_json": "Import JSON",
"import_json": "Import Dashboard JSON",
"import_grafana_json": "Import Grafana JSON",
"copy_to_clipboard": "Copy To ClipBoard",
"download_json": "Download JSON",
@ -9,7 +9,7 @@
"upload_json_file": "Upload JSON file",
"paste_json_below": "Paste JSON below",
"error_upload_json": "Invalid JSON",
"load_json": "Load JSON",
"import_and_next": "Import and Next",
"import_dashboard_by_pasting": "Import dashboard by pasting JSON or importing JSON file",
"error_loading_json": "Error loading JSON file",
"empty_json_not_allowed": "Empty JSON is not allowed",

View File

@ -1,6 +1,6 @@
{
"create_dashboard": "Create Dashboard",
"import_json": "Import JSON",
"import_json": "Import Dashboard JSON",
"import_grafana_json": "Import Grafana JSON",
"copy_to_clipboard": "Copy To ClipBoard",
"download_json": "Download JSON",
@ -9,7 +9,7 @@
"upload_json_file": "Upload JSON file",
"paste_json_below": "Paste JSON below",
"error_upload_json": "Invalid JSON",
"load_json": "Load JSON",
"import_and_next": "Import and Next",
"import_dashboard_by_pasting": "Import dashboard by pasting JSON or importing JSON file",
"error_loading_json": "Error loading JSON file",
"empty_json_not_allowed": "Empty JSON is not allowed",

View File

@ -1,26 +1,20 @@
import axios from 'api';
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
import { AxiosError } from 'axios';
import { ErrorResponse, SuccessResponse } from 'types/api';
import { PayloadProps, Props } from 'types/api/dashboard/update';
const updateDashboard = async (
props: Props,
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
try {
const response = await axios.put(`/dashboards/${props.uuid}`, {
...props.data,
});
const response = await axios.put(`/dashboards/${props.uuid}`, {
...props.data,
});
return {
statusCode: 200,
error: null,
message: response.data.status,
payload: response.data.data,
};
} catch (error) {
return ErrorResponseHandler(error as AxiosError);
}
return {
statusCode: 200,
error: null,
message: response.data.status,
payload: response.data.data,
};
};
export default updateDashboard;

View File

@ -0,0 +1,176 @@
export default function ApacheIcon(): JSX.Element {
return (
<svg
width="14"
height="14"
viewBox="0 0 14 14"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M8.7112 1.05739C8.3796 1.30831 8.08524 1.60498 7.83691 1.93853L8.17977 2.58567C8.40218 2.26279 8.64677 1.95576 8.91177 1.66681C8.93063 1.64581 8.94091 1.63596 8.94091 1.63596L8.91177 1.66681C8.66003 1.95965 8.43081 2.27111 8.22606 2.59853C8.67305 2.56672 9.11808 2.51165 9.55934 2.43353C9.60936 2.25525 9.62374 2.06886 9.60168 1.88502C9.57962 1.70118 9.52154 1.52349 9.43077 1.3621C9.43077 1.3621 9.09991 0.828957 8.7112 1.05739Z"
fill="url(#paint0_linear_2061_4195)"
/>
<path
d="M7.55932 6.49365L7.42432 6.51722L7.49332 6.50651C7.51432 6.50265 7.53703 6.49837 7.55932 6.49365Z"
fill="#BE202E"
/>
<path
opacity="0.35"
d="M7.55932 6.49365L7.42432 6.51722L7.49332 6.50651C7.51432 6.50265 7.53703 6.49837 7.55932 6.49365Z"
fill="#BE202E"
/>
<path
d="M7.67407 5.92858L7.6955 5.92558C7.72464 5.9213 7.75336 5.91616 7.78122 5.91016L7.67493 5.92816L7.67407 5.92858Z"
fill="#BE202E"
/>
<path
opacity="0.35"
d="M7.67407 5.92858L7.6955 5.92558C7.72464 5.9213 7.75336 5.91616 7.78122 5.91016L7.67493 5.92816L7.67407 5.92858Z"
fill="#BE202E"
/>
<path
d="M7.16872 4.25736C7.273 4.0625 7.37858 3.87221 7.48543 3.6865C7.59629 3.49393 7.70829 3.3075 7.82143 3.12721L7.84115 3.09507C7.95315 2.91793 8.066 2.74779 8.17972 2.58464L7.83686 1.9375L7.75886 2.03393C7.65986 2.15736 7.55743 2.29107 7.452 2.43036C7.33329 2.58893 7.21115 2.75779 7.08729 2.93564C6.97286 3.09979 6.85672 3.27164 6.74058 3.44993C6.64158 3.60121 6.54258 3.75721 6.444 3.91707L6.43286 3.93507L6.879 4.8145C6.97443 4.62707 7.071 4.44136 7.16872 4.25736Z"
fill="url(#paint1_linear_2061_4195)"
/>
<path
d="M5.13606 9.22519C5.07692 9.38748 5.01763 9.55305 4.95821 9.72191L4.95563 9.72919L4.93035 9.80076C4.89049 9.91476 4.85535 10.015 4.77563 10.2503C4.92444 10.3467 5.0416 10.4847 5.11249 10.6472C5.10283 10.4581 5.01907 10.2804 4.87935 10.1526C5.16043 10.1981 5.44862 10.1654 5.71236 10.0581C5.97609 9.95074 6.20521 9.77291 6.37463 9.54405C6.40102 9.50088 6.42464 9.45607 6.44535 9.40991C6.37459 9.49741 6.28141 9.56408 6.17575 9.6028C6.07008 9.64152 5.95589 9.65084 5.84535 9.62976C6.20937 9.49539 6.51802 9.24316 6.72221 8.91319C6.76978 8.83691 6.81563 8.75376 6.86278 8.66162C6.6974 8.84328 6.48756 8.97872 6.25393 9.05463C6.02029 9.13053 5.77091 9.14426 5.53035 9.09448L5.16949 9.13391C5.15706 9.16434 5.14721 9.19476 5.13606 9.22519Z"
fill="url(#paint2_linear_2061_4195)"
/>
<path
d="M5.30448 8.417C5.38248 8.21528 5.46276 8.01157 5.54533 7.80586C5.62419 7.60871 5.70519 7.41057 5.78833 7.21143C5.87148 7.01228 5.95633 6.81228 6.0429 6.61143C6.1309 6.40828 6.22076 6.20557 6.31248 6.00328C6.40419 5.801 6.49633 5.60257 6.5889 5.408C6.62262 5.33714 6.65662 5.26643 6.6909 5.19586C6.75005 5.07414 6.80962 4.95343 6.86962 4.83371C6.87262 4.82728 6.87605 4.82086 6.87948 4.81443L6.4329 3.93457L6.41105 3.97014C6.30733 4.14157 6.20362 4.313 6.10205 4.49128C6.00048 4.66957 5.89848 4.853 5.80205 5.03814C5.7189 5.19443 5.63776 5.35157 5.55862 5.50957L5.51148 5.606C5.41419 5.80614 5.32633 5.99943 5.24705 6.18543C5.15705 6.396 5.07762 6.59686 5.00876 6.788C4.9629 6.91357 4.92305 7.03486 4.88362 7.151C4.85233 7.25043 4.82276 7.34986 4.79448 7.451C4.72762 7.68471 4.67048 7.91771 4.62305 8.15L5.07133 9.035C5.13076 8.87671 5.19162 8.71614 5.2539 8.55328L5.30448 8.417Z"
fill="url(#paint3_linear_2061_4195)"
/>
<path
d="M4.615 8.18082C4.55868 8.46014 4.51975 8.74268 4.49843 9.02682C4.49843 9.03668 4.49843 9.04653 4.49629 9.05639C4.3547 8.8778 4.1801 8.72808 3.982 8.61539C4.24526 8.95089 4.41819 9.34823 4.48429 9.76953C4.28982 9.78674 4.0942 9.75338 3.91643 9.67268C4.05057 9.80984 4.21714 9.91096 4.40072 9.96668C4.14974 10.0146 3.9168 10.1307 3.72743 10.3022C3.97375 10.178 4.25059 10.1271 4.525 10.1557C4.21858 11.024 3.91129 11.9827 3.604 13.0001C3.64664 12.988 3.6856 12.9655 3.71739 12.9346C3.74918 12.9037 3.7728 12.8654 3.78615 12.8231C3.841 12.6388 4.20443 11.4298 4.77443 9.84068L4.82372 9.70439L4.83743 9.66625C4.89772 9.49968 4.96015 9.32968 5.02472 9.15625L5.06758 9.03753V9.03539L4.62143 8.15039C4.61929 8.16025 4.61672 8.17053 4.615 8.18082Z"
fill="url(#paint4_linear_2061_4195)"
/>
<path
d="M6.94843 4.89059L6.90986 4.96987C6.87129 5.04959 6.83214 5.13102 6.79243 5.21416C6.74957 5.30416 6.70671 5.3963 6.66386 5.49059C6.64157 5.53816 6.621 5.58573 6.59743 5.63416C6.53057 5.7793 6.46286 5.92916 6.39429 6.08373C6.30857 6.27402 6.22286 6.47173 6.13714 6.67687C6.054 6.87259 5.96971 7.07516 5.88429 7.28459C5.80314 7.48459 5.721 7.68973 5.63786 7.90002C5.56386 8.08888 5.48914 8.28273 5.41371 8.48159C5.40986 8.49145 5.40643 8.50087 5.403 8.51073C5.32814 8.70873 5.25257 8.91187 5.17629 9.12016L5.17114 9.1343L5.532 9.09487L5.51057 9.09102C6.039 8.98351 6.52026 8.7126 6.88629 8.31659C7.0686 8.1182 7.22683 7.89896 7.35771 7.66345C7.47147 7.46033 7.57252 7.25036 7.66029 7.03473C7.74386 6.83245 7.824 6.61387 7.90114 6.37645C7.79448 6.43086 7.68084 6.47037 7.56343 6.49388C7.54157 6.49859 7.52057 6.50287 7.49657 6.50673C7.47257 6.51059 7.45071 6.51445 7.42757 6.51745C7.80325 6.36305 8.10458 6.06924 8.26843 5.69759C8.12155 5.79773 7.95733 5.86966 7.78414 5.90973C7.75586 5.91616 7.72757 5.92087 7.69843 5.92516L7.677 5.92816C7.80484 5.87656 7.92565 5.80903 8.03657 5.72716C8.05843 5.71087 8.07943 5.69373 8.1 5.67573C8.13129 5.64873 8.16086 5.62045 8.18914 5.59002C8.20714 5.57116 8.22471 5.55145 8.24186 5.53087C8.28293 5.48179 8.32059 5.42996 8.35457 5.37573C8.36529 5.35859 8.376 5.34145 8.38629 5.32345C8.39957 5.29773 8.41243 5.27245 8.42486 5.24716C8.481 5.13402 8.526 5.03288 8.56157 4.94459C8.57957 4.90173 8.595 4.85888 8.60829 4.82116C8.61386 4.80616 8.619 4.79116 8.62371 4.7783C8.63786 4.73545 8.64943 4.69816 8.65843 4.66473C8.66941 4.62595 8.67828 4.58661 8.685 4.54688C8.67015 4.5586 8.65454 4.56934 8.63829 4.57902C8.4822 4.66169 8.31419 4.71953 8.14029 4.75045H8.13257L8.08157 4.75859L8.09057 4.75473L6.957 4.87902L6.94843 4.89059Z"
fill="url(#paint5_linear_2061_4195)"
/>
<path
d="M8.22475 2.59871C8.12404 2.75343 8.01389 2.92914 7.89561 3.12843L7.87675 3.16014C7.77446 3.33157 7.66604 3.52114 7.55147 3.72886C7.45261 3.90771 7.34989 4.10014 7.24332 4.30614C7.15018 4.48586 7.05418 4.67671 6.95532 4.87871L8.08889 4.75443C8.33914 4.65567 8.55501 4.48583 8.70989 4.26586C8.74804 4.211 8.78618 4.15357 8.82432 4.09443C8.94089 3.91271 9.05489 3.71257 9.15689 3.51371C9.25018 3.33322 9.33429 3.14813 9.40889 2.95914C9.44758 2.86102 9.48092 2.76087 9.50875 2.65914C9.52932 2.58028 9.54561 2.50571 9.55804 2.43457C9.11675 2.51236 8.67172 2.56715 8.22475 2.59871Z"
fill="url(#paint6_linear_2061_4195)"
/>
<path
d="M7.49234 6.50635C7.46963 6.5102 7.44648 6.51406 7.42334 6.51706C7.44648 6.51406 7.47134 6.5102 7.49234 6.50635Z"
fill="#BE202E"
/>
<path
opacity="0.35"
d="M7.49234 6.50635C7.46963 6.5102 7.44648 6.51406 7.42334 6.51706C7.44648 6.51406 7.47134 6.5102 7.49234 6.50635Z"
fill="#BE202E"
/>
<path
d="M7.49234 6.50635C7.46963 6.5102 7.44648 6.51406 7.42334 6.51706C7.44648 6.51406 7.47134 6.5102 7.49234 6.50635Z"
fill="url(#paint7_linear_2061_4195)"
/>
<defs>
<linearGradient
id="paint0_linear_2061_4195"
x1="7.26579"
y1="1.16584"
x2="9.7782"
y2="0.46843"
gradientUnits="userSpaceOnUse"
>
<stop stopColor="#F69923" />
<stop offset="0.312" stopColor="#F79A23" />
<stop offset="0.838" stopColor="#E97826" />
</linearGradient>
<linearGradient
id="paint1_linear_2061_4195"
x1="1.76109"
y1="12.4382"
x2="6.87606"
y2="1.48267"
gradientUnits="userSpaceOnUse"
>
<stop offset="0.323" stopColor="#9E2064" />
<stop offset="0.63" stopColor="#C92037" />
<stop offset="0.751" stopColor="#CD2335" />
<stop offset="1" stopColor="#E97826" />
</linearGradient>
<linearGradient
id="paint2_linear_2061_4195"
x1="3.47784"
y1="11.6287"
x2="6.5258"
y2="5.10046"
gradientUnits="userSpaceOnUse"
>
<stop stopColor="#282662" />
<stop offset="0.095" stopColor="#662E8D" />
<stop offset="0.788" stopColor="#9F2064" />
<stop offset="0.949" stopColor="#CD2032" />
</linearGradient>
<linearGradient
id="paint3_linear_2061_4195"
x1="1.94596"
y1="11.7748"
x2="7.06094"
y2="0.819304"
gradientUnits="userSpaceOnUse"
>
<stop offset="0.323" stopColor="#9E2064" />
<stop offset="0.63" stopColor="#C92037" />
<stop offset="0.751" stopColor="#CD2335" />
<stop offset="1" stopColor="#E97826" />
</linearGradient>
<linearGradient
id="paint4_linear_2061_4195"
x1="2.46768"
y1="11.0455"
x2="5.15578"
y2="5.28798"
gradientUnits="userSpaceOnUse"
>
<stop stopColor="#282662" />
<stop offset="0.095" stopColor="#662E8D" />
<stop offset="0.788" stopColor="#9F2064" />
<stop offset="0.949" stopColor="#CD2032" />
</linearGradient>
<linearGradient
id="paint5_linear_2061_4195"
x1="3.08048"
y1="12.3044"
x2="8.19546"
y2="1.3489"
gradientUnits="userSpaceOnUse"
>
<stop offset="0.323" stopColor="#9E2064" />
<stop offset="0.63" stopColor="#C92037" />
<stop offset="0.751" stopColor="#CD2335" />
<stop offset="1" stopColor="#E97826" />
</linearGradient>
<linearGradient
id="paint6_linear_2061_4195"
x1="2.70679"
y1="12.9581"
x2="7.82195"
y2="2.00223"
gradientUnits="userSpaceOnUse"
>
<stop offset="0.323" stopColor="#9E2064" />
<stop offset="0.63" stopColor="#C92037" />
<stop offset="0.751" stopColor="#CD2335" />
<stop offset="1" stopColor="#E97826" />
</linearGradient>
<linearGradient
id="paint7_linear_2061_4195"
x1="3.41759"
y1="12.4619"
x2="8.53277"
y2="1.50629"
gradientUnits="userSpaceOnUse"
>
<stop offset="0.323" stopColor="#9E2064" />
<stop offset="0.63" stopColor="#C92037" />
<stop offset="0.751" stopColor="#CD2335" />
<stop offset="1" stopColor="#E97826" />
</linearGradient>
</defs>
</svg>
);
}

View File

@ -0,0 +1,28 @@
export default function DockerIcon(): JSX.Element {
return (
<svg
width="14"
height="14"
viewBox="0 0 14 14"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<g clipPath="url(#clip0_2061_4220)">
<path
d="M13.574 5.82107C13.2652 5.60679 12.5575 5.52644 12.0042 5.63358C11.9398 5.09788 11.6439 4.62915 11.1292 4.21399L10.8332 3.99971L10.6274 4.30774C10.37 4.70951 10.2413 5.27198 10.2799 5.80768C10.2928 5.99517 10.3571 6.32998 10.5502 6.62461C10.37 6.73175 9.99686 6.86567 9.50789 6.86567H0.204685L0.17895 6.97281C0.0888774 7.5085 0.0888774 9.18254 1.14401 10.4682C1.9418 11.4458 3.12561 11.9414 4.68258 11.9414C8.05386 11.9414 10.5502 10.3209 11.7211 7.38797C12.1843 7.40136 13.1751 7.38797 13.677 6.38354C13.6898 6.35676 13.7156 6.30319 13.8056 6.10231L13.8571 5.99517L13.574 5.82107ZM7.6421 2.04443H6.22668V3.38367H7.6421V2.04443ZM7.6421 3.65151H6.22668V4.99074H7.6421V3.65151ZM5.96933 3.65151H4.5539V4.99074H5.96933V3.65151ZM4.29655 3.65151H2.88113V4.99074H4.29655V3.65151ZM2.62378 5.25859H1.20835V6.59782H2.62378V5.25859ZM4.29655 5.25859H2.88113V6.59782H4.29655V5.25859ZM5.96933 5.25859H4.5539V6.59782H5.96933V5.25859ZM7.6421 5.25859H6.22668V6.59782H7.6421V5.25859ZM9.31488 5.25859H7.89945V6.59782H9.31488V5.25859Z"
fill="#2396ED"
/>
</g>
<defs>
<clipPath id="clip0_2061_4220">
<rect
width="13.7143"
height="13.7143"
fill="white"
transform="translate(0.142822 0.143066)"
/>
</clipPath>
</defs>
</svg>
);
}

View File

@ -0,0 +1,36 @@
export default function ElasticSearchIcon(): JSX.Element {
return (
<svg
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M6.38041 6.94508L9.70241 8.45858L13.0524 5.52158C13.1019 5.27892 13.1255 5.03171 13.1229 4.78408C13.1236 3.9846 12.8681 3.20592 12.3941 2.56216C11.92 1.9184 11.2522 1.44339 10.4885 1.20675C9.72487 0.970101 8.9055 0.984259 8.15047 1.24714C7.39544 1.51003 6.74446 2.00782 6.29291 2.66758L5.73291 5.56008L6.38041 6.94508Z"
fill="#FED10A"
/>
<path
d="M2.943 10.4593C2.89356 10.7062 2.86994 10.9575 2.8725 11.2093C2.87361 12.012 3.13177 12.7932 3.60915 13.4385C4.08654 14.0838 4.75804 14.5592 5.52528 14.7952C6.29252 15.0311 7.11514 15.0151 7.87262 14.7495C8.63009 14.4839 9.28259 13.9827 9.7345 13.3193L10.2845 10.4398L9.55 9.02928L6.215 7.50928L2.943 10.4593Z"
fill="#24BBB1"
/>
<path
d="M2.92394 4.71302L5.19994 5.25002L5.69994 2.66552C5.39077 2.43015 5.01365 2.30134 4.62509 2.29839C4.23654 2.29544 3.8575 2.41852 3.54479 2.64916C3.23208 2.8798 3.00256 3.20559 2.89063 3.57768C2.77869 3.94978 2.79039 4.34813 2.92394 4.71302Z"
fill="#EF5098"
/>
<path
d="M2.72503 5.2583C2.23266 5.42016 1.80253 5.73058 1.49379 6.14687C1.18505 6.56317 1.01286 7.0649 1.00091 7.58305C0.988962 8.1012 1.13784 8.61033 1.42706 9.04041C1.71628 9.4705 2.13164 9.80042 2.61603 9.9848L5.81003 7.0998L5.22653 5.8498L2.72503 5.2583Z"
fill="#17A8E0"
/>
<path
d="M10.312 13.3197C10.6209 13.5543 10.9975 13.6825 11.3853 13.6851C11.7732 13.6877 12.1514 13.5646 12.4634 13.3342C12.7755 13.1038 13.0044 12.7785 13.116 12.407C13.2276 12.0356 13.2159 11.6379 13.0825 11.2737L10.812 10.7412L10.312 13.3197Z"
fill="#93C83E"
/>
<path
d="M10.7735 10.145L13.2735 10.7285C13.7666 10.5672 14.1976 10.257 14.507 9.84058C14.8165 9.42415 14.9891 8.922 15.0013 8.40333C15.0134 7.88467 14.8643 7.375 14.5747 6.94457C14.2851 6.51414 13.8691 6.18413 13.384 6L10.1135 8.8665L10.7735 10.145Z"
fill="#0779A1"
/>
</svg>
);
}

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,27 @@
function HerokuIcon(): JSX.Element {
return (
<svg
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<g clipPath="url(#clip0_2061_4245)">
<path
fillRule="evenodd"
clipRule="evenodd"
d="M13.6285 0H2.10787C1.31283 0 0.666748 0.644312 0.666748 1.44112V14.5602C0.666748 15.3557 1.31283 16 2.10787 16H13.6285C14.4237 16 15.0678 15.3557 15.0678 14.5602V1.44112C15.0678 0.644312 14.4237 0 13.6285 0ZM14.2677 14.5602C14.2677 14.9135 13.9815 15.1994 13.6285 15.1994H2.10787C1.75506 15.1994 1.46688 14.9135 1.46688 14.5602V1.44112C1.46688 1.08654 1.75506 0.800133 2.10787 0.800133H13.6285C13.9815 0.800133 14.2677 1.08654 14.2677 1.44112V14.5602ZM4.26699 13.6009L6.06823 12.0002L4.26699 10.3999V13.6009ZM10.7719 7.11485C10.4481 6.78927 9.8569 6.40038 8.86819 6.40038C7.78342 6.40038 6.66611 6.68302 5.86752 6.94177V2.40082H4.26704V9.33907L5.39807 8.82667C5.41688 8.81826 7.24025 8.00086 8.86819 8.00086C9.68027 8.00086 9.86022 8.44796 9.86885 8.82158V13.6009H11.4676V8.801C11.4693 8.6983 11.4592 7.81051 10.7719 7.11485ZM8.66761 5.00027H10.2681C10.9912 4.1811 11.3597 3.30903 11.4677 2.40066H9.86881C9.69063 3.30726 9.29643 4.17424 8.66761 5.00027Z"
fill="#6762A6"
/>
</g>
<defs>
<clipPath id="clip0_2061_4245">
<rect width="16" height="16" fill="white" />
</clipPath>
</defs>
</svg>
);
}
export default HerokuIcon;

View File

@ -0,0 +1,82 @@
function JuiceBoxIcon(): JSX.Element {
return (
<svg
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M7.15412 6.3041L3.80207 4.64419C3.80207 4.64419 3.79429 4.48531 3.94429 4.40531C4.09539 4.32532 6.47082 3.35204 6.66192 3.35204C6.85302 3.35204 7.97185 3.56758 8.78625 3.74868C9.60065 3.92978 11.7528 4.39198 11.7628 4.53308C11.7728 4.67419 9.56066 6.93629 9.56066 6.93629L7.15412 6.3041Z"
fill="#C3FECE"
/>
<path
d="M10.2117 5.2686C10.2117 5.2686 8.80626 4.85529 7.43855 4.53419C6.80192 4.3842 4.93091 3.992 4.93091 3.992C4.93091 3.992 5.12979 3.90534 5.27978 3.84201C5.43199 3.77868 5.5531 3.74535 5.5531 3.74535C5.5531 3.74535 6.77303 3.98867 7.67965 4.18199C8.90625 4.44309 10.6228 4.95528 10.6628 5.03639C10.7039 5.11639 10.2117 5.2686 10.2117 5.2686Z"
fill="white"
/>
<path
d="M7.48978 4.87973C7.19091 5.0875 7.48423 5.21305 8.03531 5.34193C8.63861 5.48303 9.09858 5.6908 9.48634 5.46526C9.83632 5.26304 8.9797 5.04306 8.58639 4.95862C8.28641 4.89418 7.72088 4.71974 7.48978 4.87973Z"
fill="#ACB1B2"
/>
<path
d="M8.58521 5.29749C8.58299 5.29749 8.59521 2.86317 8.59521 2.77317C8.59521 2.68318 8.49411 2.60207 8.60521 2.41097C8.71632 2.21987 9.00741 2.23987 9.00741 2.23987C9.00741 2.23987 10.2151 1.88878 10.6973 1.76767C11.1795 1.64657 12.2461 1.36547 12.2461 1.36547L12.3172 1.97544C12.3172 1.97544 11.1995 2.29098 10.6462 2.43208C10.0929 2.57319 9.33851 2.80428 9.33851 2.80428L9.2274 2.90539L9.25962 5.28415C9.25962 5.28415 9.1563 5.3997 8.9252 5.3997C8.70076 5.39859 8.58521 5.29749 8.58521 5.29749Z"
fill="#FFD816"
/>
<path
d="M12.0472 1.67768C12.0561 1.86767 12.1572 1.98544 12.2772 1.98544C12.3972 1.98544 12.4794 1.83211 12.4616 1.63212C12.4427 1.43325 12.3138 1.34214 12.2038 1.3788C12.0939 1.41547 12.0405 1.5388 12.0472 1.67768Z"
fill="#FEB804"
/>
<path
d="M9.22962 2.96094C9.20851 2.96094 9.1874 2.95539 9.16851 2.94206L8.59187 2.55763C8.54076 2.52319 8.52743 2.4543 8.56076 2.4032C8.59521 2.35209 8.66298 2.33764 8.7152 2.37209L9.29184 2.75651C9.34294 2.79095 9.35628 2.85984 9.32295 2.91095C9.30072 2.94317 9.26517 2.96094 9.22962 2.96094Z"
fill="#FEB804"
/>
<path
d="M9.31847 2.86761C9.28514 2.86761 9.25181 2.85206 9.22959 2.82317C9.08182 2.62651 8.91294 2.40319 8.88294 2.36875C8.84516 2.33208 8.83739 2.27209 8.86739 2.22653C8.90183 2.17542 8.9696 2.16098 9.02182 2.19542C9.04182 2.20876 9.05293 2.21653 9.40736 2.68873C9.44402 2.73762 9.41069 2.80761 9.3618 2.84428C9.34181 2.85983 9.34181 2.86761 9.31847 2.86761Z"
fill="#FEB804"
/>
<path
d="M9.18298 3.05427C9.17742 3.05427 9.17187 3.05427 9.1652 3.05316L8.60968 2.96539C8.54968 2.95539 8.50746 2.89872 8.51746 2.83873C8.52746 2.77873 8.58301 2.73651 8.64412 2.74651L9.19965 2.83428C9.25964 2.84428 9.31186 2.85539 9.30186 2.9165C9.29408 2.96983 9.23742 3.05427 9.18298 3.05427Z"
fill="#FEB804"
/>
<path
d="M3.79761 4.63197C3.97426 4.63197 5.39197 5.00417 6.12304 5.13861C6.85411 5.27304 9.06177 5.79524 9.13621 5.80968C9.21066 5.82413 9.19621 6.77963 9.19621 6.77963C9.19621 6.77963 10.2406 12.1793 10.2106 12.4338C10.1806 12.6871 9.49508 14.6414 9.49508 14.6414C9.49508 14.6414 9.09177 14.7014 8.06294 14.4026C7.0341 14.1037 4.31869 13.3582 4.15426 13.1493C3.98982 12.9404 4.09426 10.1806 4.00537 8.48065C3.91427 6.77963 3.79761 4.63197 3.79761 4.63197Z"
fill="#79DD8A"
/>
<path
d="M12.0294 13.2682C12.086 13.0804 11.7705 11.0672 11.7261 9.21728C11.6816 7.36849 11.8405 4.5553 11.7638 4.53308C11.6872 4.51197 9.13621 5.80968 9.13621 5.80968C9.13621 5.80968 9.1251 7.64514 9.15621 9.37616C9.1962 11.616 9.37508 14.5814 9.49396 14.6414C9.61285 14.7014 10.645 14.1237 10.9561 13.9548C11.5072 13.6559 11.9849 13.4182 12.0294 13.2682Z"
fill="#02AB46"
/>
<path
d="M10.204 6.65964L10.2095 5.26638L10.6662 5.04195L10.6795 6.56964L10.4684 6.93962L10.204 6.65964Z"
fill="#DBDFE1"
/>
<path
d="M6.16088 7.1485C5.17427 7.04295 4.49097 7.9229 4.5443 9.06951C4.60763 10.4239 5.4998 11.4216 6.46531 11.6205C7.43081 11.8194 8.36632 11.4005 8.38632 9.91947C8.40854 8.37621 7.05194 7.24294 6.16088 7.1485Z"
fill="#FEFEFD"
/>
<path
d="M6.81861 8.3851C6.81861 8.3851 6.61862 7.71292 6.01754 7.89179C5.41646 8.07067 5.09092 9.37838 6.11087 9.96724C7.09082 10.5328 7.99299 9.49504 7.67745 8.82286C7.3908 8.20956 6.81861 8.3851 6.81861 8.3851Z"
fill="#EF5B44"
/>
<path
d="M6.58968 7.56292C6.52191 7.58737 6.46858 7.86402 6.53968 8.12845C6.59302 8.32622 6.76301 8.59731 6.86189 8.57954C6.94077 8.56509 7.00077 8.254 6.92633 7.9929C6.83967 7.69181 6.66857 7.53404 6.58968 7.56292Z"
fill="#B8CF17"
/>
<path
d="M6.57531 9.12284C6.2931 9.03729 5.97534 10.0072 6.54642 10.6116C7.17639 11.2772 7.9119 10.6905 7.89191 10.5617C7.86302 10.3828 7.36971 10.2539 7.12639 9.9539C6.88307 9.6517 6.73974 9.17284 6.57531 9.12284Z"
fill="#FD8F01"
/>
<path
d="M5.67307 8.9584C5.67307 8.9584 5.72973 8.53398 5.33642 8.55731C4.95644 8.58065 5.04977 9.02951 5.04977 9.02951C5.04977 9.02951 4.73312 9.06729 4.78645 9.41838C4.82645 9.68392 5.10643 9.68059 5.10643 9.68059C5.10643 9.68059 4.79867 9.80169 4.93866 10.1528C5.0631 10.465 5.34975 10.3394 5.34975 10.3394C5.34975 10.3394 5.2442 10.6439 5.49308 10.805C5.70307 10.9405 5.88639 10.8261 5.88639 10.8261C5.88639 10.8261 5.87528 11.0861 6.16526 11.1338C6.51636 11.1916 6.66857 10.7639 6.41858 10.5661C6.22859 10.4161 6.05638 10.5328 6.05638 10.5328C6.05638 10.5328 6.09193 10.4494 6.0586 10.3539C6.03638 10.2917 6.00083 10.2683 6.00083 10.2683C6.00083 10.2683 6.30859 10.2394 6.25859 9.88947C6.2086 9.53837 5.90083 9.5817 5.90083 9.5817C5.90083 9.5817 6.07971 9.43838 6.00861 9.18061C5.93861 8.92174 5.67307 8.9584 5.67307 8.9584Z"
fill="#A281D0"
/>
<path
d="M10.5205 7.04739C10.265 7.04739 9.17503 5.96412 9.16503 5.95301C9.12504 5.90634 9.13059 5.83635 9.17726 5.79635C9.22392 5.75635 9.29281 5.76191 9.33391 5.80746C9.40391 5.88523 10.2372 6.65853 10.4772 6.80741C10.6327 6.5752 11.1383 5.55858 11.5727 4.64085C11.5994 4.5853 11.666 4.56197 11.7205 4.58752C11.776 4.61419 11.7993 4.67974 11.7738 4.73529C10.7027 6.99851 10.5972 7.02851 10.5405 7.04406C10.5339 7.0474 10.5272 7.04739 10.5205 7.04739Z"
fill="#2D802D"
/>
</svg>
);
}
export default JuiceBoxIcon;

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,38 @@
function MagicBallIcon(): JSX.Element {
return (
<svg
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M13.2883 12.3928C14.4283 11.0451 15.4015 8.62412 14.396 5.84427C13.9061 4.49101 13.3528 3.73549 12.7084 3.25552C12.3162 2.96331 10.7496 2.2278 8.67524 2.54889C7.15865 2.78443 5.1332 3.74105 3.8166 5.29763C2.54 6.80978 1.5545 8.00416 1.50895 9.1641C1.45006 10.6562 2.57445 11.9384 2.71111 12.1462C2.95443 12.515 4.61434 14.6238 7.73306 14.7316C10.4862 14.826 12.2795 13.5861 13.2883 12.3928Z"
fill="#403D3E"
/>
<path
d="M4.04763 2.43331C2.61104 3.45881 0.996679 5.55647 1.28666 8.49854C1.42777 9.93069 1.77997 10.7995 2.2855 11.4228C2.59326 11.8028 3.92875 12.9328 6.02086 13.0994C8.12964 13.2672 9.73288 12.795 11.4072 11.6306C14.8104 9.26295 14.3093 5.68313 14.1638 5.26649C14.0182 4.84984 12.9283 2.39664 9.93176 1.52446C7.28746 0.755617 5.31867 1.52446 4.04763 2.43331Z"
fill="#5E6367"
/>
<path
d="M6.57189 2.63219C4.99308 2.57553 3.48427 3.81213 3.33872 5.33871C3.19318 6.86419 4.13757 8.02635 5.6086 8.263C7.07964 8.49855 8.78066 7.60526 9.12286 5.73425C9.47618 3.80657 8.07958 2.68663 6.57189 2.63219Z"
fill="white"
/>
<path
d="M7.05971 5.24541C7.05971 5.24541 7.43969 5.16653 7.51524 4.60655C7.58968 4.05547 7.31636 3.5855 6.67862 3.41662C5.98532 3.23329 5.51535 3.61438 5.39313 4.01547C5.22314 4.57322 5.47424 4.83876 5.47424 4.83876C5.47424 4.83876 4.79427 5.00209 4.73983 5.80427C4.68872 6.5609 5.20536 6.96754 5.72422 7.09198C6.3653 7.24642 7.09193 7.07087 7.2697 6.25313C7.41747 5.57984 7.05971 5.24541 7.05971 5.24541Z"
fill="#303030"
/>
<path
d="M5.99081 4.22544C5.92971 4.45543 6.05192 4.67764 6.29191 4.73874C6.55079 4.8043 6.78633 4.71875 6.84966 4.45431C6.90521 4.21988 6.79411 4.01323 6.52079 3.94656C6.29635 3.89101 6.05748 3.97434 5.99081 4.22544Z"
fill="white"
/>
<path
d="M6.18643 5.36871C5.89533 5.27871 5.51091 5.39093 5.4498 5.78202C5.38869 6.17311 5.62313 6.3731 5.92978 6.42865C6.23643 6.4842 6.52641 6.3231 6.58308 6.00978C6.63863 5.69758 6.47641 5.45759 6.18643 5.36871Z"
fill="white"
/>
</svg>
);
}
export default MagicBallIcon;

View File

@ -0,0 +1,68 @@
export default function MongoDBIcon(): JSX.Element {
return (
<svg
width="14"
height="14"
viewBox="0 0 14 14"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M7.26568 13.0004L6.94382 12.8937C6.94382 12.8937 6.98668 11.2651 6.39739 11.1507C6.01168 10.7015 6.45439 -8.02403 7.86439 11.0868C7.59687 11.2225 7.39217 11.4564 7.29311 11.7395C7.24001 12.1577 7.23082 12.5803 7.26568 13.0004Z"
fill="url(#paint0_linear_2061_4238)"
/>
<path
d="M7.43957 11.4272C8.29654 10.7821 8.95282 9.90701 9.33214 8.90369C9.71147 7.90037 9.79826 6.81 9.58243 5.75931C8.95243 2.98002 7.46058 2.06631 7.29986 1.71745C7.1612 1.50022 7.04284 1.27068 6.94629 1.03174L7.065 8.7756C7.065 8.7756 6.819 11.1422 7.43957 11.4272Z"
fill="url(#paint1_linear_2061_4238)"
/>
<path
d="M6.78015 11.5296C6.78015 11.5296 4.15687 9.74286 4.30858 6.58214C4.32273 5.62928 4.54121 4.69054 4.94926 3.82935C5.3573 2.96816 5.94542 2.20456 6.67387 1.59014C6.759 1.51782 6.82663 1.42715 6.87168 1.32493C6.91674 1.22272 6.93805 1.11163 6.93401 1C7.0973 1.35143 7.07072 6.247 7.08787 6.81957C7.1543 9.04686 6.96401 11.1091 6.78015 11.5296Z"
fill="url(#paint2_linear_2061_4238)"
/>
<defs>
<linearGradient
id="paint0_linear_2061_4238"
x1="5.1021"
y1="7.10853"
x2="8.80138"
y2="8.36386"
gradientUnits="userSpaceOnUse"
>
<stop offset="0.231" stopColor="#999875" />
<stop offset="0.563" stopColor="#9B9977" />
<stop offset="0.683" stopColor="#A09F7E" />
<stop offset="0.768" stopColor="#A9A889" />
<stop offset="0.837" stopColor="#B7B69A" />
<stop offset="0.896" stopColor="#C9C7B0" />
<stop offset="0.948" stopColor="#DEDDCB" />
<stop offset="0.994" stopColor="#F8F6EB" />
<stop offset="1" stopColor="#FBF9EF" />
</linearGradient>
<linearGradient
id="paint1_linear_2061_4238"
x1="6.45855"
y1="0.976404"
x2="8.09399"
y2="11.1888"
gradientUnits="userSpaceOnUse"
>
<stop stopColor="#48A547" />
<stop offset="1" stopColor="#3F9143" />
</linearGradient>
<linearGradient
id="paint2_linear_2061_4238"
x1="4.08293"
y1="6.89501"
x2="8.47176"
y2="5.42519"
gradientUnits="userSpaceOnUse"
>
<stop stopColor="#41A247" />
<stop offset="0.352" stopColor="#4BA74B" />
<stop offset="0.956" stopColor="#67B554" />
<stop offset="1" stopColor="#69B655" />
</linearGradient>
</defs>
</svg>
);
}

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,22 @@
function NginxIcon(): JSX.Element {
return (
<svg
width="14"
height="14"
viewBox="0 0 14 14"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M6.97775 1H7.00561C7.14837 1.068 7.28743 1.14353 7.42218 1.22629C8.97332 2.11829 10.5245 3.01057 12.0756 3.90314C12.1356 3.93517 12.1846 3.9845 12.2163 4.04472C12.2479 4.10493 12.2607 4.17327 12.253 4.24086C12.2496 6.12186 12.253 8.00243 12.2509 9.88257C12.2307 9.9723 12.1759 10.0504 12.0983 10.0999C10.4489 11.0496 8.79932 11.9987 7.14961 12.9473C7.10963 12.9778 7.06144 12.9956 7.01125 12.9984C6.96106 13.0012 6.91118 12.9889 6.86803 12.9631C5.21661 12.0163 3.56675 11.0677 1.91846 10.1174C1.86489 10.0921 1.82003 10.0515 1.78952 10.0007C1.75901 9.94988 1.74423 9.89119 1.74703 9.832C1.74703 7.95143 1.74703 6.071 1.74703 4.19071C1.74299 4.13178 1.75661 4.07299 1.78616 4.02184C1.8157 3.97069 1.85983 3.92951 1.91289 3.90357C3.46203 3.01271 5.01118 2.12129 6.56032 1.22929C6.69832 1.15043 6.83375 1.06686 6.97775 1Z"
fill="#019639"
/>
<path
d="M3.90007 4.65924C3.90007 6.21039 3.90007 7.76167 3.90007 9.3131C3.89809 9.39901 3.91326 9.48446 3.94468 9.56445C3.9761 9.64444 4.02315 9.71736 4.08307 9.77896C4.19794 9.89243 4.34824 9.96309 4.5089 9.97916C4.66956 9.99522 4.83087 9.95572 4.96593 9.86724C5.05634 9.80581 5.13035 9.72321 5.18152 9.62662C5.23269 9.53003 5.25946 9.4224 5.2595 9.3131C5.2595 8.19024 5.25736 7.06739 5.2595 5.94453C6.28322 7.17024 7.30907 8.39424 8.33707 9.61653C8.4799 9.76122 8.65676 9.86772 8.85145 9.92628C9.04614 9.98484 9.25242 9.99357 9.45136 9.95167C9.59184 9.924 9.71973 9.85198 9.81624 9.74622C9.91275 9.64045 9.97278 9.50652 9.9875 9.3641C9.98979 7.78096 9.98979 6.19796 9.9875 4.6151C9.97275 4.44614 9.8952 4.28884 9.77016 4.17425C9.64512 4.05966 9.48168 3.99609 9.31207 3.99609C9.14247 3.99609 8.97902 4.05966 8.85399 4.17425C8.72895 4.28884 8.6514 4.44614 8.63665 4.6151C8.63665 5.75596 8.62979 6.89553 8.63665 8.03596C7.63122 6.85053 6.63822 5.65481 5.63665 4.4651C5.5046 4.29578 5.32979 4.16474 5.13023 4.08549C4.93067 4.00624 4.71359 3.98165 4.50136 4.01424C4.34059 4.03214 4.19154 4.10704 4.08124 4.22536C3.97093 4.34369 3.90666 4.49761 3.90007 4.65924Z"
fill="white"
/>
</svg>
);
}
export default NginxIcon;

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,60 @@
export default function RedisIcon(): JSX.Element {
return (
<svg
width="14"
height="14"
viewBox="0 0 14 14"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<g clipPath="url(#clip0_2061_4282)">
<path
d="M13.3198 10.158C12.5879 10.5395 8.79654 12.0984 7.98938 12.5192C7.18221 12.94 6.73382 12.936 6.09616 12.6312C5.45855 12.3263 1.42388 10.6966 0.697072 10.3492C0.333858 10.1756 0.142822 10.029 0.142822 9.8906V8.50438C0.142822 8.50438 5.3955 7.3609 6.24348 7.05667C7.09141 6.75244 7.38563 6.74145 8.10723 7.00578C8.82895 7.2702 13.1439 8.0487 13.8571 8.30992L13.8568 9.67653C13.8569 9.81356 13.6923 9.96388 13.3198 10.158Z"
fill="#912626"
/>
<path
d="M13.3195 8.77972C12.5877 9.16104 8.79642 10.72 7.98926 11.1407C7.18215 11.5616 6.73376 11.5575 6.09615 11.2527C5.45849 10.9481 1.42397 9.31805 0.697221 8.97086C-0.029529 8.62345 -0.0447433 8.38436 0.66915 8.10482C1.38304 7.82518 5.39544 6.25098 6.24353 5.94675C7.09145 5.64263 7.38561 5.63154 8.10722 5.89597C8.82888 6.16029 12.5975 7.66034 13.3106 7.9215C14.024 8.18298 14.0513 8.39829 13.3195 8.77972Z"
fill="#C6302B"
/>
<path
d="M13.3198 7.91483C12.5879 8.29637 8.79654 9.85519 7.98938 10.2762C7.18221 10.6968 6.73382 10.6928 6.09616 10.388C5.4585 10.0833 1.42388 8.45338 0.697072 8.10597C0.333858 7.9324 0.142822 7.78604 0.142822 7.64756V6.26119C0.142822 6.26119 5.3955 5.11776 6.24348 4.81353C7.09141 4.50929 7.38563 4.49826 8.10723 4.76263C8.829 5.02701 13.144 5.80535 13.8571 6.06661L13.8568 7.43338C13.8569 7.57036 13.6923 7.72069 13.3198 7.91483Z"
fill="#912626"
/>
<path
d="M13.3195 6.53701C12.5877 6.91844 8.79642 8.47726 7.98926 8.89817C7.18215 9.31892 6.73376 9.3148 6.09615 9.00998C5.45849 8.70537 1.42397 7.07541 0.697221 6.72816C-0.029529 6.38085 -0.0447433 6.14171 0.66915 5.86207C1.38304 5.58258 5.39549 4.00828 6.24353 3.7041C7.09145 3.39992 7.38561 3.38889 8.10722 3.65326C8.82888 3.91758 12.5975 5.41753 13.3106 5.6788C14.024 5.94023 14.0513 6.15558 13.3195 6.53701Z"
fill="#C6302B"
/>
<path
d="M13.3198 5.58855C12.5879 5.96998 8.79654 7.5289 7.98938 7.94987C7.18221 8.37062 6.73382 8.36649 6.09616 8.06167C5.4585 7.75701 1.42388 6.12705 0.697072 5.7798C0.333858 5.60607 0.142822 5.45965 0.142822 5.32133V3.9349C0.142822 3.9349 5.3955 2.79153 6.24348 2.48735C7.09141 2.18307 7.38563 2.17214 8.10723 2.43646C8.829 2.70083 13.144 3.47917 13.8571 3.74044L13.8568 5.10715C13.8569 5.24403 13.6923 5.39435 13.3198 5.58855Z"
fill="#912626"
/>
<path
d="M13.3195 4.21078C12.5876 4.59221 8.79639 6.15113 7.98923 6.57188C7.18212 6.99263 6.73373 6.98851 6.09612 6.68385C5.45852 6.37903 1.42394 4.74917 0.697248 4.40187C-0.0295558 4.05462 -0.0447165 3.81542 0.669123 3.53583C1.38302 3.2563 5.39546 1.68221 6.2435 1.37792C7.09143 1.07369 7.38559 1.06276 8.1072 1.32714C8.82886 1.59151 12.5975 3.09146 13.3106 3.35272C14.0239 3.61394 14.0513 3.82935 13.3195 4.21078Z"
fill="#C6302B"
/>
<path
d="M8.67572 2.86218L7.49661 2.9846L7.23267 3.61974L6.80635 2.91099L5.44483 2.78863L6.46076 2.42226L6.15594 1.85986L7.1071 2.23186L8.00378 1.93829L7.76142 2.51981L8.67572 2.86218ZM7.16228 5.94351L4.96172 5.03081L8.11494 4.54679L7.16228 5.94351ZM4.11138 3.21522C5.04219 3.21522 5.79674 3.50772 5.79674 3.86847C5.79674 4.22933 5.04219 4.52177 4.11138 4.52177C3.18058 4.52177 2.42603 4.22927 2.42603 3.86847C2.42603 3.50772 3.18058 3.21522 4.11138 3.21522Z"
fill="white"
/>
<path
d="M10.0693 3.0357L11.9355 3.77316L10.0709 4.50993L10.0693 3.0357Z"
fill="#621B1C"
/>
<path
d="M8.00464 3.85234L10.0693 3.03564L10.0709 4.50988L9.86844 4.58906L8.00464 3.85234Z"
fill="#9A2928"
/>
</g>
<defs>
<clipPath id="clip0_2061_4282">
<rect
width="13.7143"
height="13.7143"
fill="white"
transform="translate(0.142822 0.143066)"
/>
</clipPath>
</defs>
</svg>
);
}

View File

@ -0,0 +1,110 @@
function TentIcon(): JSX.Element {
return (
<svg
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M9.75393 9.01964L6.72187 8.90409L6.18079 14.0994C6.18079 14.0994 7.01185 14.0416 8.1118 14.0416C9.21174 14.0416 10.1395 14.1194 10.1395 14.1194L9.75393 9.01964Z"
fill="#8A2E08"
/>
<path
d="M3.65538 9.74625L2.9132 9.59182C2.9132 9.59182 2.73099 10.7684 2.56322 11.4973C2.39545 12.2261 2.11658 13.2116 2.11658 13.2116L3.88093 14.0882C3.88093 14.0882 5.47751 14.2282 5.50529 14.1727C5.53306 14.1171 6.70967 11.3851 6.70967 11.3851L6.98965 10.334L3.65538 9.74625Z"
fill="#FF6110"
/>
<path
d="M9.17395 10.3207L12.8715 9.55071L13.0393 9.99847C13.0393 9.99847 13.2826 11.0162 13.4171 11.5673C13.6337 12.4517 13.9126 13.2439 13.9126 13.2439L12.2538 14.2705L10.545 14.2149L9.17395 10.3207Z"
fill="#FF6110"
/>
<path
d="M2.90422 9.15296C2.90422 9.17852 2.92199 9.36406 2.9131 9.49961C2.90422 9.63516 2.87866 9.80182 2.87866 9.80182C2.87866 9.80182 2.97977 9.82404 3.0731 9.88403C3.16642 9.94403 3.35308 10.1729 3.35308 10.1729C3.35308 10.1729 3.71751 10.0385 3.93861 10.1218C4.27859 10.2496 4.42303 10.564 4.42303 10.564C4.42303 10.564 4.77078 10.2407 5.19632 10.3096C5.61185 10.3762 5.77407 10.7684 5.77407 10.7684C5.77407 10.7684 6.08627 10.5162 6.35959 10.5307C6.68291 10.5473 6.80179 10.7851 6.80179 10.7851L7.28621 9.80848L3.68529 9.15407H2.90422V9.15296Z"
fill="#AF0D03"
/>
<path
d="M1.93542 13.6716L2.13097 13.1539C2.13097 13.1539 2.99759 13.5783 3.62534 13.7649C4.25308 13.9516 4.99304 13.9772 4.99304 13.9772C4.99304 13.9772 4.98415 13.6838 4.71194 13.4716C4.43974 13.2594 4.00865 12.9028 4.00865 12.9028L4.59751 12.7061L4.92527 12.575L5.95299 13.7983L5.6219 14.6227C5.6219 14.6227 4.89971 14.716 3.74533 14.4194C2.59095 14.1227 1.93542 13.6716 1.93542 13.6716Z"
fill="#C9C9C9"
/>
<path
d="M9.104 9.63738C9.12067 9.70516 9.11289 10.2407 9.11289 10.2407L9.42732 10.5973C9.42732 10.5973 9.59731 10.3762 9.9884 10.394C10.3795 10.4107 10.5573 10.5896 10.5573 10.5896C10.5573 10.5896 10.795 10.3007 11.1183 10.2418C11.4417 10.1818 11.755 10.3518 11.755 10.3518C11.755 10.3518 11.9761 9.94403 12.2983 9.85071C12.6216 9.75738 13.0394 9.99959 13.0394 9.99959L12.8671 8.95075L9.69953 9.17852L9.104 9.63738Z"
fill="#AF0D03"
/>
<path
d="M13.8782 13.1372L14.0815 13.6549C14.0815 13.6549 13.4615 14.1394 12.6627 14.3605C11.8638 14.5816 10.7528 14.6704 10.7528 14.6704L10.4283 12.8494L11.5238 12.5183L12.1249 12.775C12.1249 12.775 11.5927 13.2227 11.4472 13.4283C11.1705 13.8172 11.2694 14.0638 11.2694 14.0638C11.2694 14.0638 12.1183 13.9872 12.7727 13.7072C13.4282 13.425 13.8782 13.1372 13.8782 13.1372Z"
fill="#C9C9C9"
/>
<path
d="M4.42432 12.8905C4.42432 12.8905 4.80096 13.0594 4.99318 13.1961C5.23094 13.3661 5.46871 13.6205 5.54537 14.0283C5.5987 14.3094 5.62203 14.6227 5.62203 14.6227C5.62203 14.6227 5.88535 14.826 6.4031 13.9605C6.92085 13.095 7.18417 11.8962 7.2275 11.0473C7.26972 10.1985 7.25306 10.0029 7.25306 10.0029L6.48865 11.2595L5.35871 12.2872L4.42432 12.8905Z"
fill="#D92F0A"
/>
<path
d="M7.25966 10.0074C7.25966 10.0074 7.34855 10.7784 6.30971 11.735C5.66419 12.3295 5.03422 12.7383 4.60314 12.9072C4.45537 12.965 4.32315 12.9417 4.22982 12.9405C3.92539 12.9361 3.98206 12.8839 4.15205 12.7972C4.35204 12.695 4.93312 12.4828 6.02084 11.4806C6.89079 10.6784 6.95079 9.87626 6.95079 9.87626L7.25855 9.7096V10.0074H7.25966Z"
fill="#FFFEFF"
/>
<path
d="M10.7949 14.666C10.7949 14.666 10.4127 14.8015 9.835 14.0205C9.25725 13.2394 9.07948 11.855 9.05393 11.4128C9.02837 10.9706 9.03726 10.1385 9.03726 10.1385L10.0994 11.4717C10.0994 11.4717 11.7471 12.7117 11.7216 12.7205C11.696 12.7294 11.1838 13.0516 10.9916 13.375C10.5994 14.0283 10.7949 14.666 10.7949 14.666Z"
fill="#D92F0A"
/>
<path
d="M8.97839 9.77071C8.97839 9.77071 8.96284 10.0618 9.0195 10.2829C9.13839 10.7495 9.54836 11.254 10.0817 11.7273C10.7694 12.3384 11.5683 12.7639 11.6271 12.7806C11.6871 12.7972 12.1116 12.815 12.1193 12.7717C12.1227 12.7572 12.1482 12.6939 12.0093 12.5928C11.7238 12.3872 11.0916 12.025 10.5739 11.6162C10.2083 11.3273 9.73947 10.9684 9.33171 10.2285C9.25727 10.094 9.28393 9.75405 9.28393 9.75405L8.97839 9.77071Z"
fill="white"
/>
<path
d="M3.32974 7.25306C3.32974 7.25306 2.44534 7.36861 2.41756 7.49416C2.38979 7.62082 2.48756 9.28962 2.48756 9.28962C2.48756 9.28962 2.85976 9.22074 3.04753 9.26185C3.2353 9.30296 3.58861 9.53183 3.58861 9.53183L4.18081 9.16741L4.60745 9.7596C4.60745 9.7596 4.90854 9.60405 5.32519 9.64516C5.74183 9.68627 6.02182 9.96736 6.02182 9.96736L6.60401 9.4885L7.25398 9.99959C7.25398 9.99959 7.55063 9.65405 8.10171 9.64405C8.65279 9.63405 8.98611 10.0396 8.98611 10.0396L9.71385 9.40517L10.4316 9.95625C10.4316 9.95625 10.7127 9.72738 11.0349 9.65516C11.3571 9.58183 11.596 9.70738 11.596 9.70738L12.0537 8.89631L12.5737 9.27074C12.5737 9.27074 12.7404 9.07297 12.9792 9.04186C13.2181 9.01075 13.5092 9.00075 13.5092 9.00075C13.5092 9.00075 13.4881 7.87748 13.4881 7.71082C13.4881 7.54416 13.4259 7.27417 13.3525 7.22195C13.2792 7.16973 12.6248 7.02419 12.6248 7.02419L3.32974 7.25306Z"
fill="#D92F0A"
/>
<path
d="M4.4997 7.73859L3.63197 7.93969L3.58752 9.53294C3.58752 9.53294 3.85195 9.45628 4.11639 9.48405C4.38082 9.51183 4.60636 9.76182 4.60636 9.76182C4.60636 9.76182 4.6008 8.72632 4.60969 8.55188C4.61858 8.37745 4.68636 8.04635 4.72302 7.98302C4.75968 7.91858 4.4997 7.73859 4.4997 7.73859Z"
fill="#E1E1E1"
/>
<path
d="M6.78407 8.07747L5.99189 8.36745C5.99189 8.36745 5.97078 9.10074 5.98967 9.39295C6.00744 9.68516 6.02078 9.96959 6.02078 9.96959C6.02078 9.96959 6.38187 9.84959 6.67519 9.86737C6.96739 9.88515 7.25293 10.0018 7.25293 10.0018L7.24182 8.433L6.78407 8.07747Z"
fill="#E1E1E1"
/>
<path
d="M8.95947 8.40634C8.95947 8.40634 8.98725 9.00075 8.99614 9.30184C9.00503 9.60294 8.98503 10.0429 8.98503 10.0429C8.98503 10.0429 9.41612 9.82293 9.75499 9.79515C10.0927 9.76738 10.4305 9.95959 10.4305 9.95959C10.4305 9.95959 10.4038 9.33851 10.3672 8.88187C10.3416 8.56299 10.2483 8.28745 10.2483 8.28745L9.38056 7.82192L8.95947 8.40634Z"
fill="#E1E1E1"
/>
<path
d="M11.5949 9.70961C11.5949 9.70961 11.8827 9.39296 12.1116 9.31963C12.3405 9.2463 12.5727 9.27296 12.5727 9.27296C12.5727 9.27296 12.5593 8.52522 12.5227 8.18635C12.486 7.84859 12.4527 7.65416 12.4527 7.65416L11.3905 7.19974L11.4205 8.03525C11.4205 8.03525 11.5461 8.31412 11.5827 8.79854C11.6183 9.28296 11.5949 9.70961 11.5949 9.70961Z"
fill="#E1E1E1"
/>
<path
d="M8.04625 4.2021L6.7641 4.77318C6.7641 4.77318 5.5986 5.64758 4.67643 6.23088C3.75425 6.81418 3.22428 7.03639 3.11429 7.08195C2.93874 7.15528 2.65431 7.24194 2.53099 7.32638C2.32655 7.46859 2.4121 7.53526 2.44543 7.54637C2.47877 7.55748 3.49871 8.01412 5.30639 8.28188C7.11408 8.54965 9.57172 8.44076 10.8317 8.19633C12.0905 7.9519 13.3526 7.22305 13.3526 7.22305C13.3526 7.22305 13.2471 7.04639 12.5482 6.74308C11.8483 6.43976 11.2539 6.11311 10.2395 5.4487C9.22508 4.78429 8.98065 4.45764 8.98065 4.45764L8.04625 4.2021Z"
fill="#FF6110"
/>
<path
d="M6.96185 5.00653C6.96185 5.00653 5.55081 6.452 3.81312 7.65305C3.62313 7.78416 3.63202 7.9397 3.63202 7.9397C3.63202 7.9397 3.77202 7.99303 4.11644 8.07303C4.46642 8.15414 4.66752 8.17414 4.66752 8.17414C4.66752 8.17414 4.8264 7.99192 5.16528 7.65305C5.77191 7.04642 6.14856 6.56533 6.62298 5.95092C6.9374 5.54316 7.31183 5.0543 7.31183 5.0543L6.96185 5.00653Z"
fill="white"
/>
<path
d="M7.5684 5.19318C7.5684 5.19318 7.0551 6.382 6.81067 6.89531C6.69512 7.13641 6.43735 7.58305 6.20403 7.95636C6.00071 8.2819 5.99182 8.36856 5.99182 8.36856C5.99182 8.36856 6.44846 8.43412 6.66956 8.43412C6.89066 8.43412 7.24064 8.43189 7.24064 8.43189C7.24064 8.43189 7.41619 7.90859 7.60285 6.85975C7.71173 6.24979 7.87061 5.18095 7.87061 5.18095L7.5684 5.19318Z"
fill="white"
/>
<path
d="M8.27954 5.23983C8.27954 5.23983 8.39509 6.38532 8.52397 6.98862C8.69841 7.80525 8.95839 8.40522 8.95839 8.40522C8.95839 8.40522 9.39837 8.38744 9.6428 8.36411C9.88723 8.34077 10.2461 8.28633 10.2461 8.28633C10.2461 8.28633 9.82613 7.6397 9.42059 6.98862C9.15283 6.55754 8.65063 5.55425 8.56953 5.16983C8.50064 4.83874 8.27954 5.23983 8.27954 5.23983Z"
fill="white"
/>
<path
d="M8.90845 5.02984C8.90845 5.06428 9.27732 5.85091 10.1906 6.83752C10.925 7.63081 11.415 8.03857 11.415 8.03857C11.415 8.03857 11.8338 7.90969 12.0216 7.8408C12.2427 7.75969 12.4527 7.65414 12.4527 7.65414C12.4527 7.65414 11.4616 6.9664 10.8783 6.48865C10.295 6.0109 9.49064 5.34649 9.26954 5.04317C9.04844 4.73874 8.90845 5.02984 8.90845 5.02984Z"
fill="white"
/>
<path
d="M8.03411 3.40993C7.78856 3.4277 7.70746 3.84102 7.57969 3.9699C7.45191 4.09767 6.76306 4.7743 6.76306 4.7743C6.76306 4.7743 6.88528 5.41316 8.09855 5.40093C9.20849 5.38871 9.41959 4.86318 9.41959 4.86318C9.41959 4.86318 8.71074 4.10989 8.54741 3.92323C8.38409 3.73547 8.36075 3.3866 8.03411 3.40993Z"
fill="#D92F0A"
/>
<path
d="M8.07516 2.86996C8.07516 2.86996 8.28515 2.8033 8.58402 2.83774C8.88845 2.8733 9.09066 2.97218 9.40731 2.96551C9.89173 2.95663 10.2817 2.72886 10.4217 2.59998C10.5617 2.4711 10.7406 2.26222 10.6472 2.19222C10.5539 2.12223 10.1562 2.22778 9.71174 2.05223C9.31954 1.89779 9.12288 1.49448 8.56624 1.43004C8.08738 1.3756 7.94739 1.57225 7.94739 1.57225L8.07516 2.86996Z"
fill="#FF6110"
/>
<path
d="M7.88843 1.43226V3.6177L8.23841 3.52437L8.20397 1.43226H7.88843Z"
fill="#D92F0A"
/>
</svg>
);
}
export default TentIcon;

View File

@ -0,0 +1,39 @@
.time-selection-target {
display: flex;
height: 32px;
padding: 6px 6px 6px 8px;
justify-content: space-between;
align-items: center;
gap: 4px;
align-self: stretch;
border-radius: 2px;
border: 1px solid var(--bg-slate-400);
background: var(--bg-ink-300);
box-shadow: none;
.button-selected-text {
display: flex;
align-items: center;
gap: 6px;
}
.selected-value {
color: var(--bg-vanilla-100);
font-family: Inter;
font-size: 12px;
font-style: normal;
font-weight: 400;
line-height: 16px; /* 133.333% */
}
}
.lightMode {
.time-selection-target {
border: 1px solid var(--bg-vanilla-300);
background: var(--bg-vanilla-300);
.selected-value {
color: var(--bg-ink-300);
}
}
}

View File

@ -1,13 +1,15 @@
import './TimePreference.styles.scss';
import { DownOutlined } from '@ant-design/icons';
import { Button, Dropdown } from 'antd';
import { Button, Dropdown, Typography } from 'antd';
import TimeItems, {
timePreferance,
timePreferenceType,
} from 'container/NewWidget/RightContainer/timeItems';
import { Globe } from 'lucide-react';
import { Dispatch, SetStateAction, useCallback, useMemo } from 'react';
import { menuItems } from './config';
import { TextContainer } from './styles';
function TimePreference({
setSelectedTime,
@ -32,13 +34,22 @@ function TimePreference({
);
return (
<TextContainer noButtonMargin>
<Dropdown menu={menu}>
<Button>
{selectedTime.name} <DownOutlined />
</Button>
</Dropdown>
</TextContainer>
<Dropdown
menu={menu}
rootClassName="time-selection-menu"
className="time-selection-target"
trigger={['click']}
>
<Button>
<div className="button-selected-text">
<Globe size={14} />
<Typography.Text className="selected-value">
{selectedTime.name}
</Typography.Text>
</div>
<DownOutlined />
</Button>
</Dropdown>
);
}

View File

@ -267,6 +267,21 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
const isTracesView = (): boolean =>
routeKey === 'TRACES_EXPLORER' || routeKey === 'TRACES_SAVE_VIEWS';
const isDashboardListView = (): boolean => routeKey === 'ALL_DASHBOARD';
const isDashboardView = (): boolean => {
/**
* need to match using regex here as the getRoute function will not work for
* routes with id
*/
const regex = /^\/dashboard\/[a-zA-Z0-9_-]+$/;
return regex.test(pathname);
};
const isDashboardWidgetView = (): boolean => {
const regex = /^\/dashboard\/[a-zA-Z0-9_-]+\/new$/;
return regex.test(pathname);
};
useEffect(() => {
if (isDarkMode) {
document.body.classList.remove('lightMode');
@ -331,7 +346,14 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
<LayoutContent>
<ChildrenContainer
style={{
margin: isLogsView() || isTracesView() ? 0 : ' 0 1rem',
margin:
isLogsView() ||
isTracesView() ||
isDashboardView() ||
isDashboardWidgetView() ||
isDashboardListView()
? 0
: '0 1rem',
}}
>
{isToDisplayLayout && !renderFullScreen && <TopNav />}

View File

@ -15,6 +15,9 @@ export const Layout = styled(LayoutComponent)`
export const LayoutContent = styled(LayoutComponent.Content)`
overflow-y: auto;
height: 100%;
&::-webkit-scrollbar {
width: 0.1rem;
}
`;
export const ChildrenContainer = styled.div`

View File

@ -22,6 +22,18 @@ export const ChartContainer = styled(Card)`
position: relative;
}
.plot-tag {
margin-left: 6px;
display: inline-flex;
padding: 0px 4px 0px 6px;
align-items: center;
gap: 6px;
border-radius: 4px;
background: var(--bg-slate-400);
backdrop-filter: blur(6px);
width: fit-content;
}
.ant-card-body {
padding: 1.5rem 0;
height: 57vh;

View File

@ -0,0 +1,244 @@
.dashboard-empty-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 500px;
.dashboard-content {
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 12px;
.heading {
display: flex;
flex-direction: column;
gap: 4px;
.icons {
color: white;
}
.welcome {
color: var(--bg-vanilla-400);
font-family: Inter;
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: 24px; /* 171.429% */
letter-spacing: -0.07px;
}
.welcome-info {
color: var(--bg-vanilla-100);
font-family: Inter;
font-size: 14px;
font-style: normal;
font-weight: 500;
line-height: 24px;
letter-spacing: -0.07px;
}
}
.actions {
display: flex;
flex-direction: column;
.actions-1 {
display: flex;
width: 560px;
padding: 12px;
justify-content: space-between;
align-items: flex-start;
border-radius: 4px 4px 0px 0px;
border: 1px solid var(--bg-slate-500);
background: var(--bg-ink-400);
.configure-button {
display: flex;
width: 113px;
height: 32px;
padding: 6px;
justify-content: center;
align-items: center;
flex-shrink: 0;
border-radius: 2px;
border: 1px solid var(--bg-slate-400);
background: var(--bg-ink-300);
color: var(--bg-vanilla-400);
font-family: Inter;
font-size: 12px;
font-style: normal;
font-weight: 500;
line-height: 10px; /* 83.333% */
letter-spacing: 0.12px;
}
.actions-configure {
display: flex;
flex-direction: column;
gap: 6px;
.actions-configure-text {
display: flex;
gap: 8px;
align-items: center;
.icons {
color: white;
}
.configure {
color: var(--bg-vanilla-100);
font-family: Inter;
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: 20px; /* 142.857% */
letter-spacing: -0.07px;
}
}
.configure-info {
color: var(--bg-vanilla-400);
font-family: Inter;
font-size: 12px;
font-style: normal;
font-weight: 400;
line-height: 20px; /* 166.667% */
letter-spacing: -0.06px;
padding-left: 22px;
}
}
.add-panel-btn {
display: flex;
width: 113px;
height: 32px;
padding: 6px;
justify-content: center;
align-items: center;
flex-shrink: 0;
border-radius: 2px;
border: 1px solid var(--bg-slate-400);
background: var(--bg-ink-300);
box-shadow: none;
color: var(--bg-vanilla-400);
font-family: Inter;
font-size: 12px;
font-style: normal;
font-weight: 500;
line-height: 10px; /* 83.333% */
letter-spacing: 0.12px;
}
.actions-add-panel {
display: flex;
flex-direction: column;
gap: 6px;
.actions-panel-text {
display: flex;
gap: 8px;
align-items: center;
.icons {
color: white;
}
.panel {
color: var(--bg-vanilla-100);
font-family: Inter;
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: 20px; /* 142.857% */
letter-spacing: -0.07px;
}
}
.panel-info {
color: var(--bg-vanilla-400);
font-family: Inter;
font-size: 12px;
font-style: normal;
font-weight: 400;
line-height: 20px; /* 166.667% */
letter-spacing: -0.06px;
padding-left: 22px;
}
}
}
}
}
}
.lightMode {
.dashboard-empty-state {
.dashboard-content {
.heading {
.icons {
color: var(--bg-ink-300);
}
.welcome {
color: var(--bg-ink-400);
}
.welcome-info {
color: var(--bg-ink-400);
}
}
.actions {
.actions-1 {
border: 1px solid var(--bg-vanilla-300);
background: var(--bg-vanilla-100);
.configure-button {
border: 1px solid var(--bg-vanilla-300);
background: var(--bg-vanilla-300);
color: var(--bg-ink-400);
}
.actions-configure {
.actions-configure-text {
.icons {
color: var(--bg-ink-400);
}
.configure {
color: var(--bg-ink-400);
}
}
.configure-info {
color: var(--bg-ink-400);
}
}
.add-panel-btn {
border: 1px solid var(--bg-vanilla-300);
background: var(--bg-vanilla-300);
color: var(--bg-ink-400);
}
.actions-add-panel {
.actions-panel-text {
.icons {
color: var(--bg-ink-300);
}
.panel {
color: var(--bg-ink-300);
}
}
.panel-info {
color: var(--bg-ink-300);
}
}
}
}
}
}
}

View File

@ -0,0 +1,105 @@
/* eslint-disable jsx-a11y/img-redundant-alt */
import './DashboardEmptyState.styles.scss';
import { PlusOutlined } from '@ant-design/icons';
import { Button, Typography } from 'antd';
import SettingsDrawer from 'container/NewDashboard/DashboardDescription/SettingsDrawer';
import useComponentPermission from 'hooks/useComponentPermission';
import { useDashboard } from 'providers/Dashboard/Dashboard';
import { useCallback } from 'react';
import { useSelector } from 'react-redux';
import { AppState } from 'store/reducers';
import AppReducer from 'types/reducer/app';
import { ROLES, USER_ROLES } from 'types/roles';
import { ComponentTypes } from 'utils/permission';
export default function DashboardEmptyState(): JSX.Element {
const {
selectedDashboard,
isDashboardLocked,
handleToggleDashboardSlider,
} = useDashboard();
const { user, role } = useSelector<AppState, AppReducer>((state) => state.app);
let permissions: ComponentTypes[] = ['add_panel'];
if (isDashboardLocked) {
permissions = ['add_panel_locked_dashboard'];
}
const userRole: ROLES | null =
selectedDashboard?.created_by === user?.email
? (USER_ROLES.AUTHOR as ROLES)
: role;
const [addPanelPermission] = useComponentPermission(permissions, userRole);
const onEmptyWidgetHandler = useCallback(() => {
handleToggleDashboardSlider(true);
}, [handleToggleDashboardSlider]);
return (
<section className="dashboard-empty-state">
<div className="dashboard-content">
<section className="heading">
<img
src="/Icons/dashboard_emoji.svg"
alt="header-image"
style={{ height: '32px', width: '32px' }}
/>
<Typography.Text className="welcome">
Welcome to your new dashboard
</Typography.Text>
<Typography.Text className="welcome-info">
Follow the steps to populate it with data and share with your teammates
</Typography.Text>
</section>
<section className="actions">
<div className="actions-1">
<div className="actions-configure">
<div className="actions-configure-text">
<img
src="/Icons/tools.svg"
alt="header-image"
style={{ height: '14px', width: '14px' }}
/>
<Typography.Text className="configure">
Configure your new dashboard
</Typography.Text>
</div>
<Typography.Text className="configure-info">
Give it a name, add description, tags and variables
</Typography.Text>
</div>
<SettingsDrawer drawerTitle="Dashboard Configuration" />
</div>
<div className="actions-1">
<div className="actions-add-panel">
<div className="actions-panel-text">
<img
src="/Icons/landscape.svg"
alt="header-image"
style={{ height: '14px', width: '14px' }}
/>
<Typography.Text className="panel">Add panels</Typography.Text>
</div>
<Typography.Text className="panel-info">
Add panels to visualize your data
</Typography.Text>
</div>
{!isDashboardLocked && addPanelPermission && (
<Button
className="add-panel-btn"
onClick={onEmptyWidgetHandler}
icon={<PlusOutlined />}
type="primary"
data-testid="add-panel"
>
New Panel
</Button>
)}
</div>
</section>
</div>
</section>
);
}

View File

@ -6,12 +6,85 @@
border: none !important;
margin-top: 0;
.row-panel {
border-radius: 4px;
background: rgba(18, 19, 23, 0.4);
padding: 8px;
display: flex;
gap: 6px;
align-items: center;
.settings-icon {
color: var(--bg-vanilla-400);
cursor: pointer;
}
.row-icon {
color: var(--bg-vanilla-400);
cursor: pointer;
}
.grip {
cursor: move;
}
.section-title {
color: var(--bg-vanilla-400);
font-family: Inter;
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: 20px; /* 142.857% */
letter-spacing: -0.07px;
}
}
.widget-graph-container {
&.graph {
height: 100%;
}
}
}
.footer {
display: flex;
flex-direction: column;
position: absolute;
bottom: 0;
width: -webkit-fill-available;
.locked-text {
align-self: flex-end;
width: 80px;
border: none;
cursor: default;
display: inline-flex;
padding: 4px 6px;
align-items: center;
gap: 4px;
border-radius: 4px 0px 0px 0px;
background: var(--bg-sakura-500);
backdrop-filter: blur(6px);
color: var(--bg-ink-500);
font-family: 'Space Mono';
font-size: 12px;
font-style: normal;
font-weight: 500;
line-height: 16px; /* 133.333% */
letter-spacing: 0.48px;
text-transform: uppercase;
.ant-btn-icon {
margin-inline-end: 0px;
}
}
.locked-bar {
background: var(--bg-sakura-500);
height: 6px;
width: 100%;
}
}
}
.widget-graph-container {
@ -32,18 +105,257 @@
}
}
.row-settings {
.ant-popover-inner {
width: 191px;
flex-shrink: 0;
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);
padding: 0px;
.menu-content {
.section-1 {
.rename-btn {
display: flex;
align-items: center;
gap: 6px;
color: var(--bg-vanilla-400);
font-family: Inter;
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: normal;
letter-spacing: 0.14px;
padding: 14px;
width: 100%;
.ant-btn-icon {
margin-inline-end: 0px;
}
}
}
.section-2 {
border-top: 1px solid #1d212d;
.remove-section {
display: flex;
align-items: center;
width: 100%;
gap: 6px;
color: var(--bg-vanilla-400);
font-family: Inter;
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: normal;
letter-spacing: 0.14px;
padding: 10px 18px 12px 14px;
color: var(--bg-cherry-400);
font-family: Inter;
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: normal;
letter-spacing: 0.14px;
.ant-btn-icon {
margin-inline-end: 0px;
}
}
}
}
}
}
.rename-section {
.ant-modal-content {
width: 384px;
height: auto;
flex-shrink: 0;
border-radius: 4px;
border: 1px solid var(--bg-slate-500);
background: var(--Ink-400, #121317);
box-shadow: 0px -4px 16px 2px rgba(0, 0, 0, 0.2);
padding: 0px;
.ant-modal-header {
padding: 16px;
background: var(--bg-ink-400);
border-bottom: 1px solid var(--bg-slate-500);
margin-bottom: 0px;
.ant-modal-title {
color: var(--bg-vanilla-100);
font-family: Inter;
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: 20px; /* 142.857% */
}
}
.ant-modal-body {
padding: 12px 16px 16px 16px;
.typography {
color: var(--bg-vanilla-100);
font-family: Inter;
font-size: 14px;
font-style: normal;
font-weight: 500;
line-height: 20px; /* 142.857% */
}
.ant-form-item {
margin-bottom: 0px;
.ant-input {
margin-top: 8px;
margin-bottom: 24px;
}
.action-btns {
display: flex;
align-items: center;
flex-direction: row-reverse;
gap: 12px;
.ok-btn {
display: flex;
align-items: center;
color: var(--bg-vanilla-100);
font-family: Inter;
font-size: 12px;
font-style: normal;
font-weight: 500;
line-height: 24px; /* 200% */
display: flex;
width: 140px;
padding: 4px 8px;
justify-content: center;
align-items: center;
gap: 4px;
border-radius: 2px;
background: var(--bg-robin-500);
.ant-btn-icon {
margin-inline-end: 0px;
}
}
.cancel-btn {
display: flex;
align-items: center;
color: var(--bg-vanilla-400);
font-family: Inter;
font-size: 12px;
font-style: normal;
font-weight: 500;
line-height: 24px; /* 200% */
display: flex;
padding: 4px 8px;
justify-content: center;
align-items: center;
gap: 4px;
border-radius: 2px;
background: var(--bg-slate-500);
.ant-btn-icon {
margin-inline-end: 0px;
}
}
}
}
}
}
}
.lightMode {
.fullscreen-grid-container {
background-color: rgb(250, 250, 250);
.react-grid-layout {
.row-panel {
background: var(--bg-vanilla-200);
.settings-icon {
color: var(--bg-ink-400);
}
.row-icon {
color: var(--bg-ink-400);
}
.section-title {
color: var(--bg-ink-400);
}
}
}
}
.widget-full-view {
.ant-modal-content {
background-color: var(--bg-vanilla-100);
}
.ant-modal-header {
background-color: var(--bg-vanilla-100);
.ant-modal-header {
background-color: var(--bg-vanilla-100);
}
}
}
.row-settings {
.ant-popover-inner {
border: 1px solid var(--bg-vanilla-300);
background: var(--bg-vanilla-100);
.menu-content {
.section-1 {
.rename-btn {
color: var(--bg-ink-400);
}
}
.section-2 {
border-top: 1px solid var(--bg-vanilla-300);
.remove-section {
color: var(--bg-cherry-400);
}
}
}
}
}
.rename-section {
.ant-modal-content {
border: 1px solid var(--bg-vanilla-300);
background: var(--bg-vanilla-100);
.ant-modal-header {
background: var(--bg-vanilla-100);
border-bottom: 1px solid var(--bg-vanilla-300);
.ant-modal-title {
color: var(--bg-ink-300);
}
}
.ant-modal-body {
.typography {
color: var(--bg-ink-100);
}
.ant-form-item {
.action-btns {
.cancel-btn {
color: var(--bg-ink-300);
background: var(--bg-vanilla-300);
}
}
}
}
}
}
}

View File

@ -1,15 +1,14 @@
import './GridCardLayout.styles.scss';
import { PlusOutlined } from '@ant-design/icons';
import { Flex, Form, Input, Modal, Tooltip, Typography } from 'antd';
import { Color } from '@signozhq/design-tokens';
import { Button, Form, Input, Modal, Typography } from 'antd';
import { useForm } from 'antd/es/form/Form';
import cx from 'classnames';
import FacingIssueBtn from 'components/facingIssueBtn/FacingIssueBtn';
import { dashboardHelpMessage } from 'components/facingIssueBtn/util';
import { SOMETHING_WENT_WRONG } from 'constants/api';
import { QueryParams } from 'constants/query';
import { PANEL_GROUP_TYPES, PANEL_TYPES } from 'constants/queryBuilder';
import { themeColors } from 'constants/theme';
import { DEFAULT_ROW_NAME } from 'container/NewDashboard/DashboardDescription/utils';
import { useUpdateDashboard } from 'hooks/dashboard/useUpdateDashboard';
import useComponentPermission from 'hooks/useComponentPermission';
import { useIsDarkMode } from 'hooks/useDarkMode';
@ -19,19 +18,18 @@ import history from 'lib/history';
import { defaultTo } from 'lodash-es';
import isEqual from 'lodash-es/isEqual';
import {
FullscreenIcon,
Check,
ChevronDown,
ChevronUp,
GripVertical,
MoveDown,
MoveUp,
Settings,
Trash2,
LockKeyhole,
X,
} from 'lucide-react';
import { useDashboard } from 'providers/Dashboard/Dashboard';
import { sortLayout } from 'providers/Dashboard/util';
import { useCallback, useEffect, useState } from 'react';
import { FullScreen, useFullScreenHandle } from 'react-full-screen';
import { useCallback, useEffect, useMemo, useState } from 'react';
import { FullScreen, FullScreenHandle } from 'react-full-screen';
import { ItemCallback, Layout } from 'react-grid-layout';
import { useTranslation } from 'react-i18next';
import { useDispatch, useSelector } from 'react-redux';
import { useLocation } from 'react-router-dom';
import { UpdateTimeInterval } from 'store/actions';
@ -40,21 +38,21 @@ import { Dashboard, Widgets } from 'types/api/dashboard/getAll';
import AppReducer from 'types/reducer/app';
import { ROLES, USER_ROLES } from 'types/roles';
import { ComponentTypes } from 'utils/permission';
import { v4 as uuid } from 'uuid';
import { EditMenuAction, ViewMenuAction } from './config';
import DashboardEmptyState from './DashboardEmptyState/DashboardEmptyState';
import GridCard from './GridCard';
import {
Button,
ButtonContainer,
Card,
CardContainer,
ReactGridLayout,
} from './styles';
import { GraphLayoutProps } from './types';
import { Card, CardContainer, ReactGridLayout } from './styles';
import { removeUndefinedValuesFromLayout } from './utils';
import { WidgetRowHeader } from './WidgetRow';
function GraphLayout({ onAddPanelHandler }: GraphLayoutProps): JSX.Element {
interface GraphLayoutProps {
handle: FullScreenHandle;
}
// eslint-disable-next-line sonarjs/cognitive-complexity
function GraphLayout(props: GraphLayoutProps): JSX.Element {
const { handle } = props;
const {
selectedDashboard,
layouts,
@ -65,14 +63,11 @@ function GraphLayout({ onAddPanelHandler }: GraphLayoutProps): JSX.Element {
isDashboardLocked,
} = useDashboard();
const { data } = selectedDashboard || {};
const handle = useFullScreenHandle();
const { pathname } = useLocation();
const dispatch = useDispatch();
const { widgets, variables } = data || {};
const { t } = useTranslation(['dashboard']);
const { featureResponse, role, user } = useSelector<AppState, AppReducer>(
(state) => state.app,
);
@ -122,6 +117,11 @@ function GraphLayout({ onAddPanelHandler }: GraphLayoutProps): JSX.Element {
userRole,
);
const [deleteWidget, editWidget] = useComponentPermission(
['delete_widget', 'edit_widget'],
role,
);
useEffect(() => {
setDashboardLayout(sortLayout(layouts));
}, [layouts]);
@ -206,80 +206,6 @@ function GraphLayout({ onAddPanelHandler }: GraphLayoutProps): JSX.Element {
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [dashboardLayout]);
function handleAddRow(): void {
if (!selectedDashboard) return;
const id = uuid();
const newRowWidgetMap: { widgets: Layout[]; collapsed: boolean } = {
widgets: [],
collapsed: false,
};
const currentRowIdx = 0;
for (let j = currentRowIdx; j < dashboardLayout.length; j++) {
if (!currentPanelMap[dashboardLayout[j].i]) {
newRowWidgetMap.widgets.push(dashboardLayout[j]);
} else {
break;
}
}
const updatedDashboard: Dashboard = {
...selectedDashboard,
data: {
...selectedDashboard.data,
layout: [
{
i: id,
w: 12,
minW: 12,
minH: 1,
maxH: 1,
x: 0,
h: 1,
y: 0,
},
...dashboardLayout.filter((e) => e.i !== PANEL_TYPES.EMPTY_WIDGET),
],
panelMap: { ...currentPanelMap, [id]: newRowWidgetMap },
widgets: [
...(selectedDashboard.data.widgets || []),
{
id,
title: 'Sample Row',
description: '',
panelTypes: PANEL_GROUP_TYPES.ROW,
},
],
},
uuid: selectedDashboard.uuid,
};
updateDashboardMutation.mutate(updatedDashboard, {
// eslint-disable-next-line sonarjs/no-identical-functions
onSuccess: (updatedDashboard) => {
if (updatedDashboard.payload) {
if (updatedDashboard.payload.data.layout)
setLayouts(sortLayout(updatedDashboard.payload.data.layout));
setSelectedDashboard(updatedDashboard.payload);
setPanelMap(updatedDashboard.payload?.data?.panelMap || {});
}
featureResponse.refetch();
},
// eslint-disable-next-line sonarjs/no-identical-functions
onError: () => {
notifications.error({
message: SOMETHING_WENT_WRONG,
});
},
});
}
const handleRowSettingsClick = (id: string): void => {
setIsSettingsModalOpen(true);
setCurrentSelectRowId(id);
};
const onSettingsModalSubmit = (): void => {
const newTitle = form.getFieldValue('title');
if (!selectedDashboard) return;
@ -330,6 +256,15 @@ function GraphLayout({ onAddPanelHandler }: GraphLayoutProps): JSX.Element {
});
};
useEffect(() => {
if (!currentSelectRowId) return;
form.setFieldValue(
'title',
(widgets?.find((widget) => widget.id === currentSelectRowId)
?.title as string) || DEFAULT_ROW_NAME,
);
}, [currentSelectRowId, form, widgets]);
// eslint-disable-next-line sonarjs/cognitive-complexity
const handleRowCollapse = (id: string): void => {
if (!selectedDashboard) return;
@ -483,192 +418,187 @@ function GraphLayout({ onAddPanelHandler }: GraphLayoutProps): JSX.Element {
},
});
};
return (
<>
<Flex justify="flex-end" gap={8} align="center">
<FacingIssueBtn
attributes={{
uuid: selectedDashboard?.uuid,
title: data?.title,
screen: 'Dashboard Details',
}}
eventName="Dashboard: Facing Issues in dashboard"
buttonText="Need help with this dashboard?"
message={dashboardHelpMessage(data, selectedDashboard)}
onHoverText="Click here to get help for this dashboard"
/>
<ButtonContainer>
<Tooltip title="Open in Full Screen">
<Button
className="periscope-btn"
loading={updateDashboardMutation.isLoading}
onClick={handle.enter}
icon={<FullscreenIcon size={16} />}
disabled={updateDashboardMutation.isLoading}
/>
</Tooltip>
{!isDashboardLocked && addPanelPermission && (
<Button
className="periscope-btn"
onClick={onAddPanelHandler}
icon={<PlusOutlined />}
data-testid="add-panel"
>
{t('dashboard:add_panel')}
</Button>
)}
{!isDashboardLocked && addPanelPermission && (
<Button
className="periscope-btn"
onClick={(): void => handleAddRow()}
icon={<PlusOutlined />}
data-testid="add-row"
>
{t('dashboard:add_row')}
</Button>
)}
</ButtonContainer>
</Flex>
<FullScreen handle={handle} className="fullscreen-grid-container">
<ReactGridLayout
cols={12}
rowHeight={45}
autoSize
width={100}
useCSSTransforms
isDraggable={!isDashboardLocked && addPanelPermission}
isDroppable={!isDashboardLocked && addPanelPermission}
isResizable={!isDashboardLocked && addPanelPermission}
allowOverlap={false}
onLayoutChange={handleLayoutChange}
onDragStop={handleDragStop}
draggableHandle=".drag-handle"
layout={dashboardLayout}
style={{ backgroundColor: isDarkMode ? '' : themeColors.snowWhite }}
>
{dashboardLayout.map((layout) => {
const { i: id } = layout;
const currentWidget = (widgets || [])?.find((e) => e.id === id);
if (currentWidget?.panelTypes === PANEL_GROUP_TYPES.ROW) {
const rowWidgetProperties = currentPanelMap[id] || {};
return (
<CardContainer
className="row-card"
isDarkMode={isDarkMode}
key={id}
data-grid={JSON.stringify(currentWidget)}
>
<div className={cx('row-panel')}>
<div style={{ display: 'flex', gap: '10px', alignItems: 'center' }}>
<Button
disabled={updateDashboardMutation.isLoading}
icon={
rowWidgetProperties.collapsed ? (
<MoveDown size={14} />
) : (
<MoveUp size={14} />
)
}
type="text"
onClick={(): void => handleRowCollapse(id)}
/>
<Typography.Text>{currentWidget.title}</Typography.Text>
<Button
icon={<Settings size={14} />}
type="text"
onClick={(): void => handleRowSettingsClick(id)}
/>
</div>
{rowWidgetProperties.collapsed && (
<Button
type="text"
icon={<GripVertical size={14} />}
className="drag-handle"
/>
)}
{!rowWidgetProperties.collapsed && (
<Button
type="text"
icon={<Trash2 size={14} />}
onClick={(): void => {
setIsDeleteModalOpen(true);
setCurrentSelectRowId(id);
}}
/>
)}
</div>
</CardContainer>
);
}
const isDashboardEmpty = useMemo(
() =>
selectedDashboard?.data.layout
? selectedDashboard?.data.layout?.length === 0
: true,
[selectedDashboard],
);
return isDashboardEmpty ? (
<DashboardEmptyState />
) : (
<FullScreen handle={handle} className="fullscreen-grid-container">
<ReactGridLayout
cols={12}
rowHeight={45}
autoSize
width={100}
useCSSTransforms
isDraggable={!isDashboardLocked && addPanelPermission}
isDroppable={!isDashboardLocked && addPanelPermission}
isResizable={!isDashboardLocked && addPanelPermission}
allowOverlap={false}
onLayoutChange={handleLayoutChange}
onDragStop={handleDragStop}
draggableHandle=".drag-handle"
layout={dashboardLayout}
style={{ backgroundColor: isDarkMode ? '' : themeColors.snowWhite }}
>
{dashboardLayout.map((layout) => {
const { i: id } = layout;
const currentWidget = (widgets || [])?.find((e) => e.id === id);
if (currentWidget?.panelTypes === PANEL_GROUP_TYPES.ROW) {
const rowWidgetProperties = currentPanelMap[id] || {};
return (
<CardContainer
className={isDashboardLocked ? '' : 'enable-resize'}
className="row-card"
isDarkMode={isDarkMode}
key={id}
data-grid={JSON.stringify(currentWidget)}
>
<Card
className="grid-item"
$panelType={currentWidget?.panelTypes || PANEL_TYPES.TIME_SERIES}
>
<GridCard
widget={(currentWidget as Widgets) || ({ id, query: {} } as Widgets)}
headerMenuList={widgetActions}
variables={variables}
version={selectedDashboard?.data?.version}
onDragSelect={onDragSelect}
<div className={cx('row-panel')}>
<div style={{ display: 'flex', gap: '6px', alignItems: 'center' }}>
{rowWidgetProperties.collapsed && (
<GripVertical
size={14}
className="drag-handle"
color={isDarkMode ? Color.BG_VANILLA_100 : Color.BG_INK_300}
cursor="move"
/>
)}
<Typography.Text className="section-title">
{currentWidget.title}
</Typography.Text>
{rowWidgetProperties.collapsed ? (
<ChevronDown
size={14}
onClick={(): void => handleRowCollapse(id)}
className="row-icon"
/>
) : (
<ChevronUp
size={14}
onClick={(): void => handleRowCollapse(id)}
className="row-icon"
/>
)}
</div>
<WidgetRowHeader
id={id}
rowWidgetProperties={rowWidgetProperties}
setCurrentSelectRowId={setCurrentSelectRowId}
setIsDeleteModalOpen={setIsDeleteModalOpen}
setIsSettingsModalOpen={setIsSettingsModalOpen}
editWidget={editWidget}
deleteWidget={deleteWidget}
/>
</Card>
</div>
</CardContainer>
);
})}
</ReactGridLayout>
<Modal
open={isSettingsModalOpen}
title="Row Options"
destroyOnClose
footer={null}
onCancel={(): void => {
setIsSettingsModalOpen(false);
setCurrentSelectRowId(null);
}}
>
<Form form={form} onFinish={onSettingsModalSubmit} requiredMark>
<Form.Item required name={['title']}>
<Input
placeholder="Enter row name here..."
defaultValue={defaultTo(
widgets?.find((widget) => widget.id === currentSelectRowId)
?.title as string,
'Sample Title',
)}
/>
</Form.Item>
<Form.Item>
<Button type="primary" htmlType="submit">
}
return (
<CardContainer
className={isDashboardLocked ? '' : 'enable-resize'}
isDarkMode={isDarkMode}
key={id}
data-grid={JSON.stringify(currentWidget)}
>
<Card
className="grid-item"
isDarkMode={isDarkMode}
$panelType={currentWidget?.panelTypes || PANEL_TYPES.TIME_SERIES}
>
<GridCard
widget={(currentWidget as Widgets) || ({ id, query: {} } as Widgets)}
headerMenuList={widgetActions}
variables={variables}
version={selectedDashboard?.data?.version}
onDragSelect={onDragSelect}
/>
</Card>
</CardContainer>
);
})}
</ReactGridLayout>
{isDashboardLocked && (
<div className="footer">
<Button
type="text"
icon={<LockKeyhole size={14} />}
className="locked-text"
>
Locked
</Button>
<div className="locked-bar" />
</div>
)}
<Modal
open={isSettingsModalOpen}
title="Rename Section"
rootClassName="rename-section"
destroyOnClose
footer={null}
onCancel={(): void => {
setIsSettingsModalOpen(false);
setCurrentSelectRowId(null);
}}
>
<Form form={form} onFinish={onSettingsModalSubmit} requiredMark>
<Typography.Text className="typography">
Enter section name
</Typography.Text>
<Form.Item required name={['title']}>
<Input
placeholder="Enter row name here..."
defaultValue={defaultTo(
widgets?.find((widget) => widget.id === currentSelectRowId)
?.title as string,
'Sample Title',
)}
/>
</Form.Item>
<Form.Item>
<div className="action-btns">
<Button
type="primary"
htmlType="submit"
className="ok-btn"
icon={<Check size={14} />}
disabled={updateDashboardMutation.isLoading}
>
Apply Changes
</Button>
</Form.Item>
</Form>
</Modal>
<Modal
open={isDeleteModalOpen}
title="Delete Row"
destroyOnClose
onCancel={(): void => {
setIsDeleteModalOpen(false);
setCurrentSelectRowId(null);
}}
onOk={(): void => handleRowDelete()}
>
<Typography.Text>Are you sure you want to delete this row</Typography.Text>
</Modal>
</FullScreen>
</>
<Button
type="text"
className="cancel-btn"
icon={<X size={14} />}
onClick={(): void => {
setIsSettingsModalOpen(false);
setCurrentSelectRowId(null);
}}
>
Cancel
</Button>
</div>
</Form.Item>
</Form>
</Modal>
<Modal
open={isDeleteModalOpen}
title="Delete Row"
destroyOnClose
onCancel={(): void => {
setIsDeleteModalOpen(false);
setCurrentSelectRowId(null);
}}
onOk={(): void => handleRowDelete()}
>
<Typography.Text>Are you sure you want to delete this row</Typography.Text>
</Modal>
</FullScreen>
);
}

View File

@ -0,0 +1,82 @@
import { Button, Popover } from 'antd';
import { EllipsisIcon, PenLine, X } from 'lucide-react';
import { useState } from 'react';
import { Layout } from 'react-grid-layout';
interface WidgetRowHeaderProps {
rowWidgetProperties: {
widgets: Layout[];
collapsed: boolean;
};
editWidget: boolean;
deleteWidget: boolean;
setIsSettingsModalOpen: (value: React.SetStateAction<boolean>) => void;
setCurrentSelectRowId: (value: React.SetStateAction<string | null>) => void;
setIsDeleteModalOpen: (value: React.SetStateAction<boolean>) => void;
id: string;
}
export function WidgetRowHeader(props: WidgetRowHeaderProps): JSX.Element {
const {
rowWidgetProperties,
editWidget,
deleteWidget,
setCurrentSelectRowId,
setIsDeleteModalOpen,
setIsSettingsModalOpen,
id,
} = props;
const [isRowSettingsOpen, setIsRowSettingsOpen] = useState<boolean>(false);
return (
<Popover
open={isRowSettingsOpen}
arrow={false}
onOpenChange={(visible): void => setIsRowSettingsOpen(visible)}
rootClassName="row-settings"
trigger="hover"
placement="bottomRight"
content={
<div className="menu-content">
<section className="section-1">
<Button
className="rename-btn"
type="text"
disabled={!editWidget}
icon={<PenLine size={14} />}
onClick={(): void => {
setIsSettingsModalOpen(true);
setCurrentSelectRowId(id);
setIsRowSettingsOpen(false);
}}
>
Rename
</Button>
</section>
{!rowWidgetProperties.collapsed && (
<section className="section-2">
<Button
className="remove-section"
type="text"
icon={<X size={14} />}
disabled={!deleteWidget}
onClick={(): void => {
setIsDeleteModalOpen(true);
setCurrentSelectRowId(id);
setIsRowSettingsOpen(false);
}}
>
Remove Section
</Button>
</section>
)}
</div>
}
>
<EllipsisIcon
size={14}
className="settings-icon"
onClick={(): void => setIsRowSettingsOpen(!isRowSettingsOpen)}
/>
</Popover>
);
}

View File

@ -1,16 +1,13 @@
import { useDashboard } from 'providers/Dashboard/Dashboard';
import { useCallback } from 'react';
import { FullScreenHandle } from 'react-full-screen';
import GraphLayoutContainer from './GridCardLayout';
function GridGraph(): JSX.Element {
const { handleToggleDashboardSlider } = useDashboard();
const onEmptyWidgetHandler = useCallback(() => {
handleToggleDashboardSlider(true);
}, [handleToggleDashboardSlider]);
return <GraphLayoutContainer onAddPanelHandler={onEmptyWidgetHandler} />;
interface GridGraphProps {
handle: FullScreenHandle;
}
function GridGraph(props: GridGraphProps): JSX.Element {
const { handle } = props;
return <GraphLayoutContainer handle={handle} />;
}
export default GridGraph;

View File

@ -8,12 +8,28 @@ const ReactGridLayoutComponent = WidthProvider(RGL);
interface CardProps {
$panelType: PANEL_TYPES;
isDarkMode: boolean;
}
export const Card = styled(CardComponent)<CardProps>`
&&& {
height: 100%;
overflow: hidden;
border-radius: 3px;
border: 1px solid var(--bg-slate-500);
background: linear-gradient(
0deg,
rgba(171, 189, 255, 0) 0%,
rgba(171, 189, 255, 0) 100%
),
#0b0c0e;
${({ isDarkMode }): StyledCSS =>
!isDarkMode &&
css`
border: 1px solid var(--bg-vanilla-300);
background: unset;
`}
}
.ant-card-body {
@ -75,6 +91,7 @@ export const ReactGridLayout = styled(ReactGridLayoutComponent)`
margin-top: 1rem;
position: relative;
min-height: 40vh;
margin: 16px;
.react-grid-item.react-grid-placeholder {
background: grey;

View File

@ -1,3 +0,0 @@
export interface GraphLayoutProps {
onAddPanelHandler: VoidFunction;
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,269 @@
.new-dashboard-templates-modal {
.ant-modal-content {
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);
padding: 0;
height: 72vh;
.ant-modal-body {
height: 100%;
}
}
.new-dashboard-templates-content-container {
height: 100%;
}
.new-dashboard-templates-content-header {
padding: 16px;
display: flex;
align-items: center;
justify-content: space-between;
border-bottom: 1px solid var(--bg-slate-500);
height: 60px;
box-sizing: border-box;
}
.new-dashboard-templates-content {
overflow: hidden;
display: flex;
position: relative;
height: calc(100% - 60px);
.new-dashboard-templates-list {
padding: 16px 8px;
height: 100%;
width: 25%;
border-right: 1px solid var(--bg-slate-500);
.new-dashboard-templates-search {
height: 32px;
margin-bottom: 16px;
}
.templates-list {
display: flex;
flex-direction: column;
gap: 8px;
padding-bottom: 16px;
overflow-y: auto;
box-sizing: border-box;
height: calc(100% - 64px);
&::-webkit-scrollbar {
height: 1rem;
width: 0.1rem;
}
.template-list-item {
display: flex;
gap: 8px;
padding: 4px 12px;
align-items: center;
cursor: pointer;
height: 32px;
box-sizing: border-box;
.template-icon {
display: flex;
height: 14px;
width: 14px;
align-items: center;
justify-content: center;
}
.template-name {
color: #c0c1c3;
font-style: normal;
font-weight: 300;
line-height: 18px;
}
&:hover {
border-radius: 3px;
background: rgba(171, 189, 255, 0.08);
}
&.active {
border-radius: 3px;
background: rgba(171, 189, 255, 0.08);
}
}
}
}
.new-dashboard-template-preview {
flex: 1;
position: relative;
.template-preview-header {
padding: 16px;
display: flex;
gap: 8px;
align-items: center;
justify-content: space-between;
.template-preview-title {
display: flex;
justify-content: center;
align-items: center;
gap: 16px;
.template-preview-icon {
height: 40px;
width: 40px;
flex-shrink: 0;
border-radius: 2px;
border: 1px solid var(--bg-ink-50);
background: var(--bg-ink-300);
display: flex;
align-items: center;
justify-content: center;
}
.template-info {
.template-name {
color: var(--bg-vanilla-100);
font-family: Inter;
font-size: 14px;
font-style: normal;
font-weight: 500;
line-height: 20px; /* 142.857% */
letter-spacing: -0.07px;
}
.template-description {
color: var(--bg-vanilla-400);
font-family: Inter;
font-size: 12px;
font-style: normal;
font-weight: 400;
line-height: 18px; /* 150% */
}
}
}
}
.template-preview-image {
display: flex;
justify-content: center;
align-items: center;
margin: 24px;
height: calc(100% - 144px);
position: relative;
img {
width: 100%;
max-width: 100%;
padding: 24px;
border: 1px solid var(--bg-ink-50);
background: var(--bg-ink-300);
max-height: 100%;
object-fit: contain;
}
}
}
}
.new-dashboard-templates-modal-footer {
.create-dashboard-json-error {
margin-bottom: 8px;
display: flex;
}
.action-btns-container {
display: flex;
justify-content: space-between;
}
}
.ant-modal-footer {
margin-top: 0;
padding: 16px;
border-top: 1px solid var(--bg-slate-500);
}
}
.lightMode {
.new-dashboard-templates-modal {
.ant-modal-content {
border: 1px solid var(--bg-vanilla-300);
background: var(--bg-vanilla-100);
}
.new-dashboard-templates-content-header {
border-bottom: 1px solid var(--bg-vanilla-300);
}
.new-dashboard-templates-content {
.new-dashboard-templates-list {
border-right: 1px solid var(--bg-vanilla-300);
.templates-list {
.template-list-item {
.template-name {
color: var(--bg-ink-300);
}
&:hover {
background: rgba(171, 189, 255, 0.08);
}
&.active {
background: rgba(171, 189, 255, 0.08);
}
}
}
}
.new-dashboard-template-preview {
.template-preview-header {
.template-preview-title {
.template-preview-icon {
border: 1px solid var(--bg-vanilla-300);
background: var(--bg-vanilla-100);
}
.template-info {
.template-name {
color: var(--bg-ink-300);
}
.template-description {
color: var(--bg-vanilla-400);
}
}
}
.create-dashboard-btn {
.ant-btn {
box-shadow: none;
}
}
}
.template-preview-image {
img {
border: 1px solid var(--bg-vanilla-300);
background: var(--bg-vanilla-100);
}
}
}
}
.ant-modal-footer {
border-top: 1px solid var(--bg-vanilla-300);
}
}
}

View File

@ -0,0 +1,225 @@
/* eslint-disable sonarjs/no-duplicate-string */
/* eslint-disable jsx-a11y/no-static-element-interactions */
/* eslint-disable jsx-a11y/click-events-have-key-events */
/* eslint-disable @typescript-eslint/explicit-function-return-type */
import './DashboardTemplatesModal.styles.scss';
import { Button, Input, Modal, Typography } from 'antd';
import ApacheIcon from 'assets/CustomIcons/ApacheIcon';
import DockerIcon from 'assets/CustomIcons/DockerIcon';
import ElasticSearchIcon from 'assets/CustomIcons/ElasticSearchIcon';
import HerokuIcon from 'assets/CustomIcons/HerokuIcon';
import KubernetesIcon from 'assets/CustomIcons/KubernetesIcon';
import MongoDBIcon from 'assets/CustomIcons/MongoDBIcon';
import MySQLIcon from 'assets/CustomIcons/MySQLIcon';
import NginxIcon from 'assets/CustomIcons/NginxIcon';
import PostgreSQLIcon from 'assets/CustomIcons/PostgreSQLIcon';
import RedisIcon from 'assets/CustomIcons/RedisIcon';
import cx from 'classnames';
import { ConciergeBell, DraftingCompass, Drill, Plus, X } from 'lucide-react';
import { ChangeEvent, useState } from 'react';
import { DashboardTemplate } from 'types/api/dashboard/getAll';
import { filterTemplates } from '../utils';
const templatesList: DashboardTemplate[] = [
{
name: 'Blank dashboard',
icon: <Drill />,
id: 'blank',
description: 'Create a custom dashboard from scratch.',
previewImage: '/Images/blankDashboardTemplatePreview.svg',
},
{
name: 'Alert Manager',
icon: <ConciergeBell />,
id: 'alertManager',
description: 'Create a custom dashboard from scratch.',
previewImage: '/Images/blankDashboardTemplatePreview.svg',
},
{
name: 'Apache',
icon: <ApacheIcon />,
id: 'apache',
description: 'Create a custom dashboard from scratch.',
previewImage: '/Images/blankDashboardTemplatePreview.svg',
},
{
name: 'Docker',
icon: <DockerIcon />,
id: 'docker',
description: 'Create a custom dashboard from scratch.',
previewImage: '/Images/blankDashboardTemplatePreview.svg',
},
{
name: 'Elasticsearch',
icon: <ElasticSearchIcon />,
id: 'elasticSearch',
description: 'Create a custom dashboard from scratch.',
previewImage: '/Images/blankDashboardTemplatePreview.svg',
},
{
name: 'MongoDB',
icon: <MongoDBIcon />,
id: 'mongoDB',
description: 'Create a custom dashboard from scratch.',
previewImage: '/Images/blankDashboardTemplatePreview.svg',
},
{
name: 'Heroku',
icon: <HerokuIcon />,
id: 'heroku',
description: 'Create a custom dashboard from scratch.',
previewImage: '/Images/blankDashboardTemplatePreview.svg',
},
{
name: 'Nginx',
icon: <NginxIcon />,
id: 'nginx',
description: 'Create a custom dashboard from scratch.',
previewImage: '/Images/blankDashboardTemplatePreview.svg',
},
{
name: 'Kubernetes',
icon: <KubernetesIcon />,
id: 'kubernetes',
description: 'Create a custom dashboard from scratch.',
previewImage: '/Images/blankDashboardTemplatePreview.svg',
},
{
name: 'MySQL',
icon: <MySQLIcon />,
id: 'mySQL',
description: 'Create a custom dashboard from scratch.',
previewImage: '/Images/blankDashboardTemplatePreview.svg',
},
{
name: 'PostgreSQL',
icon: <PostgreSQLIcon />,
id: 'postgreSQL',
description: 'Create a custom dashboard from scratch.',
previewImage: '/Images/blankDashboardTemplatePreview.svg',
},
{
name: 'Redis',
icon: <RedisIcon />,
id: 'redis',
description: 'Create a custom dashboard from scratch.',
previewImage: '/Images/redisTemplatePreview.svg',
},
{
name: 'AWS',
icon: <DraftingCompass size={14} />,
id: 'aws',
description: 'Create a custom dashboard from scratch.',
previewImage: '/Images/blankDashboardTemplatePreview.svg',
},
];
interface DashboardTemplatesModalProps {
showNewDashboardTemplatesModal: boolean;
onCreateNewDashboard: () => void;
onCancel: () => void;
}
export default function DashboardTemplatesModal({
showNewDashboardTemplatesModal,
onCreateNewDashboard,
onCancel,
}: DashboardTemplatesModalProps): JSX.Element {
const [selectedDashboardTemplate, setSelectedDashboardTemplate] = useState(
templatesList[0],
);
const [dashboardTemplates, setDashboardTemplates] = useState(templatesList);
const handleDashboardTemplateSearch = (
event: ChangeEvent<HTMLInputElement>,
) => {
const searchText = event.target.value;
const filteredTemplates = filterTemplates(searchText, templatesList);
setDashboardTemplates(filteredTemplates);
};
return (
<Modal
wrapClassName="new-dashboard-templates-modal"
open={showNewDashboardTemplatesModal}
centered
closable={false}
footer={null}
destroyOnClose
width="60vw"
>
<div className="new-dashboard-templates-content-container">
<div className="new-dashboard-templates-content-header">
<Typography.Text>New Dashboard</Typography.Text>
<X size={14} className="periscope-btn ghost" onClick={onCancel} />
</div>
<div className="new-dashboard-templates-content">
<div className="new-dashboard-templates-list">
<Input
className="new-dashboard-templates-search"
placeholder="🔍 Search..."
onChange={handleDashboardTemplateSearch}
/>
<div className="templates-list">
{dashboardTemplates.map((template) => (
<div
className={cx(
'template-list-item',
selectedDashboardTemplate.id === template.id ? 'active' : '',
)}
key={template.name}
onClick={() => setSelectedDashboardTemplate(template)}
>
<div className="template-icon">{template.icon}</div>
<div className="template-name">{template.name}</div>
</div>
))}
</div>
</div>
<div className="new-dashboard-template-preview">
<div className="template-preview-header">
<div className="template-preview-title">
<div className="template-preview-icon">
{selectedDashboardTemplate.icon}
</div>
<div className="template-info">
<div className="template-name">{selectedDashboardTemplate.name}</div>
<div className="template-description">
{selectedDashboardTemplate.description}
</div>
</div>
</div>
<div className="create-dashboard-btn">
<Button
type="primary"
className="periscope-btn primary"
icon={<Plus size={14} />}
onClick={onCreateNewDashboard}
>
New dashboard
</Button>
</div>
</div>
<div className="template-preview-image">
<img
src={selectedDashboardTemplate.previewImage}
alt={`${selectedDashboardTemplate.name}-preview`}
/>
</div>
</div>
</div>
</div>
</Modal>
);
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,88 @@
.import-json-modal {
.ant-modal-content {
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);
padding: 0;
}
.margin {
background: linear-gradient(
139deg,
rgba(18, 19, 23, 0.8) 0%,
rgba(18, 19, 23, 0.9) 98.68%
);
backdrop-filter: blur(20px);
}
.view-lines {
background: linear-gradient(
139deg,
rgba(18, 19, 23, 0.8) 0%,
rgba(18, 19, 23, 0.9) 98.68%
);
backdrop-filter: blur(20px);
}
.import-json-content-header {
padding: 16px;
display: flex;
align-items: center;
justify-content: space-between;
border-bottom: 1px solid var(--bg-slate-500);
}
.import-json-modal-footer {
.create-dashboard-json-error {
margin-bottom: 8px;
display: flex;
}
.action-btns-container {
display: flex;
justify-content: space-between;
}
}
.ant-modal-footer {
margin-top: 0;
padding: 16px;
border-top: 1px solid var(--bg-slate-500);
}
}
.lightMode {
.import-json-modal {
.ant-modal-content {
border: 1px solid var(--bg-vanilla-300);
background: var(--bg-vanilla-100);
}
.margin {
background: var(--bg-vanilla-100);
}
.view-lines {
background: var(--bg-vanilla-100);
}
.import-json-content-header {
border-bottom: 1px solid var(--bg-vanilla-300);
}
.ant-modal-footer {
border-top: 1px solid var(--bg-vanilla-300);
.ant-btn {
box-shadow: none;
}
}
}
}

View File

@ -1,20 +1,23 @@
import './importJSON.styles.scss';
import { red } from '@ant-design/colors';
import { ExclamationCircleTwoTone } from '@ant-design/icons';
import MEditor, { Monaco } from '@monaco-editor/react';
import { Color } from '@signozhq/design-tokens';
import { Button, Modal, Space, Typography, Upload, UploadProps } from 'antd';
import createDashboard from 'api/dashboard/create';
import Editor from 'components/Editor';
import ROUTES from 'constants/routes';
import { useIsDarkMode } from 'hooks/useDarkMode';
import { MESSAGE } from 'hooks/useFeatureFlag';
import { useNotifications } from 'hooks/useNotifications';
import { getUpdatedLayout } from 'lib/dashboard/getUpdatedLayout';
import history from 'lib/history';
import { MonitorDot, MoveRight, X } from 'lucide-react';
import { useState } from 'react';
import { useTranslation } from 'react-i18next';
import { generatePath } from 'react-router-dom';
import { DashboardData } from 'types/api/dashboard/getAll';
import { EditorContainer, FooterContainer } from './styles';
function ImportJSON({
isImportJSONModalVisible,
uploadedGrafana,
@ -125,62 +128,114 @@ function ImportJSON({
onModalHandler();
};
const isDarkMode = useIsDarkMode();
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_300,
},
fontFamily: 'Space Mono',
fontSize: 20,
fontWeight: 'normal',
lineHeight: 18,
letterSpacing: -0.06,
});
}
return (
<Modal
wrapClassName="import-json-modal"
open={isImportJSONModalVisible}
centered
maskClosable
closable={false}
destroyOnClose
width="70vw"
onCancel={onCancelHandler}
title={
<>
<Typography.Title level={4}>{t('import_json')}</Typography.Title>
<Typography>{t('import_dashboard_by_pasting')}</Typography>
</>
}
width="60vw"
footer={
<FooterContainer>
<Button
disabled={editorValue.length === 0}
onClick={onClickLoadJsonHandler}
loading={dashboardCreating}
>
{t('load_json')}
</Button>
{isCreateDashboardError && getErrorNode(t('error_loading_json'))}
{isFeatureAlert && (
<Typography.Text type="danger">
{MESSAGE.CREATE_DASHBOARD}
</Typography.Text>
<div className="import-json-modal-footer">
{isCreateDashboardError && (
<div className="create-dashboard-json-error">
{getErrorNode(t('error_loading_json'))}
</div>
)}
</FooterContainer>
{isUploadJSONError && (
<div className="create-dashboard-json-error">
{getErrorNode(t('error_upload_json'))}
</div>
)}
<div className="action-btns-container">
<Upload
accept=".json"
showUploadList={false}
multiple={false}
onChange={onChangeHandler}
beforeUpload={(): boolean => false}
action="none"
data={jsonData}
>
<Button
type="default"
className="periscope-btn"
icon={<MonitorDot size={14} />}
>
{' '}
{t('upload_json_file')}
</Button>
</Upload>
<Button
// disabled={editorValue.length === 0}
onClick={onClickLoadJsonHandler}
loading={dashboardCreating}
className="periscope-btn primary"
type="primary"
>
{t('import_and_next')} &nbsp; <MoveRight size={14} />
</Button>
{isFeatureAlert && (
<Typography.Text type="danger">
{MESSAGE.CREATE_DASHBOARD}
</Typography.Text>
)}
</div>
</div>
}
>
<div>
<Space direction="horizontal">
<Upload
accept=".json"
showUploadList={false}
multiple={false}
onChange={onChangeHandler}
beforeUpload={(): boolean => false}
action="none"
data={jsonData}
>
<Button type="primary">{t('upload_json_file')}</Button>
</Upload>
{isUploadJSONError && <>{getErrorNode(t('error_upload_json'))}</>}
</Space>
<div className="import-json-content-container">
<div className="import-json-content-header">
<Typography.Text>{t('import_json')}</Typography.Text>
<EditorContainer>
<Typography.Paragraph>{t('paste_json_below')}</Typography.Paragraph>
<Editor
onChange={(newValue): void => setEditorValue(newValue)}
value={editorValue}
language="json"
/>
</EditorContainer>
<X size={14} className="periscope-btn ghost" onClick={onCancelHandler} />
</div>
<MEditor
language="json"
height="40vh"
onChange={(newValue): void => setEditorValue(newValue || '')}
value={editorValue}
options={{
scrollbar: {
alwaysConsumeMouseWheel: false,
},
minimap: {
enabled: false,
},
fontSize: 14,
fontFamily: 'Space Mono',
}}
theme={isDarkMode ? 'my-theme' : 'light'}
// eslint-disable-next-line react/jsx-no-bind
beforeMount={setEditorTheme}
/>
</div>
</Modal>
);

View File

@ -1,10 +0,0 @@
import { Space } from 'antd';
import styled from 'styled-components';
export const EditorContainer = styled.div`
margin-top: 2rem;
`;
export const FooterContainer = styled(Space)`
display: flex;
`;

View File

@ -3,3 +3,13 @@
align-items: center;
}
}
.delete-btn:hover {
background-color: rgba(255, 255, 255, 0.12);
}
.lightMode {
.delete-btn:hover {
background-color: rgba(0, 0, 0, 0.06) !important;
}
}

View File

@ -3,8 +3,10 @@ import './DeleteButton.styles.scss';
import { DeleteOutlined, ExclamationCircleOutlined } from '@ant-design/icons';
import { Modal, Tooltip, Typography } from 'antd';
import { REACT_QUERY_KEY } from 'constants/reactQueryKeys';
import ROUTES from 'constants/routes';
import { useDeleteDashboard } from 'hooks/dashboard/useDeleteDashboard';
import { useNotifications } from 'hooks/useNotifications';
import history from 'lib/history';
import { useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { useQueryClient } from 'react-query';
@ -21,13 +23,15 @@ interface DeleteButtonProps {
name: string;
id: string;
isLocked: boolean;
routeToListPage?: boolean;
}
function DeleteButton({
export function DeleteButton({
createdBy,
name,
id,
isLocked,
routeToListPage,
}: DeleteButtonProps): JSX.Element {
const [modal, contextHolder] = Modal.useModal();
const { role, user } = useSelector<AppState, AppReducer>((state) => state.app);
@ -42,7 +46,7 @@ function DeleteButton({
const deleteDashboardMutation = useDeleteDashboard(id);
const openConfirmationDialog = useCallback((): void => {
modal.confirm({
const { destroy } = modal.confirm({
title: (
<Typography.Title level={5}>
Are you sure you want to delete the
@ -51,24 +55,40 @@ function DeleteButton({
</Typography.Title>
),
icon: <ExclamationCircleOutlined style={{ color: '#e42b35' }} />,
onOk() {
deleteDashboardMutation.mutateAsync(undefined, {
onSuccess: () => {
notifications.success({
message: t('dashboard:delete_dashboard_success', {
name,
}),
});
queryClient.invalidateQueries([REACT_QUERY_KEY.GET_ALL_DASHBOARDS]);
},
});
},
okText: 'Delete',
okButtonProps: { danger: true },
okButtonProps: {
danger: true,
onClick: (e) => {
e.preventDefault();
e.stopPropagation();
deleteDashboardMutation.mutateAsync(undefined, {
onSuccess: () => {
notifications.success({
message: t('dashboard:delete_dashboard_success', {
name,
}),
});
queryClient.invalidateQueries([REACT_QUERY_KEY.GET_ALL_DASHBOARDS]);
if (routeToListPage) {
history.replace(ROUTES.ALL_DASHBOARD);
}
destroy();
},
});
},
},
centered: true,
className: 'delete-modal',
});
}, [modal, name, deleteDashboardMutation, notifications, t, queryClient]);
}, [
modal,
name,
deleteDashboardMutation,
notifications,
t,
queryClient,
routeToListPage,
]);
const getDeleteTooltipContent = (): string => {
if (isLocked) {
@ -87,14 +107,17 @@ function DeleteButton({
<Tooltip placement="left" title={getDeleteTooltipContent()}>
<TableLinkText
type="danger"
onClick={(): void => {
onClick={(e): void => {
e.preventDefault();
e.stopPropagation();
if (!isLocked) {
openConfirmationDialog();
}
}}
disabled={isLocked}
className="delete-btn"
disabled={isLocked || (role === USER_ROLES.VIEWER && !isAuthor)}
>
<DeleteOutlined /> Delete
<DeleteOutlined /> Delete dashboard
</TableLinkText>
</Tooltip>
@ -103,6 +126,10 @@ function DeleteButton({
);
}
DeleteButton.defaultProps = {
routeToListPage: false,
};
// This is to avoid the type collision
function Wrapper(props: Data): JSX.Element {
const {

View File

@ -1,4 +1,4 @@
import { Dashboard } from 'types/api/dashboard/getAll';
import { Dashboard, DashboardTemplate } from 'types/api/dashboard/getAll';
export const filterDashboard = (
searchValue: string,
@ -25,3 +25,31 @@ export const filterDashboard = (
});
});
};
export const filterTemplates = (
searchValue: string,
dashboardList: DashboardTemplate[],
): DashboardTemplate[] => {
const searchValueLowerCase = searchValue?.toLowerCase();
return dashboardList.filter((item: DashboardTemplate) => {
const { name } = item;
// Check if any property value contains the searchValue
return name.toLowerCase().includes(searchValueLowerCase);
});
};
export interface DashboardDynamicColumns {
createdAt: boolean;
createdBy: boolean;
updatedAt: boolean;
updatedBy: boolean;
}
export enum DynamicColumns {
CREATED_AT = 'createdAt',
CREATED_BY = 'createdBy',
UPDATED_AT = 'updatedAt',
UPDATED_BY = 'updatedBy',
}

View File

@ -0,0 +1,161 @@
.graph-selection {
.ant-modal-content {
width: 515px;
max-height: 646px;
overflow-y: auto;
flex-shrink: 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);
padding: 0px;
.ant-modal-header {
height: 52px;
padding: 16px;
background: var(--bg-ink-400);
border-bottom: 1px solid var(--bg-slate-500);
margin-bottom: 0px;
.ant-modal-title {
color: var(--bg-vanilla-100);
font-family: Inter;
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: 20px; /* 142.857% */
}
}
.ant-modal-body {
.panel-selection {
display: flex;
flex-flow: wrap;
padding: 16px;
gap: 16px;
.selected {
background: var(--bg-slate-400);
}
.ant-card {
display: flex;
height: 80px;
width: 232px;
padding: 19px 0px;
justify-content: center;
align-items: center;
border-radius: 4px;
cursor: pointer;
border: 1px solid var(--bg-slate-400);
.ant-card-body {
padding: 0px;
border-radius: 0px;
display: flex;
flex-direction: column;
gap: 6px;
align-items: center;
.ant-typography {
margin-top: 0px;
color: var(--bg-vanilla-400);
font-family: Inter;
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: 20px; /* 142.857% */
letter-spacing: -0.07px;
}
}
.ant-card-body::before {
content: none;
}
.ant-card-body::after {
content: none;
}
}
}
}
.ant-modal-footer {
border-radius: 0px 0px 4px 4px;
border-top: 1px solid var(--bg-slate-500);
background: var(--bg-ink-400);
padding: 12px 15px;
margin-top: 0px;
.ant-btn {
width: 100%;
display: flex;
align-items: center;
flex-direction: row-reverse;
justify-content: center;
color: var(--bg-vanilla-100);
/* button/ small */
font-family: Inter;
font-size: 12px;
font-style: normal;
font-weight: 500;
line-height: 24px; /* 200% */
border-radius: 2px;
background: var(--bg-robin-500);
padding: 4px 8px;
gap: 4px;
}
}
&::-webkit-scrollbar {
width: 0.1rem;
}
}
}
.lightMode {
.graph-selection {
.ant-modal-content {
border: 1px solid var(--bg-vanilla-300);
background: var(--bg-vanilla-100);
.ant-modal-header {
background: var(--bg-vanilla-100);
border-bottom: 1px solid var(--bg-vanilla-300);
.ant-modal-title {
color: var(--bg-ink-300);
}
}
.ant-modal-body {
.panel-selection {
.selected {
background: var(--bg-vanilla-200);
}
.ant-card {
border: 1px solid var(--bg-vanilla-300);
.ant-card-body {
.ant-typography {
color: var(--bg-ink-200);
}
}
}
}
}
.ant-modal-footer {
border-top: 1px solid var(--bg-vanilla-300);
background: var(--bg-vanilla-100);
.ant-btn {
color: var(--bg-vanilla-100);
background: var(--bg-robin-500);
}
}
}
}
}

View File

@ -1,3 +1,6 @@
import './ComponentSlider.styles.scss';
import { Card, Modal } from 'antd';
import { QueryParams } from 'constants/query';
import { PANEL_TYPES } from 'constants/queryBuilder';
import createQueryParams from 'lib/createQueryParams';
@ -8,10 +11,10 @@ import { v4 as uuid } from 'uuid';
import { PANEL_TYPES_INITIAL_QUERY } from './constants';
import menuItems from './menuItems';
import { Card, Container, Text } from './styles';
import { Text } from './styles';
function DashboardGraphSlider(): JSX.Element {
const { handleToggleDashboardSlider } = useDashboard();
const { handleToggleDashboardSlider, isDashboardSliderOpen } = useDashboard();
// eslint-disable-next-line sonarjs/cognitive-complexity
const onClickHandler = (name: PANEL_TYPES) => (): void => {
@ -56,15 +59,29 @@ function DashboardGraphSlider(): JSX.Element {
}
};
const handleCardClick = (panelType: PANEL_TYPES): void => {
onClickHandler(panelType)();
};
return (
<Container>
{menuItems.map(({ name, icon, display }) => (
<Card onClick={onClickHandler(name)} id={name} key={name}>
{icon}
<Text>{display}</Text>
</Card>
))}
</Container>
<Modal
open={isDashboardSliderOpen}
onCancel={(): void => {
handleToggleDashboardSlider(false);
}}
rootClassName="graph-selection"
footer={null}
title="New Panel"
>
<div className="panel-selection">
{menuItems.map(({ name, icon, display }) => (
<Card onClick={(): void => handleCardClick(name)} id={name} key={name}>
{icon}
<Text>{display}</Text>
</Card>
))}
</div>
</Modal>
);
}

View File

@ -12,32 +12,32 @@ import {
const Items: ItemsProps[] = [
{
name: PANEL_TYPES.TIME_SERIES,
icon: <LineChart size={32} color={Color.BG_ROBIN_400} />,
icon: <LineChart size={16} color={Color.BG_ROBIN_400} />,
display: 'Time Series',
},
{
name: PANEL_TYPES.VALUE,
icon: <SigmaSquare size={32} color={Color.BG_ROBIN_400} />,
icon: <SigmaSquare size={16} color={Color.BG_ROBIN_400} />,
display: 'Value',
},
{
name: PANEL_TYPES.TABLE,
icon: <Table size={32} color={Color.BG_ROBIN_400} />,
icon: <Table size={16} color={Color.BG_ROBIN_400} />,
display: 'Table',
},
{
name: PANEL_TYPES.LIST,
icon: <List size={32} color={Color.BG_ROBIN_400} />,
icon: <List size={16} color={Color.BG_ROBIN_400} />,
display: 'List',
},
{
name: PANEL_TYPES.BAR,
icon: <BarChart3 size={32} color={Color.BG_ROBIN_400} />,
icon: <BarChart3 size={16} color={Color.BG_ROBIN_400} />,
display: 'Bar',
},
{
name: PANEL_TYPES.PIE,
icon: <PieChart size={32} color={Color.BG_ROBIN_400} />,
icon: <PieChart size={16} color={Color.BG_ROBIN_400} />,
display: 'Pie',
},
];

View File

@ -1,13 +1,230 @@
.dashboard-description-container {
box-shadow: 0px 4px 16px 0px rgba(0, 0, 0, 0.25);
.settings-container-root {
.ant-drawer-wrapper-body {
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);
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);
color: var(--bg-vanilla-400, #c0c1c3);
.ant-drawer-header {
height: 48px;
border-bottom: 1px solid var(--bg-slate-500);
padding: 14px 14px 14px 11px;
.ant-drawer-header-title {
gap: 16px;
.ant-drawer-title {
color: var(--bg-vanilla-400);
font-family: Inter;
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: 20px; /* 142.857% */
letter-spacing: -0.07px;
padding-left: 16px;
border-left: 1px solid #161922;
}
.ant-drawer-close {
height: 16px;
width: 16px;
margin-inline-end: 0px !important;
}
}
}
.ant-drawer-body {
padding: 16px;
&::-webkit-scrollbar {
width: 0.1rem;
}
}
}
}
.dashboard-description-container {
box-shadow: none;
border: none;
background: unset;
box-shadow: none;
color: var(--bg-vanilla-400);
.ant-card-body {
padding: 12px 16px;
padding: 0px;
}
.dashboard-breadcrumbs {
height: 48px;
padding: 16px;
border-bottom: 1px solid var(--bg-slate-400);
display: flex;
gap: 6px;
align-items: center;
.dashboard-btn {
display: flex;
align-items: center;
color: var(--bg-vanilla-400);
font-family: Inter;
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: 20px; /* 142.857% */
letter-spacing: -0.07px;
padding: 0px;
height: 20px;
}
.dashboard-btn:hover {
background-color: unset;
}
.id-btn {
display: flex;
align-items: center;
padding: 0px 2px;
border-radius: 2px;
background: rgba(113, 144, 249, 0.1);
color: var(--bg-robin-400);
font-family: Inter;
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: 20px; /* 142.857% */
height: 20px;
.ant-btn-icon {
margin-inline-end: 4px;
}
}
.id-btn:hover {
background: rgba(113, 144, 249, 0.1);
color: var(--bg-robin-300);
}
}
.dashbord-details {
display: flex;
justify-content: space-between;
.left-section {
display: flex;
padding: 10px 0px 0px 16px;
align-items: center;
gap: 8px;
.dashboard-title {
color: #fff;
font-family: Inter;
font-size: 16px;
font-style: normal;
font-weight: 500;
line-height: 24px; /* 150% */
letter-spacing: -0.08px;
flex-shrink: 0;
}
}
.right-section {
display: flex;
align-items: center;
padding: 10px 16px 0px 0px;
gap: 14px;
.icons {
display: flex;
align-items: center;
width: 32px;
height: 34px;
padding: 6px;
justify-content: center;
border-radius: 2px;
border: 1px solid var(--bg-slate-400);
background: var(--bg-ink-300);
color: var(--bg-vanilla-400);
font-family: Inter;
font-size: 12px;
font-style: normal;
font-weight: 500;
line-height: 10px; /* 83.333% */
letter-spacing: 0.12px;
}
.icons:hover {
background-color: unset;
}
.configure-button {
display: flex;
align-items: center;
width: 93px;
height: 34px;
padding: 6px;
justify-content: center;
border-radius: 2px;
border: 1px solid var(--bg-slate-400);
background: var(--bg-ink-300);
color: var(--bg-vanilla-400);
font-family: Inter;
font-size: 12px;
font-style: normal;
font-weight: 500;
line-height: 10px; /* 83.333% */
letter-spacing: 0.12px;
}
.add-panel-btn {
display: flex;
width: 119px;
height: 34px;
padding: 5.937px 11.875px;
justify-content: center;
align-items: center;
color: var(--bg-vanilla-100);
font-family: Inter;
font-size: 11.875px;
font-style: normal;
font-weight: 500;
line-height: 17.812px; /* 150% */
}
}
}
.dashboard-tags {
display: flex;
gap: 6px;
padding: 16px 16px 0px 16px;
.tag {
display: flex;
padding: 4px 8px;
justify-content: center;
align-items: center;
border-radius: 20px;
border: 1px solid rgba(173, 127, 88, 0.2);
background: rgba(173, 127, 88, 0.1);
color: var(--bg-sienna-400);
text-align: center;
font-family: Inter;
font-size: 14px;
font-style: normal;
font-weight: 500;
line-height: 20px; /* 142.857% */
letter-spacing: -0.07px;
margin-inline-end: 0px;
}
}
.dashboard-description-section {
max-width: 957px;
color: var(--bg-vanilla-400);
font-family: Inter;
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: 22px; /* 157.143% */
letter-spacing: -0.07px;
padding: 20px 16px 0px 16px;
}
.dashboard-variables {
padding: 16px 16px 18px 16px;
}
}
@ -19,21 +236,467 @@
overflow: hidden;
}
.dashboard-actions {
display: flex;
flex-direction: column;
gap: 8px;
}
.dashboard-settings {
width: 191px;
height: 302px;
flex-shrink: 0;
.lightMode {
.dashboard-description-container {
box-shadow: none;
border: 1px solid var(--bg-vanilla-300);
background-color: rgb(250, 250, 250);
color: var(--bg-ink-300);
.ant-popover-inner {
padding: 0px;
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%
) !important;
box-shadow: 4px 10px 16px 2px rgba(0, 0, 0, 0.2);
backdrop-filter: blur(20px);
}
.ant-card-body {
padding: 12px 16px;
.menu-content {
display: flex;
flex-direction: column;
.section-1 {
display: flex;
flex-direction: column;
align-items: start;
border-bottom: 1px solid #1d212d;
.ant-btn {
display: flex;
width: 100%;
height: 20px;
padding: 16px 18px 18px 14px;
align-items: center;
gap: 6px;
color: var(--bg-vanilla-400);
font-family: Inter;
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: normal;
letter-spacing: 0.14px;
.ant-btn-icon {
margin-inline-end: 0px;
}
}
}
.section-2 {
display: flex;
flex-direction: column;
align-items: start;
border-bottom: 1px solid #1d212d;
.ant-btn {
display: flex;
width: 100%;
height: 20px;
padding: 16px 18px 18px 14px;
align-items: center;
gap: 6px;
color: var(--bg-vanilla-400);
font-family: Inter;
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: normal;
letter-spacing: 0.14px;
.ant-btn-icon {
margin-inline-end: 0px;
}
}
}
.delete-dashboard {
display: flex;
flex-direction: column;
align-items: start;
.ant-typography {
display: flex;
width: 100%;
height: 20px;
padding: 16px 18px 18px 14px;
align-items: center;
gap: 6px;
color: var(--bg-cherry-400) !important;
font-family: Inter;
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: normal;
letter-spacing: 0.14px;
}
}
}
}
.rename-dashboard {
.ant-modal-content {
width: 384px;
flex-shrink: 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);
padding: 0px;
.ant-modal-header {
height: 52px;
padding: 16px;
background: var(--bg-ink-400);
border-bottom: 1px solid var(--bg-slate-500);
margin-bottom: 0px;
.ant-modal-title {
color: var(--bg-vanilla-100);
font-family: Inter;
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: 20px; /* 142.857% */
width: 349px;
height: 20px;
}
}
.ant-modal-body {
padding: 16px;
.dashboard-content {
display: flex;
flex-direction: column;
gap: 8px;
.name-text {
color: var(--bg-vanilla-100);
font-family: Inter;
font-size: 14px;
font-style: normal;
font-weight: 500;
line-height: 20px; /* 142.857% */
}
.dashboard-name-input {
display: flex;
padding: 6px 6px 6px 8px;
align-items: center;
gap: 4px;
align-self: stretch;
border-radius: 0px 2px 2px 0px;
border: 1px solid var(--bg-slate-400);
background: var(--bg-ink-300);
}
}
}
.ant-modal-footer {
padding: 16px;
margin-top: 0px;
.dashboard-rename {
display: flex;
flex-direction: row-reverse;
gap: 12px;
.cancel-btn {
display: flex;
padding: 4px 8px;
justify-content: center;
align-items: center;
gap: 4px;
border-radius: 2px;
background: var(--bg-slate-500);
.ant-btn-icon {
margin-inline-end: 0px;
}
}
.rename-btn {
display: flex;
align-items: center;
display: flex;
width: 169px;
padding: 4px 8px;
justify-content: center;
align-items: center;
gap: 4px;
border-radius: 2px;
background: var(--bg-robin-500);
.ant-btn-icon {
margin-inline-end: 0px;
}
}
}
}
}
}
.section-naming {
.ant-modal-content {
width: 384px;
flex-shrink: 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);
padding: 0px;
.ant-modal-header {
height: 52px;
padding: 16px;
background: var(--bg-ink-400);
border-bottom: 1px solid var(--bg-slate-500);
margin-bottom: 0px;
.ant-modal-title {
color: var(--bg-vanilla-100);
font-family: Inter;
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: 20px; /* 142.857% */
width: 349px;
height: 20px;
}
}
.ant-modal-body {
padding: 16px;
.section-naming-content {
display: flex;
flex-direction: column;
gap: 8px;
.name-text {
color: var(--bg-vanilla-100);
font-family: Inter;
font-size: 14px;
font-style: normal;
font-weight: 500;
line-height: 20px; /* 142.857% */
}
.section-name-input {
display: flex;
width: 320px;
padding: 6px 6px 6px 8px;
align-items: center;
gap: 4px;
align-self: stretch;
border-radius: 0px 2px 2px 0px;
border: 1px solid var(--bg-slate-400);
background: var(--bg-ink-300);
}
}
}
.ant-modal-footer {
padding: 16px;
margin-top: 0px;
.dashboard-rename {
display: flex;
flex-direction: row-reverse;
gap: 12px;
.cancel-btn {
display: flex;
padding: 4px 8px;
justify-content: center;
align-items: center;
gap: 4px;
border-radius: 2px;
background: var(--bg-slate-500);
.ant-btn-icon {
margin-inline-end: 0px;
}
}
.rename-btn {
display: flex;
align-items: center;
display: flex;
width: 140px;
padding: 4px 8px;
justify-content: center;
align-items: center;
gap: 4px;
border-radius: 2px;
background: var(--bg-robin-500);
.ant-btn-icon {
margin-inline-end: 0px;
}
}
}
}
}
}
.lightMode {
.settings-container-root {
.ant-drawer-wrapper-body {
border-left: 1px solid var(--bg-vanilla-300);
background: var(--bg-vanilla-100);
.ant-drawer-header {
border-bottom: 1px solid var(--bg-vanilla-300);
.ant-drawer-header-title {
.ant-drawer-title {
color: var(--bg-ink-400);
border-left: 1px solid var(--bg-vanilla-300);
}
.ant-drawer-close {
color: var(--bg-ink-300);
}
}
}
}
}
.dashboard-description-container {
color: var(--bg-ink-400);
.dashboard-breadcrumbs {
border-bottom: 1px solid var(--bg-vanilla-300);
.dashboard-btn {
color: var(--bg-ink-400);
}
}
.dashbord-details {
.left-section {
.dashboard-title {
color: var(--bg-ink-300);
}
}
.right-section {
.icons {
border: 1px solid var(--bg-vanilla-300);
background: var(--bg-vanilla-100);
color: var(--bg-ink-400);
}
.configure-button {
border: 1px solid var(--bg-vanilla-300);
background: var(--bg-vanilla-100);
color: var(--bg-ink-400);
}
.add-panel-btn {
color: var(--bg-vanilla-100);
}
}
}
.dashboard-description-section {
color: var(--bg-ink-400);
}
}
.dashboard-settings {
.ant-popover-inner {
border: 1px solid var(--bg-vanilla-300);
background: var(--bg-vanilla-100) !important;
}
.menu-content {
display: flex;
flex-direction: column;
.section-1 {
border-bottom: 1px solid var(--bg-vanilla-300);
.ant-btn {
color: var(--bg-ink-300);
}
}
.section-2 {
border-bottom: 1px solid var(--bg-vanilla-300);
.ant-btn {
color: var(--bg-ink-300);
}
}
}
}
.rename-dashboard {
.ant-modal-content {
border: 1px solid var(--bg-vanilla-300);
background: var(--bg-vanilla-100);
.ant-modal-header {
background: var(--bg-vanilla-100);
border-bottom: 1px solid var(--bg-vanilla-300);
.ant-modal-title {
color: var(--bg-ink-300);
}
}
.ant-modal-body {
.dashboard-content {
.name-text {
color: var(--bg-ink-300);
}
.dashboard-name-input {
border: 1px solid var(--bg-vanilla-300);
background: var(--bg-vanilla-100);
}
}
}
.ant-modal-footer {
.dashboard-rename {
.cancel-btn {
background: var(--bg-vanilla-300);
}
}
}
}
}
.section-naming {
.ant-modal-content {
border: 1px solid var(--bg-vanilla-300);
background: var(--bg-vanilla-100);
.ant-modal-header {
background: var(--bg-vanilla-100);
border-bottom: 1px solid var(--bg-vanilla-300);
.ant-modal-title {
color: var(--bg-ink-300);
}
}
.ant-modal-body {
.section-naming-content {
.name-text {
color: var(--bg-ink-300);
}
.section-name-input {
border: 1px solid var(--bg-vanilla-300);
background: var(--bg-vanilla-100);
}
}
}
.ant-modal-footer {
.dashboard-rename {
.cancel-btn {
background: var(--bg-vanilla-300);
}
}
}
}
}
}

View File

@ -1,6 +1,8 @@
import { Button, Tooltip } from 'antd';
import { Cog } from 'lucide-react';
import { useState } from 'react';
import './Description.styles.scss';
import { Button } from 'antd';
import ConfigureIcon from 'assets/Integrations/ConfigureIcon';
import { useRef, useState } from 'react';
import DashboardSettingsContent from '../DashboardSettings';
import { DrawerContainer } from './styles';
@ -8,34 +10,38 @@ import { DrawerContainer } from './styles';
function SettingsDrawer({ drawerTitle }: { drawerTitle: string }): JSX.Element {
const [visible, setVisible] = useState<boolean>(false);
const variableViewModeRef = useRef<() => void>();
const showDrawer = (): void => {
setVisible(true);
};
const onClose = (): void => {
setVisible(false);
variableViewModeRef?.current?.();
};
return (
<>
<Tooltip title="Configure" placement="left">
<Button
className="periscope-btn"
onClick={showDrawer}
style={{ width: '100%' }}
data-testid="show-drawer"
icon={<Cog size={16} />}
/>
</Tooltip>
<Button
type="text"
className="configure-button"
icon={<ConfigureIcon />}
data-testid="show-drawer"
onClick={showDrawer}
>
Configure
</Button>
<DrawerContainer
title={drawerTitle}
placement="right"
width="60%"
width="50%"
onClose={onClose}
open={visible}
rootClassName="settings-container-root"
>
<DashboardSettingsContent />
<DashboardSettingsContent variableViewModeRef={variableViewModeRef} />
</DrawerContainer>
</>
);

View File

@ -1,25 +1,66 @@
import './Description.styles.scss';
import { LockFilled, UnlockFilled } from '@ant-design/icons';
import { Button, Card, Col, Row, Tag, Tooltip, Typography } from 'antd';
import { PlusOutlined } from '@ant-design/icons';
import { Button, Card, Input, Modal, Popover, Tag, Typography } from 'antd';
import { SOMETHING_WENT_WRONG } from 'constants/api';
import { PANEL_GROUP_TYPES, PANEL_TYPES } from 'constants/queryBuilder';
import ROUTES from 'constants/routes';
import { DeleteButton } from 'container/ListOfDashboard/TableComponents/DeleteButton';
import DateTimeSelectionV2 from 'container/TopNav/DateTimeSelectionV2';
import { useUpdateDashboard } from 'hooks/dashboard/useUpdateDashboard';
import useComponentPermission from 'hooks/useComponentPermission';
import { Share2 } from 'lucide-react';
import { useNotifications } from 'hooks/useNotifications';
import history from 'lib/history';
import { isEmpty } from 'lodash-es';
import {
Check,
ClipboardCopy,
Ellipsis,
FileJson,
FolderKanban,
Fullscreen,
LayoutGrid,
LockKeyhole,
PenLine,
X,
} from 'lucide-react';
import { useDashboard } from 'providers/Dashboard/Dashboard';
import { useState } from 'react';
import { sortLayout } from 'providers/Dashboard/util';
import { useCallback, useEffect, useState } from 'react';
import { FullScreenHandle } from 'react-full-screen';
import { Layout } from 'react-grid-layout';
import { useTranslation } from 'react-i18next';
import { useSelector } from 'react-redux';
import { useCopyToClipboard } from 'react-use';
import { AppState } from 'store/reducers';
import { DashboardData } from 'types/api/dashboard/getAll';
import { Dashboard, DashboardData } from 'types/api/dashboard/getAll';
import AppReducer from 'types/reducer/app';
import { USER_ROLES } from 'types/roles';
import { ROLES, USER_ROLES } from 'types/roles';
import { ComponentTypes } from 'utils/permission';
import { v4 as uuid } from 'uuid';
import DashboardGraphSlider from '../ComponentsSlider';
import { Base64Icons } from '../DashboardSettings/General/utils';
import DashboardVariableSelection from '../DashboardVariablesSelection';
import SettingsDrawer from './SettingsDrawer';
import ShareModal from './ShareModal';
import { DEFAULT_ROW_NAME, downloadObjectAsJson } from './utils';
function DashboardDescription(): JSX.Element {
interface DashboardDescriptionProps {
handle: FullScreenHandle;
}
// eslint-disable-next-line sonarjs/cognitive-complexity
function DashboardDescription(props: DashboardDescriptionProps): JSX.Element {
const { handle } = props;
const {
selectedDashboard,
panelMap,
setPanelMap,
layouts,
setLayouts,
isDashboardLocked,
setSelectedDashboard,
handleToggleDashboardSlider,
handleDashboardLockToggle,
} = useDashboard();
@ -30,12 +71,30 @@ function DashboardDescription(): JSX.Element {
}
: ({} as DashboardData);
const { title = '', tags, description } = selectedData || {};
const { title = '', description, tags, image = Base64Icons[0] } =
selectedData || {};
const [openDashboardJSON, setOpenDashboardJSON] = useState<boolean>(false);
const [updatedTitle, setUpdatedTitle] = useState<string>(title);
const { user, role } = useSelector<AppState, AppReducer>((state) => state.app);
const [sectionName, setSectionName] = useState<string>(DEFAULT_ROW_NAME);
const updateDashboardMutation = useUpdateDashboard();
const { featureResponse, user, role } = useSelector<AppState, AppReducer>(
(state) => state.app,
);
const [editDashboard] = useComponentPermission(['edit_dashboard'], role);
const [isDashboardSettingsOpen, setIsDashbordSettingsOpen] = useState<boolean>(
false,
);
const [isRenameDashboardOpen, setIsRenameDashboardOpen] = useState<boolean>(
false,
);
const [isPanelNameModalOpen, setIsPanelNameModalOpen] = useState<boolean>(
false,
);
let isAuthor = false;
@ -43,91 +102,399 @@ function DashboardDescription(): JSX.Element {
isAuthor = selectedDashboard?.created_by === user?.email;
}
const onToggleHandler = (): void => {
setOpenDashboardJSON((state) => !state);
};
let permissions: ComponentTypes[] = ['add_panel'];
if (isDashboardLocked) {
permissions = ['add_panel_locked_dashboard'];
}
const { notifications } = useNotifications();
const userRole: ROLES | null =
selectedDashboard?.created_by === user?.email
? (USER_ROLES.AUTHOR as ROLES)
: role;
const [addPanelPermission] = useComponentPermission(permissions, userRole);
const onEmptyWidgetHandler = useCallback(() => {
handleToggleDashboardSlider(true);
}, [handleToggleDashboardSlider]);
const handleLockDashboardToggle = (): void => {
setIsDashbordSettingsOpen(false);
handleDashboardLockToggle(!isDashboardLocked);
};
const onNameChangeHandler = (): void => {
if (!selectedDashboard) {
return;
}
const updatedDashboard = {
...selectedDashboard,
data: {
...selectedDashboard.data,
title: updatedTitle,
},
};
updateDashboardMutation.mutate(updatedDashboard, {
onSuccess: (updatedDashboard) => {
notifications.success({
message: 'Dashboard renamed successfully',
});
setIsRenameDashboardOpen(false);
if (updatedDashboard.payload)
setSelectedDashboard(updatedDashboard.payload);
},
onError: () => {
notifications.error({
message: SOMETHING_WENT_WRONG,
});
setIsRenameDashboardOpen(true);
},
});
};
const [state, setCopy] = useCopyToClipboard();
const { t } = useTranslation(['dashboard', 'common']);
useEffect(() => {
if (state.error) {
notifications.error({
message: t('something_went_wrong', {
ns: 'common',
}),
});
}
if (state.value) {
notifications.success({
message: t('success', {
ns: 'common',
}),
});
}
}, [state.error, state.value, t, notifications]);
function handleAddRow(): void {
if (!selectedDashboard) return;
const id = uuid();
const newRowWidgetMap: { widgets: Layout[]; collapsed: boolean } = {
widgets: [],
collapsed: false,
};
const currentRowIdx = 0;
for (let j = currentRowIdx; j < layouts.length; j++) {
if (!panelMap[layouts[j].i]) {
newRowWidgetMap.widgets.push(layouts[j]);
} else {
break;
}
}
const updatedDashboard: Dashboard = {
...selectedDashboard,
data: {
...selectedDashboard.data,
layout: [
{
i: id,
w: 12,
minW: 12,
minH: 1,
maxH: 1,
x: 0,
h: 1,
y: 0,
},
...layouts.filter((e) => e.i !== PANEL_TYPES.EMPTY_WIDGET),
],
panelMap: { ...panelMap, [id]: newRowWidgetMap },
widgets: [
...(selectedDashboard.data.widgets || []),
{
id,
title: sectionName,
description: '',
panelTypes: PANEL_GROUP_TYPES.ROW,
},
],
},
uuid: selectedDashboard.uuid,
};
updateDashboardMutation.mutate(updatedDashboard, {
// eslint-disable-next-line sonarjs/no-identical-functions
onSuccess: (updatedDashboard) => {
if (updatedDashboard.payload) {
if (updatedDashboard.payload.data.layout)
setLayouts(sortLayout(updatedDashboard.payload.data.layout));
setSelectedDashboard(updatedDashboard.payload);
setPanelMap(updatedDashboard.payload?.data?.panelMap || {});
}
featureResponse.refetch();
setIsPanelNameModalOpen(false);
setSectionName(DEFAULT_ROW_NAME);
},
// eslint-disable-next-line sonarjs/no-identical-functions
onError: () => {
notifications.error({
message: SOMETHING_WENT_WRONG,
});
},
});
}
return (
<Card className="dashboard-description-container">
<Row gutter={16}>
<Col flex={1} span={9}>
<Typography.Title
level={4}
style={{ padding: 0, margin: 0 }}
data-testid="dashboard-landing-name"
>
{isDashboardLocked && (
<Tooltip title="Dashboard Locked" placement="top">
<LockFilled /> &nbsp;
</Tooltip>
)}
{title}
</Typography.Title>
{description && (
<Typography
className="dashboard-description"
data-testid="dashboard-landing-desc"
>
{description}
</Typography>
)}
{tags && (
<div style={{ margin: '0.5rem 0' }}>
{tags?.map((tag) => (
<Tag key={tag}>{tag}</Tag>
))}
</div>
)}
</Col>
<Col span={14}>
<Row justify="end">
<DashboardVariableSelection />
</Row>
</Col>
<Col span={1} style={{ textAlign: 'right' }}>
{selectedData && (
<ShareModal
isJSONModalVisible={openDashboardJSON}
onToggleHandler={onToggleHandler}
selectedData={selectedData}
<section className="dashboard-breadcrumbs">
<Button
type="text"
icon={<LayoutGrid size={14} />}
className="dashboard-btn"
onClick={(): void => history.push(ROUTES.ALL_DASHBOARD)}
>
Dashboard /
</Button>
<Button
type="text"
className="id-btn"
icon={
// eslint-disable-next-line jsx-a11y/img-redundant-alt
<img
src={image}
alt="dashboard-image"
style={{ height: '14px', width: '14px' }}
/>
}
>
{title}
</Button>
</section>
<section className="dashbord-details">
<div className="left-section">
<img
src={image}
alt="dashboard-img"
style={{ width: '16px', height: '16px' }}
/>
<Typography.Text className="dashboard-title">{title}</Typography.Text>
{isDashboardLocked && <LockKeyhole size={14} />}
</div>
<div className="right-section">
<DateTimeSelectionV2 showAutoRefresh hideShareModal />
<Popover
open={isDashboardSettingsOpen}
arrow={false}
onOpenChange={(visible): void => setIsDashbordSettingsOpen(visible)}
rootClassName="dashboard-settings"
content={
<div className="menu-content">
<section className="section-1">
{(isAuthor || role === USER_ROLES.ADMIN) && (
<Button
type="text"
icon={<LockKeyhole size={14} />}
onClick={handleLockDashboardToggle}
>
{isDashboardLocked ? 'Unlock Dashboard' : 'Lock Dashboard'}
</Button>
)}
{!isDashboardLocked && editDashboard && (
<Button
type="text"
icon={<PenLine size={14} />}
onClick={(): void => {
setIsRenameDashboardOpen(true);
setIsDashbordSettingsOpen(false);
}}
>
Rename
</Button>
)}
<Button
type="text"
icon={<Fullscreen size={14} />}
onClick={handle.enter}
>
Full screen
</Button>
</section>
<section className="section-2">
{!isDashboardLocked && addPanelPermission && (
<Button
type="text"
icon={<FolderKanban size={14} />}
onClick={(): void => {
setIsPanelNameModalOpen(true);
setIsDashbordSettingsOpen(false);
}}
>
New section
</Button>
)}
<Button
type="text"
icon={<FileJson size={14} />}
onClick={(): void => {
downloadObjectAsJson(selectedData, selectedData.title);
setIsDashbordSettingsOpen(false);
}}
>
Export JSON
</Button>
<Button
type="text"
icon={<ClipboardCopy size={14} />}
onClick={(): void => {
setCopy(JSON.stringify(selectedData, null, 2));
setIsDashbordSettingsOpen(false);
}}
>
Copy as JSON
</Button>
</section>
<section className="delete-dashboard">
<DeleteButton
createdBy={selectedDashboard?.created_by || ''}
name={selectedDashboard?.data.title || ''}
id={String(selectedDashboard?.uuid) || ''}
isLocked={isDashboardLocked}
routeToListPage
/>
</section>
</div>
}
trigger="click"
placement="bottomRight"
>
<Button icon={<Ellipsis size={14} />} type="text" className="icons" />
</Popover>
{!isDashboardLocked && editDashboard && (
<SettingsDrawer drawerTitle="Dashboard Configuration" />
)}
{!isDashboardLocked && addPanelPermission && (
<Button
className="add-panel-btn"
onClick={onEmptyWidgetHandler}
icon={<PlusOutlined />}
type="primary"
data-testid="add-panel"
>
New Panel
</Button>
)}
</div>
</section>
{(tags?.length || 0) > 0 && (
<div className="dashboard-tags">
{tags?.map((tag) => (
<Tag key={tag} className="tag">
{tag}
</Tag>
))}
</div>
)}
{!isEmpty(description) && (
<section className="dashboard-description-section">{description}</section>
)}
<section className="dashboard-variables">
<DashboardVariableSelection />
</section>
<DashboardGraphSlider />
<div className="dashboard-actions">
{!isDashboardLocked && editDashboard && (
<SettingsDrawer drawerTitle={title} />
)}
<Tooltip title="Share" placement="left">
<Button
className="periscope-btn"
style={{ width: '100%' }}
onClick={onToggleHandler}
icon={<Share2 size={16} />}
/>
</Tooltip>
{(isAuthor || role === USER_ROLES.ADMIN) && (
<Tooltip
placement="left"
title={isDashboardLocked ? 'Unlock Dashboard' : 'Lock Dashboard'}
>
<Button
style={{ width: '100%' }}
className="periscope-btn"
onClick={handleLockDashboardToggle}
icon={isDashboardLocked ? <LockFilled /> : <UnlockFilled />}
/>
</Tooltip>
)}
<Modal
open={isRenameDashboardOpen}
title="Rename Dashboard"
onOk={(): void => {
// handle update dashboard here
}}
onCancel={(): void => {
setIsRenameDashboardOpen(false);
}}
rootClassName="rename-dashboard"
footer={
<div className="dashboard-rename">
<Button
type="primary"
icon={<Check size={14} />}
className="rename-btn"
onClick={onNameChangeHandler}
disabled={updateDashboardMutation.isLoading}
>
Rename Dashboard
</Button>
<Button
type="text"
icon={<X size={14} />}
className="cancel-btn"
onClick={(): void => setIsRenameDashboardOpen(false)}
>
Cancel
</Button>
</div>
</Col>
</Row>
}
>
<div className="dashboard-content">
<Typography.Text className="name-text">Enter a new name</Typography.Text>
<Input
data-testid="dashboard-name"
className="dashboard-name-input"
value={updatedTitle}
onChange={(e): void => setUpdatedTitle(e.target.value)}
/>
</div>
</Modal>
<Modal
open={isPanelNameModalOpen}
title="New Section"
rootClassName="section-naming"
onOk={(): void => handleAddRow()}
onCancel={(): void => {
setIsPanelNameModalOpen(false);
setSectionName(DEFAULT_ROW_NAME);
}}
footer={
<div className="dashboard-rename">
<Button
type="primary"
icon={<Check size={14} />}
className="rename-btn"
onClick={(): void => handleAddRow()}
disabled={updateDashboardMutation.isLoading}
>
Create Section
</Button>
<Button
type="text"
icon={<X size={14} />}
className="cancel-btn"
onClick={(): void => {
setIsPanelNameModalOpen(false);
setSectionName(DEFAULT_ROW_NAME);
}}
>
Cancel
</Button>
</div>
}
>
<div className="section-naming-content">
<Typography.Text className="name-text">Enter Section name</Typography.Text>
<Input
data-testid="section-name"
className="section-name-input"
value={sectionName}
onChange={(e): void => setSectionName(e.target.value)}
/>
</div>
</Modal>
</Card>
);
}

View File

@ -12,3 +12,5 @@ export function downloadObjectAsJson(
downloadAnchorNode.click();
downloadAnchorNode.remove();
}
export const DEFAULT_ROW_NAME = 'Sample Row';

View File

@ -3,3 +3,134 @@
color: rgb(207, 19, 34);
font-style: italic;
}
.dashboard-variable-settings-table {
.variable-name-drag {
display: flex;
align-items: center;
gap: 10px;
.ant-table-cell {
padding: 0px !important;
border: none !important;
color: var(--bg-robin-400);
font-family: 'Space Mono';
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: 18px; /* 128.571% */
letter-spacing: -0.07px;
}
}
.variable-description-actions {
display: flex;
align-items: center;
gap: 10px;
flex: 1 0 0;
.variable-description {
color: var(--bg-sienna-400);
font-family: 'Space Mono';
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: 18px; /* 128.571% */
letter-spacing: -0.07px;
}
.actions-btns {
position: absolute;
right: 10px;
display: none;
&:hover {
display: inline-flex;
}
.edit-variable-button {
width: 26px;
height: 22px;
border-radius: 2px;
display: flex;
padding: 4px 6px;
align-items: center;
gap: 3px;
background: var(--bg-slate-400);
}
.delete-variable-button {
width: 26px;
height: 22px;
border-radius: 2px;
background: rgba(229, 72, 77, 0.1);
display: flex;
padding: 4px 6px;
align-items: center;
gap: 3px;
color: red;
}
}
}
.ant-table-cell-row-hover {
.actions-btns {
display: inline-flex;
}
}
.ant-table-thead {
.ant-table-cell {
padding: 8px 12px;
border: 1px solid var(--bg-slate-500);
background: unset;
}
.ant-table-cell::before {
display: none;
}
}
.ant-table-tbody {
.ant-table-cell {
padding: 14px;
border: 1px solid var(--bg-slate-500);
}
}
.ant-table-row {
.ant-table-cell:nth-child(even) {
background: rgba(22, 25, 34, 0.4);
}
}
}
.lightMode {
.dashboard-variable-settings-table {
.variable-description-actions {
.actions-btns {
.edit-variable-button {
background: var(--bg-vanilla-300);
}
}
}
.ant-table-thead {
.ant-table-cell {
border: 1px solid var(--bg-vanilla-300);
}
}
.ant-table-tbody {
.ant-table-cell {
border: 1px solid var(--bg-vanilla-300);
}
}
.ant-table-row {
.ant-table-cell:nth-child(even) {
background: var(--bg-vanilla-200);
}
}
}
}

View File

@ -0,0 +1,69 @@
.settings-tabs {
.ant-tabs-nav-list {
width: 228px;
height: 32px;
flex-shrink: 0;
border-radius: 2px;
border: 1px solid var(--bg-slate-400);
background: var(--bg-ink-400);
box-shadow: 0px 0px 8px 0px rgba(0, 0, 0, 0.1);
transition: opacity 0.1s !important;
.ant-tabs-tab + .ant-tabs-tab {
margin: 0px;
}
.overview-btn {
width: 114px;
display: flex;
align-items: center;
justify-content: center;
}
.variables-btn {
width: 114px;
display: flex;
align-items: center;
justify-content: center;
}
.ant-tabs-ink-bar {
display: none;
}
.ant-tabs-tab-active {
.overview-btn {
border-radius: 2px 0px 0px 2px;
background: var(--bg-slate-400);
}
.variables-btn {
border-radius: 2px 0px 0px 2px;
background: var(--bg-slate-400);
}
}
}
.ant-tabs-nav::before {
border-bottom: none;
}
}
.lightMode {
.settings-tabs {
.ant-tabs-nav-list {
border: 1px solid var(--bg-vanilla-300);
background: var(--bg-vanilla-100);
.ant-tabs-tab-active {
.overview-btn {
background: var(--bg-vanilla-300);
}
.variables-btn {
background: var(--bg-vanilla-300);
}
}
}
}
}

View File

@ -0,0 +1,31 @@
.tags-input {
display: flex;
border: none;
padding: 0px;
width: 183px;
height: 24px;
padding: 3px 1px;
flex-shrink: 0;
}
.tag-container {
color: var(--bg-sienna-400);
font-family: 'Space Mono';
font-size: 13px;
font-style: normal;
font-weight: 500;
line-height: 20px; /* 153.846% */
letter-spacing: 0.52px;
height: 24px;
flex-shrink: 0;
border-radius: 50px;
border: 1px solid rgba(173, 127, 88, 0.2);
background: rgba(173, 127, 88, 0.1);
padding: 2px 8px;
}
.edit-input {
.ant-form-item {
margin-bottom: 0px;
}
}

View File

@ -1,5 +1,6 @@
import { PlusOutlined } from '@ant-design/icons';
import { Col, Tooltip, Typography } from 'antd';
import './AddTags.styles.scss';
import { Col, Tooltip } from 'antd';
import Input from 'components/Input';
import { Dispatch, SetStateAction, useState } from 'react';
@ -7,7 +8,6 @@ import { InputContainer, NewTagContainer, TagsContainer } from './styles';
function AddTags({ tags, setTags }: AddTagsProps): JSX.Element {
const [inputValue, setInputValue] = useState<string>('');
const [inputVisible, setInputVisible] = useState<boolean>(false);
const [editInputIndex, setEditInputIndex] = useState(-1);
const [editInputValue, setEditInputValue] = useState('');
@ -15,7 +15,6 @@ function AddTags({ tags, setTags }: AddTagsProps): JSX.Element {
if (inputValue) {
setTags([...tags, inputValue]);
}
setInputVisible(false);
setInputValue('');
};
@ -32,10 +31,6 @@ function AddTags({ tags, setTags }: AddTagsProps): JSX.Element {
setTags(newTags);
};
const showInput = (): void => {
setInputVisible(true);
};
const onChangeHandler = (
value: string,
func: Dispatch<SetStateAction<string>>,
@ -48,7 +43,7 @@ function AddTags({ tags, setTags }: AddTagsProps): JSX.Element {
{tags.map((tag, index) => {
if (editInputIndex === index) {
return (
<Col key={tag} lg={4}>
<Col key={tag} lg={4} className="edit-input">
<Input
size="small"
value={editInputValue}
@ -65,7 +60,12 @@ function AddTags({ tags, setTags }: AddTagsProps): JSX.Element {
const isLongTag = tag.length > 20;
const tagElem = (
<NewTagContainer closable key={tag} onClose={(): void => handleClose(tag)}>
<NewTagContainer
closable
key={tag}
onClose={(): void => handleClose(tag)}
className="tag-container"
>
<span
onDoubleClick={(e): void => {
setEditInputIndex(index);
@ -87,32 +87,19 @@ function AddTags({ tags, setTags }: AddTagsProps): JSX.Element {
);
})}
{inputVisible && (
<InputContainer lg={4}>
<Input
type="text"
size="small"
value={inputValue}
onChangeHandler={(event): void =>
onChangeHandler(event.target.value, setInputValue)
}
onBlurHandler={handleInputConfirm}
onPressEnterHandler={handleInputConfirm}
/>
</InputContainer>
)}
{!inputVisible && (
<NewTagContainer icon={<PlusOutlined />} onClick={showInput}>
<Typography
style={{
fontSize: '12px',
}}
>
New Tag
</Typography>
</NewTagContainer>
)}
<InputContainer>
<Input
type="text"
value={inputValue}
rootClassName="tags-input"
placeholder="Start typing your tag name"
onChangeHandler={(event): void =>
onChangeHandler(event.target.value, setInputValue)
}
onBlurHandler={handleInputConfirm}
onPressEnterHandler={handleInputConfirm}
/>
</InputContainer>
</TagsContainer>
);
}

View File

@ -4,6 +4,8 @@ import styled from 'styled-components';
export const TagsContainer = styled.div`
display: flex;
align-items: center;
flex-flow: wrap;
gap: 6px;
`;
export const NewTagContainer = styled(Tag)`
@ -23,4 +25,6 @@ export const InputContainer = styled(Col)`
> div {
margin: 0;
}
padding-left: 0px !important;
padding-right: 0px !important;
`;

View File

@ -0,0 +1,197 @@
.overview-content {
display: flex;
flex-direction: column;
.overview-settings {
border-radius: 3px;
border: 1px solid var(--bg-slate-500);
padding: 16px !important;
.name-icon-input {
display: flex;
.dashboard-image-input {
.ant-select-selector {
display: flex;
width: 32px;
height: 32px;
padding: 6px;
justify-content: center;
align-items: center;
border-radius: 2px 0px 0px 2px;
border: 1px solid var(--bg-slate-400);
background: var(--bg-ink-300);
.ant-select-selection-item {
display: flex;
align-items: center;
.list-item-image {
height: 16px;
width: 16px;
}
}
}
}
.dashboard-name-input {
border-radius: 0px 2px 2px 0px;
border: 1px solid var(--bg-slate-400);
background: var(--bg-ink-300);
}
}
.dashboard-name {
color: var(--bg-vanilla-400);
font-family: Inter;
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: 20px; /* 142.857% */
}
.description-text-area {
padding: 6px 6px 6px 8px;
border-radius: 2px;
border: 1px solid var(--bg-slate-400);
background: var(--bg-ink-300);
}
}
.overview-settings-footer {
display: flex;
justify-content: space-between;
align-items: center;
width: -webkit-fill-available;
padding: 12px 16px 12px 0px;
position: fixed;
bottom: 0;
height: 32px;
border-top: 1px solid var(--bg-slate-500);
background: var(--bg-ink-400);
.unsaved {
display: flex;
align-items: center;
gap: 8px;
.unsaved-dot {
width: 6px;
height: 6px;
border-radius: 50px;
background: var(--bg-robin-500);
box-shadow: 0px 0px 6px 0px rgba(78, 116, 248, 0.4);
}
.unsaved-changes {
color: var(--bg-robin-400);
font-family: Inter;
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: 24px; /* 171.429% */
letter-spacing: -0.07px;
}
}
.footer-action-btns {
display: flex;
gap: 8px;
.discard-btn {
margin: '16px 0';
color: var(--bg-vanilla-100);
font-family: Inter;
font-size: 12px;
font-style: normal;
font-weight: 500;
line-height: 24px;
}
.save-btn {
margin: 0px !important;
color: var(--bg-vanilla-100);
font-family: Inter;
font-size: 12px;
font-style: normal;
font-weight: 500;
line-height: 24px;
}
}
}
}
.dashboard-image-input {
&.ant-select-dropdown {
padding: 0px !important;
}
.ant-select-item {
padding: 0px;
align-items: center;
justify-content: center;
.ant-select-item-option-content {
display: flex;
align-items: center;
justify-content: center;
.list-item-image {
height: 16px;
width: 16px;
}
}
}
}
.lightMode {
.overview-content {
.overview-settings {
border: 1px solid var(--bg-vanilla-300);
.name-icon-input {
.dashboard-image-input {
.ant-select-selector {
border: 1px solid var(--bg-vanilla-200);
background: var(--bg-vanilla-300);
}
}
.dashboard-name-input {
border: 1px solid var(--bg-vanilla-200);
background: var(--bg-vanilla-300);
}
}
.dashboard-name {
color: var(--bg-ink-400);
}
.description-text-area {
border: 1px solid var(--bg-vanilla-300);
background: var(--bg-vanilla-300);
}
}
.overview-settings-footer {
border-top: 1px solid var(--bg-vanilla-300);
background: var(--bg-vanilla-100);
.unsaved {
.unsaved-dot {
background: var(--bg-robin-500);
}
.unsaved-changes {
color: var(--bg-robin-400);
}
}
.footer-action-btns {
.discard-btn {
color: var(--bg-ink-300);
background-color: var(--bg-vanilla-300);
}
.save-btn {
color: var(--bg-vanilla-300);
}
}
}
}
}

View File

@ -1,14 +1,20 @@
import { SaveOutlined } from '@ant-design/icons';
import { Col, Input, Space, Typography } from 'antd';
import './GeneralSettings.styles.scss';
import { Col, Input, Select, Space, Typography } from 'antd';
import { SOMETHING_WENT_WRONG } from 'constants/api';
import AddTags from 'container/NewDashboard/DashboardSettings/General/AddTags';
import { useUpdateDashboard } from 'hooks/dashboard/useUpdateDashboard';
import { useNotifications } from 'hooks/useNotifications';
import { isEqual } from 'lodash-es';
import { Check, X } from 'lucide-react';
import { useDashboard } from 'providers/Dashboard/Dashboard';
import { useState } from 'react';
import { useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { Button } from './styles';
import { Base64Icons } from './utils';
const { Option } = Select;
function GeneralDashboardSettings(): JSX.Element {
const { selectedDashboard, setSelectedDashboard } = useDashboard();
@ -17,13 +23,18 @@ function GeneralDashboardSettings(): JSX.Element {
const selectedData = selectedDashboard?.data;
const { title = '', tags = [], description = '' } = selectedData || {};
const { title = '', tags = [], description = '', image = Base64Icons[0] } =
selectedData || {};
const [updatedTitle, setUpdatedTitle] = useState<string>(title);
const [updatedTags, setUpdatedTags] = useState<string[]>(tags || []);
const [updatedDescription, setUpdatedDescription] = useState(
description || '',
);
const [updatedImage, setUpdatedImage] = useState<string>(image);
const [numberOfUnsavedChanges, setNumberOfUnsavedChanges] = useState<number>(
0,
);
const { t } = useTranslation('common');
@ -40,6 +51,7 @@ function GeneralDashboardSettings(): JSX.Element {
description: updatedDescription,
tags: updatedTags,
title: updatedTitle,
image: updatedImage,
},
},
{
@ -57,48 +69,135 @@ function GeneralDashboardSettings(): JSX.Element {
);
};
return (
<Col>
<Space direction="vertical" style={{ width: '100%' }}>
<div>
<Typography style={{ marginBottom: '0.5rem' }}>Name</Typography>
<Input
data-testid="dashboard-name"
value={updatedTitle}
onChange={(e): void => setUpdatedTitle(e.target.value)}
/>
</div>
useEffect(() => {
let numberOfUnsavedChanges = 0;
if (!isEqual(updatedTitle, selectedData?.title)) {
numberOfUnsavedChanges += 1;
}
if (!isEqual(updatedDescription, selectedData?.description)) {
numberOfUnsavedChanges += 1;
}
if (!isEqual(updatedTags, selectedData?.tags)) {
numberOfUnsavedChanges += 1;
}
if (!isEqual(updatedImage, selectedData?.image)) {
numberOfUnsavedChanges += 1;
}
setNumberOfUnsavedChanges(numberOfUnsavedChanges);
}, [
selectedData?.description,
selectedData?.image,
selectedData?.tags,
selectedData?.title,
updatedDescription,
updatedImage,
updatedTags,
updatedTitle,
]);
<div>
<Typography style={{ marginBottom: '0.5rem' }}>Description</Typography>
<Input.TextArea
data-testid="dashboard-desc"
rows={5}
value={updatedDescription}
onChange={(e): void => setUpdatedDescription(e.target.value)}
/>
const discardHandler = (): void => {
setUpdatedTitle(title);
setUpdatedImage(image);
setUpdatedTags(tags);
setUpdatedDescription(description);
};
return (
<div className="overview-content">
<Col className="overview-settings">
<Space
direction="vertical"
style={{
width: '100%',
display: 'flex',
flexDirection: 'column',
gap: '21px',
}}
>
<div>
<Typography style={{ marginBottom: '0.5rem' }} className="dashboard-name">
Dashboard Name
</Typography>
<section className="name-icon-input">
<Select
defaultActiveFirstOption
data-testid="dashboard-image"
suffixIcon={null}
rootClassName="dashboard-image-input"
value={updatedImage}
onChange={(value: string): void => setUpdatedImage(value)}
>
{Base64Icons.map((icon) => (
<Option value={icon} key={icon}>
<img src={icon} alt="dashboard-icon" className="list-item-image" />
</Option>
))}
</Select>
<Input
data-testid="dashboard-name"
className="dashboard-name-input"
value={updatedTitle}
onChange={(e): void => setUpdatedTitle(e.target.value)}
/>
</section>
</div>
<div>
<Typography style={{ marginBottom: '0.5rem' }} className="dashboard-name">
Description
</Typography>
<Input.TextArea
data-testid="dashboard-desc"
rows={6}
value={updatedDescription}
className="description-text-area"
onChange={(e): void => setUpdatedDescription(e.target.value)}
/>
</div>
<div>
<Typography style={{ marginBottom: '0.5rem' }} className="dashboard-name">
Tags
</Typography>
<AddTags tags={updatedTags} setTags={setUpdatedTags} />
</div>
</Space>
</Col>
{numberOfUnsavedChanges > 0 && (
<div className="overview-settings-footer">
<div className="unsaved">
<div className="unsaved-dot" />
<Typography.Text className="unsaved-changes">
{numberOfUnsavedChanges} Unsaved change
</Typography.Text>
</div>
<div className="footer-action-btns">
<Button
disabled={updateDashboardMutation.isLoading}
icon={<X size={14} />}
onClick={discardHandler}
type="text"
className="discard-btn"
>
Discard
</Button>
<Button
style={{
margin: '16px 0',
}}
disabled={updateDashboardMutation.isLoading}
loading={updateDashboardMutation.isLoading}
icon={<Check size={14} />}
data-testid="save-dashboard-config"
onClick={onSaveHandler}
type="primary"
className="save-btn"
>
{t('save')}
</Button>
</div>
</div>
<div>
<Typography style={{ marginBottom: '0.5rem' }}>Tags</Typography>
<AddTags tags={updatedTags} setTags={setUpdatedTags} />
</div>
<div>
<Button
style={{
margin: '16px 0',
}}
disabled={updateDashboardMutation.isLoading}
loading={updateDashboardMutation.isLoading}
icon={<SaveOutlined />}
data-testid="save-dashboard-config"
onClick={onSaveHandler}
type="primary"
>
{t('save')}
</Button>
</div>
</Space>
</Col>
)}
</div>
);
}

File diff suppressed because one or more lines are too long

View File

@ -6,3 +6,485 @@
margin-bottom: 1rem;
flex-direction: column;
}
.variable-item-container {
display: flex;
flex-direction: column;
border-radius: 3px;
border: 1px solid var(--bg-slate-500);
.all-variables {
display: flex;
padding: 10px 16px;
align-items: center;
gap: 8px;
border-bottom: 1px solid var(--bg-slate-500);
.all-variables-btn {
display: flex;
align-items: center;
height: 24px;
padding: 0px;
color: #c0c1c3;
font-family: Inter;
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: 18px; /* 128.571% */
}
.all-variables-btn:hover {
background-color: unset !important;
}
}
.variable-item-content {
padding: 12px 16px 20px 16px;
display: flex;
flex-direction: column;
gap: 20px;
.variable-name-section {
flex-direction: column;
gap: 8px;
.typography-variables {
color: var(--bg-vanilla-400);
font-family: Inter;
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: 20px; /* 142.857% */
}
.name-input {
border-radius: 2px;
border: 1px solid var(--bg-slate-400);
background: var(--bg-ink-300);
padding: 6px 6px 6px 8px;
}
}
.variable-description-section {
flex-direction: column;
gap: 8px;
.typography-variables {
color: var(--bg-vanilla-400);
font-family: Inter;
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: 20px; /* 142.857% */
}
.description-input {
border-radius: 2px;
border: 1px solid var(--bg-slate-400);
background: var(--bg-ink-300);
padding: 6px 6px 6px 8px;
}
}
.variable-type-section {
justify-content: space-between;
margin-bottom: 0px;
align-items: center;
.typography-variables {
color: var(--bg-vanilla-400);
font-family: Inter;
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: 20px; /* 142.857% */
}
.variable-type-btn-group {
display: flex;
width: 342px;
height: 32px;
flex-shrink: 0;
border-radius: 2px;
border: 1px solid var(--bg-slate-400);
background: var(--bg-ink-400);
box-shadow: 0px 0px 8px 0px rgba(0, 0, 0, 0.1);
.ant-btn {
width: 114px;
height: 32px;
flex-shrink: 0;
border-radius: 2px 0px 0px 2px;
}
.variable-type-btn {
display: flex;
align-items: center;
justify-content: center;
}
.variable-type-btn + .variable-type-btn {
border-left: 1px solid var(--bg-slate-400);
}
.selected {
background: var(--bg-slate-400);
}
}
}
.sort-values-section {
justify-content: space-between;
margin-bottom: 0;
.typography-variables {
color: var(--bg-vanilla-100);
font-family: Inter;
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: 20px; /* 142.857% */
letter-spacing: -0.07px;
}
.typography-sort {
color: var(--bg-vanilla-400);
font-family: Inter;
font-size: 12px;
font-style: normal;
font-weight: 400;
line-height: 20px; /* 166.667% */
letter-spacing: -0.06px;
}
.sort-input {
border-radius: 2px;
border: 1px solid var(--bg-slate-400);
.ant-select-selector {
width: 192px;
height: 32px;
padding: 6px 6px 6px 8px;
background: var(--bg-ink-300);
}
}
}
.multiple-values-section {
justify-content: space-between;
margin-bottom: 0;
.typography-variables {
color: var(--bg-vanilla-400);
font-family: Inter;
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: 20px; /* 142.857% */
letter-spacing: -0.07px;
width: 339px;
}
}
.all-option-section {
justify-content: space-between;
margin-bottom: 0;
.typography-variables {
color: var(--bg-vanilla-400);
font-family: Inter;
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: 20px; /* 142.857% */
letter-spacing: -0.07px;
width: 339px;
}
}
.variable-textbox-section {
justify-content: space-between;
margin-bottom: 0;
align-items: center;
.typography-variables {
color: var(--bg-vanilla-400);
font-family: Inter;
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: 20px; /* 142.857% */
}
.default-input {
display: flex;
height: 32px;
padding: 6px 6px 6px 8px;
align-items: flex-start;
gap: 4px;
flex: 1 0 0;
border-radius: 2px;
border: 1px solid var(--bg-slate-400);
background: var(--bg-ink-300);
width: 342px;
}
}
.variable-custom-section {
margin-bottom: 0px;
.custom-collapse {
width: 100%;
border-radius: 3px 3px 0px 0px;
border: 1px solid var(--bg-slate-500);
.ant-collapse-item {
border-bottom: none;
}
.ant-collapse-header {
height: 38px;
border-radius: 3px 3px 0px 0px;
background: var(--bg-ink-300);
align-items: center;
padding: 12px;
gap: 8px;
.ant-collapse-expand-icon {
padding-inline-end: 0px;
}
.ant-collapse-header-text {
color: var(--bg-robin-400);
font-family: 'Space Mono';
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: 18px; /* 128.571% */
display: flex;
padding: 1px 2px;
align-items: center;
gap: 10px;
border-radius: 2px;
background: rgba(113, 144, 249, 0.08);
}
}
.ant-collapse-content {
border-top: none;
.ant-collapse-content-box {
padding: 0px;
}
.comma-input {
height: 109px;
border: none;
}
}
}
}
.variables-preview-section {
display: flex;
flex-direction: column;
margin-bottom: 0px;
border-radius: 0px 0px 3px 3px;
border: 1px solid var(--bg-slate-500);
height: 108px;
margin-top: -20px;
border-collapse: collapse;
gap: 5px;
flex-flow: column;
.typography-variables {
color: var(--bg-robin-400);
font-family: 'Space Mono';
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: 18px; /* 128.571% */
display: inline-flex;
padding: 1px 2px;
align-items: center;
gap: 10px;
border-radius: 0px 0px 2px 0px;
background: rgba(113, 144, 249, 0.08);
}
.preview-values {
padding: 4.5px 11px;
display: flex;
overflow-y: auto;
flex-flow: wrap;
gap: 8px;
.ant-tag {
height: 30px;
color: var(--bg-vanilla-100);
font-family: 'Space Mono';
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: 20px;
display: inline-flex;
letter-spacing: -0.07px;
align-items: center;
border-radius: 2px;
border: 1px solid var(--bg-slate-300);
margin-inline-end: 0px;
}
}
}
}
}
.variable-item-footer {
margin-top: 12px;
display: flex;
flex-direction: row-reverse;
.footer-btn-discard {
display: flex;
align-items: center;
border-radius: 2px;
border: 1px solid var(--bg-slate-400);
background: var(--bg-slate-500);
height: 34px;
padding: 4px 8px 4px 10px;
box-shadow: none;
}
.footer-btn-save {
display: flex;
align-items: center;
border-radius: 2px;
border: 1px solid var(--bg-robin-500);
background: var(--bg-robin-500);
width: 123px;
height: 34px;
padding: 4px 8px 4px 10px;
box-shadow: none;
}
}
.lightMode {
.variable-item-container {
border: 1px solid var(--bg-vanilla-300);
.all-variables {
border-bottom: 1px solid var(--bg-vanilla-300);
.all-variables-btn {
color: var(--bg-ink-300);
}
}
.variable-item-content {
.variable-name-section {
.typography-variables {
color: var(--bg-ink-400);
}
.name-input {
border: 1px solid var(--bg-vanilla-300);
background: var(--bg-vanilla-300);
}
}
.variable-description-section {
.typography-variables {
color: var(--bg-ink-400);
}
.description-input {
border: 1px solid var(--bg-vanilla-300);
background: var(--bg-vanilla-300);
}
}
.variable-type-section {
.typography-variables {
color: var(--bg-slate-400);
}
.variable-type-btn-group {
border: 1px solid var(--bg-vanilla-300);
background: var(--bg-vanilla-300);
.variable-type-btn + .variable-type-btn {
border-left: 1px solid var(--bg-vanilla-200);
}
.selected {
background: var(--bg-vanilla-200);
}
}
}
.sort-values-section {
.typography-variables {
color: var(--bg-ink-300);
}
.typography-sort {
color: var(--bg-ink-400);
}
.sort-input {
border: 1px solid var(--bg-vanilla-300);
.ant-select-selector {
background: var(--bg-vanilla-300);
border: var(--bg-vanilla-300);
}
}
}
.multiple-values-section {
.typography-variables {
color: var(--bg-ink-400);
}
}
.all-option-section {
.typography-variables {
color: var(--bg-ink-400);
}
}
.variable-textbox-section {
.typography-variables {
color: var(--bg-ink-400);
}
.default-input {
border: 1px solid var(--bg-vanilla-300);
background: var(--bg-vanilla-300);
color: var(--bg-ink-300);
}
}
.variable-custom-section {
.custom-collapse {
border: 1px solid var(--bg-vanilla-300);
.ant-collapse-header {
background: var(--bg-vanilla-300);
}
}
}
.variables-preview-section {
border: 1px solid var(--bg-vanilla-300);
.preview-values {
.ant-tag {
color: var(--bg-slate-300);
border: 1px solid var(--bg-vanilla-300);
}
}
}
}
}
.variable-item-footer {
.footer-btn-discard {
border: 1px solid var(--bg-vanilla-300);
background: var(--bg-vanilla-200);
}
}
}

View File

@ -2,20 +2,28 @@
import './VariableItem.styles.scss';
import { orange } from '@ant-design/colors';
import { Button, Divider, Input, Select, Switch, Tag, Typography } from 'antd';
import { Button, Collapse, Input, Select, Switch, Tag, Typography } from 'antd';
import dashboardVariablesQuery from 'api/dashboard/variables/dashboardVariablesQuery';
import cx from 'classnames';
import Editor from 'components/Editor';
import { REACT_QUERY_KEY } from 'constants/reactQueryKeys';
import { commaValuesParser } from 'lib/dashbaordVariables/customCommaValuesParser';
import sortValues from 'lib/dashbaordVariables/sortVariableValues';
import { map } from 'lodash-es';
import {
ArrowLeft,
Check,
ClipboardType,
DatabaseZap,
LayoutList,
X,
} from 'lucide-react';
import { useCallback, useEffect, useState } from 'react';
import { useQuery } from 'react-query';
import {
IDashboardVariable,
TSortVariableValuesType,
TVariableQueryType,
VariableQueryTypeArr,
VariableSortTypeArr,
} from 'types/api/dashboard/getAll';
import { v4 as generateUUID } from 'uuid';
@ -79,7 +87,6 @@ function VariableItem({
const [errorPreview, setErrorPreview] = useState<string | null>(null);
useEffect(() => {
setPreviewValues([]);
if (queryType === 'CUSTOM') {
setPreviewValues(
sortValues(
@ -88,6 +95,9 @@ function VariableItem({
) as never,
);
}
if (queryType === 'QUERY') {
setPreviewValues((prev) => sortValues(prev, variableSortType) as never);
}
}, [
queryType,
variableCustomValue,
@ -121,13 +131,16 @@ function VariableItem({
// Fetches the preview values for the SQL variable query
const handleQueryResult = (response: any): void => {
if (response?.payload?.variableValues)
if (response?.payload?.variableValues) {
setPreviewValues(
sortValues(
response.payload?.variableValues || [],
variableSortType,
) as never,
);
} else {
setPreviewValues([]);
}
};
const { isFetching: previewLoading, refetch: runQuery } = useQuery(
@ -169,219 +182,288 @@ function VariableItem({
}, []);
return (
<div className="variable-item-container">
<div className="variable-item-content">
<VariableItemRow>
<LabelContainer>
<Typography>Name</Typography>
</LabelContainer>
<div>
<Input
placeholder="Unique name of the variable"
style={{ width: 400 }}
value={variableName}
onChange={(e): void => {
setVariableName(e.target.value);
setErrorName(
!validateName(e.target.value) && e.target.value !== variableData.name,
);
}}
/>
<div>
<Typography.Text type="warning">
{errorName ? 'Variable name already exists' : ''}
</Typography.Text>
</div>
</div>
</VariableItemRow>
<VariableItemRow>
<LabelContainer>
<Typography>Description</Typography>
</LabelContainer>
<Input.TextArea
value={variableDescription}
placeholder="Write description of the variable"
style={{ width: 400 }}
onChange={(e): void => setVariableDescription(e.target.value)}
/>
</VariableItemRow>
<VariableItemRow>
<LabelContainer>
<Typography>Type</Typography>
</LabelContainer>
<Select
defaultActiveFirstOption
style={{ width: 400 }}
onChange={(e: TVariableQueryType): void => {
setQueryType(e);
}}
value={queryType}
<>
<div className="variable-item-container">
<div className="all-variables">
<Button
type="text"
className="all-variables-btn"
icon={<ArrowLeft size={14} />}
onClick={onCancel}
>
<Option value={VariableQueryTypeArr[0]}>Query</Option>
<Option value={VariableQueryTypeArr[1]}>Textbox</Option>
<Option value={VariableQueryTypeArr[2]}>Custom</Option>
</Select>
</VariableItemRow>
<Typography.Title
level={5}
style={{ marginTop: '1rem', marginBottom: '1rem' }}
>
Options
</Typography.Title>
{queryType === 'QUERY' && (
<div className="query-container">
All variables
</Button>
</div>
<div className="variable-item-content">
<VariableItemRow className="variable-name-section">
<LabelContainer>
<Typography>Query</Typography>
<Typography className="typography-variables">Name</Typography>
</LabelContainer>
<div style={{ flex: 1, position: 'relative' }}>
<Editor
language="sql"
value={variableQueryValue}
onChange={(e): void => setVariableQueryValue(e)}
height="240px"
options={{
fontSize: 13,
wordWrap: 'on',
lineNumbers: 'off',
glyphMargin: false,
folding: false,
lineDecorationsWidth: 0,
lineNumbersMinChars: 0,
minimap: {
enabled: false,
},
<div>
<Input
placeholder="Unique name of the variable"
value={variableName}
className="name-input"
onChange={(e): void => {
setVariableName(e.target.value);
setErrorName(
!validateName(e.target.value) && e.target.value !== variableData.name,
);
}}
/>
<div>
<Typography.Text type="warning">
{errorName ? 'Variable name already exists' : ''}
</Typography.Text>
</div>
</div>
</VariableItemRow>
<VariableItemRow className="variable-description-section">
<LabelContainer>
<Typography className="typography-variables">Description</Typography>
</LabelContainer>
<Input.TextArea
value={variableDescription}
placeholder="Enter a description for the variable"
className="description-input"
rows={3}
onChange={(e): void => setVariableDescription(e.target.value)}
/>
</VariableItemRow>
<VariableItemRow className="variable-type-section">
<LabelContainer>
<Typography className="typography-variables">Variable Type</Typography>
</LabelContainer>
<div className="variable-type-btn-group">
<Button
type="primary"
size="small"
onClick={handleTestRunQuery}
style={{
position: 'absolute',
bottom: 0,
type="text"
icon={<DatabaseZap size={14} />}
className={cx(
// eslint-disable-next-line sonarjs/no-duplicate-string
'variable-type-btn',
queryType === 'QUERY' ? 'selected' : '',
)}
onClick={(): void => {
setQueryType('QUERY');
setPreviewValues([]);
}}
loading={previewLoading}
>
Test Run Query
Query
</Button>
<Button
type="text"
icon={<ClipboardType size={14} />}
className={cx(
'variable-type-btn',
queryType === 'TEXTBOX' ? 'selected' : '',
)}
onClick={(): void => {
setQueryType('TEXTBOX');
setPreviewValues([]);
}}
>
Textbox
</Button>
<Button
type="text"
icon={<LayoutList size={14} />}
className={cx(
'variable-type-btn',
queryType === 'CUSTOM' ? 'selected' : '',
)}
onClick={(): void => {
setQueryType('CUSTOM');
setPreviewValues([]);
}}
>
Custom
</Button>
</div>
</div>
)}
{queryType === 'CUSTOM' && (
<VariableItemRow>
<LabelContainer>
<Typography>Values separated by comma</Typography>
</LabelContainer>
<Input.TextArea
value={variableCustomValue}
placeholder="1, 10, mykey, mykey:myvalue"
style={{ width: 400 }}
onChange={(e): void => {
setVariableCustomValue(e.target.value);
setPreviewValues(
sortValues(
commaValuesParser(e.target.value),
variableSortType,
) as never,
);
}}
/>
</VariableItemRow>
)}
{queryType === 'TEXTBOX' && (
<VariableItemRow>
<LabelContainer>
<Typography>Default Value</Typography>
</LabelContainer>
<Input
value={variableTextboxValue}
onChange={(e): void => {
setVariableTextboxValue(e.target.value);
}}
placeholder="Default value if any"
style={{ width: 400 }}
/>
</VariableItemRow>
)}
{(queryType === 'QUERY' || queryType === 'CUSTOM') && (
<>
<VariableItemRow>
{queryType === 'QUERY' && (
<div className="query-container">
<LabelContainer>
<Typography>Preview of Values</Typography>
</LabelContainer>
<div style={{ flex: 1 }}>
{errorPreview ? (
<Typography style={{ color: orange[5] }}>{errorPreview}</Typography>
) : (
map(previewValues, (value, idx) => (
<Tag key={`${value}${idx}`}>{value.toString()}</Tag>
))
)}
</div>
</VariableItemRow>
<VariableItemRow>
<LabelContainer>
<Typography>Sort</Typography>
<Typography>Query</Typography>
</LabelContainer>
<Select
defaultActiveFirstOption
style={{ width: 400 }}
defaultValue={VariableSortTypeArr[0]}
value={variableSortType}
onChange={(value: TSortVariableValuesType): void =>
setVariableSortType(value)
}
>
<Option value={VariableSortTypeArr[0]}>Disabled</Option>
<Option value={VariableSortTypeArr[1]}>Ascending</Option>
<Option value={VariableSortTypeArr[2]}>Descending</Option>
</Select>
</VariableItemRow>
<VariableItemRow>
<LabelContainer>
<Typography>Enable multiple values to be checked</Typography>
</LabelContainer>
<Switch
checked={variableMultiSelect}
onChange={(e): void => {
setVariableMultiSelect(e);
if (!e) {
setVariableShowALLOption(false);
}
}}
<div style={{ flex: 1, position: 'relative' }}>
<Editor
language="sql"
value={variableQueryValue}
onChange={(e): void => setVariableQueryValue(e)}
height="240px"
options={{
fontSize: 13,
wordWrap: 'on',
lineNumbers: 'off',
glyphMargin: false,
folding: false,
lineDecorationsWidth: 0,
lineNumbersMinChars: 0,
minimap: {
enabled: false,
},
}}
/>
<Button
type="primary"
size="small"
onClick={handleTestRunQuery}
style={{
position: 'absolute',
bottom: 0,
}}
loading={previewLoading}
>
Test Run Query
</Button>
</div>
</div>
)}
{queryType === 'CUSTOM' && (
<VariableItemRow className="variable-custom-section">
<Collapse
collapsible="header"
rootClassName="custom-collapse"
defaultActiveKey={['1']}
items={[
{
key: '1',
label: 'Options',
children: (
<Input.TextArea
value={variableCustomValue}
placeholder="Enter options separated by commas."
rootClassName="comma-input"
onChange={(e): void => {
setVariableCustomValue(e.target.value);
setPreviewValues(
sortValues(
commaValuesParser(e.target.value),
variableSortType,
) as never,
);
}}
/>
),
},
]}
/>
</VariableItemRow>
{variableMultiSelect && (
<VariableItemRow>
)}
{queryType === 'TEXTBOX' && (
<VariableItemRow className="variable-textbox-section">
<LabelContainer>
<Typography className="typography-variables">Default Value</Typography>
</LabelContainer>
<Input
value={variableTextboxValue}
className="default-input"
onChange={(e): void => {
setVariableTextboxValue(e.target.value);
}}
placeholder="Enter a default value (if any)..."
style={{ width: 400 }}
/>
</VariableItemRow>
)}
{(queryType === 'QUERY' || queryType === 'CUSTOM') && (
<>
<VariableItemRow className="variables-preview-section">
<LabelContainer style={{ width: '100%' }}>
<Typography className="typography-variables">
Preview of Values
</Typography>
</LabelContainer>
<div className="preview-values">
{errorPreview ? (
<Typography style={{ color: orange[5] }}>{errorPreview}</Typography>
) : (
map(previewValues, (value, idx) => (
<Tag key={`${value}${idx}`}>{value.toString()}</Tag>
))
)}
</div>
</VariableItemRow>
<VariableItemRow className="sort-values-section">
<LabelContainer>
<Typography>Include an option for ALL values</Typography>
<Typography className="typography-variables">Sort Values</Typography>
<Typography className="typography-sort">
Sort the query output values
</Typography>
</LabelContainer>
<Select
defaultActiveFirstOption
defaultValue={VariableSortTypeArr[0]}
value={variableSortType}
onChange={(value: TSortVariableValuesType): void =>
setVariableSortType(value)
}
className="sort-input"
>
<Option value={VariableSortTypeArr[0]}>Disabled</Option>
<Option value={VariableSortTypeArr[1]}>Ascending</Option>
<Option value={VariableSortTypeArr[2]}>Descending</Option>
</Select>
</VariableItemRow>
<VariableItemRow className="multiple-values-section">
<LabelContainer>
<Typography className="typography-variables">
Enable multiple values to be checked
</Typography>
</LabelContainer>
<Switch
checked={variableShowALLOption}
onChange={(e): void => setVariableShowALLOption(e)}
checked={variableMultiSelect}
onChange={(e): void => {
setVariableMultiSelect(e);
if (!e) {
setVariableShowALLOption(false);
}
}}
/>
</VariableItemRow>
)}
</>
)}
{variableMultiSelect && (
<VariableItemRow className="all-option-section">
<LabelContainer>
<Typography className="typography-variables">
Include an option for ALL values
</Typography>
</LabelContainer>
<Switch
checked={variableShowALLOption}
onChange={(e): void => setVariableShowALLOption(e)}
/>
</VariableItemRow>
)}
</>
)}
</div>
</div>
<div className="variable-item-footer">
<Divider />
<VariableItemRow>
<Button type="primary" onClick={handleSave} disabled={errorName}>
Save
<Button
type="default"
onClick={onCancel}
icon={<X size={14} />}
className="footer-btn-discard"
>
Discard
</Button>
<Button type="default" onClick={onCancel}>
Cancel
<Button
type="primary"
onClick={handleSave}
disabled={errorName}
icon={<Check size={14} />}
className="footer-btn-save"
>
Save Variable
</Button>
</VariableItemRow>
</div>
</div>
</>
);
}

View File

@ -1,7 +1,6 @@
import '../DashboardSettings.styles.scss';
import { blue, red } from '@ant-design/colors';
import { MenuOutlined, PlusOutlined } from '@ant-design/icons';
import { HolderOutlined, PlusOutlined } from '@ant-design/icons';
import type { DragEndEvent, UniqueIdentifier } from '@dnd-kit/core';
import {
DndContext,
@ -18,7 +17,7 @@ import { RowProps } from 'antd/lib';
import { convertVariablesToDbFormat } from 'container/NewDashboard/DashboardVariablesSelection/util';
import { useUpdateDashboard } from 'hooks/dashboard/useUpdateDashboard';
import { useNotifications } from 'hooks/useNotifications';
import { PencilIcon, TrashIcon } from 'lucide-react';
import { PenLine, Trash2 } from 'lucide-react';
import { useDashboard } from 'providers/Dashboard/Dashboard';
import React, { useEffect, useRef, useState } from 'react';
import { useTranslation } from 'react-i18next';
@ -53,25 +52,33 @@ function TableRow({ children, ...props }: RowProps): JSX.Element {
// eslint-disable-next-line react/jsx-props-no-spreading
<tr {...props} ref={setNodeRef} style={style} {...attributes}>
{React.Children.map(children, (child) => {
if ((child as React.ReactElement).key === 'sort') {
if ((child as React.ReactElement).key === 'name') {
return React.cloneElement(child as React.ReactElement, {
children: (
<MenuOutlined
ref={setActivatorNodeRef}
style={{ touchAction: 'none', cursor: 'move' }}
// eslint-disable-next-line react/jsx-props-no-spreading
{...listeners}
/>
<div className="variable-name-drag">
<HolderOutlined
ref={setActivatorNodeRef}
style={{ touchAction: 'none', cursor: 'move' }}
// eslint-disable-next-line react/jsx-props-no-spreading
{...listeners}
/>
{child}
</div>
),
});
}
return child;
})}
</tr>
);
}
function VariablesSetting(): JSX.Element {
function VariablesSetting({
variableViewModeRef,
}: {
variableViewModeRef: React.MutableRefObject<(() => void) | undefined>;
}): JSX.Element {
const variableToDelete = useRef<IDashboardVariable | null>(null);
const [deleteVariableModal, setDeleteVariableModal] = useState(false);
@ -111,6 +118,13 @@ function VariablesSetting(): JSX.Element {
setVariableViewMode(viewType);
};
useEffect(() => {
if (variableViewModeRef) {
// eslint-disable-next-line no-param-reassign
variableViewModeRef.current = onDoneVariableViewMode;
}
}, [variableViewModeRef]);
const updateMutation = useUpdateDashboard();
useEffect(() => {
@ -245,47 +259,42 @@ function VariablesSetting(): JSX.Element {
!existingVariableNamesMap[name];
const columns = [
{
key: 'sort',
width: '10%',
},
{
title: 'Variable',
dataIndex: 'name',
width: '40%',
width: '50%',
key: 'name',
},
{
title: 'Description',
dataIndex: 'description',
width: '35%',
width: '50%',
key: 'description',
},
{
title: 'Actions',
width: '15%',
key: 'action',
render: (variable: IDashboardVariable): JSX.Element => (
<Space>
<Button
type="text"
style={{ padding: 8, cursor: 'pointer', color: blue[5] }}
onClick={(): void => onVariableViewModeEnter('EDIT', variable)}
>
<PencilIcon size={14} />
</Button>
<Button
type="text"
style={{ padding: 8, color: red[6], cursor: 'pointer' }}
onClick={(): void => {
if (variable) {
onVariableDeleteHandler(variable);
}
}}
>
<TrashIcon size={14} />
</Button>
</Space>
<div className="variable-description-actions">
<Typography.Text className="variable-description">
{variable.description}
</Typography.Text>
<Space className="actions-btns">
<Button
type="text"
onClick={(): void => onVariableViewModeEnter('EDIT', variable)}
className="edit-variable-button"
>
<PenLine size={14} />
</Button>
<Button
type="text"
onClick={(): void => {
if (variable) {
onVariableDeleteHandler(variable);
}
}}
className="delete-variable-button"
>
<Trash2 size={14} />
</Button>
</Space>
</div>
),
},
];
@ -353,6 +362,10 @@ function VariablesSetting(): JSX.Element {
flexDirection: 'row',
justifyContent: 'flex-end',
padding: '0.5rem 0',
position: 'absolute',
top: '-56px',
right: '0px',
zIndex: '1',
}}
>
<Button
@ -385,6 +398,7 @@ function VariablesSetting(): JSX.Element {
columns={columns}
pagination={false}
dataSource={variablesTableData}
className="dashboard-variable-settings-table"
/>
</SortableContext>
</DndContext>

View File

@ -1,19 +1,38 @@
import { Tabs } from 'antd';
import './DashboardSettingsContent.styles.scss';
import { Button, Tabs } from 'antd';
import { Braces, Table } from 'lucide-react';
import GeneralDashboardSettings from './General';
import VariablesSetting from './Variables';
function DashboardSettingsContent(): JSX.Element {
function DashboardSettingsContent({
variableViewModeRef,
}: {
variableViewModeRef: React.MutableRefObject<(() => void) | undefined>;
}): JSX.Element {
const items = [
{
label: 'General',
label: (
<Button type="text" icon={<Table size="14" />} className="overview-btn">
Overview
</Button>
),
key: 'general',
children: <GeneralDashboardSettings />,
},
{ label: 'Variables', key: 'variables', children: <VariablesSetting /> },
{
label: (
<Button type="text" icon={<Braces size={14} />} className="variables-btn">
Variables
</Button>
),
key: 'variables',
children: <VariablesSetting variableViewModeRef={variableViewModeRef} />,
},
];
return <Tabs items={items} animated />;
return <Tabs items={items} animated className="settings-tabs" />;
}
export default DashboardSettingsContent;

View File

@ -1,16 +1,63 @@
.variable-name {
font-size: 0.8rem;
min-width: 100px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
color: gray;
}
.variable-item {
display: flex;
align-items: center;
.variable-name {
display: flex;
min-width: 56px;
height: 32px;
padding: 6px 6px 6px 8px;
align-items: center;
gap: 4px;
border-radius: 2px 0px 0px 2px;
border: 1px solid var(--bg-slate-400);
background: var(--bg-ink-300);
color: var(--bg-robin-300);
font-family: Inter;
font-size: 12px;
font-style: normal;
font-weight: 400;
line-height: 16px; /* 133.333% */
}
.variable-value {
display: flex;
min-width: 120px;
height: 32px;
padding: 6px 6px 6px 8px;
align-items: center;
gap: 4px;
border-radius: 0px 2px 2px 0px;
border: 1px solid var(--bg-slate-400);
border-left: none;
background: var(--bg-ink-400);
color: var(--bg-vanilla-400);
font-family: Inter;
font-size: 12px;
font-style: normal;
font-weight: 400;
line-height: 16px; /* 133.333% */
}
.variable-select {
.ant-select-dropdown {
max-width: 300px;
}
}
}
.lightMode {
.variable-item {
.variable-name {
border: 1px solid var(--bg-vanilla-300);
background: var(--bg-vanilla-100);
color: var(--bg-robin-300);
}
.variable-value {
border: 1px solid var(--bg-vanilla-300);
background: var(--bg-vanilla-100);
color: var(--bg-ink-400);
}
}
}

View File

@ -124,7 +124,7 @@ function DashboardVariableSelection(): JSX.Element | null {
);
return (
<Row>
<Row style={{ display: 'flex', gap: '12px' }}>
{orderBasedSortedVariables &&
Array.isArray(orderBasedSortedVariables) &&
orderBasedSortedVariables.length > 0 &&

View File

@ -16,7 +16,7 @@ import { VariableResponseProps } from 'types/api/dashboard/variables/query';
import { popupContainer } from 'utils/selectPopupContainer';
import { variablePropsToPayloadVariables } from '../utils';
import { SelectItemStyle, VariableContainer, VariableValue } from './styles';
import { SelectItemStyle } from './styles';
import { areArraysEqual } from './util';
const ALL_SELECT_VALUE = '__ALL__';
@ -214,11 +214,11 @@ function VariableItem({
}, [variableData.type, variableData.customValue]);
return (
<VariableContainer className="variable-item">
<div className="variable-item">
<Typography.Text className="variable-name" ellipsis>
${variableData.name}
</Typography.Text>
<VariableValue>
<div className="variable-value">
{variableData.type === 'TEXTBOX' ? (
<Input
placeholder="Enter value"
@ -283,8 +283,8 @@ function VariableItem({
</Popover>
</span>
)}
</VariableValue>
</VariableContainer>
</div>
</div>
);
}

View File

@ -1,17 +1,17 @@
import GridGraphLayout from 'container/GridCardLayout';
import ComponentsSlider from 'container/NewDashboard/ComponentsSlider';
import { useDashboard } from 'providers/Dashboard/Dashboard';
import { FullScreenHandle } from 'react-full-screen';
import { GridComponentSliderContainer } from './styles';
function GridGraphs(): JSX.Element {
const { isDashboardSliderOpen } = useDashboard();
interface GridGraphsProps {
handle: FullScreenHandle;
}
function GridGraphs(props: GridGraphsProps): JSX.Element {
const { handle } = props;
return (
<GridComponentSliderContainer>
{isDashboardSliderOpen && <ComponentsSlider />}
<GridGraphLayout />
<GridGraphLayout handle={handle} />
</GridComponentSliderContainer>
);
}

View File

@ -1,12 +1,15 @@
import { useFullScreenHandle } from 'react-full-screen';
import Description from './DashboardDescription';
import GridGraphs from './GridGraphs';
function NewDashboard(): JSX.Element {
const handle = useFullScreenHandle();
return (
<>
<Description />
<GridGraphs />
</>
<div style={{ overflowX: 'hidden' }}>
<Description handle={handle} />
<GridGraphs handle={handle} />
</div>
);
}

View File

@ -1,184 +1,184 @@
.explorer-columns-renderer {
margin-top: 10px;
margin-top: 10px;
margin-bottom: 30px;
.title {
display: flex;
align-items: center;
gap: 4px;
}
.title {
display: flex;
align-items: center;
gap: 4px;
padding-left: 16px;
}
.ant-typography {
color: var(rgba(255, 255, 255, 0.85));
font-family: "Inter";
font-size: 13px;
font-style: normal;
font-weight: 400;
line-height: 22px;
letter-spacing: 0.5px;
}
.ant-typography {
color: var(rgba(255, 255, 255, 0.85));
font-family: 'Inter';
font-size: 13px;
font-style: normal;
font-weight: 400;
line-height: 22px;
letter-spacing: 0.5px;
}
.ant-divider {
margin: 8px 0 !important;
border: 0.5px solid var(--bg-slate-400);
}
.ant-divider {
margin: 8px 0 !important;
border: 0.5px solid var(--bg-slate-400);
}
.explorer-columns-contents {
display: flex;
justify-content: space-between;
align-items: center;
.explorer-columns-contents {
display: flex;
justify-content: space-between;
align-items: center;
padding-left: 16px;
padding-right: 8px;
.explorer-columns {
display: flex;
align-items: center;
gap: 12px;
overflow-x: scroll;
min-width: 90%;
.explorer-columns {
display: flex;
align-items: center;
gap: 12px;
overflow-x: scroll;
min-width: 90%;
.explorer-columns-list {
display: flex !important;
}
.explorer-column-card {
display: flex;
align-items: center;
justify-content: space-between;
padding: 4px;
min-width: 200px;
border-radius: 2px;
border: 1px solid var(--colorBorder, rgba(118, 136, 201, 0.12));
background: var(--bg-slate-500);
cursor: unset;
.explorer-columns-list {
display: flex !important;
}
.explorer-column-title {
display: flex;
align-items: center;
gap: 8px;
font-family: Inter;
font-size: 12px;
cursor: grab;
}
.lucide-trash2 {
cursor: pointer !important;
}
.explorer-column-card {
display: flex;
align-items: center;
justify-content: space-between;
padding: 4px;
min-width: 200px;
border-radius: 2px;
border: 1px solid var(--colorBorder, rgba(118, 136, 201, 0.12));
background: var(--bg-slate-500);
cursor: unset;
}
}
.explorer-column-title {
display: flex;
align-items: center;
gap: 8px;
font-family: Inter;
font-size: 12px;
cursor: grab;
}
.explorer-columns::-webkit-scrollbar {
height: 0px; /* Height of the scrollbar */
}
.lucide-trash2 {
cursor: pointer !important;
}
}
}
.action-btn {
display: flex;
align-items: center;
justify-content: center;
width: 32px;
height: 32px;
padding: 0px 16px;
border-radius: 2px;
background: var(--bg-robin-400);
}
}
.explorer-columns::-webkit-scrollbar {
height: 0px; /* Height of the scrollbar */
}
.action-btn {
display: flex;
align-items: center;
justify-content: center;
width: 32px;
height: 32px;
padding: 0px 16px;
border-radius: 2px;
background: var(--bg-robin-400);
}
}
}
.explorer-columns-search {
border: 1px solid rgba(118, 136, 201, 0.12);
border-radius: 6px;
padding: 0px;
background:#141414;
> input {
height: 32px;
padding: 0 6px;
}
border: 1px solid rgba(118, 136, 201, 0.12);
border-radius: 6px;
padding: 0px;
background: #141414;
> input {
height: 32px;
padding: 0 6px;
}
}
.explorer-columns-dropdown {
height: 200px;
background-color: var(--bg-slate-500);
overflow: hidden !important;
.ant-dropdown-menu {
padding: 0;
height: 200px;
background-color: var(--bg-slate-500);
overflow: hidden !important;
.ant-dropdown-menu {
padding: 0;
.ant-dropdown-menu-item {
padding: 4px;
.ant-checkbox-wrapper {
padding: 2px 8px !important;
}
.ant-dropdown-menu-item {
padding: 4px;
.ant-checkbox-wrapper {
padding: 2px 8px !important;
}
.attribute-columns {
display: flex;
flex-direction: column;
height: 160px;
overflow: scroll;
}
.attribute-columns {
display: flex;
flex-direction: column;
height: 160px;
overflow: scroll;
}
.attribute-columns::-webkit-scrollbar {
width: 3px; /* Width of the scrollbar */
}
.attribute-columns::-webkit-scrollbar-track {
background: var(--bg-slate-500); /* Color of the track */
}
.attribute-columns::-webkit-scrollbar-thumb {
background: var(--bg-vanilla-400); /* Color of the thumb */
border-radius: 4px; /* Roundness of the thumb */
}
.attribute-columns::-webkit-scrollbar-thumb:hover {
background: var(--bg-vanilla-300); /* Color of the thumb on hover */
}
}
}
.attribute-columns::-webkit-scrollbar {
width: 3px; /* Width of the scrollbar */
}
.attribute-columns::-webkit-scrollbar-track {
background: var(--bg-slate-500); /* Color of the track */
}
.attribute-columns::-webkit-scrollbar-thumb {
background: var(--bg-vanilla-400); /* Color of the thumb */
border-radius: 4px; /* Roundness of the thumb */
}
.attribute-columns::-webkit-scrollbar-thumb:hover {
background: var(--bg-vanilla-300); /* Color of the thumb on hover */
}
}
}
}
.lightMode {
.explorer-columns-renderer {
.explorer-columns-renderer {
.ant-divider {
border: 0.5px solid var(--bg-vanilla-300);
}
.ant-divider {
border: 0.5px solid var(--bg-vanilla-300);
}
.explorer-columns {
.explorer-column-card {
border: 1px solid var(--colorBorder, rgba(118, 136, 201, 0.12));
background: var(--bg-vanilla-200);
}
}
.explorer-columns {
.explorer-column-card {
border: 1px solid var(--colorBorder, rgba(118, 136, 201, 0.12));
background: var(--bg-vanilla-200);
}
}
.explorer-columns-search {
border: 1px solid rgba(118, 136, 201, 0.12);
}
}
.explorer-columns-search {
border: 1px solid rgba(118, 136, 201, 0.12);
}
}
.explorer-columns-dropdown {
background-color: var(--bg-vanilla-100);
.explorer-columns-dropdown {
background-color: var(--bg-vanilla-100);
.ant-dropdown-menu-item {
.attribute-columns {
&::-webkit-scrollbar {
width: 3px; /* Width of the scrollbar */
}
.ant-dropdown-menu-item {
.attribute-columns {
&::-webkit-scrollbar {
width: 3px; /* Width of the scrollbar */
}
&::-webkit-scrollbar-track {
background: var(--bg-vanilla-200); /* Color of the track */
}
&::-webkit-scrollbar-thumb {
background: var(--bg-vanilla-400); /* Color of the thumb */
}
&::-webkit-scrollbar-thumb:hover {
background: var(--bg-vanilla-300); /* Color of the thumb on hover */
}
}
}
}
&::-webkit-scrollbar-track {
background: var(--bg-vanilla-200); /* Color of the track */
}
.explorer-columns-search {
background: var(--bg-vanilla-100);
}
&::-webkit-scrollbar-thumb {
background: var(--bg-vanilla-400); /* Color of the thumb */
}
&::-webkit-scrollbar-thumb:hover {
background: var(--bg-vanilla-300); /* Color of the thumb on hover */
}
}
}
}
.explorer-columns-search {
background: var(--bg-vanilla-100);
}
}

View File

@ -0,0 +1,16 @@
.query-section-left-container {
border: none;
border-top: 1px solid var(--Slate-400, #1d212d);
background: var(--Ink-500, #0b0c0e);
.ant-card-body {
padding: 0px;
}
}
.lightMode {
.query-section-left-container {
border-top: 1px solid var(--bg-vanilla-300);
background: var(--bg-vanilla-100);
}
}

View File

@ -1,7 +1,8 @@
.query-header-container {
.action-btn {
display: flex;
align-items: center;
justify-content: center;
}
padding: 0px 8px 0px 16px;
.action-btn {
display: flex;
align-items: center;
justify-content: center;
}
}

View File

@ -21,7 +21,11 @@ function ClickHouseQueryContainer(): JSX.Element | null {
queryData={q}
/>
))}
<QueryButton onClick={addQueryHandler} icon={<PlusOutlined />}>
<QueryButton
onClick={addQueryHandler}
icon={<PlusOutlined />}
style={{ margin: '0.4rem 1rem' }}
>
Query
</QueryButton>
</>

View File

@ -25,7 +25,11 @@ function PromQLQueryContainer(): JSX.Element | null {
/>
),
)}
<QueryButton onClick={addQueryHandler} icon={<PlusOutlined />}>
<QueryButton
onClick={addQueryHandler}
icon={<PlusOutlined />}
style={{ margin: '0.4rem 1rem' }}
>
Query
</QueryButton>
</>

View File

@ -8,6 +8,15 @@
display: flex;
align-items: center;
justify-content: center;
gap: 4px;
color: #fff;
font-family: Inter;
font-size: 12px;
font-style: normal;
font-weight: 400;
line-height: 18px; /* 150% */
letter-spacing: -0.06px;
padding: 7px 23px;
.prom-ql-icon {
height: 14px;
@ -17,6 +26,7 @@
}
.ant-btn-default {
border-color: transparent;
box-shadow: none;
}
}
.ant-tabs-tab-active {
@ -27,16 +37,26 @@
.ant-tabs-nav {
margin: 0px;
margin-bottom: 0.5rem;
.ant-tabs-nav-wrap {
padding: 8px 16px;
}
.ant-tabs-extra-content {
padding-right: 8px;
}
}
.ant-tabs-nav::before {
border-bottom: none !important;
}
.ant-tabs-nav-list {
border: 1px solid var(--bg-slate-200);
border-radius: 2px;
border: 1px solid var(--bg-slate-400);
background: var(--bg-ink-400);
box-shadow: 0px 0px 8px 0px rgba(0, 0, 0, 0.1);
}
.ant-tabs-tab + .ant-tabs-tab {
border-left: 1px solid var(--bg-slate-200) !important;
border-left: 1px solid var(--bg-slate-400) !important;
}
.stage-run-query {
display: flex;
@ -46,11 +66,16 @@
.lightMode {
.dashboard-navigation {
.nav-btns {
color: var(---bg-ink-300);
background-color: var(--bg-vanilla-200);
}
.ant-tabs-nav-list {
border: 1px solid var(--bg-vanilla-300);
background: var(--bg-vanilla-300);
}
.ant-tabs-tab + .ant-tabs-tab {
border-left: 1px solid var(--bg-vanilla-200) !important;
border-left: 1px solid var(--bg-vanilla-100) !important;
}
.ant-tabs-tab-active {
.nav-btns {

View File

@ -147,11 +147,10 @@ function QuerySection({
{
key: EQueryType.QUERY_BUILDER,
label: (
<Tooltip title="Query Builder">
<Button className="nav-btns">
<Atom size={14} />
</Button>
</Tooltip>
<Button className="nav-btns">
<Atom size={14} />
<Typography>Query Builder</Typography>
</Button>
),
tab: <Typography>Query Builder</Typography>,
children: (
@ -169,11 +168,10 @@ function QuerySection({
{
key: EQueryType.QUERY_BUILDER,
label: (
<Tooltip title="Query Builder">
<Button className="nav-btns">
<Atom size={14} />
</Button>
</Tooltip>
<Button className="nav-btns">
<Atom size={14} />
<Typography>Query Builder</Typography>
</Button>
),
tab: <Typography>Query Builder</Typography>,
children: (
@ -187,11 +185,10 @@ function QuerySection({
{
key: EQueryType.CLICKHOUSE,
label: (
<Tooltip title="ClickHouse">
<Button className="nav-btns">
<Terminal size={14} />
</Button>
</Tooltip>
<Button className="nav-btns">
<Terminal size={14} />
<Typography>ClickHouse Query</Typography>
</Button>
),
tab: <Typography>ClickHouse Query</Typography>,
children: <ClickHouseQueryContainer />,
@ -204,6 +201,7 @@ function QuerySection({
<PromQLIcon
fillColor={isDarkMode ? Color.BG_VANILLA_200 : Color.BG_INK_300}
/>
<Typography>PromQL</Typography>
</Button>
</Tooltip>
),

View File

@ -1,28 +1,14 @@
import { EQueryType } from 'types/common/dashboard';
import { Tag } from '../styles';
function QueryTypeTag({ queryType }: IQueryTypeTagProps): JSX.Element {
switch (queryType) {
case EQueryType.QUERY_BUILDER:
return (
<span>
<Tag color="geekblue">Query Builder</Tag>
</span>
);
return <span>Query Builder</span>;
case EQueryType.CLICKHOUSE:
return (
<span>
<Tag color="orange">ClickHouse Query</Tag>
</span>
);
return <span>ClickHouse Query</span>;
case EQueryType.PROM:
return (
<span>
<Tag color="green">PromQL</Tag>
</span>
);
return <span>PromQL</span>;
default:
return <span />;
}

View File

@ -1,8 +1,8 @@
import { PANEL_TYPES } from 'constants/queryBuilder';
import { Spline } from 'lucide-react';
import { EQueryType } from 'types/common/dashboard';
import QueryTypeTag from '../QueryTypeTag';
import { PlotTagWrapperStyled } from './styles';
interface IPlotTagProps {
queryType: EQueryType;
@ -15,9 +15,10 @@ function PlotTag({ queryType, panelType }: IPlotTagProps): JSX.Element | null {
}
return (
<PlotTagWrapperStyled $panelType={panelType}>
Plotted using <QueryTypeTag queryType={queryType} />
</PlotTagWrapperStyled>
<div className="plot-tag">
<Spline size={14} />
Plotted with <QueryTypeTag queryType={queryType} />
</div>
);
}

View File

@ -0,0 +1,42 @@
.widget-graph {
border: none;
background-color: unset;
background-image: radial-gradient(var(--bg-slate-400) 1px, transparent 0);
background-size: 20px 20px;
padding: 16px;
.header {
display: flex;
align-items: center;
justify-content: space-between;
.plot-tag {
display: inline-flex;
padding: 4px 4px 4px 6px;
align-items: center;
gap: 6px;
border-radius: 4px;
background: var(--Slate-400, #1d212d);
backdrop-filter: blur(6px);
width: fit-content;
}
}
.header:has(.date-time-selector:only-child) {
justify-content: end;
}
}
.lightMode {
.widget-graph {
background-color: var(--bg-vanilla-100);
background-image: radial-gradient(var(--bg-vanilla-400) 1px, transparent 0);
background-size: 20px 20px;
.header {
.plot-tag {
background: var(--bg-vanilla-300);
}
}
}
}

View File

@ -2,6 +2,7 @@ import { QueryParams } from 'constants/query';
import { PANEL_TYPES } from 'constants/queryBuilder';
import PanelWrapper from 'container/PanelWrapper/PanelWrapper';
import { CustomTimeType } from 'container/TopNav/DateTimeSelectionV2/config';
import { useIsDarkMode } from 'hooks/useDarkMode';
import useUrlQuery from 'hooks/useUrlQuery';
import { GetQueryResultsProps } from 'lib/dashboard/getQueryResults';
import GetMinMax from 'lib/getMinMax';
@ -84,8 +85,24 @@ function WidgetGraph({
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const isDarkMode = useIsDarkMode();
return (
<div ref={graphRef} style={{ height: '100%' }}>
<div
ref={graphRef}
style={{
height: '80%',
width: '80%',
margin: 'auto auto',
borderRadius: '3px',
border: isDarkMode
? '1px solid var(--bg-slate-500)'
: '1px solid var(--bg-vanilla-300)',
background: isDarkMode
? 'linear-gradient(0deg, rgba(171, 189, 255, 0.00) 0%, rgba(171, 189, 255, 0.00) 100%), #0B0C0E'
: 'var(--bg-vanilla-100)',
}}
>
<PanelWrapper
widget={selectedWidget}
queryResponse={queryResponse}

View File

@ -1,6 +1,10 @@
import './WidgetGraph.styles.scss';
import { InfoCircleOutlined } from '@ant-design/icons';
import { Card } from 'container/GridCardLayout/styles';
import DateTimeSelectionV2 from 'container/TopNav/DateTimeSelectionV2';
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
import { useIsDarkMode } from 'hooks/useDarkMode';
import { memo } from 'react';
import { WidgetGraphContainerProps } from '../../types';
@ -16,13 +20,22 @@ function WidgetGraph({
}: WidgetGraphContainerProps): JSX.Element {
const { currentQuery } = useQueryBuilder();
const isDarkMode = useIsDarkMode();
if (selectedWidget === undefined) {
return <Card $panelType={selectedGraph}>Invalid widget</Card>;
return (
<Card $panelType={selectedGraph} isDarkMode={isDarkMode}>
Invalid widget
</Card>
);
}
return (
<Container $panelType={selectedGraph}>
<PlotTag queryType={currentQuery.queryType} panelType={selectedGraph} />
<Container $panelType={selectedGraph} className="widget-graph">
<div className="header">
<PlotTag queryType={currentQuery.queryType} panelType={selectedGraph} />
<DateTimeSelectionV2 showAutoRefresh={false} hideShareModal />
</div>
{queryResponse.error && (
<AlertIconContainer color="red" title={queryResponse.error.message}>
<InfoCircleOutlined />

View File

@ -12,13 +12,10 @@ export const Container = styled(Card)<Props>`
}
.ant-card-body {
padding: ${({ $panelType }): string =>
$panelType === PANEL_TYPES.TABLE || $panelType === PANEL_TYPES.LIST
? '0 0'
: '1.5rem 0'};
height: 60vh;
display: flex;
flex-direction: column;
padding: 0px;
}
`;

View File

@ -1,3 +1,5 @@
import './LeftContainer.styles.scss';
import { DEFAULT_ENTITY_VERSION } from 'constants/app';
import { initialQueriesMap, PANEL_TYPES } from 'constants/queryBuilder';
import { REACT_QUERY_KEY } from 'constants/reactQueryKeys';
@ -97,7 +99,7 @@ function LeftContainer({
setRequestData={setRequestData}
selectedWidget={selectedWidget}
/>
<QueryContainer>
<QueryContainer className="query-section-left-container">
<QuerySection selectedGraph={selectedGraph} queryResponse={queryResponse} />
{selectedGraph === PANEL_TYPES.LIST && (
<ExplorerColumnsRenderer

View File

@ -3,11 +3,6 @@ import styled from 'styled-components';
export const QueryContainer = styled(Card)`
&&& {
margin-top: 1rem;
min-height: 23.5%;
}
.ant-card-body {
padding: 12px;
}
`;

View File

@ -2,3 +2,78 @@
display: grid;
grid-template-columns: 1fr max-content;
}
.edit-header {
display: flex;
height: 48px;
flex-shrink: 0;
border-bottom: 1px solid var(--bg-slate-500);
background: var(--bg-ink-500);
justify-content: space-between;
align-items: center;
padding: 0px 12px 0px 16px;
.left-header {
display: flex;
gap: 16px;
align-items: center;
.discard-icon {
color: var(--bg-vanilla-100);
cursor: pointer;
}
.configure-panel {
color: var(--bg-vanilla-100);
font-family: Inter;
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: 20px; /* 142.857% */
letter-spacing: -0.07px;
border-left: 1px solid var(--bg-slate-500);
padding-left: 16px;
}
}
.save-btn {
display: flex;
height: 32px;
width: 121px;
padding: 4px 12px 4px 10px;
justify-content: center;
align-items: center;
gap: 4px;
border-radius: 2px;
background: var(--bg-robin-500);
color: var(--bg-vanilla-100);
text-align: center;
font-family: Inter;
font-size: 12px;
font-style: normal;
font-weight: 500;
line-height: 18px; /* 150% */
.ant-btn-icon {
margin-inline-end: 0px !important;
}
}
}
.lightMode {
.edit-header {
border-bottom: 1px solid var(--bg-vanilla-300);
background: var(--bg-vanilla-200);
.left-header {
.discard-icon {
color: var(--bg-ink-300);
}
.configure-panel {
color: var(--bg-slate-300);
border-left: 1px solid var(--bg-vanilla-300);
}
}
}
}

View File

@ -0,0 +1,414 @@
.right-container {
display: flex;
flex-direction: column;
.header {
display: flex;
padding: 14px 14px 14px 12px;
align-items: center;
gap: 8px;
.purple-dot {
width: 8px;
height: 8px;
border-radius: 2px;
background: var(--bg-robin-400);
}
.header-text {
color: var(--bg-vanilla-400);
font-family: Inter;
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: 20px; /* 142.857% */
letter-spacing: -0.07px;
}
}
.name-description {
display: flex;
flex-direction: column;
padding: 12px 12px 16px 12px;
border-top: 1px solid var(--bg-slate-500);
border-bottom: 1px solid var(--bg-slate-500);
gap: 8px;
.typography {
color: var(--bg-vanilla-400);
font-family: 'Space Mono';
font-size: 13px;
font-style: normal;
font-weight: 400;
line-height: 18px; /* 138.462% */
letter-spacing: 0.52px;
text-transform: uppercase;
}
.name-input {
display: flex;
padding: 6px 6px 6px 8px;
align-items: center;
gap: 4px;
flex: 1 0 0;
align-self: stretch;
border-radius: 2px;
border: 1px solid var(--bg-slate-400);
background: var(--bg-ink-300);
color: var(--bg-vanilla-100);
font-family: Inter;
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: 18px; /* 128.571% */
letter-spacing: -0.07px;
margin-bottom: 16px;
}
.description-input {
border-style: unset;
.ant-input {
display: flex;
height: 80px;
padding: 6px 6px 6px 8px;
align-items: flex-start;
gap: 4px;
border-radius: 2px;
border: 1px solid var(--bg-slate-400);
background: var(--bg-ink-300);
color: var(--bg-vanilla-100);
font-family: Inter;
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: 18px; /* 128.571% */
letter-spacing: -0.07px;
}
}
}
.panel-config {
display: flex;
flex-direction: column;
padding: 12px 12px 16px 12px;
gap: 8px;
border-bottom: 1px solid var(--bg-slate-500);
.typography {
color: var(--bg-vanilla-400);
font-family: 'Space Mono';
font-size: 13px;
font-style: normal;
font-weight: 400;
line-height: 18px; /* 138.462% */
letter-spacing: 0.52px;
text-transform: uppercase;
}
.panel-type-select {
.ant-select-selector {
display: flex;
height: 32px;
padding: 6px 6px 6px 8px;
align-items: center;
gap: 4px;
flex-shrink: 0;
border-radius: 2px;
border: 1px solid var(--bg-slate-400);
background: var(--bg-ink-300);
}
.select-option {
display: flex;
align-items: center;
gap: 6px;
.icon {
display: flex;
align-items: center;
}
.display {
color: var(--bg-vanilla-100);
font-family: Inter;
font-size: 12px;
font-style: normal;
font-weight: 400;
line-height: 16px; /* 133.333% */
}
}
}
.fill-gaps {
margin-top: 16px;
display: flex;
padding: 12px;
justify-content: space-between;
align-items: center;
border-radius: 2px;
border: 1px solid var(--bg-slate-400);
background: var(--bg-ink-400);
.fill-gaps-text {
color: var(--bg-vanilla-400);
font-family: 'Space Mono';
font-size: 13px;
font-style: normal;
font-weight: 400;
line-height: 18px; /* 138.462% */
letter-spacing: 0.52px;
text-transform: uppercase;
}
}
.panel-time-text {
margin-top: 16px;
color: var(--bg-vanilla-400);
font-family: 'Space Mono';
font-size: 13px;
font-style: normal;
font-weight: 400;
line-height: 18px; /* 138.462% */
letter-spacing: 0.52px;
text-transform: uppercase;
}
.y-axis-unit-selector {
margin-top: 16px;
display: flex;
flex-direction: column;
gap: 8px;
.heading {
color: var(--bg-vanilla-400);
font-family: 'Space Mono';
font-size: 13px;
font-style: normal;
font-weight: 400;
line-height: 18px; /* 138.462% */
letter-spacing: 0.52px;
text-transform: uppercase;
}
.input {
display: flex;
height: 32px;
padding: 6px 6px 6px 8px;
align-items: center;
gap: 4px;
align-self: stretch;
border-radius: 2px;
border: 1px solid var(--bg-slate-400);
background: var(--bg-ink-300);
.ant-input {
background: var(--bg-ink-300);
}
}
}
.soft-min-max {
display: flex;
align-items: center;
justify-content: space-between;
margin-top: 4px;
gap: 12px;
.container {
display: flex;
height: 32px;
align-items: center;
width: 50%;
border-radius: 2px;
border: 1px solid var(--bg-slate-400);
background: var(--bg-ink-400);
box-shadow: 0px 0px 8px 0px rgba(0, 0, 0, 0.1);
.text {
color: var(--bg-vanilla-400);
font-family: 'Space Mono';
font-size: 12px;
font-style: normal;
font-weight: 400;
line-height: 16px; /* 133.333% */
letter-spacing: 0.48px;
text-transform: uppercase;
width: 50%;
padding: 8px;
}
.input {
width: 50%;
border: none;
border-left: 1px solid var(--bg-slate-400);
}
}
}
}
.alerts {
display: flex;
padding: 12px;
align-items: center;
justify-content: space-between;
border-bottom: 1px solid var(--bg-slate-500);
cursor: pointer;
.left-section {
display: flex;
align-items: center;
gap: 8px;
.bell-icon {
color: var(--bg-vanilla-400);
}
.alerts-text {
color: var(--bg-vanilla-400);
font-family: Inter;
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: 20px; /* 142.857% */
letter-spacing: 0.14px;
}
}
.plus-icon {
color: var(--bg-vanilla-400);
}
}
}
.select-option {
display: flex;
align-items: center;
gap: 6px;
.icon {
display: flex;
align-items: center;
}
.display {
color: var(--bg-vanilla-100);
font-family: Inter;
font-size: 12px;
font-style: normal;
font-weight: 400;
line-height: 16px; /* 133.333% */
}
}
.lightMode {
.right-container {
background-color: var(--bg-vanilla-100);
.header {
.header-text {
color: var(--bg-ink-400);
}
}
.name-description {
border-top: 1px solid var(--bg-vanilla-300);
border-bottom: 1px solid var(--bg-vanilla-300);
.typography {
color: var(--bg-ink-400);
}
.name-input {
border: 1px solid var(--bg-vanilla-300);
background: var(--bg-vanilla-300);
color: var(--bg-ink-300);
}
.description-input {
.ant-input {
border: 1px solid var(--bg-vanilla-300);
background: var(--bg-vanilla-300);
color: var(--bg-ink-300);
}
}
}
.panel-config {
border-bottom: 1px solid var(--bg-vanilla-300);
.typography {
color: var(--bg-ink-400);
}
.panel-type-select {
.ant-select-selector {
border: 1px solid var(--bg-vanilla-300);
background: var(--bg-vanilla-300);
}
.select-option {
.display {
color: var(--bg-ink-300);
}
}
}
.fill-gaps {
border: 1px solid var(--bg-vanilla-300);
background: var(--bg-vanilla-300);
.fill-gaps-text {
color: var(--bg-ink-400);
}
}
.panel-time-text {
color: var(--bg-ink-400);
}
.y-axis-unit-selector {
.heading {
color: var(--bg-ink-400);
}
.input {
border: 1px solid var(--bg-vanilla-300);
background: var(--bg-vanilla-300);
.ant-input {
background: var(--bg-vanilla-300);
}
}
}
.soft-min-max {
.container {
border: 1px solid var(--bg-vanilla-300);
background: var(--bg-vanilla-300);
.text {
color: var(--bg-ink-300);
}
.input {
border-left: 1px solid var(--bg-vanilla-300);
}
}
}
}
.alerts {
border-bottom: 1px solid var(--bg-vanilla-300);
.left-section {
.bell-icon {
color: var(--bg-ink-300);
}
.alerts-text {
color: var(--bg-ink-300);
}
}
.plus-icon {
color: var(--bg-ink-300);
}
}
}
.select-option {
.display {
color: var(--bg-ink-100);
}
}
}

View File

@ -1,7 +1,27 @@
.color-selector-button {
border: none;
.color-selector-space {
display: flex;
justify-content: space-between;
align-items: center;
width: 100%;
}
.color-selector-light {
border: 1px solid #d9d9d9;
.color-selector-button {
border: none;
width: 100%;
.ant-btn {
box-shadow: none;
background-color: unset;
}
}
.lightMode {
.color-selector-button {
background-color: var(--bg-vanilla-300);
.ant-btn {
box-shadow: none;
background-color: unset;
}
}
}

View File

@ -4,7 +4,6 @@ import { DownOutlined } from '@ant-design/icons';
import { Button, ColorPicker, Dropdown, Space } from 'antd';
import { Color } from 'antd/es/color-picker';
import { MenuProps } from 'antd/lib';
import { useIsDarkMode } from 'hooks/useDarkMode';
import useDebounce from 'hooks/useDebounce';
import { Dispatch, SetStateAction, useEffect, useState } from 'react';
@ -18,8 +17,6 @@ function ColorSelector({
const debounceColor = useDebounce(colorFromPicker);
const isDarkMode = useIsDarkMode();
useEffect(() => {
if (debounceColor) {
setColor(debounceColor);
@ -69,11 +66,9 @@ function ColorSelector({
<Dropdown menu={{ items }} trigger={['click']}>
<Button
onClick={(e): void => e.preventDefault()}
className={
isDarkMode ? 'color-selector-button' : 'color-selector-button-light'
}
className="color-selector-button"
>
<Space>
<Space className="color-selector-space">
<CustomColor color={thresholdColor} />
<DownOutlined />
</Space>

View File

@ -1,18 +1,29 @@
.custom-color-container {
display: flex;
gap: 10px;
align-items: center;
display: flex;
gap: 8px;
align-items: center;
.custom-color-typography-dark {
color: #fff !important;
}
.custom-color-typography-dark {
color: #fff !important;
font-family: 'Space Mono';
font-size: 12px;
font-style: normal;
font-weight: 400;
line-height: 16px; /* 133.333% */
}
.custom-color-typography-light {
color: #000 !important;
}
.custom-color-typography-light {
color: #000 !important;
font-family: 'Space Mono';
font-size: 12px;
font-style: normal;
font-weight: 400;
line-height: 16px; /* 133.333% */
}
.custom-color-tag {
width: 20px;
height: 20px;
}
}
.custom-color-tag {
width: 12px;
height: 12px;
border-radius: 12px;
}
}

View File

@ -1,14 +1,13 @@
.show-case-container {
padding: 5px 15px;
border-radius: 5px;
display: inline-block;
padding: 5px 15px;
display: inline-block;
}
.show-case-dark {
background-color: #141414;
background-color: #141414;
}
.show-case-light {
background-color: rgb(255, 255, 255);
border: 1px solid #141414;
}
background-color: rgb(255, 255, 255);
border: 1px solid #141414;
}

View File

@ -1,18 +1,24 @@
import './ShowCaseValue.styles.scss';
import cx from 'classnames';
import { useIsDarkMode } from 'hooks/useDarkMode';
import { ShowCaseValueProps } from './types';
function ShowCaseValue({ width, value }: ShowCaseValueProps): JSX.Element {
function ShowCaseValue({
width,
value,
className = '',
}: ShowCaseValueProps): JSX.Element {
const isDarkMode = useIsDarkMode();
return (
<div
className={
className={cx(
isDarkMode
? `show-case-container show-case-dark`
: `show-case-container show-case-light`
}
: `show-case-container show-case-light`,
className,
)}
style={{ minWidth: width }}
>
{value}

View File

@ -1,58 +1,427 @@
.operator-input-root {
width: auto !important;
color: var(--bg-vanilla-400);
font-family: 'Space Mono';
font-size: 12px;
font-style: normal;
font-weight: 400;
line-height: 16px; /* 133.333% */
letter-spacing: 0.48px;
}
.threshold-container {
margin-top: 12px;
.threshold-card {
padding: 0px;
border-radius: 10px;
position: relative;
margin-top: 10px;
.threshold-card-container {
display: flex;
position: relative;
flex-direction: column;
align-items: flex-start;
gap: 12px;
flex-shrink: 0;
padding: 12px;
border-radius: 2px;
border: 1px solid var(--bg-slate-500);
background: var(--bg-ink-400);
.threshold-card-container {
display: flex;
flex-direction: column;
gap: 10px;
}
.edit-action-btns {
position: absolute;
right: -8px;
top: -14px;
display: none;
.ant-typography {
font-size: 14px;
font-weight: 400;
line-height: 22px;
}
.ant-btn {
display: flex;
width: 24px;
height: 24px;
padding: 4px;
justify-content: center;
align-items: center;
gap: 10px;
flex-shrink: 0;
background: var(--bg-slate-400);
}
.ant-typograph-dark {
color: #FFFFFF73;
}
.ant-btn + .ant-btn {
border-left: 1px solid var(--bg-slate-200);
}
.ant-typograph-light {
color: #00000073;
}
.edit-btn {
border-radius: 2px 0px 0px 2px;
}
}
.delete-btn {
border-radius: 0px 2px 2px 0px;
}
}
.threshold-card-dark {
background-color: #1F1F1F;
}
.time-series-alerts {
display: flex;
border-radius: 2px;
border: 1px solid var(--bg-slate-400);
background: var(--bg-ink-400);
box-shadow: 0px 0px 8px 0px rgba(0, 0, 0, 0.1);
width: 100%;
align-items: center;
.threshold-card-light {
background-color: rgb(255, 255, 255);
}
.label {
color: var(--bg-vanilla-400);
font-family: 'Space Mono';
font-size: 12px;
font-style: normal;
font-weight: 400;
line-height: 16px; /* 133.333% */
letter-spacing: 0.48px;
text-transform: uppercase;
width: 20%;
padding: 8px;
}
.threshold-action-button {
position: absolute;
right: 10px;
top: 10px;
}
.label-input {
height: 32px;
width: 80%;
flex-shrink: 0;
border-radius: 2px;
border-left: 1px solid var(--bg-slate-400);
background: var(--bg-ink-400);
box-shadow: 0px 0px 8px 0px rgba(0, 0, 0, 0.1);
color: var(--bg-vanilla-100);
font-family: 'Space Mono';
font-size: 12px;
font-style: normal;
font-weight: 400;
line-height: 16px; /* 133.333% */
padding: 8px;
}
}
.threshold-action-icon {
font-size: 16px;
}
.value-table-alerts {
display: flex;
align-items: center;
gap: 10px;
.threshold-units-selector {
display: flex;
align-items: center;
}
.operator-input {
.ant-select-selector {
background-color: var(--bg-ink-300) !important;
min-width: 50px;
max-width: 150px;
color: var(--bg-vanilla-100);
font-family: 'Space Mono';
font-size: 12px;
font-style: normal;
font-weight: 400;
line-height: 16px; /* 133.333% */
}
}
.threshold-color-picker {
display: flex;
flex-direction: column;
}
}
.typography {
color: var(--bg-vanilla-400);
font-family: 'Space Mono';
font-size: 12px;
font-style: normal;
font-weight: 400;
line-height: 16px; /* 133.333% */
letter-spacing: 0.48px;
}
.typography-preview {
color: var(--bg-vanilla-400);
font-family: 'Space Mono';
font-size: 12px;
font-style: normal;
font-weight: 400;
line-height: 16px; /* 133.333% */
letter-spacing: 0.48px;
background-color: var(--bg-ink-300) !important;
max-width: 150px;
overflow-x: hidden;
text-overflow: ellipsis;
}
}
.threshold-units-selector {
display: flex;
border-radius: 2px 2px 0px 0px;
border: 1px solid var(--bg-slate-400);
background: var(--bg-ink-400);
box-shadow: 0px 0px 8px 0px rgba(0, 0, 0, 0.1);
height: 32px;
flex-shrink: 0;
width: 100%;
.unit-input {
width: 50%;
border: none;
color: var(--bg-vanilla-400);
font-family: 'Space Mono';
font-size: 12px;
font-style: normal;
font-weight: 400;
line-height: 16px; /* 133.333% */
background: var(--bg-ink-400);
}
.unit-selection {
width: 50%;
border: none;
border-left: 1px solid var(--bg-slate-400);
color: var(--bg-vanilla-400);
font-family: 'Space Mono';
font-size: 12px;
font-style: normal;
font-weight: 400;
line-height: 16px; /* 133.333% */
.ant-select-selector {
border: none;
height: unset;
color: var(--bg-vanilla-400);
font-family: 'Space Mono';
font-size: 12px;
font-style: normal;
font-weight: 400;
line-height: 16px; /* 133.333% */
}
}
.unit-selection-prev {
width: 50%;
border: none;
border-left: 1px solid var(--bg-slate-400);
color: var(--bg-vanilla-400);
font-family: 'Space Mono';
font-size: 12px;
font-style: normal;
font-weight: 400;
line-height: 16px; /* 133.333% */
background: var(--bg-ink-400);
}
}
.thresholds-color-selector {
display: flex;
align-items: center;
border-radius: 0px 0px 2px 2px;
border: 1px solid var(--bg-slate-400);
border-top: none;
background: var(--bg-ink-400);
box-shadow: 0px 0px 8px 0px rgba(0, 0, 0, 0.1);
height: 32px;
flex-shrink: 0;
width: 100%;
margin-top: -12px;
.color-selector {
width: 50%;
color: var(--bg-vanilla-100);
font-family: 'Space Mono';
font-size: 12px;
font-style: normal;
font-weight: 400;
line-height: 16px; /* 133.333% */
background: var(--bg-ink-400);
.ant-btn {
box-shadow: none;
height: auto;
}
}
.color-format {
width: 50%;
border: none;
border-left: 1px solid var(--bg-slate-400);
color: var(--bg-vanilla-100);
font-family: 'Space Mono';
font-size: 12px;
font-style: normal;
font-weight: 400;
line-height: 16px; /* 133.333% */
.ant-select-selector {
border: none;
height: unset;
color: var(--bg-vanilla-400);
font-family: 'Space Mono';
font-size: 12px;
font-style: normal;
font-weight: 400;
line-height: 16px; /* 133.333% */
background-color: unset;
}
}
.color-format-prev {
width: 50%;
border: none;
border-left: 1px solid var(--bg-slate-400);
color: var(--bg-vanilla-100);
font-family: 'Space Mono';
font-size: 12px;
font-style: normal;
font-weight: 400;
line-height: 16px; /* 133.333% */
background: var(--bg-ink-400);
}
}
.threshold-action-button {
display: flex;
align-items: center;
width: 100%;
gap: 12px;
.discard-btn {
display: flex;
width: 50%;
align-items: center;
height: 34px;
padding: 4px 8px 4px 10px;
justify-content: center;
gap: 6px;
flex: 1 0 0;
border-radius: 2px;
border: 1px solid var(--bg-slate-400);
background: var(--bg-slate-500);
box-shadow: none;
}
.save-changes {
display: flex;
width: 50%;
align-items: center;
height: 34px;
padding: 4px 8px 4px 10px;
justify-content: center;
gap: 6px;
flex: 1 0 0;
border-radius: 2px;
border: 1px solid var(--bg-robin-500);
background: var(--bg-robin-500);
box-shadow: none;
}
}
}
.threshold-card-container:hover {
.edit-action-btns {
display: flex;
}
}
}
.lightMode {
.operator-input-root {
color: var(--bg-ink-400);
}
.threshold-container {
.threshold-card-container {
border: 1px solid var(--bg-vanilla-300);
background: var(--bg-vanilla-100);
.edit-action-btns {
.ant-btn {
background: var(--bg-vanilla-300);
}
.ant-btn + .ant-btn {
border-left: 1px solid var(--bg-vanilla-100);
}
}
.time-series-alerts {
border: 1px solid var(--bg-vanilla-300);
background: var(--bg-vanilla-100);
.label {
color: var(--bg-ink-400);
}
.label-input {
border-left: 1px solid var(--bg-vanilla-300);
background: var(--bg-vanilla-300);
color: var(--bg-ink-300);
border: 1px solid var(--bg-vanilla-300);
box-shadow: none;
}
}
.value-table-alerts {
.operator-input {
.ant-select-selector {
background-color: var(--bg-vanilla-300) !important;
color: var(--bg-ink-300);
}
}
.typography {
color: var(--bg-ink-400);
}
.typography-preview {
color: var(--bg-ink-400);
border: 1px solid var(--bg-vanilla-300);
background-color: var(--bg-vanilla-300) !important;
}
}
.threshold-units-selector {
border: 1px solid var(--bg-vanilla-300);
background: var(--bg-vanilla-300);
.unit-input {
color: var(--bg-ink-400);
background: var(--bg-vanilla-300);
}
.unit-selection {
border-left: 1px solid var(--bg-vanilla-100);
color: var(--bg-ink-400);
.ant-select-selector {
background-color: var(--bg-vanilla-300);
color: var(--bg-ink-400);
}
}
.unit-selection-prev {
border-left: 1px solid var(--bg-vanilla-100);
color: var(--bg-ink-400);
background: var(--bg-vanilla-300);
}
}
.thresholds-color-selector {
border: 1px solid var(--bg-vanilla-300);
border-top: 1px solid var(--bg-vanilla-100);
background: var(--bg-vanilla-300);
.color-selector {
color: var(--bg-ink-300);
background: var(--bg-vanilla-300);
border: 1px solid var(--bg-vanilla-300);
border-top: 1px solid var(--bg-vanilla-100);
}
.color-format {
border-left: 1px solid var(--bg-vanilla-100);
color: var(--bg-ink-300);
.ant-select-selector {
color: var(--bg-ink-400);
}
}
.color-format-prev {
border-left: 1px solid var(--bg-vanilla-100);
color: var(--bg-ink-300);
background: var(--bg-vanilla-300);
}
}
.threshold-action-button {
.discard-btn {
border: 1px solid var(--bg-vanilla-300);
background: var(--bg-vanilla-300);
}
.save-changes {
color: var(--bg-vanilla-100);
}
}
}
}
}

View File

@ -1,18 +1,10 @@
/* eslint-disable sonarjs/cognitive-complexity */
import './Threshold.styles.scss';
import { CheckOutlined, DeleteOutlined, EditOutlined } from '@ant-design/icons';
import {
Card,
Divider,
Input,
InputNumber,
Select,
Space,
Typography,
} from 'antd';
import { Button, Input, InputNumber, Select, Space, Typography } from 'antd';
import { PANEL_TYPES } from 'constants/queryBuilder';
import { useIsDarkMode } from 'hooks/useDarkMode';
import { Check, Pencil, Trash2, X } from 'lucide-react';
import { useRef, useState } from 'react';
import { useDrag, useDrop, XYCoord } from 'react-dnd';
@ -29,6 +21,7 @@ import { ThresholdProps } from './types';
const wrapStyle = {
flexWrap: 'wrap',
gap: '10px',
} as React.CSSProperties;
function Threshold({
@ -90,6 +83,16 @@ function Threshold({
);
};
const discardHandler = (): void => {
setIsEditMode(false);
setOperator(thresholdOperator);
setValue(thresholdValue);
setUnit(thresholdUnit);
setColor(thresholdColor);
setFormat(thresholdFormat);
setLabel(thresholdLabel);
setTableSelectedOption(thresholdTableOptions);
};
const editHandler = (): void => {
setIsEditMode(true);
};
@ -187,7 +190,6 @@ function Threshold({
setLabel(event.target.value);
};
const backgroundColor = !isDarkMode ? '#ffffff' : '#141414';
const allowDragAndDrop = panelTypeVsDragAndDrop[selectedGraph];
return (
@ -197,147 +199,165 @@ function Threshold({
data-handler-id={handlerId}
className="threshold-container"
>
<Card
className={
isDarkMode
? `threshold-card threshold-card-dark`
: `threshold-card threshold-card-light`
}
>
<div className="threshold-card-container">
<div className="threshold-action-button">
{isEditMode ? (
<CheckOutlined onClick={saveHandler} />
) : (
<EditOutlined className="threshold-action-icon" onClick={editHandler} />
)}
<Divider type="vertical" />
<DeleteOutlined
className="threshold-action-icon"
<div className="threshold-card-container">
{!isEditMode && (
<div className="edit-action-btns">
<Button
type="text"
icon={<Pencil size={14} />}
className="edit-btn"
onClick={editHandler}
/>
<Button
type="text"
icon={<Trash2 size={14} />}
className="delete-btn"
onClick={deleteHandler}
/>
</div>
<div>
<Space
direction={
selectedGraph === PANEL_TYPES.TABLE ? 'vertical' : 'horizontal'
}
>
{selectedGraph === PANEL_TYPES.TIME_SERIES && (
<Space style={wrapStyle}>
<Typography.Text>Label</Typography.Text>
{isEditMode ? (
<Input
defaultValue={label}
onChange={handleLabelChange}
bordered={!isDarkMode}
style={{ backgroundColor }}
/>
) : (
<ShowCaseValue width="180px" value={label || 'none'} />
)}
</Space>
)}
<div style={{ width: '100%' }}>
{selectedGraph === PANEL_TYPES.TIME_SERIES && (
<div className="time-series-alerts">
<Typography.Text className="label">Label</Typography.Text>
{isEditMode ? (
<Input
defaultValue={label}
onChange={handleLabelChange}
bordered={!isDarkMode}
className="label-input"
/>
) : (
<ShowCaseValue value={label || 'none'} className="label-input" />
)}
{(selectedGraph === PANEL_TYPES.VALUE ||
selectedGraph === PANEL_TYPES.TABLE) && (
<>
<Typography.Text>
If value {selectedGraph === PANEL_TYPES.TABLE ? 'in' : 'is'}
</Typography.Text>
{isEditMode ? (
<>
{selectedGraph === PANEL_TYPES.TABLE && (
<Space style={wrapStyle}>
<Select
style={{
minWidth: '150px',
backgroundColor,
borderRadius: '5px',
}}
defaultValue={tableSelectedOption}
options={tableOptions}
bordered={!isDarkMode}
showSearch
onChange={handleTableOptionsChange}
/>
<Typography.Text>is</Typography.Text>
</Space>
)}
</div>
)}
{(selectedGraph === PANEL_TYPES.VALUE ||
selectedGraph === PANEL_TYPES.TABLE) && (
<div className="value-table-alerts">
<Typography.Text className="typography">
If value {selectedGraph === PANEL_TYPES.TABLE ? 'in' : 'is'}
</Typography.Text>
{isEditMode ? (
<div>
{selectedGraph === PANEL_TYPES.TABLE && (
<Space style={wrapStyle}>
<Select
style={{ minWidth: '73px', backgroundColor }}
defaultValue={operator}
options={operatorOptions}
onChange={handleOperatorChange}
defaultValue={tableSelectedOption}
options={tableOptions}
bordered={!isDarkMode}
showSearch
onChange={handleTableOptionsChange}
rootClassName="operator-input-root"
className="operator-input"
/>
</>
) : (
<>
{selectedGraph === PANEL_TYPES.TABLE && (
<Space style={wrapStyle}>
<ShowCaseValue width="150px" value={tableSelectedOption} />
<Typography.Text>is</Typography.Text>
</Space>
)}
<ShowCaseValue width="49px" value={operator} />
</>
<Typography.Text className="typography">is</Typography.Text>
</Space>
)}
</>
)}
</Space>
</div>
<div className="threshold-units-selector">
<Space style={wrapStyle}>
{isEditMode ? (
<InputNumber
style={{ backgroundColor }}
defaultValue={value}
onChange={handleValueChange}
bordered={!isDarkMode}
/>
<Select
defaultValue={operator}
options={operatorOptions}
onChange={handleOperatorChange}
bordered={!isDarkMode}
style={{ marginLeft: '10px' }}
rootClassName="operator-input-root"
className="operator-input"
/>
</div>
) : (
<ShowCaseValue width="60px" value={value} />
<div>
{selectedGraph === PANEL_TYPES.TABLE && (
<Space>
<ShowCaseValue
value={tableSelectedOption}
className="typography-preview"
/>
<Typography.Text
className="typography"
style={{ marginRight: '10px' }}
>
is
</Typography.Text>
</Space>
)}
<ShowCaseValue
width="50px"
value={operator}
className="typography-preview"
/>
</div>
)}
{isEditMode ? (
<Select
style={{ minWidth: '200px', backgroundColor }}
bordered={!isDarkMode}
defaultValue={unit}
options={unitOptions}
onChange={handleUnitChange}
showSearch
/>
) : (
<ShowCaseValue width="200px" value={unit} />
)}
</Space>
</div>
<div>
<Space direction="vertical">
<Typography.Text>Show with</Typography.Text>
<Space style={wrapStyle}>
{isEditMode ? (
<>
<ColorSelector setColor={setColor} thresholdColor={color} />
<Select
style={{ minWidth: '100px', backgroundColor }}
defaultValue={format}
options={showAsOptions}
onChange={handlerFormatChange}
bordered={!isDarkMode}
/>
</>
) : (
<>
<ShowCaseValue width="120px" value={<CustomColor color={color} />} />
<ShowCaseValue width="100px" value={format} />
</>
)}
</Space>
</Space>
</div>
</div>
)}
</div>
</Card>
<div className="threshold-units-selector">
{isEditMode ? (
<InputNumber
defaultValue={value}
onChange={handleValueChange}
className="unit-input"
/>
) : (
<ShowCaseValue value={value} className="unit-input" />
)}
{isEditMode ? (
<Select
defaultValue={unit}
options={unitOptions}
onChange={handleUnitChange}
showSearch
className="unit-selection"
/>
) : (
<ShowCaseValue value={unit} className="unit-selection-prev" />
)}
</div>
<div className="thresholds-color-selector">
{isEditMode ? (
<>
<div className="color-selector">
<ColorSelector setColor={setColor} thresholdColor={color} />
</div>
<Select
defaultValue={format}
options={showAsOptions}
onChange={handlerFormatChange}
rootClassName="color-format"
/>
</>
) : (
<>
<ShowCaseValue
value={<CustomColor color={color} />}
className="color-selector"
/>
<ShowCaseValue
width="100px"
value={format}
className="color-format-prev"
/>
</>
)}
</div>
{isEditMode && (
<div className="threshold-action-button">
<Button
className="discard-btn"
icon={<X size={14} />}
onClick={discardHandler}
>
Discard
</Button>
<Button
className="save-changes"
icon={<Check size={14} />}
onClick={saveHandler}
>
Save Changes
</Button>
</div>
)}
</div>
</div>
);
}

View File

@ -1,18 +1,54 @@
.threshold-selector-container {
.threshold-selector-button {
margin-top: 20px;
width: 100%;
border-radius: 10px;
padding: 10px 0;
height: 50px;
border-color: #1C64F2;
color: #1C64F2;
span {
font-size: 14px;
font-weight: 400;
line-height: 22px;
letter-spacing: 0.5px;
}
}
}
padding: 12px;
padding-bottom: 80px;
.threshold-select {
display: flex;
align-items: center;
justify-content: space-between;
cursor: pointer;
.icon {
color: var(--bg-vanilla-400);
cursor: pointer;
}
.left-section {
display: flex;
align-items: center;
gap: 8px;
.icon {
color: var(--bg-vanilla-400);
}
.text {
color: var(--bg-vanilla-400);
font-family: Inter;
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: 20px; /* 142.857% */
letter-spacing: 0.14px;
}
}
}
}
.lightMode {
.threshold-selector-container {
.threshold-select {
.icon {
color: var(--bg-ink-400);
}
.left-section {
.icon {
color: var(--bg-ink-400);
}
.text {
color: var(--bg-ink-400);
}
}
}
}
}

View File

@ -1,9 +1,12 @@
/* eslint-disable jsx-a11y/no-static-element-interactions */
/* eslint-disable jsx-a11y/click-events-have-key-events */
import './ThresholdSelector.styles.scss';
import { Button, Typography } from 'antd';
import { Typography } from 'antd';
import { ColumnsType } from 'antd/es/table';
import { Events } from 'constants/events';
import { RowData } from 'lib/query/createTableColumnsFromQuery';
import { Antenna, Plus } from 'lucide-react';
import { useCallback, useEffect, useState } from 'react';
import { DndProvider } from 'react-dnd';
import { HTML5Backend } from 'react-dnd-html5-backend';
@ -52,7 +55,6 @@ function ThresholdSelector({
const addThresholdHandler = (): void => {
setThresholds([
...thresholds,
{
index: uuid(),
isEditEnabled: true,
@ -66,6 +68,7 @@ function ThresholdSelector({
selectedGraph,
thresholdTableOptions: tableOptions[0]?.value || '',
},
...thresholds,
]);
};
@ -79,7 +82,13 @@ function ThresholdSelector({
return (
<DndProvider backend={HTML5Backend}>
<div className="threshold-selector-container">
<Typography.Text>Thresholds</Typography.Text>
<div className="threshold-select" onClick={addThresholdHandler}>
<div className="left-section">
<Antenna size={14} className="icon" />
<Typography.Text className="text">Thresholds</Typography.Text>
</div>
<Plus size={14} onClick={addThresholdHandler} className="icon" />
</div>
{thresholds.map((threshold, idx) => (
<Threshold
key={threshold.index}
@ -100,9 +109,6 @@ function ThresholdSelector({
thresholdTableOptions={threshold.thresholdTableOptions}
/>
))}
<Button className="threshold-selector-button" onClick={addThresholdHandler}>
+ Add threshold
</Button>
</div>
</DndProvider>
);

View File

@ -22,8 +22,9 @@ export type ThresholdProps = {
};
export type ShowCaseValueProps = {
width: string;
width?: string;
value: ReactNode;
className?: string;
};
export type CustomColorProps = {

View File

@ -1,4 +1,4 @@
import { AutoComplete, Col, Input, Typography } from 'antd';
import { AutoComplete, Input, Typography } from 'antd';
import { find } from 'lodash-es';
import { Dispatch, SetStateAction } from 'react';
@ -29,10 +29,11 @@ function YAxisUnitSelector({
value: options.name,
}));
return (
<Col style={{ marginBottom: 12, marginTop: 12 }}>
<Typography.Text>{fieldLabel}</Typography.Text>
<div className="y-axis-unit-selector">
<Typography.Text className="heading">{fieldLabel}</Typography.Text>
<AutoComplete
style={{ width: '100%' }}
rootClassName="y-axis-root-popover"
options={options}
defaultValue={findCategoryById(defaultValue)?.name}
onSelect={onSelectHandler}
@ -45,9 +46,9 @@ function YAxisUnitSelector({
return false;
}}
>
<Input size="large" placeholder="Unit" allowClear />
<Input placeholder="Unit" allowClear rootClassName="input" />
</AutoComplete>
</Col>
</div>
);
}

View File

@ -1,15 +1,8 @@
import { UploadOutlined } from '@ant-design/icons';
import {
Button,
Divider,
Input,
InputNumber,
Select,
Space,
Switch,
Typography,
} from 'antd';
import InputComponent from 'components/Input';
/* eslint-disable jsx-a11y/no-static-element-interactions */
/* eslint-disable jsx-a11y/click-events-have-key-events */
import './RightContainer.styles.scss';
import { Input, InputNumber, Select, Space, Switch, Typography } from 'antd';
import TimePreference from 'components/TimePreferenceDropDown';
import { PANEL_TYPES } from 'constants/queryBuilder';
import GraphTypes, {
@ -17,6 +10,7 @@ import GraphTypes, {
} from 'container/NewDashboard/ComponentsSlider/menuItems';
import useCreateAlerts from 'hooks/queryBuilder/useCreateAlerts';
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
import { ConciergeBell, Plus } from 'lucide-react';
import {
Dispatch,
SetStateAction,
@ -35,7 +29,6 @@ import {
panelTypeVsThreshold,
panelTypeVsYAxisUnit,
} from './constants';
import { Container, Title } from './styles';
import ThresholdSelector from './Threshold/ThresholdSelector';
import { ThresholdProps } from './Threshold/types';
import { timePreferance } from './timeItems';
@ -118,67 +111,72 @@ function RightContainer({
);
return (
<Container>
<Title>Panel Type</Title>
<Select
onChange={setGraphHandler}
value={selectedGraph}
style={{ width: '100%', marginBottom: 24 }}
>
{graphTypes.map((item) => (
<Option key={item.name} value={item.name}>
{item.display}
</Option>
))}
</Select>
<Title>Panel Attributes</Title>
<div className="right-container">
<section className="header">
<div className="purple-dot" />
<Typography.Text className="header-text">Panel details</Typography.Text>
</section>
<section className="name-description">
<Typography.Text className="typography">Name</Typography.Text>
<Input
placeholder="Enter the panel name here..."
onChange={(event): void => onChangeHandler(setTitle, event.target.value)}
value={title}
rootClassName="name-input"
/>
<Typography.Text className="typography">Description</Typography.Text>
<TextArea
placeholder="Enter the panel description here..."
bordered
allowClear
value={description}
onChange={(event): void =>
onChangeHandler(setDescription, event.target.value)
}
rootClassName="description-input"
/>
</section>
<section className="panel-config">
<Typography.Text className="typography">Panel Type</Typography.Text>
<Select
onChange={setGraphHandler}
value={selectedGraph}
style={{ width: '100%' }}
className="panel-type-select"
>
{graphTypes.map((item) => (
<Option key={item.name} value={item.name}>
<div className="select-option">
<div className="icon">{item.icon}</div>
<Typography.Text className="display">{item.display}</Typography.Text>
</div>
</Option>
))}
</Select>
<InputComponent
label="Panel Title"
size="middle"
placeholder="Title"
labelOnTop
onChangeHandler={(event): void =>
onChangeHandler(setTitle, event.target.value)
}
value={title}
/>
{allowFillSpans && (
<Space className="fill-gaps">
<Typography className="fill-gaps-text">Fill gaps</Typography>
<Switch
checked={isFillSpans}
size="small"
onChange={(checked): void => setIsFillSpans(checked)}
/>
</Space>
)}
<Title light="true">Description</Title>
<TextArea
placeholder="Write something describing the panel"
bordered
allowClear
value={description}
onChange={(event): void =>
onChangeHandler(setDescription, event.target.value)
}
/>
{allowFillSpans && (
<Space style={{ marginTop: 10 }} direction="vertical">
<Typography>Fill gaps</Typography>
<Switch
checked={isFillSpans}
onChange={(checked): void => setIsFillSpans(checked)}
/>
</Space>
)}
{allowPanelTimePreference && (
<Title light="true">Panel Time Preference</Title>
)}
<Space direction="vertical">
{allowPanelTimePreference && (
<TimePreference
{...{
selectedTime,
setSelectedTime,
}}
/>
<>
<Typography.Text className="panel-time-text">
Panel Time Preference
</Typography.Text>
<TimePreference
{...{
selectedTime,
setSelectedTime,
}}
/>
</>
)}
{allowYAxisUnit && (
@ -188,50 +186,51 @@ function RightContainer({
fieldLabel={selectedGraphType === 'Value' ? 'Unit' : 'Y Axis Unit'}
/>
)}
{allowCreateAlerts && (
<Button icon={<UploadOutlined />} onClick={onCreateAlertsHandler}>
Create Alerts from Queries
</Button>
{allowSoftMinMax && (
<section className="soft-min-max">
<section className="container">
<Typography.Text className="text">Soft Min</Typography.Text>
<InputNumber
type="number"
value={softMin}
onChange={softMinHandler}
rootClassName="input"
/>
</section>
<section className="container">
<Typography.Text className="text">Soft Max</Typography.Text>
<InputNumber
value={softMax}
type="number"
rootClassName="input"
onChange={softMaxHandler}
/>
</section>
</section>
)}
</Space>
</section>
{allowSoftMinMax && (
<>
<Divider />
<Typography.Text style={{ display: 'block', margin: '5px 0' }}>
Soft Min
</Typography.Text>
<InputNumber
type="number"
value={softMin}
style={{ display: 'block', width: '100%' }}
onChange={softMinHandler}
/>
<Typography.Text style={{ display: 'block', margin: '5px 0' }}>
Soft Max
</Typography.Text>
<InputNumber
value={softMax}
type="number"
style={{ display: 'block', width: '100%' }}
onChange={softMaxHandler}
/>
</>
{allowCreateAlerts && (
<section className="alerts" onClick={onCreateAlertsHandler}>
<div className="left-section">
<ConciergeBell size={14} className="bell-icon" />
<Typography.Text className="alerts-text">Alerts</Typography.Text>
</div>
<Plus size={14} className="plus-icon" />
</section>
)}
{allowThreshold && (
<>
<Divider />
<section>
<ThresholdSelector
thresholds={thresholds}
setThresholds={setThresholds}
yAxisUnit={yAxisUnit}
selectedGraph={selectedGraph}
/>
</>
</section>
)}
</Container>
</div>
);
}

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