diff --git a/ui/index.html b/ui/index.html index 2f66dee1f7..8d56cd023a 100644 --- a/ui/index.html +++ b/ui/index.html @@ -1,4 +1,4 @@ - + diff --git a/ui/src/api/axiosClient.ts b/ui/src/api/axiosClient.ts index dd78232cf0..20a4d93512 100644 --- a/ui/src/api/axiosClient.ts +++ b/ui/src/api/axiosClient.ts @@ -1,14 +1,11 @@ -import axios from 'axios'; +import axios from "axios"; const vmClarityApiAxiosClient = axios.create({ baseURL: `${window.location.origin}/api`, }); const vmClarityUIBackendAxiosClient = axios.create({ - baseURL: `${window.location.origin}/ui/api`, + baseURL: `${window.location.origin}/ui/api`, }); -export { - vmClarityApiAxiosClient, - vmClarityUIBackendAxiosClient, -}; +export { vmClarityApiAxiosClient, vmClarityUIBackendAxiosClient }; diff --git a/ui/src/api/constants.ts b/ui/src/api/constants.ts index 8f8493a747..7666e0d39a 100644 --- a/ui/src/api/constants.ts +++ b/ui/src/api/constants.ts @@ -1,6 +1,6 @@ const QUERY_KEYS = { - assets: 'assets', - findings: 'findings', + assets: "assets", + findings: "findings", }; export default QUERY_KEYS; diff --git a/ui/src/api/vmClarityApi.ts b/ui/src/api/vmClarityApi.ts index b8c5c96de2..aa541a61f1 100644 --- a/ui/src/api/vmClarityApi.ts +++ b/ui/src/api/vmClarityApi.ts @@ -1,11 +1,19 @@ -import { vmClarityApiAxiosClient, vmClarityUIBackendAxiosClient } from './axiosClient'; -import { VMClarityUIBackendApi } from './generated-ui-backend'; -import { VMClarityApi } from './generated-api'; +import { + vmClarityApiAxiosClient, + vmClarityUIBackendAxiosClient, +} from "./axiosClient"; +import { VMClarityUIBackendApi } from "./generated-ui-backend"; +import { VMClarityApi } from "./generated-api"; -const vmClarityApi = new VMClarityApi(undefined, undefined, vmClarityApiAxiosClient); -const vmClarityUIBackend = new VMClarityUIBackendApi(undefined, undefined, vmClarityUIBackendAxiosClient); +const vmClarityApi = new VMClarityApi( + undefined, + undefined, + vmClarityApiAxiosClient, +); +const vmClarityUIBackend = new VMClarityUIBackendApi( + undefined, + undefined, + vmClarityUIBackendAxiosClient, +); -export { - vmClarityApi, - vmClarityUIBackend, -}; +export { vmClarityApi, vmClarityUIBackend }; diff --git a/ui/src/components/Arrow/arrow.scss b/ui/src/components/Arrow/arrow.scss index 12dc343ea9..33bb9984b6 100644 --- a/ui/src/components/Arrow/arrow.scss +++ b/ui/src/components/Arrow/arrow.scss @@ -1,11 +1,11 @@ .arrow-icon { - &.right-arrow { - transform: rotate(180deg); - } - &.top-arrow { - transform: rotate(90deg); - } - &.bottom-arrow { - transform: rotate(-90deg); - } -} \ No newline at end of file + &.right-arrow { + transform: rotate(180deg); + } + &.top-arrow { + transform: rotate(90deg); + } + &.bottom-arrow { + transform: rotate(-90deg); + } +} diff --git a/ui/src/components/Arrow/index.jsx b/ui/src/components/Arrow/index.jsx index 2546aef380..bd175f50d9 100644 --- a/ui/src/components/Arrow/index.jsx +++ b/ui/src/components/Arrow/index.jsx @@ -1,30 +1,40 @@ -import React from 'react'; -import classnames from 'classnames'; -import Icon, { ICON_NAMES } from 'components/Icon'; +import React from "react"; +import classnames from "classnames"; +import Icon, { ICON_NAMES } from "components/Icon"; -import './arrow.scss'; +import "./arrow.scss"; export const ARROW_NAMES = { - TOP: "top", - BOTTOM: "bottom", - RIGHT: "right", - LEFT: "left" -} + TOP: "top", + BOTTOM: "bottom", + RIGHT: "right", + LEFT: "left", +}; -const Arrow = ({name=ARROW_NAMES.TOP, onClick, disabled, small=false}) => { - if (!Object.values(ARROW_NAMES).includes(name)) { - console.error(`Arrow name '${name}' does not exist`); - } +const Arrow = ({ + name = ARROW_NAMES.TOP, + onClick, + disabled, + small = false, +}) => { + if (!Object.values(ARROW_NAMES).includes(name)) { + console.error(`Arrow name '${name}' does not exist`); + } - return ( - - ); -} + return ( + + ); +}; -export default Arrow; \ No newline at end of file +export default Arrow; diff --git a/ui/src/components/BackRouteButton/back-route-button.scss b/ui/src/components/BackRouteButton/back-route-button.scss index 14b0a671d3..87a05030a1 100644 --- a/ui/src/components/BackRouteButton/back-route-button.scss +++ b/ui/src/components/BackRouteButton/back-route-button.scss @@ -1,15 +1,15 @@ -@import 'utils/scss_variables.module.scss'; +@import "utils/scss_variables.module.scss"; .back-route-button { - color: $color-main; - display: flex; - align-items: center; - font-size: 18px; - line-height: 18px; - cursor: pointer; - margin-bottom: 24px; + color: $color-main; + display: flex; + align-items: center; + font-size: 18px; + line-height: 18px; + cursor: pointer; + margin-bottom: 24px; - .icon { - margin-right: 14px; - } -} \ No newline at end of file + .icon { + margin-right: 14px; + } +} diff --git a/ui/src/components/BackRouteButton/index.jsx b/ui/src/components/BackRouteButton/index.jsx index 43f9d85952..b37c73668f 100644 --- a/ui/src/components/BackRouteButton/index.jsx +++ b/ui/src/components/BackRouteButton/index.jsx @@ -1,18 +1,18 @@ -import React from 'react'; -import { useNavigate } from 'react-router-dom'; -import Arrow, { ARROW_NAMES } from 'components/Arrow'; +import React from "react"; +import { useNavigate } from "react-router-dom"; +import Arrow, { ARROW_NAMES } from "components/Arrow"; -import './back-route-button.scss'; +import "./back-route-button.scss"; -const BackRouteButton = ({title, pathname}) => { - const navigate = useNavigate(); +const BackRouteButton = ({ title, pathname }) => { + const navigate = useNavigate(); - return ( -
navigate(pathname)}> - -
{title}
-
- ) -} + return ( +
navigate(pathname)}> + +
{title}
+
+ ); +}; -export default BackRouteButton; \ No newline at end of file +export default BackRouteButton; diff --git a/ui/src/components/Button/button.scss b/ui/src/components/Button/button.scss index 26364993c3..75def56bdc 100644 --- a/ui/src/components/Button/button.scss +++ b/ui/src/components/Button/button.scss @@ -1,60 +1,60 @@ -@import 'utils/scss_variables.module.scss'; +@import "utils/scss_variables.module.scss"; .clarity-button { - border: none; - background-color: transparent; - height: 26px; - font-size: 12px; + border: none; + background-color: transparent; + height: 26px; + font-size: 12px; - &.clarity-button--primary, - &.clarity-button--secondary { - font-weight: 400; - line-height: 14px; - text-align: center; - box-sizing: border-box; - border-radius: 50px; - display: inline-block; - padding: 0px 20px; + &.clarity-button--primary, + &.clarity-button--secondary { + font-weight: 400; + line-height: 14px; + text-align: center; + box-sizing: border-box; + border-radius: 50px; + display: inline-block; + padding: 0px 20px; - &:disabled { - background-color: $color-grey-light; - color: $color-grey; - } + &:disabled { + background-color: $color-grey-light; + color: $color-grey; } - &.clarity-button--primary { - background-color: $color-main-light; - color: $color-main-dark; + } + &.clarity-button--primary { + background-color: $color-main-light; + color: $color-main-dark; - &:hover:not(:disabled) { - background-color: $color-main-lighter; - } + &:hover:not(:disabled) { + background-color: $color-main-lighter; } - &.clarity-button--secondary { - background-color: $color-blue; - color: $color-main-dark; + } + &.clarity-button--secondary { + background-color: $color-blue; + color: $color-main-dark; - &:hover:not(:disabled) { - background-color: $color-blue-light; - } + &:hover:not(:disabled) { + background-color: $color-blue-light; } - &.clarity-button--tertiary { - line-height: 16px; - color: $color-main; - text-decoration: underline; - padding: 0; + } + &.clarity-button--tertiary { + line-height: 16px; + color: $color-main; + text-decoration: underline; + padding: 0; - &:hover:not(:disabled) { - color: $color-main-dark; - } - &:disabled { - color: $color-grey; - text-decoration: none; - } - } - &.clickable:not(:disabled) { - cursor: pointer; + &:hover:not(:disabled) { + color: $color-main-dark; } &:disabled { - cursor: not-allowed; + color: $color-grey; + text-decoration: none; } -} \ No newline at end of file + } + &.clickable:not(:disabled) { + cursor: pointer; + } + &:disabled { + cursor: not-allowed; + } +} diff --git a/ui/src/components/Button/index.jsx b/ui/src/components/Button/index.jsx index a967f6b605..4ad0a56393 100644 --- a/ui/src/components/Button/index.jsx +++ b/ui/src/components/Button/index.jsx @@ -1,24 +1,31 @@ -import React from 'react'; -import classnames from 'classnames'; +import React from "react"; +import classnames from "classnames"; -import './button.scss'; +import "./button.scss"; -const Button = ({children, secondary, tertiary, disabled, onClick, className}) => ( - -) +const Button = ({ + children, + secondary, + tertiary, + disabled, + onClick, + className, +}) => ( + +); -export default Button; \ No newline at end of file +export default Button; diff --git a/ui/src/components/ButtonWithIcon/button-with-icon.scss b/ui/src/components/ButtonWithIcon/button-with-icon.scss index 4353c2c57e..74d121bc25 100644 --- a/ui/src/components/ButtonWithIcon/button-with-icon.scss +++ b/ui/src/components/ButtonWithIcon/button-with-icon.scss @@ -1,10 +1,10 @@ -@import 'utils/scss_variables.module.scss'; +@import "utils/scss_variables.module.scss"; .button-with-icon.clarity-button { - display: flex; - align-items: center; + display: flex; + align-items: center; - .icon { - margin-right: 5px; - } -} \ No newline at end of file + .icon { + margin-right: 5px; + } +} diff --git a/ui/src/components/ButtonWithIcon/index.jsx b/ui/src/components/ButtonWithIcon/index.jsx index a292c1ac36..a92f951582 100644 --- a/ui/src/components/ButtonWithIcon/index.jsx +++ b/ui/src/components/ButtonWithIcon/index.jsx @@ -1,16 +1,25 @@ +import React from "react"; +import classnames from "classnames"; +import Button from "components/Button"; +import Icon from "components/Icon"; -import React from 'react'; -import classnames from 'classnames'; -import Button from 'components/Button'; -import Icon from 'components/Icon'; +import "./button-with-icon.scss"; -import './button-with-icon.scss'; - -const ButtonWithIcon = ({onClick, iconName, disabled, className, children}) => ( - +const ButtonWithIcon = ({ + onClick, + iconName, + disabled, + className, + children, +}) => ( + ); -export default ButtonWithIcon; \ No newline at end of file +export default ButtonWithIcon; diff --git a/ui/src/components/CloseButton/close-button.scss b/ui/src/components/CloseButton/close-button.scss index c3b1471a34..7b943b909d 100644 --- a/ui/src/components/CloseButton/close-button.scss +++ b/ui/src/components/CloseButton/close-button.scss @@ -1,3 +1,3 @@ .close-button { - cursor: pointer; -} \ No newline at end of file + cursor: pointer; +} diff --git a/ui/src/components/CloseButton/index.jsx b/ui/src/components/CloseButton/index.jsx index 8e9679c87f..f1f7621e50 100644 --- a/ui/src/components/CloseButton/index.jsx +++ b/ui/src/components/CloseButton/index.jsx @@ -1,10 +1,15 @@ -import React from 'react'; -import Icon, { ICON_NAMES } from 'components/Icon'; +import React from "react"; +import Icon, { ICON_NAMES } from "components/Icon"; -import './close-button.scss'; +import "./close-button.scss"; -const CloseButton = ({onClose, small=false}) => ( - -) +const CloseButton = ({ onClose, small = false }) => ( + +); -export default CloseButton; \ No newline at end of file +export default CloseButton; diff --git a/ui/src/components/ContentContainer/content-container.scss b/ui/src/components/ContentContainer/content-container.scss index 99a3f7390b..388fc59d2c 100644 --- a/ui/src/components/ContentContainer/content-container.scss +++ b/ui/src/components/ContentContainer/content-container.scss @@ -1,18 +1,18 @@ -@import 'utils/scss_variables.module.scss'; +@import "utils/scss_variables.module.scss"; .content-container-wrapper { - background-color: white; - border: 1px solid $color-grey-light; - box-shadow: 0px 0px 10px rgba(34, 43, 54, 0.12); - min-height: 500px; - position: relative; - display: flex; - flex-direction: column; + background-color: white; + border: 1px solid $color-grey-light; + box-shadow: 0px 0px 10px rgba(34, 43, 54, 0.12); + min-height: 500px; + position: relative; + display: flex; + flex-direction: column; - &.with-margin { - margin: $main-content-padding; - } - > * { - flex-grow: 1; - } -} \ No newline at end of file + &.with-margin { + margin: $main-content-padding; + } + > * { + flex-grow: 1; + } +} diff --git a/ui/src/components/ContentContainer/index.jsx b/ui/src/components/ContentContainer/index.jsx index b6caefb9d8..4f4f2b038b 100644 --- a/ui/src/components/ContentContainer/index.jsx +++ b/ui/src/components/ContentContainer/index.jsx @@ -1,12 +1,16 @@ -import React from 'react'; -import classnames from 'classnames'; +import React from "react"; +import classnames from "classnames"; -import './content-container.scss'; +import "./content-container.scss"; -const ContentContainer = ({children, withMargin=false}) => ( -
- {children} -
+const ContentContainer = ({ children, withMargin = false }) => ( +
+ {children} +
); -export default ContentContainer; \ No newline at end of file +export default ContentContainer; diff --git a/ui/src/components/CopyButton/index.jsx b/ui/src/components/CopyButton/index.jsx index 88ff0c9a53..e14c6b53db 100644 --- a/ui/src/components/CopyButton/index.jsx +++ b/ui/src/components/CopyButton/index.jsx @@ -1,11 +1,11 @@ -import React from 'react'; -import copy from 'copy-to-clipboard'; -import Button from 'components/Button'; +import React from "react"; +import copy from "copy-to-clipboard"; +import Button from "components/Button"; -const CopyButton = ({copyText}) => ( - -) +const CopyButton = ({ copyText }) => ( + +); -export default CopyButton; \ No newline at end of file +export default CopyButton; diff --git a/ui/src/components/DetailsPageWrapper/details-page-wrapper.scss b/ui/src/components/DetailsPageWrapper/details-page-wrapper.scss index 72f7e256da..4a3acb7b64 100644 --- a/ui/src/components/DetailsPageWrapper/details-page-wrapper.scss +++ b/ui/src/components/DetailsPageWrapper/details-page-wrapper.scss @@ -1,18 +1,18 @@ -@import 'utils/scss_variables.module.scss'; +@import "utils/scss_variables.module.scss"; .details-page-wrapper { - &.with-padding { - padding: $main-content-padding; - } - .details-page-content-wrapper { - .details-page-title { - display: flex; - align-items: baseline; - margin-bottom: 22px; + &.with-padding { + padding: $main-content-padding; + } + .details-page-content-wrapper { + .details-page-title { + display: flex; + align-items: baseline; + margin-bottom: 22px; - .details-page-title-sub { - margin-left: 10px; - } - } + .details-page-title-sub { + margin-left: 10px; + } } -} \ No newline at end of file + } +} diff --git a/ui/src/components/DetailsPageWrapper/index.jsx b/ui/src/components/DetailsPageWrapper/index.jsx index 8840cc74e6..82c03298ce 100644 --- a/ui/src/components/DetailsPageWrapper/index.jsx +++ b/ui/src/components/DetailsPageWrapper/index.jsx @@ -1,48 +1,81 @@ -import React from 'react'; -import classnames from 'classnames'; -import { useLocation, useParams } from 'react-router-dom'; -import BackRouteButton from 'components/BackRouteButton'; -import ContentContainer from 'components/ContentContainer'; -import Loader from 'components/Loader'; -import Title from 'components/Title'; -import { useFetch } from 'hooks'; +import React from "react"; +import classnames from "classnames"; +import { useLocation, useParams } from "react-router-dom"; +import BackRouteButton from "components/BackRouteButton"; +import ContentContainer from "components/ContentContainer"; +import Loader from "components/Loader"; +import Title from "components/Title"; +import { useFetch } from "hooks"; -import './details-page-wrapper.scss'; +import "./details-page-wrapper.scss"; -const DetailsContentWrapper = ({data, getTitleData, detailsContent: DetailsContent, fetchData}) => { - const {title, subTitle} = getTitleData(data); +const DetailsContentWrapper = ({ + data, + getTitleData, + detailsContent: DetailsContent, + fetchData, +}) => { + const { title, subTitle } = getTitleData(data); - return ( -
-
- {title} - {!!subTitle &&
{subTitle}
} -
- -
- ) -} + return ( +
+
+ {title} + {!!subTitle &&
{subTitle}
} +
+ + + +
+ ); +}; -const DetailsPageWrapper = ({className, backTitle, url, expand, select, getTitleData, detailsContent, withPadding=false}) => { - const {pathname} = useLocation(); - const params = useParams(); - const {id} = params; - const innerPath = params["*"]; - - const expandParams = !!expand ? `$expand=${expand}` : ""; - const selectParams = !!select ? `$select=${select}` : ""; - const [{loading, data, error}, fetchData] = useFetch( - `${url}/${id}${!!expandParams || !!selectParams ? "?" : ""}${selectParams}${!!selectParams ? "&" : ""}${expandParams}` - ); +const DetailsPageWrapper = ({ + className, + backTitle, + url, + expand, + select, + getTitleData, + detailsContent, + withPadding = false, +}) => { + const { pathname } = useLocation(); + const params = useParams(); + const { id } = params; + const innerPath = params["*"]; - return ( -
- - {loading ? : (!!error ? null : - ) - } -
- ) -} + const expandParams = !!expand ? `$expand=${expand}` : ""; + const selectParams = !!select ? `$select=${select}` : ""; + const [{ loading, data, error }, fetchData] = useFetch( + `${url}/${id}${!!expandParams || !!selectParams ? "?" : ""}${selectParams}${!!selectParams ? "&" : ""}${expandParams}`, + ); -export default DetailsPageWrapper; \ No newline at end of file + return ( +
+ + {loading ? ( + + ) : !!error ? null : ( + + )} +
+ ); +}; + +export default DetailsPageWrapper; diff --git a/ui/src/components/DoublePaneDisplay/double-pane-display.scss b/ui/src/components/DoublePaneDisplay/double-pane-display.scss index 519c5de224..3e7350cf4e 100644 --- a/ui/src/components/DoublePaneDisplay/double-pane-display.scss +++ b/ui/src/components/DoublePaneDisplay/double-pane-display.scss @@ -1,15 +1,15 @@ -@import 'utils/scss_variables.module.scss'; +@import "utils/scss_variables.module.scss"; .double-pane-display-wrapper { - display: flex; - height: 100%; + display: flex; + height: 100%; - > * { - width: 50%; - padding: $main-content-padding; - overflow-x: auto; - } - .left-pane-display { - border-right: 1px solid $color-grey-light; - } -} \ No newline at end of file + > * { + width: 50%; + padding: $main-content-padding; + overflow-x: auto; + } + .left-pane-display { + border-right: 1px solid $color-grey-light; + } +} diff --git a/ui/src/components/DoublePaneDisplay/index.jsx b/ui/src/components/DoublePaneDisplay/index.jsx index 6b7d2a8d8f..6ea23805d1 100644 --- a/ui/src/components/DoublePaneDisplay/index.jsx +++ b/ui/src/components/DoublePaneDisplay/index.jsx @@ -1,13 +1,21 @@ -import React from 'react'; -import classnames from 'classnames'; +import React from "react"; +import classnames from "classnames"; -import './double-pane-display.scss'; +import "./double-pane-display.scss"; -const DoublePaneDisplay = ({className, rightPlaneDisplay: RightPlaneDisplay, leftPaneDisplay: LeftPaneDisplay}) => ( -
-
{!!LeftPaneDisplay && }
-
{!!RightPlaneDisplay && }
+const DoublePaneDisplay = ({ + className, + rightPlaneDisplay: RightPlaneDisplay, + leftPaneDisplay: LeftPaneDisplay, +}) => ( +
+
+ {!!LeftPaneDisplay && }
-) +
+ {!!RightPlaneDisplay && } +
+
+); -export default DoublePaneDisplay; \ No newline at end of file +export default DoublePaneDisplay; diff --git a/ui/src/components/DoublePaneDisplay/index.test.tsx b/ui/src/components/DoublePaneDisplay/index.test.tsx index d8886287aa..a6934548c9 100644 --- a/ui/src/components/DoublePaneDisplay/index.test.tsx +++ b/ui/src/components/DoublePaneDisplay/index.test.tsx @@ -1,17 +1,23 @@ -import React from 'react'; -import { describe, test, expect } from 'vitest' -import { render } from '@testing-library/react'; -import DoublePaneDisplay from './index'; +import React from "react"; +import { describe, test, expect } from "vitest"; +import { render } from "@testing-library/react"; +import DoublePaneDisplay from "./index"; -describe('DoublePaneDisplay', () => { - test('renders without error', () => { - const wrapper = render(); - expect(wrapper).toBeTruthy(); +describe("DoublePaneDisplay", () => { + test("renders without error", () => { + const wrapper = render( + , + ); + expect(wrapper).toBeTruthy(); - const left = wrapper.container.querySelector('.left-pane-display'); - expect(left).toBeTruthy(); + const left = wrapper.container.querySelector(".left-pane-display"); + expect(left).toBeTruthy(); - const right = wrapper.container.querySelector('.right-pane-display'); - expect(right).toBeTruthy(); - }); + const right = wrapper.container.querySelector(".right-pane-display"); + expect(right).toBeTruthy(); + }); }); diff --git a/ui/src/components/DropdownSelect/dropdown-select.scss b/ui/src/components/DropdownSelect/dropdown-select.scss index 94280948aa..c838005864 100644 --- a/ui/src/components/DropdownSelect/dropdown-select.scss +++ b/ui/src/components/DropdownSelect/dropdown-select.scss @@ -1,10 +1,10 @@ .dropdown-select { - &.small { - .dropdown-select__multi-value__label { - font-size: 9px; - } + &.small { + .dropdown-select__multi-value__label { + font-size: 9px; } - .dropdown-select__input-container { - margin: 0 2px; - } -} \ No newline at end of file + } + .dropdown-select__input-container { + margin: 0 2px; + } +} diff --git a/ui/src/components/DropdownSelect/index.jsx b/ui/src/components/DropdownSelect/index.jsx index 282c316cfe..2f651707f1 100644 --- a/ui/src/components/DropdownSelect/index.jsx +++ b/ui/src/components/DropdownSelect/index.jsx @@ -1,117 +1,141 @@ -import React from 'react'; -import Select from 'react-select'; -import CreatableSelect from 'react-select/creatable'; -import classnames from 'classnames'; +import React from "react"; +import Select from "react-select"; +import CreatableSelect from "react-select/creatable"; +import classnames from "classnames"; -import COLORS from 'utils/scss_variables.module.scss'; +import COLORS from "utils/scss_variables.module.scss"; -import './dropdown-select.scss'; +import "./dropdown-select.scss"; const DropdownSelect = (props) => { - const {items, value, onChange, creatable=false, clearable=false, disabled=false, placeholder="Select...", isMulti=false, - className, components, small, onBlur} = props; + const { + items, + value, + onChange, + creatable = false, + clearable = false, + disabled = false, + placeholder = "Select...", + isMulti = false, + className, + components, + small, + onBlur, + } = props; - const SelectComponent = creatable ? CreatableSelect : Select; - const height = small ? 20 : 36; - const innerHeight = height - 2; - - return ( - ({ - ...provided, - minHeight: height, - borderRadius: 4, - borderColor: COLORS["color-grey-light"], - boxShadow: "none", - "&:hover": { - ...provided["&:hover"], - borderColor: COLORS["color-grey-light"] - }, - backgroundColor: "white", - cursor: "pointer", - fontSize: small ? 10 : 14, - lineHeight: small ? "14px" : "18px" - }), - option: (provided, state) => { - const {isSelected, isDisabled} = state; - - return ({ - ...provided, - color: isSelected ? COLORS["color-grey-dark"] : (isDisabled ? COLORS["color-grey-light"] : COLORS["color-grey-dark"]), - backgroundColor: isSelected ? COLORS["color-grey-lighter"] : "transparent", - fontWeight: isSelected ? "bold" : "normal", - cursor: "pointer" - }); - }, - placeholder: (provided, state) => ({ - ...provided, - color: state.isDisabled ? COLORS["color-grey"] : COLORS["color-grey-dark"], - ...((small && !isMulti) ? {height: innerHeight} : {}) - }), - menu: (provided) => ({ - ...provided, - borderRadius: 2, - border: `1px solid ${COLORS["color-grey"]}`, - borderTop: `2px solid ${COLORS["color-main-light"]}`, - fontSize: 14, - lineHeight: "18px" - }), - indicatorsContainer: (provided) => ({ - ...provided, - height: innerHeight - }), - indicatorSeparator: (provided) => ({ - ...provided, - display: small ? "none" : "block" - }), - dropdownIndicator: (provided) => ({ - ...provided, - padding: small ? 0 : 8, - }), - valueContainer: (provided) => ({ - ...provided, - minHeight: innerHeight, - padding: (small && isMulti) ? "0 8px" : "2px 8px" - }), - singleValue: (provided) => ({ - ...provided, - ...(small ? {height: innerHeight} : {}) - }), - multiValue: (provided) => ({ - ...provided, - backgroundColor: COLORS["color-blue-light"], - border: `1px solid ${COLORS["color-blue"]}`, - ...(small ? {height: 14} : {}) - }), - multiValueRemove: (provided, state) => { - const {isDisabled} = state; - const backgroundColor = isDisabled ? COLORS["color-grey-lighter"] : COLORS["color-blue-light"]; - const color = isDisabled ? COLORS["color-grey"] : COLORS["color-main"]; + const SelectComponent = creatable ? CreatableSelect : Select; + const height = small ? 20 : 36; + const innerHeight = height - 2; - return ({ - ...provided, - ":hover": { - ...provided[":hover"], - color: color, - backgroundColor: backgroundColor, - } - }) - } - }} - /> - ) -} + return ( + ({ + ...provided, + minHeight: height, + borderRadius: 4, + borderColor: COLORS["color-grey-light"], + boxShadow: "none", + "&:hover": { + ...provided["&:hover"], + borderColor: COLORS["color-grey-light"], + }, + backgroundColor: "white", + cursor: "pointer", + fontSize: small ? 10 : 14, + lineHeight: small ? "14px" : "18px", + }), + option: (provided, state) => { + const { isSelected, isDisabled } = state; -export default DropdownSelect; \ No newline at end of file + return { + ...provided, + color: isSelected + ? COLORS["color-grey-dark"] + : isDisabled + ? COLORS["color-grey-light"] + : COLORS["color-grey-dark"], + backgroundColor: isSelected + ? COLORS["color-grey-lighter"] + : "transparent", + fontWeight: isSelected ? "bold" : "normal", + cursor: "pointer", + }; + }, + placeholder: (provided, state) => ({ + ...provided, + color: state.isDisabled + ? COLORS["color-grey"] + : COLORS["color-grey-dark"], + ...(small && !isMulti ? { height: innerHeight } : {}), + }), + menu: (provided) => ({ + ...provided, + borderRadius: 2, + border: `1px solid ${COLORS["color-grey"]}`, + borderTop: `2px solid ${COLORS["color-main-light"]}`, + fontSize: 14, + lineHeight: "18px", + }), + indicatorsContainer: (provided) => ({ + ...provided, + height: innerHeight, + }), + indicatorSeparator: (provided) => ({ + ...provided, + display: small ? "none" : "block", + }), + dropdownIndicator: (provided) => ({ + ...provided, + padding: small ? 0 : 8, + }), + valueContainer: (provided) => ({ + ...provided, + minHeight: innerHeight, + padding: small && isMulti ? "0 8px" : "2px 8px", + }), + singleValue: (provided) => ({ + ...provided, + ...(small ? { height: innerHeight } : {}), + }), + multiValue: (provided) => ({ + ...provided, + backgroundColor: COLORS["color-blue-light"], + border: `1px solid ${COLORS["color-blue"]}`, + ...(small ? { height: 14 } : {}), + }), + multiValueRemove: (provided, state) => { + const { isDisabled } = state; + const backgroundColor = isDisabled + ? COLORS["color-grey-lighter"] + : COLORS["color-blue-light"]; + const color = isDisabled + ? COLORS["color-grey"] + : COLORS["color-main"]; + + return { + ...provided, + ":hover": { + ...provided[":hover"], + color: color, + backgroundColor: backgroundColor, + }, + }; + }, + }} + /> + ); +}; + +export default DropdownSelect; diff --git a/ui/src/components/EmptyDisplay/empty-display.scss b/ui/src/components/EmptyDisplay/empty-display.scss index 51a6ed6bda..ae7679335a 100644 --- a/ui/src/components/EmptyDisplay/empty-display.scss +++ b/ui/src/components/EmptyDisplay/empty-display.scss @@ -1,32 +1,32 @@ -@import 'utils/scss_variables.module.scss'; +@import "utils/scss_variables.module.scss"; .empty-display-wrapper { - position: absolute; - top: 15%; - left: 50%; - transform: translateX(-50%); + position: absolute; + top: 15%; + left: 50%; + transform: translateX(-50%); - .empty-display { - display: flex; - flex-direction: column; - align-items: center; - - .empty-dispaly-title { - font-weight: 400; - font-size: 18px; - line-height: 22px; - color: $color-main; - text-align: center; - } - img { - margin: 50px 0; - } - .empty-display-buttons { - display: flex; - - .clarity-button:not(:last-child) { - margin-right: 10px; - } - } + .empty-display { + display: flex; + flex-direction: column; + align-items: center; + + .empty-dispaly-title { + font-weight: 400; + font-size: 18px; + line-height: 22px; + color: $color-main; + text-align: center; + } + img { + margin: 50px 0; + } + .empty-display-buttons { + display: flex; + + .clarity-button:not(:last-child) { + margin-right: 10px; + } } -} \ No newline at end of file + } +} diff --git a/ui/src/components/EmptyDisplay/index.jsx b/ui/src/components/EmptyDisplay/index.jsx index a1d3cb4ee7..6c8e447ef1 100644 --- a/ui/src/components/EmptyDisplay/index.jsx +++ b/ui/src/components/EmptyDisplay/index.jsx @@ -1,23 +1,29 @@ -import React from 'react'; -import { ICON_NAMES } from 'components/Icon'; -import ButtonWithIcon from 'components/ButtonWithIcon'; -import Button from 'components/Button'; +import React from "react"; +import { ICON_NAMES } from "components/Icon"; +import ButtonWithIcon from "components/ButtonWithIcon"; +import Button from "components/Button"; -import emptyImage from 'utils/images/empty.svg'; +import emptyImage from "utils/images/empty.svg"; -import './empty-display.scss'; +import "./empty-display.scss"; -const EmptyDisplay = ({message, title, onClick, subTitle, onSubClick}) => ( -
-
-
{message}
- no-data -
- {subTitle && } - {title} -
-
+const EmptyDisplay = ({ message, title, onClick, subTitle, onSubClick }) => ( +
+
+
{message}
+ no-data +
+ {subTitle && ( + + )} + + {title} + +
-) +
+); -export default EmptyDisplay; \ No newline at end of file +export default EmptyDisplay; diff --git a/ui/src/components/ErrorMessageDisplay/error-message-display.scss b/ui/src/components/ErrorMessageDisplay/error-message-display.scss index ad4d328fce..b71de07851 100644 --- a/ui/src/components/ErrorMessageDisplay/error-message-display.scss +++ b/ui/src/components/ErrorMessageDisplay/error-message-display.scss @@ -1,22 +1,21 @@ -@import 'utils/scss_variables.module.scss'; - +@import "utils/scss_variables.module.scss"; .error-message-display { - margin: 10px 0 20px 0; + margin: 10px 0 20px 0; - .error-message-display-title { - font-weight: 700; - font-size: 14px; - line-height: 18px; - color: $color-warning; - margin-bottom: 5px; - } - .error-message-display-message { - font-weight: 400; - font-size: 12px; - line-height: 18px; - background-color: $color-status-yellow; - color: $color-grey-black; - padding: 5px 10px; - } -} \ No newline at end of file + .error-message-display-title { + font-weight: 700; + font-size: 14px; + line-height: 18px; + color: $color-warning; + margin-bottom: 5px; + } + .error-message-display-message { + font-weight: 400; + font-size: 12px; + line-height: 18px; + background-color: $color-status-yellow; + color: $color-grey-black; + padding: 5px 10px; + } +} diff --git a/ui/src/components/ErrorMessageDisplay/index.jsx b/ui/src/components/ErrorMessageDisplay/index.jsx index 39395c2613..6484cda909 100644 --- a/ui/src/components/ErrorMessageDisplay/index.jsx +++ b/ui/src/components/ErrorMessageDisplay/index.jsx @@ -1,12 +1,12 @@ -import React from 'react'; +import React from "react"; -import './error-message-display.scss'; +import "./error-message-display.scss"; -const ErrorMessageDisplay = ({children, title}) => ( -
- {!!title &&
{title}
} -
{children}
-
-) +const ErrorMessageDisplay = ({ children, title }) => ( +
+ {!!title &&
{title}
} +
{children}
+
+); -export default ErrorMessageDisplay; \ No newline at end of file +export default ErrorMessageDisplay; diff --git a/ui/src/components/ExpandableList/expandable-list.scss b/ui/src/components/ExpandableList/expandable-list.scss index 0815d0c1e7..4011f8d6df 100644 --- a/ui/src/components/ExpandableList/expandable-list.scss +++ b/ui/src/components/ExpandableList/expandable-list.scss @@ -1,25 +1,24 @@ -@import 'utils/scss_variables.module.scss'; +@import "utils/scss_variables.module.scss"; .expandable-list-display-wrapper { - display: flex; - flex-direction: column; - - .arrow-icon { - align-self: center; - } - .expandable-list-items { - .expandable-list-item-wrapper { - &:not(:last-child) { - margin-bottom: 7px; - } - .expandable-list-item { - width: 100%; - display: inline-block; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - } - } - + display: flex; + flex-direction: column; + + .arrow-icon { + align-self: center; + } + .expandable-list-items { + .expandable-list-item-wrapper { + &:not(:last-child) { + margin-bottom: 7px; + } + .expandable-list-item { + width: 100%; + display: inline-block; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } } -} \ No newline at end of file + } +} diff --git a/ui/src/components/ExpandableList/index.jsx b/ui/src/components/ExpandableList/index.jsx index e4360c9c00..d71d1ee378 100644 --- a/ui/src/components/ExpandableList/index.jsx +++ b/ui/src/components/ExpandableList/index.jsx @@ -1,51 +1,53 @@ -import React, { useState } from 'react'; -import classnames from 'classnames'; -import Arrow, { ARROW_NAMES } from 'components/Arrow'; -import Tag from 'components/Tag'; +import React, { useState } from "react"; +import classnames from "classnames"; +import Arrow, { ARROW_NAMES } from "components/Arrow"; +import Tag from "components/Tag"; -import './expandable-list.scss'; -import { ValueWithFallback } from 'components/ValueWithFallback'; +import "./expandable-list.scss"; +import { ValueWithFallback } from "components/ValueWithFallback"; const MINIMIZED_LEN = 1; -const ExpandableList = ({items, withTagWrap=false}) => { - const allItems = items || []; - const minmizedItems = allItems.slice(0, MINIMIZED_LEN); +const ExpandableList = ({ items, withTagWrap = false }) => { + const allItems = items || []; + const minmizedItems = allItems.slice(0, MINIMIZED_LEN); - const [itemsToDisplay, setItemsToDisplay] = useState(allItems.length > MINIMIZED_LEN ? minmizedItems : allItems); - const isOpen = itemsToDisplay.length === allItems.length; + const [itemsToDisplay, setItemsToDisplay] = useState( + allItems.length > MINIMIZED_LEN ? minmizedItems : allItems, + ); + const isOpen = itemsToDisplay.length === allItems.length; - return ( -
-
-
- { - - {itemsToDisplay.map((item, index) => ( -
-
- {withTagWrap ? {item} : item} -
-
- ))} -
- } + return ( +
+
+
+ { + + {itemsToDisplay.map((item, index) => ( +
+
+ {withTagWrap ? {item} : item} +
- {minmizedItems.length !== allItems.length && - { - event.stopPropagation(); - event.preventDefault(); - - setItemsToDisplay(isOpen ? minmizedItems : allItems); - }} - small - /> - } -
+ ))} + + }
- ) -} + {minmizedItems.length !== allItems.length && ( + { + event.stopPropagation(); + event.preventDefault(); + + setItemsToDisplay(isOpen ? minmizedItems : allItems); + }} + small + /> + )} +
+
+ ); +}; export default ExpandableList; diff --git a/ui/src/components/FieldSet/field-set.scss b/ui/src/components/FieldSet/field-set.scss index 52424f7d2b..ad00d277d6 100644 --- a/ui/src/components/FieldSet/field-set.scss +++ b/ui/src/components/FieldSet/field-set.scss @@ -1,7 +1,7 @@ -@import 'utils/scss_variables.module.scss'; +@import "utils/scss_variables.module.scss"; -fieldset { - border-radius: 5px; - padding: 12px; - border: solid 1px $color-grey; -} +fieldset { + border-radius: 5px; + padding: 12px; + border: solid 1px $color-grey; +} diff --git a/ui/src/components/FieldSet/index.jsx b/ui/src/components/FieldSet/index.jsx index deb86819d7..e94c0a9d1c 100644 --- a/ui/src/components/FieldSet/index.jsx +++ b/ui/src/components/FieldSet/index.jsx @@ -1,9 +1,10 @@ -import React from 'react'; +import React from "react"; -import './field-set.scss'; +import "./field-set.scss"; -export const FieldSet = ({ legend, children}) => -
- {legend} - {children} -
+export const FieldSet = ({ legend, children }) => ( +
+ {legend} + {children} +
+); diff --git a/ui/src/components/Filter/FilterButton/filter-button.scss b/ui/src/components/Filter/FilterButton/filter-button.scss index d741ac323c..96dcb75933 100644 --- a/ui/src/components/Filter/FilterButton/filter-button.scss +++ b/ui/src/components/Filter/FilterButton/filter-button.scss @@ -1,25 +1,25 @@ -@import 'utils/scss_variables.module.scss'; +@import "utils/scss_variables.module.scss"; .filter-button { - border: 1px solid $color-blue; - color: $color-main-dark; - background-color: $color-blue; - font-weight: 400; - font-size: 14px; - border-radius: 100px; - box-sizing: border-box; - height: 28px; - padding: 0 10px; - display: flex; - align-items: center; - cursor: pointer; + border: 1px solid $color-blue; + color: $color-main-dark; + background-color: $color-blue; + font-weight: 400; + font-size: 14px; + border-radius: 100px; + box-sizing: border-box; + height: 28px; + padding: 0 10px; + display: flex; + align-items: center; + cursor: pointer; - &.pressed { - border: 1px solid $color-findings-3; - color: $color-findings-3; - background-color: white; - } - .icon { - margin-right: 5px; - } -} \ No newline at end of file + &.pressed { + border: 1px solid $color-findings-3; + color: $color-findings-3; + background-color: white; + } + .icon { + margin-right: 5px; + } +} diff --git a/ui/src/components/Filter/FilterButton/index.jsx b/ui/src/components/Filter/FilterButton/index.jsx index 7fc2ad2ca0..c36cacb6c3 100644 --- a/ui/src/components/Filter/FilterButton/index.jsx +++ b/ui/src/components/Filter/FilterButton/index.jsx @@ -1,14 +1,18 @@ -import React from 'react'; -import classnames from 'classnames'; -import Icon, { ICON_NAMES } from 'components/Icon'; +import React from "react"; +import classnames from "classnames"; +import Icon, { ICON_NAMES } from "components/Icon"; -import './filter-button.scss'; +import "./filter-button.scss"; -const FilterButton = ({onClick, pressed, children}) => ( - +const FilterButton = ({ onClick, pressed, children }) => ( + ); -export default FilterButton; \ No newline at end of file +export default FilterButton; diff --git a/ui/src/components/Filter/filter.scss b/ui/src/components/Filter/filter.scss index 1009f12738..47117b2c1c 100644 --- a/ui/src/components/Filter/filter.scss +++ b/ui/src/components/Filter/filter.scss @@ -1,61 +1,61 @@ -@import 'utils/scss_variables.module.scss'; +@import "utils/scss_variables.module.scss"; .general-filter-wrapper { - .show-filters-button { - display: flex; - align-items: center; - margin-bottom: 20px; - - &.pressed { - background-color: $color-grey-lighter; - } - .icon { - height: 16px; - width: 16px; - margin-right: 5px; - } + .show-filters-button { + display: flex; + align-items: center; + margin-bottom: 20px; + + &.pressed { + background-color: $color-grey-lighter; + } + .icon { + height: 16px; + width: 16px; + margin-right: 5px; + } + } + .filter-form-wrapper { + margin-top: 14px; + + .filter-form { + display: flex; + align-items: center; + + .form-field-wrapper { + margin-right: 10px; + margin-bottom: 0; + min-width: 150px; + } + .add-filter-button { + height: 30px; + padding: 7px; + border-radius: 2px; + } } - .filter-form-wrapper { - margin-top: 14px; + } + .filters-display-wrapper { + display: flex; + flex-wrap: wrap; - .filter-form { - display: flex; - align-items: center; - - .form-field-wrapper { - margin-right: 10px; - margin-bottom: 0; - min-width: 150px; - } - .add-filter-button { - height: 30px; - padding: 7px; - border-radius: 2px; - } - } + &:not(.is-empty) { + margin-top: 14px; } - .filters-display-wrapper { - display: flex; - flex-wrap: wrap; - - &:not(.is-empty) { - margin-top: 14px; - } - .filter-display-item { - color: $color-grey-black; - background-color: $color-blue-light; - border: 1px solid $color-blue; - box-sizing: border-box; - font-size: 10px; - padding: 5px 10px; - margin-bottom: 5px; - display: flex; - align-items: center; - margin-right: 10px; + .filter-display-item { + color: $color-grey-black; + background-color: $color-blue-light; + border: 1px solid $color-blue; + box-sizing: border-box; + font-size: 10px; + padding: 5px 10px; + margin-bottom: 5px; + display: flex; + align-items: center; + margin-right: 10px; - .icon { - margin-left: 10px; - } - } + .icon { + margin-left: 10px; + } } + } } diff --git a/ui/src/components/Filter/index.jsx b/ui/src/components/Filter/index.jsx index 5e7843bf39..f61d0e8def 100644 --- a/ui/src/components/Filter/index.jsx +++ b/ui/src/components/Filter/index.jsx @@ -1,140 +1,200 @@ -import React, { useEffect, useState } from 'react'; -import { isUndefined, isEmpty, isEqual } from 'lodash'; -import { Formik, Form, useFormikContext } from 'formik'; -import classnames from 'classnames'; -import Icon, { ICON_NAMES } from 'components/Icon'; -import Button from 'components/Button'; -import { SelectField, MultiselectField, TextField, DateField } from 'components/Form'; -import CopyButton from 'components/CopyButton'; -import { OPERATORS, formatFiltersToOdataItems, getValueLabel } from './utils'; -import FilterButton from './FilterButton'; - -import './filter.scss'; - -export { - OPERATORS, - formatFiltersToOdataItems -} - -const getScopeConfig = (filtersConfig, scope) => filtersConfig.find(item => item.value === scope) || {}; - -const DateTimeField = props => - -const FormFields = ({onAdd, filtersConfig}) => { - const {values: formValues, setFieldValue, resetForm} = useFormikContext(); - const {scope, operator, value} = formValues; - - const selectedScopeData = getScopeConfig(filtersConfig, scope); - const {isNumber, isDate, customOdataFormat} = selectedScopeData; - const operatorByScopeItems = selectedScopeData.operators || []; - const selectedOperatorData = operatorByScopeItems.find(item => item.value === operator); - const {valueItems, creatable, isSingleSelect} = selectedOperatorData || {}; - - const ValueField = isDate ? DateTimeField : (isUndefined(valueItems) ? TextField : (isSingleSelect ? SelectField : MultiselectField)); - const valuePlaceholder = isUndefined(valueItems) ? "Enter value..." : "Select value..."; - const disableButton = isEmpty(value); - - useEffect(() => { - setFieldValue("operator", ""); - setFieldValue("value", ""); - }, [scope, setFieldValue]); - - useEffect(() => { - setFieldValue("value", ""); - }, [operator, setFieldValue]); - - return ( - - - - - - - ) -} - -const Filter = ({filters, onFilterUpdate, filtersConfig, filtersOnCopyText}) => { - const [showFiltersForm, setShowFiltersForm] = useState(false); - - return ( -
- setShowFiltersForm(!showFiltersForm)} pressed={showFiltersForm}>Filters - {showFiltersForm && -
- -
- onFilterUpdate([...filters, filterData])} - filtersConfig={filtersConfig} - /> - -
-
- } -
- { - filters.map(({scope, operator, value}, index) => { - const {label: scopeLabel, operators: configOperators} = getScopeConfig(filtersConfig, scope); - - const operatorLabel = OPERATORS[operator].label; - const valueItems = configOperators.find(configOperator => configOperator.value === operator)?.valueItems || []; - const formattedValue = Array.isArray(value) ? - value.map(valueItem => getValueLabel(valueItems, valueItem)).join(" or ") : getValueLabel(valueItems, value); - - return ( -
- {`${scopeLabel} ${operatorLabel} `}{formattedValue} - { - const newFilters = filters.filter(filterItem => !(filterItem.scope === scope && filterItem.operator === operator && isEqual(filterItem.value, value))); - - onFilterUpdate(newFilters); - }} - size={10} - /> -
- ) - }) - } - {!isEmpty(filters) && - <> - - {!!filtersOnCopyText &&
} - - } -
+import React, { useEffect, useState } from "react"; +import { isUndefined, isEmpty, isEqual } from "lodash"; +import { Formik, Form, useFormikContext } from "formik"; +import classnames from "classnames"; +import Icon, { ICON_NAMES } from "components/Icon"; +import Button from "components/Button"; +import { + SelectField, + MultiselectField, + TextField, + DateField, +} from "components/Form"; +import CopyButton from "components/CopyButton"; +import { OPERATORS, formatFiltersToOdataItems, getValueLabel } from "./utils"; +import FilterButton from "./FilterButton"; + +import "./filter.scss"; + +export { OPERATORS, formatFiltersToOdataItems }; + +const getScopeConfig = (filtersConfig, scope) => + filtersConfig.find((item) => item.value === scope) || {}; + +const DateTimeField = (props) => ( + +); + +const FormFields = ({ onAdd, filtersConfig }) => { + const { values: formValues, setFieldValue, resetForm } = useFormikContext(); + const { scope, operator, value } = formValues; + + const selectedScopeData = getScopeConfig(filtersConfig, scope); + const { isNumber, isDate, customOdataFormat } = selectedScopeData; + const operatorByScopeItems = selectedScopeData.operators || []; + const selectedOperatorData = operatorByScopeItems.find( + (item) => item.value === operator, + ); + const { valueItems, creatable, isSingleSelect } = selectedOperatorData || {}; + + const ValueField = isDate + ? DateTimeField + : isUndefined(valueItems) + ? TextField + : isSingleSelect + ? SelectField + : MultiselectField; + const valuePlaceholder = isUndefined(valueItems) + ? "Enter value..." + : "Select value..."; + const disableButton = isEmpty(value); + + useEffect(() => { + setFieldValue("operator", ""); + setFieldValue("value", ""); + }, [scope, setFieldValue]); + + useEffect(() => { + setFieldValue("value", ""); + }, [operator, setFieldValue]); + + return ( + + + + + + + ); +}; + +const Filter = ({ + filters, + onFilterUpdate, + filtersConfig, + filtersOnCopyText, +}) => { + const [showFiltersForm, setShowFiltersForm] = useState(false); + + return ( +
+ setShowFiltersForm(!showFiltersForm)} + pressed={showFiltersForm} + > + Filters + + {showFiltersForm && ( +
+ +
+ onFilterUpdate([...filters, filterData])} + filtersConfig={filtersConfig} + /> + +
- ); -} + )} +
+ {filters.map(({ scope, operator, value }, index) => { + const { label: scopeLabel, operators: configOperators } = + getScopeConfig(filtersConfig, scope); + + const operatorLabel = OPERATORS[operator].label; + const valueItems = + configOperators.find( + (configOperator) => configOperator.value === operator, + )?.valueItems || []; + const formattedValue = Array.isArray(value) + ? value + .map((valueItem) => getValueLabel(valueItems, valueItem)) + .join(" or ") + : getValueLabel(valueItems, value); + + return ( +
+ + {`${scopeLabel} ${operatorLabel} `} + {formattedValue} + + { + const newFilters = filters.filter( + (filterItem) => + !( + filterItem.scope === scope && + filterItem.operator === operator && + isEqual(filterItem.value, value) + ), + ); + + onFilterUpdate(newFilters); + }} + size={10} + /> +
+ ); + })} + {!isEmpty(filters) && ( + <> + + {!!filtersOnCopyText && ( +
+ +
+ )} + + )} +
+
+ ); +}; -export default Filter; \ No newline at end of file +export default Filter; diff --git a/ui/src/components/Filter/utils.js b/ui/src/components/Filter/utils.js index f73f77b039..f0e860dd7d 100644 --- a/ui/src/components/Filter/utils.js +++ b/ui/src/components/Filter/utils.js @@ -1,45 +1,51 @@ export const OPERATORS = { - eq: {value: "eq", label: "is"}, - ne: {value: "ne", label: "is not"}, - startswith: {value: "startswith", label: "starts with"}, - endswith: {value: "endswith", label: "ends with"}, - contains: {value: "contains", label: "contains"}, - notcontains: {value: "notcontains", label: "don't contain"}, - gt: {value: "gt", label: "greater than"}, - ge: {value: "ge", label: "greater than or equal to"}, - lt: {value: "lt", label: "less than"}, - le: {value: "le", label: "less than or equal to"} -} + eq: { value: "eq", label: "is" }, + ne: { value: "ne", label: "is not" }, + startswith: { value: "startswith", label: "starts with" }, + endswith: { value: "endswith", label: "ends with" }, + contains: { value: "contains", label: "contains" }, + notcontains: { value: "notcontains", label: "don't contain" }, + gt: { value: "gt", label: "greater than" }, + ge: { value: "ge", label: "greater than or equal to" }, + lt: { value: "lt", label: "less than" }, + le: { value: "le", label: "less than or equal to" }, +}; const SPECIAL_CASE_OPERATORS = [ - OPERATORS.contains.value, - OPERATORS.startswith.value, - OPERATORS.endswith.value + OPERATORS.contains.value, + OPERATORS.startswith.value, + OPERATORS.endswith.value, ]; export const formatFiltersToOdataItems = (filters) => { - return filters.reduce((acc, curr) => { - const {scope, operator, value, isNumber, isDate, customOdataFormat} = curr; - const valuesList = Array.isArray(value) ? value : [value]; - - const formatValueItem = valueItem => isNumber ? valueItem : `'${isDate ? (new Date(valueItem)).toISOString() : valueItem}'`; - const formatItem = valueItem => { - if (SPECIAL_CASE_OPERATORS.includes(operator)) { - return `${operator}(${scope},${formatValueItem(valueItem)})`; - } - - return `${scope} ${operator} ${formatValueItem(valueItem)}`; - } + return filters.reduce((acc, curr) => { + const { scope, operator, value, isNumber, isDate, customOdataFormat } = + curr; + const valuesList = Array.isArray(value) ? value : [value]; + + const formatValueItem = (valueItem) => + isNumber + ? valueItem + : `'${isDate ? new Date(valueItem).toISOString() : valueItem}'`; + const formatItem = (valueItem) => { + if (SPECIAL_CASE_OPERATORS.includes(operator)) { + return `${operator}(${scope},${formatValueItem(valueItem)})`; + } + + return `${scope} ${operator} ${formatValueItem(valueItem)}`; + }; - return [ - ...acc, - !!customOdataFormat ? customOdataFormat(valuesList, operator, scope) :`(${valuesList.map(valueItem => formatItem(valueItem)).join(` or `)})` - ]; - }, []); + return [ + ...acc, + !!customOdataFormat + ? customOdataFormat(valuesList, operator, scope) + : `(${valuesList.map((valueItem) => formatItem(valueItem)).join(` or `)})`, + ]; + }, []); }; -export const getValueLabel = (valueItems=[], value) => { - const valueItem = valueItems.find(valueItem => valueItem.value === value); - - return !!valueItem ? valueItem.label : value; -}; \ No newline at end of file +export const getValueLabel = (valueItems = [], value) => { + const valueItem = valueItems.find((valueItem) => valueItem.value === value); + + return !!valueItem ? valueItem.label : value; +}; diff --git a/ui/src/components/Form/FieldError/field-error.scss b/ui/src/components/Form/FieldError/field-error.scss index 81b687e9bf..68ef143c6a 100644 --- a/ui/src/components/Form/FieldError/field-error.scss +++ b/ui/src/components/Form/FieldError/field-error.scss @@ -1,8 +1,8 @@ -@import 'utils/scss_variables.module.scss'; +@import "utils/scss_variables.module.scss"; .form-field-error { - font-size: 10px; - line-height: 16px; - color: $color-error; - margin-top: 5px; -} \ No newline at end of file + font-size: 10px; + line-height: 16px; + color: $color-error; + margin-top: 5px; +} diff --git a/ui/src/components/Form/FieldError/index.jsx b/ui/src/components/Form/FieldError/index.jsx index 51e5c745d6..89d4c20c83 100644 --- a/ui/src/components/Form/FieldError/index.jsx +++ b/ui/src/components/Form/FieldError/index.jsx @@ -1,9 +1,9 @@ -import React from 'react'; +import React from "react"; -import './field-error.scss'; +import "./field-error.scss"; -const FieldError = ({children}) => ( -
{children}
+const FieldError = ({ children }) => ( +
{children}
); export default FieldError; diff --git a/ui/src/components/Form/FieldLabel/field-label.scss b/ui/src/components/Form/FieldLabel/field-label.scss index 194916793c..d886658a0f 100644 --- a/ui/src/components/Form/FieldLabel/field-label.scss +++ b/ui/src/components/Form/FieldLabel/field-label.scss @@ -1,15 +1,15 @@ -@import 'utils/scss_variables.module.scss'; +@import "utils/scss_variables.module.scss"; .form-field-label-wrapper { - display: flex; - align-items: center; - margin-bottom: 12px; + display: flex; + align-items: center; + margin-bottom: 12px; - .form-field-label { - font-weight: 400; - font-size: 16px; - line-height: 22px; - color: $color-grey-black; - margin-right: 10px; - } -} \ No newline at end of file + .form-field-label { + font-weight: 400; + font-size: 16px; + line-height: 22px; + color: $color-grey-black; + margin-right: 10px; + } +} diff --git a/ui/src/components/Form/FieldLabel/index.jsx b/ui/src/components/Form/FieldLabel/index.jsx index eb141e1b5f..3144ed1758 100644 --- a/ui/src/components/Form/FieldLabel/index.jsx +++ b/ui/src/components/Form/FieldLabel/index.jsx @@ -1,13 +1,15 @@ -import React from 'react'; -import InfoIcon from 'components/InfoIcon'; +import React from "react"; +import InfoIcon from "components/InfoIcon"; -import './field-label.scss'; +import "./field-label.scss"; -const FieldLabel = ({children, tooltipText, tooltipId}) => ( -
- - {!!tooltipText && } -
+const FieldLabel = ({ children, tooltipText, tooltipId }) => ( +
+ + {!!tooltipText && ( + + )} +
); -export default FieldLabel; \ No newline at end of file +export default FieldLabel; diff --git a/ui/src/components/Form/form-fields/CheckboxField/checkbox-field.scss b/ui/src/components/Form/form-fields/CheckboxField/checkbox-field.scss index 34ae423518..a00446fc97 100644 --- a/ui/src/components/Form/form-fields/CheckboxField/checkbox-field.scss +++ b/ui/src/components/Form/form-fields/CheckboxField/checkbox-field.scss @@ -1,75 +1,74 @@ -@import 'utils/scss_variables.module.scss'; +@import "utils/scss_variables.module.scss"; $checkbox-size: 20px; $checkmark-size: 6px; .form-field-wrapper.checkbox-field-wrapper { - .checkbox-wrapper { - display: inline-flex; - align-items: center; - position: relative; - cursor: pointer; - - .inner-checkbox-wrapper { - position: relative; - width: $checkbox-size; - - .checkmark { - position: absolute; - top: 0; - left: 0; - height: $checkbox-size; - width: $checkbox-size; - background-color: white; - border: 1px solid $color-grey-light; - border-radius: 2px; - box-sizing: border-box; - - &:after { - content: ""; - position: absolute; - display: none; - width: $checkmark-size; - height: calc(#{$checkmark-size} + 6px); - top: 1px; - left: 5px; - border: solid $color-main-light; - border-width: 0 2px 2px 0; - transform: rotate(45deg); - } - &.half-selected:after { - border: none; - background-color: $color-main-light; - transform: rotate(0deg); - display: block; - } - } + .checkbox-wrapper { + display: inline-flex; + align-items: center; + position: relative; + cursor: pointer; + + .inner-checkbox-wrapper { + position: relative; + width: $checkbox-size; + + .checkmark { + position: absolute; + top: 0; + left: 0; + height: $checkbox-size; + width: $checkbox-size; + background-color: white; + border: 1px solid $color-grey-light; + border-radius: 2px; + box-sizing: border-box; - input { - opacity: 0; - cursor: pointer; - - &:checked ~ .checkmark { - &:after { - display: block; - } - } - - } + &:after { + content: ""; + position: absolute; + display: none; + width: $checkmark-size; + height: calc(#{$checkmark-size} + 6px); + top: 1px; + left: 5px; + border: solid $color-main-light; + border-width: 0 2px 2px 0; + transform: rotate(45deg); } - .checkbox-title { - margin-left: 10px; + &.half-selected:after { + border: none; + background-color: $color-main-light; + transform: rotate(0deg); + display: block; } - &.disabled { - cursor: not-allowed; - color: $color-grey; - - input { - cursor: not-allowed; - } - .checkmark { - border-color: $color-grey-light; - } + } + + input { + opacity: 0; + cursor: pointer; + + &:checked ~ .checkmark { + &:after { + display: block; + } } + } + } + .checkbox-title { + margin-left: 10px; + } + &.disabled { + cursor: not-allowed; + color: $color-grey; + + input { + cursor: not-allowed; + } + .checkmark { + border-color: $color-grey-light; + } } + } } diff --git a/ui/src/components/Form/form-fields/CheckboxField/index.jsx b/ui/src/components/Form/form-fields/CheckboxField/index.jsx index 175ecafdd0..910939cc63 100644 --- a/ui/src/components/Form/form-fields/CheckboxField/index.jsx +++ b/ui/src/components/Form/form-fields/CheckboxField/index.jsx @@ -1,34 +1,63 @@ -import React from 'react'; -import classnames from 'classnames'; -import { useField } from 'formik'; -import InfoIcon from 'components/InfoIcon'; -import FieldError from 'components/Form/FieldError'; -import FieldLabel from 'components/Form/FieldLabel'; +import React from "react"; +import classnames from "classnames"; +import { useField } from "formik"; +import InfoIcon from "components/InfoIcon"; +import FieldError from "components/Form/FieldError"; +import FieldLabel from "components/Form/FieldLabel"; -import './checkbox-field.scss'; +import "./checkbox-field.scss"; -const CheckboxField = ({title, className, label, disabled, tooltipText, ...props}) => { - const [field, meta, helpers] = useField(props); - const {name} = field; - const {value} = meta; - const {setValue} = helpers; +const CheckboxField = ({ + title, + className, + label, + disabled, + tooltipText, + ...props +}) => { + const [field, meta, helpers] = useField(props); + const { name } = field; + const { value } = meta; + const { setValue } = helpers; - const tooltipId = `form-tooltip-${name}`; - - return ( -
- {!!label && {label}} - - {meta.touched && meta.error && {meta.error}} + const tooltipId = `form-tooltip-${name}`; + + return ( +
+ {!!label && ( + + {label} + + )} + + {meta.touched && meta.error && {meta.error}} +
+ ); +}; -export default CheckboxField; \ No newline at end of file +export default CheckboxField; diff --git a/ui/src/components/Form/form-fields/CronField/cron-field.scss b/ui/src/components/Form/form-fields/CronField/cron-field.scss index d0402f4aef..b5984fd4b3 100644 --- a/ui/src/components/Form/form-fields/CronField/cron-field.scss +++ b/ui/src/components/Form/form-fields/CronField/cron-field.scss @@ -1,81 +1,81 @@ -@import 'utils/scss_variables.module.scss'; +@import "utils/scss_variables.module.scss"; .form-field-wrapper.cron-field-wrapper { - .cron-field-title { - font-weight: 700; - font-size: 14px; - line-height: 18px; - margin-bottom: 10px; + .cron-field-title { + font-weight: 700; + font-size: 14px; + line-height: 18px; + margin-bottom: 10px; + } + .cron-field-quick-options-wrapper { + .cron-field-quick-options { + display: flex; + + .clarity-tag:not(:last-child) { + margin-right: 14px; + } } - .cron-field-quick-options-wrapper { - .cron-field-quick-options { - display: flex; - - .clarity-tag:not(:last-child) { - margin-right: 14px; - } - } - .cron-field-quick-separator { - display: flex; - align-items: center; - margin: 30px 0; + .cron-field-quick-separator { + display: flex; + align-items: center; + margin: 30px 0; - .cron-field-quick-separator-line { - border-bottom: 1px solid $color-grey-light; - flex-grow: 1; - } - .cron-field-quick-separator-text { - margin: 0 10px; - } - } + .cron-field-quick-separator-line { + border-bottom: 1px solid $color-grey-light; + flex-grow: 1; + } + .cron-field-quick-separator-text { + margin: 0 10px; + } } - .cron-field-output-wrapper { - font-size: 14px; - line-height: 18px; - margin-top: 30px; + } + .cron-field-output-wrapper { + font-size: 14px; + line-height: 18px; + margin-top: 30px; - .cron-field-output-expression { - color: $color-grey; - margin-top: 10px; - } + .cron-field-output-expression { + color: $color-grey; + margin-top: 10px; } - .cron-field-select { - > .cron-field-select-field:first-child { - > span { - display: none; - } - > .cron-field-select-select { - margin: 0; - } - } - .cron-field-select-field .cron-field-select-select { - outline: none; - font-family: CiscoSansTT; - color: $color-grey-black; - z-index: 0; - - .ant-select-selector { - height: 36px; - border-radius: 2px; - border-color: $color-grey-light !important; - outline: none !important; - box-shadow: none !important; - - .ant-select-selection-search, - .ant-select-selection-item { - outline: none !important; - color: $color-grey-black !important; - } - } + } + .cron-field-select { + > .cron-field-select-field:first-child { + > span { + display: none; + } + > .cron-field-select-select { + margin: 0; + } + } + .cron-field-select-field .cron-field-select-select { + outline: none; + font-family: CiscoSansTT; + color: $color-grey-black; + z-index: 0; + + .ant-select-selector { + height: 36px; + border-radius: 2px; + border-color: $color-grey-light !important; + outline: none !important; + box-shadow: none !important; + + .ant-select-selection-search, + .ant-select-selection-item { + outline: none !important; + color: $color-grey-black !important; } + } } + } } .cron-field-select-select-dropdown { - border-top: 2px solid $color-main-light; - border-radius: 0; + border-top: 2px solid $color-main-light; + border-radius: 0; - .ant-select-item-option.ant-select-item-option-selected { - background-color: $color-grey-light; - } -} \ No newline at end of file + .ant-select-item-option.ant-select-item-option-selected { + background-color: $color-grey-light; + } +} diff --git a/ui/src/components/Form/form-fields/CronField/index.jsx b/ui/src/components/Form/form-fields/CronField/index.jsx index e4d3bf4299..b78e3bc00e 100644 --- a/ui/src/components/Form/form-fields/CronField/index.jsx +++ b/ui/src/components/Form/form-fields/CronField/index.jsx @@ -1,54 +1,63 @@ -import React from 'react'; -import classnames from 'classnames'; -import { Cron } from 'react-js-cron'; -import { useField } from 'formik'; -import Tag from 'components/Tag'; -import { cronExpressionToHuman } from 'utils/utils'; +import React from "react"; +import classnames from "classnames"; +import { Cron } from "react-js-cron"; +import { useField } from "formik"; +import Tag from "components/Tag"; +import { cronExpressionToHuman } from "utils/utils"; -import './cron-field.scss'; +import "./cron-field.scss"; -const CronTitle = ({children}) =>
{children}
; +const CronTitle = ({ children }) => ( +
{children}
+); const CronField = (props) => { - const {className, quickOptions=[]} = props; - const [field, meta, helpers] = useField(props); - const {value} = field; - const {setValue, setTouched} = helpers; - - return ( -
setTouched(true, true)}> - {!!quickOptions && -
- Quick options -
- {quickOptions.map(({value, label}, index) => ( - setValue(value)}>{label} - ))} -
-
-
-
or
-
-
-
- } - Every - - {!!value && -
-
{cronExpressionToHuman(value)}
-
{`Cron expression: ${value}`}
-
- } + const { className, quickOptions = [] } = props; + const [field, meta, helpers] = useField(props); + const { value } = field; + const { setValue, setTouched } = helpers; + + return ( +
setTouched(true, true)} + > + {!!quickOptions && ( +
+ Quick options +
+ {quickOptions.map(({ value, label }, index) => ( + setValue(value)}> + {label} + + ))} +
+
+
+
or
+
+
+
+ )} + Every + + {!!value && ( +
+
{cronExpressionToHuman(value)}
+
{`Cron expression: ${value}`}
- ) -} + )} +
+ ); +}; -export default CronField; \ No newline at end of file +export default CronField; diff --git a/ui/src/components/Form/form-fields/DateField/date-field.scss b/ui/src/components/Form/form-fields/DateField/date-field.scss index e7a2dd0b4d..0db29ef270 100644 --- a/ui/src/components/Form/form-fields/DateField/date-field.scss +++ b/ui/src/components/Form/form-fields/DateField/date-field.scss @@ -1,96 +1,96 @@ -@import 'utils/scss_variables.module.scss'; +@import "utils/scss_variables.module.scss"; $calendar-font-size: 11px; $calendar-backgroud-color: white; .form-field-wrapper.date-field-wrapper { - .react-datetime-picker.date-field-select { - height: 36px; - background-color: white; - - .react-datetime-picker__wrapper { - border: 1px solid $color-grey-light; - border-radius: 2px; + .react-datetime-picker.date-field-select { + height: 36px; + background-color: white; - .react-datetime-picker__inputGroup { - padding: 0 8px; - - .react-datetime-picker__inputGroup__input, - .react-datetime-picker__inputGroup__leadingZero { - color: $color-grey-black; - font-size: 14px; - - &:invalid { - background-color: white; - } - &:focus-visible { - outline: none; - } - } - } + .react-datetime-picker__wrapper { + border: 1px solid $color-grey-light; + border-radius: 2px; + + .react-datetime-picker__inputGroup { + padding: 0 8px; + + .react-datetime-picker__inputGroup__input, + .react-datetime-picker__inputGroup__leadingZero { + color: $color-grey-black; + font-size: 14px; + + &:invalid { + background-color: white; + } + &:focus-visible { + outline: none; + } } + } } - .react-datetime-picker__calendar { - width: 230px; - - .react-calendar.date-field-select-calendar { - border: 1px solid $color-grey-light; - border-radius: 2px; + } + .react-datetime-picker__calendar { + width: 230px; + + .react-calendar.date-field-select-calendar { + border: 1px solid $color-grey-light; + border-radius: 2px; - .react-calendar__navigation { - margin-bottom: 0; - - .react-calendar__navigation__label { - background-color: $calendar-backgroud-color; - color: $color-main; - font-size: $calendar-font-size; - font-weight: bold; - text-transform: uppercase; - } - .react-calendar__navigation__arrow { - color: $color-grey-dark; - - &:disabled { - background-color: $calendar-backgroud-color; - color: $color-grey-light; - } - &:hover, - &:focus { - background-color: $calendar-backgroud-color; - } - } - } - .react-calendar__month-view__weekdays__weekday { - color: $color-grey-dark; - font-size: $calendar-font-size; - font-weight: normal; - - abbr { - text-decoration: none; - text-transform: none; - } - } - .react-calendar__tile { - padding-top: 7px; - padding-bottom: 7px; - color: $color-grey-dark; - font-size: $calendar-font-size; + .react-calendar__navigation { + margin-bottom: 0; - &:disabled { - background-color: $calendar-backgroud-color; - color: $color-grey-light; - } - &.react-calendar__tile--now { - background-color: $calendar-backgroud-color; - } - &.react-calendar__tile--active { - background-color: $color-main; - color: white; - } - &:hover { - background-color: $color-grey-light; - } - } + .react-calendar__navigation__label { + background-color: $calendar-backgroud-color; + color: $color-main; + font-size: $calendar-font-size; + font-weight: bold; + text-transform: uppercase; + } + .react-calendar__navigation__arrow { + color: $color-grey-dark; + + &:disabled { + background-color: $calendar-backgroud-color; + color: $color-grey-light; + } + &:hover, + &:focus { + background-color: $calendar-backgroud-color; + } + } + } + .react-calendar__month-view__weekdays__weekday { + color: $color-grey-dark; + font-size: $calendar-font-size; + font-weight: normal; + + abbr { + text-decoration: none; + text-transform: none; + } + } + .react-calendar__tile { + padding-top: 7px; + padding-bottom: 7px; + color: $color-grey-dark; + font-size: $calendar-font-size; + + &:disabled { + background-color: $calendar-backgroud-color; + color: $color-grey-light; + } + &.react-calendar__tile--now { + background-color: $calendar-backgroud-color; + } + &.react-calendar__tile--active { + background-color: $color-main; + color: white; + } + &:hover { + background-color: $color-grey-light; } + } } -} \ No newline at end of file + } +} diff --git a/ui/src/components/Form/form-fields/DateField/index.jsx b/ui/src/components/Form/form-fields/DateField/index.jsx index e8904b29fa..375542df0d 100644 --- a/ui/src/components/Form/form-fields/DateField/index.jsx +++ b/ui/src/components/Form/form-fields/DateField/index.jsx @@ -1,55 +1,74 @@ -import React from 'react'; -import classnames from 'classnames'; -import { isNull, isEmpty } from 'lodash'; -import DateTimePicker from 'react-datetime-picker'; -import { useField } from 'formik'; -import Arrow from 'components/Arrow'; -import { formatDateBy } from 'utils/utils'; -import FieldError from 'components/Form/FieldError'; -import FieldLabel from 'components/Form/FieldLabel'; +import React from "react"; +import classnames from "classnames"; +import { isNull, isEmpty } from "lodash"; +import DateTimePicker from "react-datetime-picker"; +import { useField } from "formik"; +import Arrow from "components/Arrow"; +import { formatDateBy } from "utils/utils"; +import FieldError from "components/Form/FieldError"; +import FieldLabel from "components/Form/FieldLabel"; -import './date-field.scss'; +import "./date-field.scss"; const DateField = (props) => { - const {label, className, tooltipText, displayFormat="MMM dd", valueFormat="YYYY-MM-DD", disabled, minDate} = props; - const [field, meta, helpers] = useField(props); - const {name, value} = field; - const {setValue, setTouched} = helpers; - - const formattedValue = !!value ? new Date(value) : null; + const { + label, + className, + tooltipText, + displayFormat = "MMM dd", + valueFormat = "YYYY-MM-DD", + disabled, + minDate, + } = props; + const [field, meta, helpers] = useField(props); + const { name, value } = field; + const { setValue, setTouched } = helpers; - return ( -
setTouched(true, true)}> - {!isEmpty(label) && {label}} - setValue(isNull(date) ? "" : formatDateBy(date, valueFormat))} - value={formattedValue} - className="date-field-select" - calendarClassName="date-field-select-calendar" - calendarIcon={null} - clearIcon={null} - disableClock={true} - format={displayFormat} - name={name} - openWidgetsOnFocus={true} - minDate={minDate} - prevLabel={} - nextLabel={} - prev2Label={null} - next2Label={null} - minDetail="month" - calendarType="US" - disabled={disabled} - yearPlaceholder="____" - dayPlaceholder="__" - hourPlaceholder="__" - minutePlaceholder="__" - monthPlaceholder="__" - secondPlaceholder="__" - /> - {meta.touched && meta.error && {meta.error}} -
- ) -} + const formattedValue = !!value ? new Date(value) : null; -export default DateField; \ No newline at end of file + return ( +
setTouched(true, true)} + > + {!isEmpty(label) && ( + + {label} + + )} + + setValue(isNull(date) ? "" : formatDateBy(date, valueFormat)) + } + value={formattedValue} + className="date-field-select" + calendarClassName="date-field-select-calendar" + calendarIcon={null} + clearIcon={null} + disableClock={true} + format={displayFormat} + name={name} + openWidgetsOnFocus={true} + minDate={minDate} + prevLabel={} + nextLabel={} + prev2Label={null} + next2Label={null} + minDetail="month" + calendarType="US" + disabled={disabled} + yearPlaceholder="____" + dayPlaceholder="__" + hourPlaceholder="__" + minutePlaceholder="__" + monthPlaceholder="__" + secondPlaceholder="__" + /> + {meta.touched && meta.error && {meta.error}} +
+ ); +}; + +export default DateField; diff --git a/ui/src/components/Form/form-fields/FieldsPair/fields-pair.scss b/ui/src/components/Form/form-fields/FieldsPair/fields-pair.scss index 0bfe57e16c..58f54e76ea 100644 --- a/ui/src/components/Form/form-fields/FieldsPair/fields-pair.scss +++ b/ui/src/components/Form/form-fields/FieldsPair/fields-pair.scss @@ -1,47 +1,47 @@ -@import 'utils/scss_variables.module.scss'; +@import "utils/scss_variables.module.scss"; $action-width: 40px; $actions-padding: 10px; .form-field-wrapper.fields-pair-field-wrapper { - .fields-wrapper { - background: rgba(34, 37, 41, 0.05); - padding: 20px; - padding-bottom: 10px; - margin-bottom: 10px; + .fields-wrapper { + background: rgba(34, 37, 41, 0.05); + padding: 20px; + padding-bottom: 10px; + margin-bottom: 10px; - .form-field-wrapper { - margin-bottom: 17px; - } - .field-with-actions-container { - display: flex; - justify-content: space-between; - - .form-field-wrapper { - width: calc(100% - 2 * #{$action-width} - 2 * #{$actions-padding}); - } - .actions-wrapper { - display: flex; + .form-field-wrapper { + margin-bottom: 17px; + } + .field-with-actions-container { + display: flex; + justify-content: space-between; + + .form-field-wrapper { + width: calc(100% - 2 * #{$action-width} - 2 * #{$actions-padding}); + } + .actions-wrapper { + display: flex; + + .field-action { + height: 36px; + width: $action-width; + border: 1px solid transparent; + border-radius: 5px; + background-color: $color-blue; + box-sizing: border-box; + display: flex; + align-items: center; + justify-content: center; + margin-left: $actions-padding; + cursor: pointer; - .field-action { - height: 36px; - width: $action-width; - border: 1px solid transparent; - border-radius: 5px; - background-color: $color-blue; - box-sizing: border-box; - display: flex; - align-items: center; - justify-content: center; - margin-left: $actions-padding; - cursor: pointer; - - &.disabled { - background-color: $color-grey-light; - cursor: not-allowed; - } - } - } + &.disabled { + background-color: $color-grey-light; + cursor: not-allowed; + } } + } } -} \ No newline at end of file + } +} diff --git a/ui/src/components/Form/form-fields/FieldsPair/index.jsx b/ui/src/components/Form/form-fields/FieldsPair/index.jsx index 2215a35042..fda57661e1 100644 --- a/ui/src/components/Form/form-fields/FieldsPair/index.jsx +++ b/ui/src/components/Form/form-fields/FieldsPair/index.jsx @@ -1,111 +1,166 @@ -import React, { useEffect } from 'react'; -import classnames from 'classnames'; -import { isEmpty } from 'lodash'; -import { FieldArray, useField, useFormikContext } from 'formik'; -import { usePrevious } from 'hooks'; -import FieldLabel from 'components/Form/FieldLabel'; -import Icon, { ICON_NAMES } from 'components/Icon'; - -import './fields-pair.scss'; - -const FieldAction = ({iconName, onClick, disabled, count}) => ( -
- -
-) - -const FieldItemWrapper = ({name, value, index, push, remove, replace, firstFieldProps, secondFieldProps, disabled, count}) => { - const {component: FirstFieldComponent, emptyValue: firstEmptyValue="", ...firstProps} = firstFieldProps; - const {component: SecondFieldComponent, getDependentFieldProps, emptyValue: secondEmptyValue="", ...secondProps} = secondFieldProps; - - const firstKey = firstProps.key; - const secondKey = secondProps.key; - - const firstValue = value[index][firstKey]; - const prevFirstValue = usePrevious(firstValue); - - const prevCount = usePrevious(count); - - const allowRemove = value.length > 1; - - const formattedFirstProps = { - ...firstProps, - disabled - }; - const formattedSecondProps = { - ...secondProps, - ...(!getDependentFieldProps ? {} : {...getDependentFieldProps(value[index]), index}) - }; - formattedSecondProps.disabled = disabled || formattedSecondProps.disabled || isEmpty(firstValue); - - useEffect(() => { - if (count === prevCount && !!prevFirstValue && prevFirstValue !== firstValue) { - replace(index, {...value[index], [secondKey]: secondEmptyValue}); - } - }, [prevFirstValue, firstValue, index, value, secondKey, secondEmptyValue, replace, count, prevCount]); - - return ( -
-
- -
- remove(index)} - disabled={disabled || !allowRemove} - /> - push({[firstKey]: firstEmptyValue, [secondKey]: secondEmptyValue})} - disabled={disabled} - /> -
-
- +import React, { useEffect } from "react"; +import classnames from "classnames"; +import { isEmpty } from "lodash"; +import { FieldArray, useField, useFormikContext } from "formik"; +import { usePrevious } from "hooks"; +import FieldLabel from "components/Form/FieldLabel"; +import Icon, { ICON_NAMES } from "components/Icon"; + +import "./fields-pair.scss"; + +const FieldAction = ({ iconName, onClick, disabled, count }) => ( +
+ +
+); + +const FieldItemWrapper = ({ + name, + value, + index, + push, + remove, + replace, + firstFieldProps, + secondFieldProps, + disabled, + count, +}) => { + const { + component: FirstFieldComponent, + emptyValue: firstEmptyValue = "", + ...firstProps + } = firstFieldProps; + const { + component: SecondFieldComponent, + getDependentFieldProps, + emptyValue: secondEmptyValue = "", + ...secondProps + } = secondFieldProps; + + const firstKey = firstProps.key; + const secondKey = secondProps.key; + + const firstValue = value[index][firstKey]; + const prevFirstValue = usePrevious(firstValue); + + const prevCount = usePrevious(count); + + const allowRemove = value.length > 1; + + const formattedFirstProps = { + ...firstProps, + disabled, + }; + const formattedSecondProps = { + ...secondProps, + ...(!getDependentFieldProps + ? {} + : { ...getDependentFieldProps(value[index]), index }), + }; + formattedSecondProps.disabled = + disabled || formattedSecondProps.disabled || isEmpty(firstValue); + + useEffect(() => { + if ( + count === prevCount && + !!prevFirstValue && + prevFirstValue !== firstValue + ) { + replace(index, { ...value[index], [secondKey]: secondEmptyValue }); + } + }, [ + prevFirstValue, + firstValue, + index, + value, + secondKey, + secondEmptyValue, + replace, + count, + prevCount, + ]); + + return ( +
+
+ +
+ remove(index)} + disabled={disabled || !allowRemove} + /> + + push({ + [firstKey]: firstEmptyValue, + [secondKey]: secondEmptyValue, + }) + } + disabled={disabled} + />
- ); -} +
+ +
+ ); +}; const FieldsPair = (props) => { - const {label, className, tooltipText} = props; - const [field] = useField(props); - - const {name, value} = field; - - const {validateForm} = useFormikContext(); - - useEffect(() => { - validateForm(); - }, [value, validateForm]) - - return ( -
- {!isEmpty(label) && {label}} - - {({remove, push, replace}) => value.map((item, index) => ( - - ))} - -
- ) -} + const { label, className, tooltipText } = props; + const [field] = useField(props); + + const { name, value } = field; + + const { validateForm } = useFormikContext(); + + useEffect(() => { + validateForm(); + }, [value, validateForm]); + + return ( +
+ {!isEmpty(label) && ( + + {label} + + )} + + {({ remove, push, replace }) => + value.map((item, index) => ( + + )) + } + +
+ ); +}; -export default FieldsPair; \ No newline at end of file +export default FieldsPair; diff --git a/ui/src/components/Form/form-fields/MultiselectField/index.jsx b/ui/src/components/Form/form-fields/MultiselectField/index.jsx index d731627b37..490992f35c 100644 --- a/ui/src/components/Form/form-fields/MultiselectField/index.jsx +++ b/ui/src/components/Form/form-fields/MultiselectField/index.jsx @@ -1,100 +1,145 @@ -import React, { useState, useEffect, useMemo } from 'react'; -import { cloneDeep, isEqual, isEmpty } from 'lodash'; -import classnames from 'classnames'; -import { useField } from 'formik'; -import { components } from 'react-select'; -import DropdownSelect from 'components/DropdownSelect'; -import Loader from 'components/Loader'; -import FieldError from 'components/Form/FieldError'; -import FieldLabel from 'components/Form/FieldLabel'; -import { usePrevious } from 'hooks'; - -import './multiselect-field.scss'; - -const ConnectorMultiValueContainer = ({connector, ...props}) => ( -
-
{connector}
- -
-) - -const PrevixLabelControl = ({children, label, ...props}) => { - const hasValue = !isEmpty(props.getValue()); - - return ( - - {hasValue && {label}} - {children} - - ) -} +import React, { useState, useEffect, useMemo } from "react"; +import { cloneDeep, isEqual, isEmpty } from "lodash"; +import classnames from "classnames"; +import { useField } from "formik"; +import { components } from "react-select"; +import DropdownSelect from "components/DropdownSelect"; +import Loader from "components/Loader"; +import FieldError from "components/Form/FieldError"; +import FieldLabel from "components/Form/FieldLabel"; +import { usePrevious } from "hooks"; + +import "./multiselect-field.scss"; + +const ConnectorMultiValueContainer = ({ connector, ...props }) => ( +
+
{connector}
+ +
+); + +const PrevixLabelControl = ({ children, label, ...props }) => { + const hasValue = !isEmpty(props.getValue()); + + return ( + + {hasValue && {label}} + {children} + + ); +}; const getMissingValueItemKeys = (valueKeys, items) => { - const missingItems = valueKeys.filter(key => !items.find(item => item.value === key)); + const missingItems = valueKeys.filter( + (key) => !items.find((item) => item.value === key), + ); - if(missingItems.length > 0) { - items = cloneDeep(items); - missingItems.forEach(item => { - items.push({value: item, label: item}); - }); - } + if (missingItems.length > 0) { + items = cloneDeep(items); + missingItems.forEach((item) => { + items.push({ value: item, label: item }); + }); + } - return items; -} + return items; +}; const MultiselectField = (props) => { - const {items: fieldItems=[], placeholder, creatable=false, disabled, className, label, tooltipText, connector, - prefixLabel, loading} = props; - const [field, meta, helpers] = useField(props); - const {value} = meta; - const {name} = field; - const {setValue} = helpers; - - const formattedItems = useMemo(() => ( - creatable ? getMissingValueItemKeys(value || [], fieldItems) : fieldItems - ), [creatable, fieldItems, value]); - const prevFormattedItems = usePrevious(formattedItems); - - const [items, setItems] = useState(formattedItems); - - useEffect(() => { - if (!isEqual(formattedItems, prevFormattedItems)) { - setItems(formattedItems); - } - }, [prevFormattedItems, formattedItems]); - - const selectedItems = items.filter(item => value?.includes(item.value)); - - return ( -
- {!!label && {label}} - { - const formattedSelectedItems = selectedItems || []; - const valueKeys = formattedSelectedItems.map(item => item.value); - - if (creatable) { - setItems(getMissingValueItemKeys(valueKeys, items)); - } - - setValue(valueKeys); - }} - creatable={creatable} - disabled={disabled || loading} - placeholder={placeholder} - isMulti={true} - components={{ - ...(connector ? {MultiValueContainer: props => } : {}), - ...(prefixLabel ? {Control: props => } : {}), - }} - /> - {loading && } - {meta.error && {meta.error}} -
- ) -} - -export default MultiselectField; \ No newline at end of file + const { + items: fieldItems = [], + placeholder, + creatable = false, + disabled, + className, + label, + tooltipText, + connector, + prefixLabel, + loading, + } = props; + const [field, meta, helpers] = useField(props); + const { value } = meta; + const { name } = field; + const { setValue } = helpers; + + const formattedItems = useMemo( + () => + creatable ? getMissingValueItemKeys(value || [], fieldItems) : fieldItems, + [creatable, fieldItems, value], + ); + const prevFormattedItems = usePrevious(formattedItems); + + const [items, setItems] = useState(formattedItems); + + useEffect(() => { + if (!isEqual(formattedItems, prevFormattedItems)) { + setItems(formattedItems); + } + }, [prevFormattedItems, formattedItems]); + + const selectedItems = items.filter((item) => value?.includes(item.value)); + + return ( +
+ {!!label && ( + + {label} + + )} + { + const formattedSelectedItems = selectedItems || []; + const valueKeys = formattedSelectedItems.map((item) => item.value); + + if (creatable) { + setItems(getMissingValueItemKeys(valueKeys, items)); + } + + setValue(valueKeys); + }} + creatable={creatable} + disabled={disabled || loading} + placeholder={placeholder} + isMulti={true} + components={{ + ...(connector + ? { + MultiValueContainer: (props) => ( + + ), + } + : {}), + ...(prefixLabel + ? { + Control: (props) => ( + + ), + } + : {}), + }} + /> + {loading && } + {meta.error && {meta.error}} +
+ ); +}; + +export default MultiselectField; diff --git a/ui/src/components/Form/form-fields/MultiselectField/multiselect-field.scss b/ui/src/components/Form/form-fields/MultiselectField/multiselect-field.scss index 1bbfbddafc..fdcb5e8962 100644 --- a/ui/src/components/Form/form-fields/MultiselectField/multiselect-field.scss +++ b/ui/src/components/Form/form-fields/MultiselectField/multiselect-field.scss @@ -1,22 +1,22 @@ .form-field-wrapper.multiselect-field-wrapper { - position: relative; - - .dropdown-select__value-container { - .multi-select-custom-item-with-connector { - display: flex; - align-items: center; - - .multi-select-connector { - margin: 0 10px; - } - &:first-child .multi-select-connector { - display: none; - } - } + position: relative; + + .dropdown-select__value-container { + .multi-select-custom-item-with-connector { + display: flex; + align-items: center; + + .multi-select-connector { + margin: 0 10px; + } + &:first-child .multi-select-connector { + display: none; + } } - .dropdown-select__control.multi-select-custom-control-with-label { - .multi-select-control-label { - margin-left: 10px; - } + } + .dropdown-select__control.multi-select-custom-control-with-label { + .multi-select-control-label { + margin-left: 10px; } -} \ No newline at end of file + } +} diff --git a/ui/src/components/Form/form-fields/RadioField/index.jsx b/ui/src/components/Form/form-fields/RadioField/index.jsx index 6d4e529869..c6f6a7c53a 100644 --- a/ui/src/components/Form/form-fields/RadioField/index.jsx +++ b/ui/src/components/Form/form-fields/RadioField/index.jsx @@ -1,37 +1,55 @@ -import React from 'react'; -import classnames from 'classnames'; -import { useField } from 'formik'; -import FieldError from 'components/Form/FieldError'; -import FieldLabel from 'components/Form/FieldLabel'; +import React from "react"; +import classnames from "classnames"; +import { useField } from "formik"; +import FieldError from "components/Form/FieldError"; +import FieldLabel from "components/Form/FieldLabel"; -import './radio-field.scss'; +import "./radio-field.scss"; -const RadioField = ({items, className, label, disabled, tooltipText, ...props}) => { - const [field, meta, helpers] = useField(props); - const {name} = field; - const {setValue} = helpers; - - return ( -
- {!!label && {label}} - { - items.map(({value, label}) => ( - - )) - } - {meta.touched && meta.error && {meta.error}} -
- ) -} +const RadioField = ({ + items, + className, + label, + disabled, + tooltipText, + ...props +}) => { + const [field, meta, helpers] = useField(props); + const { name } = field; + const { setValue } = helpers; -export default RadioField; \ No newline at end of file + return ( +
+ {!!label && ( + + {label} + + )} + {items.map(({ value, label }) => ( + + ))} + {meta.touched && meta.error && {meta.error}} +
+ ); +}; + +export default RadioField; diff --git a/ui/src/components/Form/form-fields/RadioField/radio-field.scss b/ui/src/components/Form/form-fields/RadioField/radio-field.scss index c2cab9356a..8ea8a5ee5d 100644 --- a/ui/src/components/Form/form-fields/RadioField/radio-field.scss +++ b/ui/src/components/Form/form-fields/RadioField/radio-field.scss @@ -1,62 +1,62 @@ -@import 'utils/scss_variables.module.scss'; +@import "utils/scss_variables.module.scss"; $selector-size: 20px; $selector-inner-size: 14px; $selector-inner-padding: 3px; .form-field-wrapper.radio-field-wrapper { - .radio-field-item { - display: flex; - align-items: center; - position: relative; - margin-bottom: 12px; - padding-left: 22px; - cursor: pointer; + .radio-field-item { + display: flex; + align-items: center; + position: relative; + margin-bottom: 12px; + padding-left: 22px; + cursor: pointer; - &.disabled { - cursor: not-allowed; + &.disabled { + cursor: not-allowed; - .radio-text { - color: $color-grey; - } - .checkmark { - border-color: $color-grey; + .radio-text { + color: $color-grey; + } + .checkmark { + border-color: $color-grey; - &:after { - background-color: $color-grey !important; - } - } + &:after { + background-color: $color-grey !important; } - .radio-text { - padding-left: 10px; - -webkit-box-decoration-break: clone; - box-decoration-break: clone; - } - input { - position: absolute; - opacity: 0; - cursor: pointer; + } + } + .radio-text { + padding-left: 10px; + -webkit-box-decoration-break: clone; + box-decoration-break: clone; + } + input { + position: absolute; + opacity: 0; + cursor: pointer; - &:checked ~ .checkmark:after { - content: ""; - position: absolute; - display: block; - top: $selector-inner-padding; - left: $selector-inner-padding; - width: $selector-inner-size; - height: $selector-inner-size; - border-radius: 50px; - background: $color-main-light; - } - } - .checkmark { - position: absolute; - left: 0; - height: $selector-size; - width: $selector-size; - background-color: white; - border-radius: 50px; - border: 1px solid $color-grey-light; - } + &:checked ~ .checkmark:after { + content: ""; + position: absolute; + display: block; + top: $selector-inner-padding; + left: $selector-inner-padding; + width: $selector-inner-size; + height: $selector-inner-size; + border-radius: 50px; + background: $color-main-light; + } + } + .checkmark { + position: absolute; + left: 0; + height: $selector-size; + width: $selector-size; + background-color: white; + border-radius: 50px; + border: 1px solid $color-grey-light; } -} \ No newline at end of file + } +} diff --git a/ui/src/components/Form/form-fields/SelectField/index.jsx b/ui/src/components/Form/form-fields/SelectField/index.jsx index 541388ed04..45c969d546 100644 --- a/ui/src/components/Form/form-fields/SelectField/index.jsx +++ b/ui/src/components/Form/form-fields/SelectField/index.jsx @@ -1,81 +1,108 @@ -import React, { useEffect, useState, useMemo } from 'react'; -import { cloneDeep, isNull, isEqual } from 'lodash'; -import classnames from 'classnames'; -import { useField } from 'formik'; -import { usePrevious } from 'hooks'; -import DropdownSelect from 'components/DropdownSelect'; -import Loader from 'components/Loader'; -import FieldError from 'components/Form/FieldError'; -import FieldLabel from 'components/Form/FieldLabel'; +import React, { useEffect, useState, useMemo } from "react"; +import { cloneDeep, isNull, isEqual } from "lodash"; +import classnames from "classnames"; +import { useField } from "formik"; +import { usePrevious } from "hooks"; +import DropdownSelect from "components/DropdownSelect"; +import Loader from "components/Loader"; +import FieldError from "components/Form/FieldError"; +import FieldLabel from "components/Form/FieldLabel"; -import './select-field.scss'; +import "./select-field.scss"; const getMissingValueItemKeys = (valueKey, items) => { - if (isNull(valueKey)) { - return items; - } + if (isNull(valueKey)) { + return items; + } - const valueInItems = items.find(item => item.value === valueKey); + const valueInItems = items.find((item) => item.value === valueKey); - if (!valueInItems) { - items = cloneDeep(items); - items.push({value: valueKey, label: valueKey}); - } + if (!valueInItems) { + items = cloneDeep(items); + items.push({ value: valueKey, label: valueKey }); + } - return items; -} + return items; +}; const SelectField = (props) => { - const {items: fieldItems=[], placeholder, creatable=false, clearable=false, disabled, className, label, tooltipText, - components={}, loading} = props; - const [field, meta, helpers] = useField(props); - const {value} = meta; - const {name} = field; - const {setValue, setTouched} = helpers; - - const formattedItems = useMemo(() => ( - creatable && value !== "" ? getMissingValueItemKeys(value, fieldItems) : fieldItems - ), [creatable, fieldItems, value]); - const prevFormattedItems = usePrevious(formattedItems); - - const [items, setItems] = useState(formattedItems); + const { + items: fieldItems = [], + placeholder, + creatable = false, + clearable = false, + disabled, + className, + label, + tooltipText, + components = {}, + loading, + } = props; + const [field, meta, helpers] = useField(props); + const { value } = meta; + const { name } = field; + const { setValue, setTouched } = helpers; + + const formattedItems = useMemo( + () => + creatable && value !== "" + ? getMissingValueItemKeys(value, fieldItems) + : fieldItems, + [creatable, fieldItems, value], + ); + const prevFormattedItems = usePrevious(formattedItems); + + const [items, setItems] = useState(formattedItems); + + useEffect(() => { + if (!isEqual(formattedItems, prevFormattedItems)) { + setItems(formattedItems); + } + }, [prevFormattedItems, formattedItems]); + + const selectedValue = items.find((item) => item.value === value) || null; + + return ( +
+ {!!label && ( + + {label} + + )} + { + const { value } = selectedItem || {}; - useEffect(() => { - if (!isEqual(formattedItems, prevFormattedItems)) { - setItems(formattedItems); - } - }, [prevFormattedItems, formattedItems]); + if (creatable) { + setItems(getMissingValueItemKeys(value, items)); + } - const selectedValue = items.find(item => item.value === value) || null; - - return ( -
- {!!label && {label}} - { - const {value} = selectedItem || {}; - - if (creatable) { - setItems(getMissingValueItemKeys(value, items)); - } - - setTouched(true, true); - setValue(value); - }} - onBlur={() => setTouched(true, true)} - creatable={creatable} - clearable={clearable} - disabled={disabled || loading} - placeholder={placeholder} - components={components} - /> - {loading && } - {meta.touched && meta.error && {meta.error}} -
- ) -} + setTouched(true, true); + setValue(value); + }} + onBlur={() => setTouched(true, true)} + creatable={creatable} + clearable={clearable} + disabled={disabled || loading} + placeholder={placeholder} + components={components} + /> + {loading && } + {meta.touched && meta.error && {meta.error}} +
+ ); +}; -export default SelectField; \ No newline at end of file +export default SelectField; diff --git a/ui/src/components/Form/form-fields/SelectField/select-field.scss b/ui/src/components/Form/form-fields/SelectField/select-field.scss index 21218c5a78..2e40cb989c 100644 --- a/ui/src/components/Form/form-fields/SelectField/select-field.scss +++ b/ui/src/components/Form/form-fields/SelectField/select-field.scss @@ -1,3 +1,3 @@ .form-field-wrapper.select-field-wrapper { - position: relative; -} \ No newline at end of file + position: relative; +} diff --git a/ui/src/components/Form/form-fields/TextField/index.jsx b/ui/src/components/Form/form-fields/TextField/index.jsx index bd4c410573..973b01bea8 100644 --- a/ui/src/components/Form/form-fields/TextField/index.jsx +++ b/ui/src/components/Form/form-fields/TextField/index.jsx @@ -1,21 +1,42 @@ -import React from 'react'; -import classnames from 'classnames'; -import { useField } from 'formik'; -import FieldError from 'components/Form/FieldError'; -import FieldLabel from 'components/Form/FieldLabel'; +import React from "react"; +import classnames from "classnames"; +import { useField } from "formik"; +import FieldError from "components/Form/FieldError"; +import FieldLabel from "components/Form/FieldLabel"; -import './text-field.scss'; +import "./text-field.scss"; -const TextField = ({className, label, disabled, tooltipText, type="text", placeholder, ...props}) => { - const [field, meta] = useField(props); - - return ( -
- {!!label && {label}} - - {meta.touched && meta.error && {meta.error}} -
- ) -} +const TextField = ({ + className, + label, + disabled, + tooltipText, + type = "text", + placeholder, + ...props +}) => { + const [field, meta] = useField(props); -export default TextField; \ No newline at end of file + return ( +
+ {!!label && ( + + {label} + + )} + + {meta.touched && meta.error && {meta.error}} +
+ ); +}; + +export default TextField; diff --git a/ui/src/components/Form/form-fields/TextField/text-field.scss b/ui/src/components/Form/form-fields/TextField/text-field.scss index c0baa5eb91..40f543a21f 100644 --- a/ui/src/components/Form/form-fields/TextField/text-field.scss +++ b/ui/src/components/Form/form-fields/TextField/text-field.scss @@ -1,31 +1,31 @@ -@import 'utils/scss_variables.module.scss'; +@import "utils/scss_variables.module.scss"; .form-field-wrapper.text-field { - .form-field { - color: $color-grey-black; - border: 1px solid $color-grey-light; - border-radius: 4px; - padding: 2px 8px; - box-sizing: border-box; - outline: none; - font-size: 14px; - line-height: 18px; - font-family: CiscoSansTT; - background-color: white; - height: $field-height; - width: 100%; + .form-field { + color: $color-grey-black; + border: 1px solid $color-grey-light; + border-radius: 4px; + padding: 2px 8px; + box-sizing: border-box; + outline: none; + font-size: 14px; + line-height: 18px; + font-family: CiscoSansTT; + background-color: white; + height: $field-height; + width: 100%; - &::placeholder { - color: $color-grey-dark; - } - &:disabled { - background-color: white; - color: $color-grey; - cursor: not-allowed; + &::placeholder { + color: $color-grey-dark; + } + &:disabled { + background-color: white; + color: $color-grey; + cursor: not-allowed; - &::placeholder { - color: $color-grey; - } - } + &::placeholder { + color: $color-grey; + } } -} \ No newline at end of file + } +} diff --git a/ui/src/components/Form/form-fields/TimeField/index.jsx b/ui/src/components/Form/form-fields/TimeField/index.jsx index eeee6ec8a4..f39673b5a8 100644 --- a/ui/src/components/Form/form-fields/TimeField/index.jsx +++ b/ui/src/components/Form/form-fields/TimeField/index.jsx @@ -1,26 +1,42 @@ -import React from 'react'; -import { isEmpty } from 'lodash'; -import classnames from 'classnames'; -import TimePicker from 'react-time-picker'; -import { useField } from 'formik'; -import FieldError from 'components/Form/FieldError'; -import FieldLabel from 'components/Form/FieldLabel'; +import React from "react"; +import { isEmpty } from "lodash"; +import classnames from "classnames"; +import TimePicker from "react-time-picker"; +import { useField } from "formik"; +import FieldError from "components/Form/FieldError"; +import FieldLabel from "components/Form/FieldLabel"; -import './time-field.scss'; +import "./time-field.scss"; const TimeField = (props) => { - const {label, className, tooltipText} = props; - const [field, meta, helpers] = useField(props); - const {name, value} = field; - const {setValue, setTouched} = helpers; + const { label, className, tooltipText } = props; + const [field, meta, helpers] = useField(props); + const { name, value } = field; + const { setValue, setTouched } = helpers; - return ( -
setTouched(true, true)}> - {!isEmpty(label) && {label}} - - {meta.touched && meta.error && {meta.error}} -
- ) -} + return ( +
setTouched(true, true)} + > + {!isEmpty(label) && ( + + {label} + + )} + + {meta.touched && meta.error && {meta.error}} +
+ ); +}; -export default TimeField; \ No newline at end of file +export default TimeField; diff --git a/ui/src/components/Form/form-fields/TimeField/time-field.scss b/ui/src/components/Form/form-fields/TimeField/time-field.scss index 10921c3b9b..5f28e96fe2 100644 --- a/ui/src/components/Form/form-fields/TimeField/time-field.scss +++ b/ui/src/components/Form/form-fields/TimeField/time-field.scss @@ -1,27 +1,27 @@ -@import 'utils/scss_variables.module.scss'; +@import "utils/scss_variables.module.scss"; .form-field-wrapper.time-field-wrapper { - .react-time-picker { - height: 34px; - border: 1px solid $color-grey-light; - color: $color-grey-black; - font-size: 14px; + .react-time-picker { + height: 34px; + border: 1px solid $color-grey-light; + color: $color-grey-black; + font-size: 14px; - .react-time-picker__wrapper { - border: none; + .react-time-picker__wrapper { + border: none; - .react-time-picker__inputGroup { - padding: 0 7px; - min-width: 50px; + .react-time-picker__inputGroup { + padding: 0 7px; + min-width: 50px; - input { - outline: none; - - &::placeholder { - color: $color-grey; - } - } - } + input { + outline: none; + + &::placeholder { + color: $color-grey; + } } + } } -} \ No newline at end of file + } +} diff --git a/ui/src/components/Form/form.scss b/ui/src/components/Form/form.scss index 93ebe38c2c..0958b46af0 100644 --- a/ui/src/components/Form/form.scss +++ b/ui/src/components/Form/form.scss @@ -1,25 +1,25 @@ -@import 'utils/scss_variables.module.scss'; +@import "utils/scss_variables.module.scss"; .form-wrapper { - .main-error-message { - font-size: 10px; - line-height: 14px; - color: $color-grey-black; - background-color: $color-error-light; - border: 1px solid $color-error; - padding: 10px; - margin-bottom: 30px; - display: flex; - align-items: center; + .main-error-message { + font-size: 10px; + line-height: 14px; + color: $color-grey-black; + background-color: $color-error-light; + border: 1px solid $color-error; + padding: 10px; + margin-bottom: 30px; + display: flex; + align-items: center; - .icon { - color: $color-error; - margin-right: 10px; - min-width: 22px; - } - } - .form-field-wrapper { - min-width: 200px; - margin-bottom: 30px; + .icon { + color: $color-error; + margin-right: 10px; + min-width: 22px; } + } + .form-field-wrapper { + min-width: 200px; + margin-bottom: 30px; + } } diff --git a/ui/src/components/Form/index.jsx b/ui/src/components/Form/index.jsx index 186838cdf9..116c554e70 100644 --- a/ui/src/components/Form/index.jsx +++ b/ui/src/components/Form/index.jsx @@ -1,112 +1,155 @@ -import React, { useEffect } from 'react'; -import { Formik, Form, useFormikContext } from 'formik'; -import { isNull, isEmpty, cloneDeep } from 'lodash'; -import classnames from 'classnames'; -import Loader from 'components/Loader'; -import Button from 'components/Button'; -import Icon, { ICON_NAMES } from 'components/Icon'; -import { useFetch, FETCH_METHODS, usePrevious } from 'hooks'; -import * as validators from './validators'; -import SelectField from './form-fields/SelectField'; -import MultiselectField from './form-fields/MultiselectField'; -import TextField from './form-fields/TextField'; -import RadioField from './form-fields/RadioField'; -import FieldsPair from './form-fields/FieldsPair'; -import CheckboxField from './form-fields/CheckboxField'; -import DateField from './form-fields/DateField'; -import TimeField from './form-fields/TimeField'; -import CronField from './form-fields/CronField'; -import FieldLabel from './FieldLabel'; - -import './form.scss'; +import React, { useEffect } from "react"; +import { Formik, Form, useFormikContext } from "formik"; +import { isNull, isEmpty, cloneDeep } from "lodash"; +import classnames from "classnames"; +import Loader from "components/Loader"; +import Button from "components/Button"; +import Icon, { ICON_NAMES } from "components/Icon"; +import { useFetch, FETCH_METHODS, usePrevious } from "hooks"; +import * as validators from "./validators"; +import SelectField from "./form-fields/SelectField"; +import MultiselectField from "./form-fields/MultiselectField"; +import TextField from "./form-fields/TextField"; +import RadioField from "./form-fields/RadioField"; +import FieldsPair from "./form-fields/FieldsPair"; +import CheckboxField from "./form-fields/CheckboxField"; +import DateField from "./form-fields/DateField"; +import TimeField from "./form-fields/TimeField"; +import CronField from "./form-fields/CronField"; +import FieldLabel from "./FieldLabel"; + +import "./form.scss"; export { - validators, - useFormikContext, - SelectField, - MultiselectField, - TextField, - RadioField, - CheckboxField, - FieldsPair, - DateField, - TimeField, - CronField, - FieldLabel -} - -const FormComponent = ({children, className, submitUrl, getSubmitParams, onSubmitSuccess, onSubmitError, saveButtonTitle="Finish", hideSaveButton=false}) => { - const {values, isSubmitting, isValidating, setSubmitting, status, setStatus, isValid, setErrors} = useFormikContext(); - - const [{loading, data, error}, submitFormData] = useFetch(submitUrl, {loadOnMount: false}); - const prevLoading = usePrevious(loading); - - const handleSubmit = () => { - setSubmitting(true); - - const submitQueryParams = !!getSubmitParams ? getSubmitParams(cloneDeep(values)) : {}; - submitFormData({method: FETCH_METHODS.POST, submitData: values, ...submitQueryParams}); + validators, + useFormikContext, + SelectField, + MultiselectField, + TextField, + RadioField, + CheckboxField, + FieldsPair, + DateField, + TimeField, + CronField, + FieldLabel, +}; + +const FormComponent = ({ + children, + className, + submitUrl, + getSubmitParams, + onSubmitSuccess, + onSubmitError, + saveButtonTitle = "Finish", + hideSaveButton = false, +}) => { + const { + values, + isSubmitting, + isValidating, + setSubmitting, + status, + setStatus, + isValid, + setErrors, + } = useFormikContext(); + + const [{ loading, data, error }, submitFormData] = useFetch(submitUrl, { + loadOnMount: false, + }); + const prevLoading = usePrevious(loading); + + const handleSubmit = () => { + setSubmitting(true); + + const submitQueryParams = !!getSubmitParams + ? getSubmitParams(cloneDeep(values)) + : {}; + submitFormData({ + method: FETCH_METHODS.POST, + submitData: values, + ...submitQueryParams, + }); + }; + + useEffect(() => { + if (prevLoading && !loading) { + setSubmitting(false); + setStatus(null); + + if (isNull(error)) { + if (!!onSubmitSuccess) { + onSubmitSuccess(data); + } + } else { + const { message, errors } = error; + + if (!!message) { + setStatus(message); + } + + if (!isEmpty(errors)) { + setErrors(errors); + } + + if (!!onSubmitError) { + onSubmitError(); + } + } } - - useEffect(() => { - if (prevLoading && !loading) { - setSubmitting(false); - setStatus(null); - - if (isNull(error)) { - if (!!onSubmitSuccess) { - onSubmitSuccess(data); - } - } else { - const {message, errors} = error; - - if (!!message) { - setStatus(message); - } - - if (!isEmpty(errors)) { - setErrors(errors); - } - - if (!!onSubmitError) { - onSubmitError(); - } - } - } - }, [prevLoading, loading, error, data, setSubmitting, setStatus, onSubmitSuccess, setErrors, onSubmitError]); - - if (isSubmitting || loading) { - return ; - } - - const disableSubmitClick = isSubmitting || isValidating || !isValid; - - return ( -
- {!!status && -
- -
{status}
-
- } - {children} - {!hideSaveButton && - - } -
- ) -} - -const FormWrapper = ({children, initialValues, validate, ...props}) => { - return ( - - - {children} - - - ) -} - -export default FormWrapper; \ No newline at end of file + }, [ + prevLoading, + loading, + error, + data, + setSubmitting, + setStatus, + onSubmitSuccess, + setErrors, + onSubmitError, + ]); + + if (isSubmitting || loading) { + return ; + } + + const disableSubmitClick = isSubmitting || isValidating || !isValid; + + return ( +
+ {!!status && ( +
+ +
{status}
+
+ )} + {children} + {!hideSaveButton && ( + + )} +
+ ); +}; + +const FormWrapper = ({ children, initialValues, validate, ...props }) => { + return ( + + {children} + + ); +}; + +export default FormWrapper; diff --git a/ui/src/components/Form/validators.js b/ui/src/components/Form/validators.js index fe3cc685df..c145d53dc6 100644 --- a/ui/src/components/Form/validators.js +++ b/ui/src/components/Form/validators.js @@ -1,15 +1,16 @@ -import { isEmpty, isNumber } from 'lodash'; +import { isEmpty, isNumber } from "lodash"; -export const validateRequired = value => ( - isEmpty(value) && !isNumber(value) && value !== 0 ? "This field is required" : undefined -); +export const validateRequired = (value) => + isEmpty(value) && !isNumber(value) && value !== 0 + ? "This field is required" + : undefined; export const keyValueListValidator = (valuesList) => { - const errorItem = valuesList.find(item => { - const valueList = item.split("="); + const errorItem = valuesList.find((item) => { + const valueList = item.split("="); - return valueList.length !== 2 || valueList.find(item => item === ""); - }) + return valueList.length !== 2 || valueList.find((item) => item === ""); + }); - return !!errorItem ? "Values must be in the key=value format" : null; -} \ No newline at end of file + return !!errorItem ? "Values must be in the key=value format" : null; +}; diff --git a/ui/src/components/Icon/IconTemplates.jsx b/ui/src/components/Icon/IconTemplates.jsx index a860dacb37..316d1cc192 100644 --- a/ui/src/components/Icon/IconTemplates.jsx +++ b/ui/src/components/Icon/IconTemplates.jsx @@ -1,11 +1,19 @@ -import React from 'react'; -import { ICON_NAMES } from './utils'; +import React from "react"; +import { ICON_NAMES } from "./utils"; const IconTemplates = () => ( - - - - + + + - - - + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - + + + - - - - - - - + + + + + + + - - - + + + - - - + + + - - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + - - - + + + - - - - - + + + + + - - - + + + - + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - - - - + + + + + + - - - + 13.2073 10.3619 12.4546C10.3619 11.7019 9.75095 11.091 8.99822 11.091Z" + /> + + + ); -export default IconTemplates; \ No newline at end of file +export default IconTemplates; diff --git a/ui/src/components/Icon/icon.scss b/ui/src/components/Icon/icon.scss index 4b5c9017b1..e9e0356cde 100644 --- a/ui/src/components/Icon/icon.scss +++ b/ui/src/components/Icon/icon.scss @@ -1,16 +1,16 @@ -@import 'utils/scss_variables.module.scss'; +@import "utils/scss_variables.module.scss"; .icon { - display: inline-block; - stroke-width: 0; - stroke: currentColor; - fill: currentColor; + display: inline-block; + stroke-width: 0; + stroke: currentColor; + fill: currentColor; - &.clickable:not(.disabled) { - cursor: pointer; - } - &.disabled { - cursor: not-allowed; - color: $color-grey; - } -} \ No newline at end of file + &.clickable:not(.disabled) { + cursor: pointer; + } + &.disabled { + cursor: not-allowed; + color: $color-grey; + } +} diff --git a/ui/src/components/Icon/index.jsx b/ui/src/components/Icon/index.jsx index 9da9b90810..a8cb83627d 100644 --- a/ui/src/components/Icon/index.jsx +++ b/ui/src/components/Icon/index.jsx @@ -1,38 +1,42 @@ -import React from 'react'; -import classnames from 'classnames'; -import { isEmpty } from 'lodash'; -import { ICON_NAMES } from './utils'; -import IconTemplates from './IconTemplates'; +import React from "react"; +import classnames from "classnames"; +import { isEmpty } from "lodash"; +import { ICON_NAMES } from "./utils"; +import IconTemplates from "./IconTemplates"; -import './icon.scss'; +import "./icon.scss"; -export { - ICON_NAMES, - IconTemplates -} +export { ICON_NAMES, IconTemplates }; -const Icon = ({name, className, onClick, disabled, size=22, style={}}) => { - if (!Object.values(ICON_NAMES).includes(name)) { - console.error(`Icon name '${name}' does not exist`); - } - - return ( - !disabled && !!onClick ? onClick(event) : undefined} - style={{...style, height: `${size}px`, width: `${size}px`}} - > - - - ) -} +const Icon = ({ + name, + className, + onClick, + disabled, + size = 22, + style = {}, +}) => { + if (!Object.values(ICON_NAMES).includes(name)) { + console.error(`Icon name '${name}' does not exist`); + } -export default Icon; \ No newline at end of file + return ( + (!disabled && !!onClick ? onClick(event) : undefined)} + style={{ ...style, height: `${size}px`, width: `${size}px` }} + > + + + ); +}; + +export default Icon; diff --git a/ui/src/components/Icon/utils.js b/ui/src/components/Icon/utils.js index e7e3d8c5f1..dd406fa4b0 100644 --- a/ui/src/components/Icon/utils.js +++ b/ui/src/components/Icon/utils.js @@ -1,34 +1,34 @@ export const ICON_NAMES = { - DASHBOARD: "dashboard", - SCANS: "scans", - ASSETS: "assets", - ASSET_SCANS: "asset-scans", - FINDINGS: "findings", - DUPLICATE: "duplicate", - X_MARK: "x-mark", - CHECK_MARK: "check-mark", - BLOCK: "block", - SORT: "sort", - CHEVRON_RIGHT: "chevron-right", - CHEVRON_RIGHT_DOUBLE: "chevron-right-double", - ARROW_HEAD_LEFT: "arrow-head-left", - ARROW_UP: "ARROW_UP", - PLUS: "plus", - MINUS: "minus", - PLAY: "play", - STOP: "stop", - DELETE: "delete", - EDIT: "edit", - INFO: "info", - SHIELD: "shield", - WARNING: "warning", - BOMB: "bomb", - COG: "cog", - KEY: "key", - BUG: "bug", - GHOST: "ghost", - PACKAGE: "package", - REFRESH: "refresh", - STROKE: "stroke", - FILTER: "filter" -} \ No newline at end of file + DASHBOARD: "dashboard", + SCANS: "scans", + ASSETS: "assets", + ASSET_SCANS: "asset-scans", + FINDINGS: "findings", + DUPLICATE: "duplicate", + X_MARK: "x-mark", + CHECK_MARK: "check-mark", + BLOCK: "block", + SORT: "sort", + CHEVRON_RIGHT: "chevron-right", + CHEVRON_RIGHT_DOUBLE: "chevron-right-double", + ARROW_HEAD_LEFT: "arrow-head-left", + ARROW_UP: "ARROW_UP", + PLUS: "plus", + MINUS: "minus", + PLAY: "play", + STOP: "stop", + DELETE: "delete", + EDIT: "edit", + INFO: "info", + SHIELD: "shield", + WARNING: "warning", + BOMB: "bomb", + COG: "cog", + KEY: "key", + BUG: "bug", + GHOST: "ghost", + PACKAGE: "package", + REFRESH: "refresh", + STROKE: "stroke", + FILTER: "filter", +}; diff --git a/ui/src/components/IconWithTooltip/index.jsx b/ui/src/components/IconWithTooltip/index.jsx index fa4d631a41..013c9141c2 100644 --- a/ui/src/components/IconWithTooltip/index.jsx +++ b/ui/src/components/IconWithTooltip/index.jsx @@ -1,13 +1,16 @@ -import React from 'react'; -import Icon from 'components/Icon'; -import { TooltipWrapper } from 'components/Tooltip'; +import React from "react"; +import Icon from "components/Icon"; +import { TooltipWrapper } from "components/Tooltip"; -const IconWithTooltip = ({tooltipId, tooltipText, size=22, ...props}) => ( -
- - - -
-) +const IconWithTooltip = ({ tooltipId, tooltipText, size = 22, ...props }) => ( +
+ + + +
+); -export default IconWithTooltip; \ No newline at end of file +export default IconWithTooltip; diff --git a/ui/src/components/InfoIcon/index.jsx b/ui/src/components/InfoIcon/index.jsx index b4fe9ba442..8cf837d820 100644 --- a/ui/src/components/InfoIcon/index.jsx +++ b/ui/src/components/InfoIcon/index.jsx @@ -1,14 +1,18 @@ -import React from 'react'; -import classnames from 'classnames'; -import Icon, { ICON_NAMES } from 'components/Icon'; -import { TooltipWrapper } from 'components/Tooltip'; +import React from "react"; +import classnames from "classnames"; +import Icon, { ICON_NAMES } from "components/Icon"; +import { TooltipWrapper } from "components/Tooltip"; -import './info-icon.scss'; +import "./info-icon.scss"; -const InfoIcon = ({tooltipId, tooltipText, large=false}) => ( - - - -) +const InfoIcon = ({ tooltipId, tooltipText, large = false }) => ( + + + +); -export default InfoIcon; \ No newline at end of file +export default InfoIcon; diff --git a/ui/src/components/InfoIcon/info-icon.scss b/ui/src/components/InfoIcon/info-icon.scss index 2d2fa2a57a..24dcf37af9 100644 --- a/ui/src/components/InfoIcon/info-icon.scss +++ b/ui/src/components/InfoIcon/info-icon.scss @@ -1,27 +1,27 @@ -@import 'utils/scss_variables.module.scss'; +@import "utils/scss_variables.module.scss"; $size: 12px; $size-large: 14px; .info-icon-wrapper { - background-color: $color-grey-light; - width: $size; - height: $size; - border-radius: 50%; - display: inline-flex !important; - align-items: center; - justify-content: space-around; - - &.large { - width: $size-large; - height: $size-large; - background-color: $color-main; + background-color: $color-grey-light; + width: $size; + height: $size; + border-radius: 50%; + display: inline-flex !important; + align-items: center; + justify-content: space-around; + + &.large { + width: $size-large; + height: $size-large; + background-color: $color-main; - .icon { - color: white; - } - } .icon { - color: $color-grey-black; + color: white; } -} \ No newline at end of file + } + .icon { + color: $color-grey-black; + } +} diff --git a/ui/src/components/LinksList/index.jsx b/ui/src/components/LinksList/index.jsx index 18d6ea0104..36775df279 100644 --- a/ui/src/components/LinksList/index.jsx +++ b/ui/src/components/LinksList/index.jsx @@ -1,32 +1,36 @@ -import React from 'react'; -import { useNavigate } from 'react-router-dom'; -import Arrow, { ARROW_NAMES } from 'components/Arrow'; +import React from "react"; +import { useNavigate } from "react-router-dom"; +import Arrow, { ARROW_NAMES } from "components/Arrow"; -import './links-list.scss'; +import "./links-list.scss"; -const LinksList = ({items}) => { - const navigate = useNavigate(); +const LinksList = ({ items }) => { + const navigate = useNavigate(); - const onItemClick = (path, callback) => { - if (!!callback) { - callback(path); - } - - navigate(path); + const onItemClick = (path, callback) => { + if (!!callback) { + callback(path); } - return ( -
- { - items.map(({path, component: Component, callback}, index) => ( -
onItemClick(path, callback)}> -
- -
- )) - } + navigate(path); + }; + + return ( +
+ {items.map(({ path, component: Component, callback }, index) => ( +
onItemClick(path, callback)} + > +
+ +
+
- ) -} + ))} +
+ ); +}; -export default LinksList; \ No newline at end of file +export default LinksList; diff --git a/ui/src/components/LinksList/links-list.scss b/ui/src/components/LinksList/links-list.scss index b0c3a5d764..0842d48599 100644 --- a/ui/src/components/LinksList/links-list.scss +++ b/ui/src/components/LinksList/links-list.scss @@ -1,23 +1,23 @@ -@import 'utils/scss_variables.module.scss'; +@import "utils/scss_variables.module.scss"; .links-list-wrapper { - .links-list-item { - display: flex; - align-items: center; - justify-content: space-between; - padding: 10px 20px; - min-height: 50px; - box-sizing: border-box; - box-shadow: 0px 0px 10px rgba(177, 171, 189, 0.2); - border: 1px solid $color-grey-light; - overflow: hidden; - cursor: pointer; + .links-list-item { + display: flex; + align-items: center; + justify-content: space-between; + padding: 10px 20px; + min-height: 50px; + box-sizing: border-box; + box-shadow: 0px 0px 10px rgba(177, 171, 189, 0.2); + border: 1px solid $color-grey-light; + overflow: hidden; + cursor: pointer; - &:not(:last-child) { - margin-bottom: 10px; - } - &:hover { - background-color: $color-blue-light; - } + &:not(:last-child) { + margin-bottom: 10px; } -} \ No newline at end of file + &:hover { + background-color: $color-blue-light; + } + } +} diff --git a/ui/src/components/ListAndDetailsRouter/index.jsx b/ui/src/components/ListAndDetailsRouter/index.jsx index b6f61be128..17827dd76d 100644 --- a/ui/src/components/ListAndDetailsRouter/index.jsx +++ b/ui/src/components/ListAndDetailsRouter/index.jsx @@ -1,13 +1,17 @@ -import React from 'react'; -import { Route, Routes, Outlet } from 'react-router-dom'; +import React from "react"; +import { Route, Routes, Outlet } from "react-router-dom"; -const ListAndDetailsRouter = ({listComponent: ListComponent, detailsComponent: DetailsComponent, detailsPath=":id"}) => ( - - }> - } /> - } /> - - -) +const ListAndDetailsRouter = ({ + listComponent: ListComponent, + detailsComponent: DetailsComponent, + detailsPath = ":id", +}) => ( + + }> + } /> + } /> + + +); -export default ListAndDetailsRouter; \ No newline at end of file +export default ListAndDetailsRouter; diff --git a/ui/src/components/Loader/index.jsx b/ui/src/components/Loader/index.jsx index f7cc9a2667..f3ae21d6e2 100644 --- a/ui/src/components/Loader/index.jsx +++ b/ui/src/components/Loader/index.jsx @@ -1,20 +1,20 @@ -import React from 'react'; -import classnames from 'classnames'; -import { SpinnerCircularFixed } from 'spinners-react'; +import React from "react"; +import classnames from "classnames"; +import { SpinnerCircularFixed } from "spinners-react"; -import COLORS from 'utils/scss_variables.module.scss'; +import COLORS from "utils/scss_variables.module.scss"; -import './loader.scss'; +import "./loader.scss"; -const Loader = ({large=false, small=false, absolute=true}) => ( - +const Loader = ({ large = false, small = false, absolute = true }) => ( + ); -export default Loader; \ No newline at end of file +export default Loader; diff --git a/ui/src/components/Loader/loader.scss b/ui/src/components/Loader/loader.scss index 9f2e59a59b..f0d11d46e4 100644 --- a/ui/src/components/Loader/loader.scss +++ b/ui/src/components/Loader/loader.scss @@ -1,8 +1,8 @@ .clarity-loader { - &.absolute { - position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - } -} \ No newline at end of file + &.absolute { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + } +} diff --git a/ui/src/components/Modal/index.jsx b/ui/src/components/Modal/index.jsx index 83fc60368c..2ee0c0fef5 100644 --- a/ui/src/components/Modal/index.jsx +++ b/ui/src/components/Modal/index.jsx @@ -1,55 +1,93 @@ -import React, { useEffect, useState } from 'react'; -import ReactDOM from 'react-dom'; -import classnames from 'classnames'; -import CloseButton from 'components/CloseButton'; -import Button from 'components/Button'; -import Title from 'components/Title'; +import React, { useEffect, useState } from "react"; +import ReactDOM from "react-dom"; +import classnames from "classnames"; +import CloseButton from "components/CloseButton"; +import Button from "components/Button"; +import Title from "components/Title"; -import './modal.scss'; +import "./modal.scss"; const Modal = (props) => { - const {title, isMediumTitle=false, children, onClose, className, height=380, width=720, stickLeft=false, onDone, doneTitle="Done", disableDone=false, hideCancel=false, - hideSubmit=false} = props; + const { + title, + isMediumTitle = false, + children, + onClose, + className, + height = 380, + width = 720, + stickLeft = false, + onDone, + doneTitle = "Done", + disableDone = false, + hideCancel = false, + hideSubmit = false, + } = props; - const [portalContainer, setPortalContainer] = useState(null); + const [portalContainer, setPortalContainer] = useState(null); - useEffect(() => { - const container = document.querySelector("#main-wrapper"); + useEffect(() => { + const container = document.querySelector("#main-wrapper"); - if (!container) { - return; - } - - setPortalContainer(container); - }, []); - - if (!portalContainer) { - return null; + if (!container) { + return; } - return ReactDOM.createPortal( -
{ - event.stopPropagation(); - event.preventDefault(); - - onClose(); - }}> -
event.stopPropagation()} + setPortalContainer(container); + }, []); + + if (!portalContainer) { + return null; + } + + return ReactDOM.createPortal( +
{ + event.stopPropagation(); + event.preventDefault(); + + onClose(); + }} + > +
event.stopPropagation()} + > + + {title} + +
{children}
+ +
+ {!hideCancel && ( + + )} + {!hideSubmit && ( + } - {!hideSubmit && } -
-
-
, - portalContainer - ); -} - -export default Modal; \ No newline at end of file + {doneTitle} + + )} +
+
+
, + portalContainer, + ); +}; + +export default Modal; diff --git a/ui/src/components/Modal/modal.scss b/ui/src/components/Modal/modal.scss index 8ec5c5c26d..6c3e676864 100644 --- a/ui/src/components/Modal/modal.scss +++ b/ui/src/components/Modal/modal.scss @@ -1,53 +1,53 @@ -@import 'utils/scss_variables.module.scss'; +@import "utils/scss_variables.module.scss"; $padding: 30px; .modal-outer-wrapper { - position: absolute; - top: 0; - left: $side-bar-width; - width: calc(100% - #{$side-bar-width}); - height: 100%; - background-color: rgba(#E4E6E8, 0.8); - z-index: 1; + position: absolute; + top: 0; + left: $side-bar-width; + width: calc(100% - #{$side-bar-width}); + height: 100%; + background-color: rgba(#e4e6e8, 0.8); + z-index: 1; - .modal-inner-wrapper { - position: absolute; - background: white; - box-sizing: border-box; - box-shadow: -8px 12px 19px rgba(55, 72, 95, 0.13); + .modal-inner-wrapper { + position: absolute; + background: white; + box-sizing: border-box; + box-shadow: -8px 12px 19px rgba(55, 72, 95, 0.13); - &:not(.stick-left) { - top: 40%; - left: 50%; - transform: translate(-50%, -50%); - } - &.stick-left { - top: 0; - bottom: 0; - left: 0; - } - .close-button { - position: absolute; - top: $padding; - right: $padding; - } - .modal-content { - font-size: 14px; - line-height: 18px; - color: $color-grey-black; - } - .modal-actions { - display: flex; - align-items: center; - justify-content: flex-end; - position: absolute; - right: $padding; - bottom: $padding; + &:not(.stick-left) { + top: 40%; + left: 50%; + transform: translate(-50%, -50%); + } + &.stick-left { + top: 0; + bottom: 0; + left: 0; + } + .close-button { + position: absolute; + top: $padding; + right: $padding; + } + .modal-content { + font-size: 14px; + line-height: 18px; + color: $color-grey-black; + } + .modal-actions { + display: flex; + align-items: center; + justify-content: flex-end; + position: absolute; + right: $padding; + bottom: $padding; - button:not(:last-child) { - margin-right: 10px; - } - } + button:not(:last-child) { + margin-right: 10px; + } } -} \ No newline at end of file + } +} diff --git a/ui/src/components/Notification/index.jsx b/ui/src/components/Notification/index.jsx index 3254314902..b7ea8b6d5c 100644 --- a/ui/src/components/Notification/index.jsx +++ b/ui/src/components/Notification/index.jsx @@ -1,41 +1,46 @@ -import React, { useEffect, useState } from 'react'; -import ReactDOM from 'react-dom'; -import classnames from 'classnames'; -import CloseButton from 'components/CloseButton'; +import React, { useEffect, useState } from "react"; +import ReactDOM from "react-dom"; +import classnames from "classnames"; +import CloseButton from "components/CloseButton"; -import './notification.scss'; +import "./notification.scss"; export const NOTIFICATION_TYPES = { - INFO: "INFO", - ERROR: "ERROR" -} + INFO: "INFO", + ERROR: "ERROR", +}; -const Notification = ({message, type, onClose}) => { - const [portalContainer, setPortalContainer] = useState(null); +const Notification = ({ message, type, onClose }) => { + const [portalContainer, setPortalContainer] = useState(null); - useEffect(() => { - const container = document.querySelector("main"); + useEffect(() => { + const container = document.querySelector("main"); - if (!container) { - return; - } - - setPortalContainer(container); - }, []); - - if (!portalContainer) { - return null; + if (!container) { + return; } - const notificationType = type || NOTIFICATION_TYPES.INFO; - - return ReactDOM.createPortal( -
-
{message}
- -
, - portalContainer - ); -} - -export default Notification; \ No newline at end of file + setPortalContainer(container); + }, []); + + if (!portalContainer) { + return null; + } + + const notificationType = type || NOTIFICATION_TYPES.INFO; + + return ReactDOM.createPortal( +
+
{message}
+ +
, + portalContainer, + ); +}; + +export default Notification; diff --git a/ui/src/components/Notification/notification.scss b/ui/src/components/Notification/notification.scss index edb32b981b..b816671662 100644 --- a/ui/src/components/Notification/notification.scss +++ b/ui/src/components/Notification/notification.scss @@ -1,25 +1,25 @@ -@import 'utils/scss_variables.module.scss'; +@import "utils/scss_variables.module.scss"; .notification-wrapper { - position: absolute; - top: 0; - left: 0; - right: 0; - background-color: $color-blue; - color: $color-grey-black; - height: 50px; - font-size: 12px; - line-height: 18px; - display: flex; - align-items: center; - padding: 0 30px; - z-index: 3; - filter: drop-shadow(0px 5px 10px rgba(34, 43, 54, 0.11)); + position: absolute; + top: 0; + left: 0; + right: 0; + background-color: $color-blue; + color: $color-grey-black; + height: 50px; + font-size: 12px; + line-height: 18px; + display: flex; + align-items: center; + padding: 0 30px; + z-index: 3; + filter: drop-shadow(0px 5px 10px rgba(34, 43, 54, 0.11)); - &.error { - background-color: $color-error-light; - } - .notification-content { - width: 100%; - } -} \ No newline at end of file + &.error { + background-color: $color-error-light; + } + .notification-content { + width: 100%; + } +} diff --git a/ui/src/components/ProgressBar/index.jsx b/ui/src/components/ProgressBar/index.jsx index 64deb42377..97e88b8fb1 100644 --- a/ui/src/components/ProgressBar/index.jsx +++ b/ui/src/components/ProgressBar/index.jsx @@ -1,43 +1,82 @@ -import React from 'react'; -import classnames from 'classnames'; -import Icon, { ICON_NAMES } from 'components/Icon'; -import IconWithTooltip from 'components/IconWithTooltip'; +import React from "react"; +import classnames from "classnames"; +import Icon, { ICON_NAMES } from "components/Icon"; +import IconWithTooltip from "components/IconWithTooltip"; -import COLORS from 'utils/scss_variables.module.scss'; +import COLORS from "utils/scss_variables.module.scss"; -import './progress-bar.scss'; +import "./progress-bar.scss"; export const STATUS_MAPPPING = { - IN_PROGRESS: {value: "IN_PROGRESS", color: COLORS["color-main"]}, - SUCCESS: {value: "SUCCESS", icon: ICON_NAMES.CHECK_MARK, color: COLORS["color-success"]}, - ERROR: {value: "ERROR", icon: ICON_NAMES.X_MARK, color: COLORS["color-error"]}, - STOPPED: {value: "STOPPED", icon: ICON_NAMES.BLOCK, color: COLORS["color-grey"]}, - WARNING: {value: "WARNING", icon: ICON_NAMES.WARNING, color: COLORS["color-success"], iconColor: COLORS["color-warning"]} -} + IN_PROGRESS: { value: "IN_PROGRESS", color: COLORS["color-main"] }, + SUCCESS: { + value: "SUCCESS", + icon: ICON_NAMES.CHECK_MARK, + color: COLORS["color-success"], + }, + ERROR: { + value: "ERROR", + icon: ICON_NAMES.X_MARK, + color: COLORS["color-error"], + }, + STOPPED: { + value: "STOPPED", + icon: ICON_NAMES.BLOCK, + color: COLORS["color-grey"], + }, + WARNING: { + value: "WARNING", + icon: ICON_NAMES.WARNING, + color: COLORS["color-success"], + iconColor: COLORS["color-warning"], + }, +}; -const ProgressBar = ({status=STATUS_MAPPPING.IN_PROGRESS.value, itemsCompleted=0, itemsLeft=0, width="100%", message=null, messageTooltipId=null, customeTitle}) => { - const totalItems = itemsCompleted + itemsLeft; - const percent = status === STATUS_MAPPPING.IN_PROGRESS.value ? (!!totalItems ? Math.round((itemsCompleted / totalItems) * 100) : 0) : 100; +const ProgressBar = ({ + status = STATUS_MAPPPING.IN_PROGRESS.value, + itemsCompleted = 0, + itemsLeft = 0, + width = "100%", + message = null, + messageTooltipId = null, + customeTitle, +}) => { + const totalItems = itemsCompleted + itemsLeft; + const percent = + status === STATUS_MAPPPING.IN_PROGRESS.value + ? !!totalItems + ? Math.round((itemsCompleted / totalItems) * 100) + : 0 + : 100; - const {icon, color, iconColor} = STATUS_MAPPPING[status]; - const progressIconColor = iconColor || color; - const IconComponent = !!message ? IconWithTooltip : Icon; + const { icon, color, iconColor } = STATUS_MAPPPING[status]; + const progressIconColor = iconColor || color; + const IconComponent = !!message ? IconWithTooltip : Icon; - return ( -
-
-
-
- {!icon ?
{!!customeTitle ? customeTitle : `${percent}%`}
: - - } + return ( +
+
+
+
+ {!icon ? ( +
+ {!!customeTitle ? customeTitle : `${percent}%`}
- ) -} + ) : ( + + )} +
+ ); +}; -export default ProgressBar; \ No newline at end of file +export default ProgressBar; diff --git a/ui/src/components/ProgressBar/progress-bar.scss b/ui/src/components/ProgressBar/progress-bar.scss index adaf93cc34..9f00fdd857 100644 --- a/ui/src/components/ProgressBar/progress-bar.scss +++ b/ui/src/components/ProgressBar/progress-bar.scss @@ -1,29 +1,29 @@ -@import 'utils/scss_variables.module.scss'; +@import "utils/scss_variables.module.scss"; $loader-border-radius: 3px; .progress-bar-wrapper { - display: flex; - align-items: center; - width: 100%; + display: flex; + align-items: center; + width: 100%; - .progress-bar-container { - position: relative; - height: 6px; - background-color: $color-grey-light; - border-radius: $loader-border-radius; + .progress-bar-container { + position: relative; + height: 6px; + background-color: $color-grey-light; + border-radius: $loader-border-radius; - .progress-bar-filler { - border-radius: $loader-border-radius; - height: 100%; - } + .progress-bar-filler { + border-radius: $loader-border-radius; + height: 100%; } - .progress-bar-title, - .icon { - margin-left: 10px; - } - .progress-bar-title { - font-size: 14px; - line-height: 16px; - } -} \ No newline at end of file + } + .progress-bar-title, + .icon { + margin-left: 10px; + } + .progress-bar-title { + font-size: 14px; + line-height: 16px; + } +} diff --git a/ui/src/components/ScanProgressBar/index.jsx b/ui/src/components/ScanProgressBar/index.jsx index d3f30816cc..f9c3e77258 100644 --- a/ui/src/components/ScanProgressBar/index.jsx +++ b/ui/src/components/ScanProgressBar/index.jsx @@ -1,51 +1,103 @@ -import React from 'react'; -import ProgressBar, { STATUS_MAPPPING } from 'components/ProgressBar'; -import ErrorMessageDisplay from 'components/ErrorMessageDisplay'; +import React from "react"; +import ProgressBar, { STATUS_MAPPPING } from "components/ProgressBar"; +import ErrorMessageDisplay from "components/ErrorMessageDisplay"; -import './scan-progress-bar.scss'; +import "./scan-progress-bar.scss"; export const SCAN_STATES = { - Pending: {state: "Pending", title: "Pending"}, - Discovered: {state: "Discovered", title: "Discovered"}, - InProgress: {state: "InProgress", title: "In progress"}, - Failed: {state: "Failed", title: "Failed"}, - Done: {state: "Done", title: "Done"}, - Aborted: {state: "Aborted", title: "Aborted"}, -} + Pending: { state: "Pending", title: "Pending" }, + Discovered: { state: "Discovered", title: "Discovered" }, + InProgress: { state: "InProgress", title: "In progress" }, + Failed: { state: "Failed", title: "Failed" }, + Done: { state: "Done", title: "Done" }, + Aborted: { state: "Aborted", title: "Aborted" }, +}; const SCAN_STATES_AND_REASONS_MAPPINGS = [ - {...SCAN_STATES.Pending, reason: "Created", status: STATUS_MAPPPING.IN_PROGRESS.value}, - {...SCAN_STATES.Discovered, reason: "AssetsDiscovered", status: STATUS_MAPPPING.IN_PROGRESS.value}, - {...SCAN_STATES.InProgress, reason: "AssetScansRunning", status: STATUS_MAPPPING.IN_PROGRESS.value}, - {...SCAN_STATES.Failed, reason: "Cancellation", status: STATUS_MAPPPING.STOPPED.value}, - {...SCAN_STATES.Failed, reason: "Timeout", status: STATUS_MAPPPING.ERROR.value, errorTitle: "Scan has been timed out"}, - {...SCAN_STATES.Failed, reason: "AssetScanFailed", status: STATUS_MAPPPING.ERROR.value, errorTitle: "Some of the elements were failed to be scanned"}, - {...SCAN_STATES.Done, reason: "NothingToScan", status: STATUS_MAPPPING.SUCCESS.value}, - {...SCAN_STATES.Done, reason: "Success", status: STATUS_MAPPPING.SUCCESS.value}, - {...SCAN_STATES.Aborted, reason: "Cancellation", status: STATUS_MAPPPING.STOPPED.value} + { + ...SCAN_STATES.Pending, + reason: "Created", + status: STATUS_MAPPPING.IN_PROGRESS.value, + }, + { + ...SCAN_STATES.Discovered, + reason: "AssetsDiscovered", + status: STATUS_MAPPPING.IN_PROGRESS.value, + }, + { + ...SCAN_STATES.InProgress, + reason: "AssetScansRunning", + status: STATUS_MAPPPING.IN_PROGRESS.value, + }, + { + ...SCAN_STATES.Failed, + reason: "Cancellation", + status: STATUS_MAPPPING.STOPPED.value, + }, + { + ...SCAN_STATES.Failed, + reason: "Timeout", + status: STATUS_MAPPPING.ERROR.value, + errorTitle: "Scan has been timed out", + }, + { + ...SCAN_STATES.Failed, + reason: "AssetScanFailed", + status: STATUS_MAPPPING.ERROR.value, + errorTitle: "Some of the elements were failed to be scanned", + }, + { + ...SCAN_STATES.Done, + reason: "NothingToScan", + status: STATUS_MAPPPING.SUCCESS.value, + }, + { + ...SCAN_STATES.Done, + reason: "Success", + status: STATUS_MAPPPING.SUCCESS.value, + }, + { + ...SCAN_STATES.Aborted, + reason: "Cancellation", + status: STATUS_MAPPPING.STOPPED.value, + }, ]; -const ScanProgressBar = ({itemsCompleted, itemsLeft, state, stateReason, stateMessage, barWidth, isMinimized=false, minimizedTooltipId=null}) => { - const {status, errorTitle} = SCAN_STATES_AND_REASONS_MAPPINGS.find(item => item.state === state && item.reason === stateReason) || {}; +const ScanProgressBar = ({ + itemsCompleted, + itemsLeft, + state, + stateReason, + stateMessage, + barWidth, + isMinimized = false, + minimizedTooltipId = null, +}) => { + const { status, errorTitle } = + SCAN_STATES_AND_REASONS_MAPPINGS.find( + (item) => item.state === state && item.reason === stateReason, + ) || {}; - return ( -
- - {!isMinimized && errorTitle && -
-
{errorTitle}
- {stateMessage || errorTitle} -
- } + return ( +
+ + {!isMinimized && errorTitle && ( +
+
{errorTitle}
+ + {stateMessage || errorTitle} +
- ) -} + )} +
+ ); +}; -export default ScanProgressBar; \ No newline at end of file +export default ScanProgressBar; diff --git a/ui/src/components/ScanProgressBar/scan-progress-bar.scss b/ui/src/components/ScanProgressBar/scan-progress-bar.scss index 6996f35979..5b470c2d65 100644 --- a/ui/src/components/ScanProgressBar/scan-progress-bar.scss +++ b/ui/src/components/ScanProgressBar/scan-progress-bar.scss @@ -1,13 +1,13 @@ -@import 'utils/scss_variables.module.scss'; +@import "utils/scss_variables.module.scss"; .scan-progres-bar-wrapper { - .error-display-wrapper { - margin-top: 25px; + .error-display-wrapper { + margin-top: 25px; - .error-display-title { - font-weight: 400; - font-size: 14px; - line-height: 18px; - } + .error-display-title { + font-weight: 400; + font-size: 14px; + line-height: 18px; } -} \ No newline at end of file + } +} diff --git a/ui/src/components/SeverityDisplay/index.jsx b/ui/src/components/SeverityDisplay/index.jsx index eb153985ea..22d3303474 100644 --- a/ui/src/components/SeverityDisplay/index.jsx +++ b/ui/src/components/SeverityDisplay/index.jsx @@ -1,30 +1,40 @@ -import React from 'react'; -import Icon from 'components/Icon'; -import { toCapitalized } from 'utils/utils'; -import { VULNERABIITY_FINDINGS_ITEM } from 'utils/systemConsts'; +import React from "react"; +import Icon from "components/Icon"; +import { toCapitalized } from "utils/utils"; +import { VULNERABIITY_FINDINGS_ITEM } from "utils/systemConsts"; -import './severity-display.scss'; +import "./severity-display.scss"; -import COLORS from 'utils/scss_variables.module.scss'; +import COLORS from "utils/scss_variables.module.scss"; export const SEVERITY_ITEMS = { - CRITICAL: {valueKey: "CRITICAL", color: COLORS["color-error-dark"]}, - HIGH: {valueKey: "HIGH", color: COLORS["color-error"]}, - MEDIUM: {valueKey: "MEDIUM", color: COLORS["color-warning"]}, - LOW: {valueKey: "LOW", color: COLORS["color-warning-low"]}, - NEGLIGIBLE: {valueKey: "NEGLIGIBLE", color: COLORS["color-status-blue"]}, - NONE: {valueKey: "NONE", color: COLORS["color-status-blue"]} -} + CRITICAL: { valueKey: "CRITICAL", color: COLORS["color-error-dark"] }, + HIGH: { valueKey: "HIGH", color: COLORS["color-error"] }, + MEDIUM: { valueKey: "MEDIUM", color: COLORS["color-warning"] }, + LOW: { valueKey: "LOW", color: COLORS["color-warning-low"] }, + NEGLIGIBLE: { valueKey: "NEGLIGIBLE", color: COLORS["color-status-blue"] }, + NONE: { valueKey: "NONE", color: COLORS["color-status-blue"] }, +}; -const SeverityDisplay = ({severity, score}) => { - const {color} = SEVERITY_ITEMS[severity]; +const SeverityDisplay = ({ severity, score }) => { + const { color } = SEVERITY_ITEMS[severity]; - return ( -
- {!!score ?
{score}
: } -
{toCapitalized(severity)}
-
- ) -} + return ( +
+ {!!score ? ( +
{score}
+ ) : ( + + )} +
+ {toCapitalized(severity)} +
+
+ ); +}; -export default SeverityDisplay; \ No newline at end of file +export default SeverityDisplay; diff --git a/ui/src/components/SeverityDisplay/severity-display.scss b/ui/src/components/SeverityDisplay/severity-display.scss index 2f024a7c97..683c16dea4 100644 --- a/ui/src/components/SeverityDisplay/severity-display.scss +++ b/ui/src/components/SeverityDisplay/severity-display.scss @@ -1,19 +1,19 @@ .severity-display { - display: flex; - align-items: center; + display: flex; + align-items: center; - .severity-title { - margin-left: 5px; - } + .severity-title { + margin-left: 5px; + } } .severity-with-cvss-display { - display: flex; - align-items: center; + display: flex; + align-items: center; - .cvss-score-display { - margin-left: 3px; - font-size: 10px; - line-height: 18px; - } -} \ No newline at end of file + .cvss-score-display { + margin-left: 3px; + font-size: 10px; + line-height: 18px; + } +} diff --git a/ui/src/components/SeverityWithCvssDisplay/index.jsx b/ui/src/components/SeverityWithCvssDisplay/index.jsx index ac73213e1c..8b4c4288f1 100644 --- a/ui/src/components/SeverityWithCvssDisplay/index.jsx +++ b/ui/src/components/SeverityWithCvssDisplay/index.jsx @@ -1,35 +1,52 @@ -import React from 'react'; -import SeverityDisplay, { SEVERITY_ITEMS } from 'components/SeverityDisplay'; -import { TooltipWrapper } from 'components/Tooltip'; -import { toCapitalized } from 'utils/utils'; +import React from "react"; +import SeverityDisplay, { SEVERITY_ITEMS } from "components/SeverityDisplay"; +import { TooltipWrapper } from "components/Tooltip"; +import { toCapitalized } from "utils/utils"; -import './severity-with-cvss-display.scss'; +import "./severity-with-cvss-display.scss"; -export { - SEVERITY_ITEMS -} +export { SEVERITY_ITEMS }; -const SeverityWithCvssDisplay = ({severity, cvssScore, cvssSeverity, compareTooltipId}) => { - if (!severity) { - return null; - } - - const showSeverityTooltip = !!cvssSeverity && cvssSeverity !== severity && - !(cvssSeverity === SEVERITY_ITEMS.NONE.value && severity === SEVERITY_ITEMS.NEGLIGIBLE.value); +const SeverityWithCvssDisplay = ({ + severity, + cvssScore, + cvssSeverity, + compareTooltipId, +}) => { + if (!severity) { + return null; + } - const severityNotAlignedMessage = ( -
- {`Although the CVSS base impact score is ${cvssScore} (${toCapitalized(cvssSeverity || "")}), the linux distribution severity reflects the risk more accurately.`} -
+ const showSeverityTooltip = + !!cvssSeverity && + cvssSeverity !== severity && + !( + cvssSeverity === SEVERITY_ITEMS.NONE.value && + severity === SEVERITY_ITEMS.NEGLIGIBLE.value ); - - return ( -
- - {!!cvssScore &&
{`(cvss ${cvssScore})`}
} - {!!showSeverityTooltip && *} -
- ); -} -export default SeverityWithCvssDisplay; \ No newline at end of file + const severityNotAlignedMessage = ( +
+ {`Although the CVSS base impact score is ${cvssScore} (${toCapitalized(cvssSeverity || "")}), the linux distribution severity reflects the risk more accurately.`} +
+ ); + + return ( +
+ + {!!cvssScore && ( +
{`(cvss ${cvssScore})`}
+ )} + {!!showSeverityTooltip && ( + + * + + )} +
+ ); +}; + +export default SeverityWithCvssDisplay; diff --git a/ui/src/components/SeverityWithCvssDisplay/severity-with-cvss-display.scss b/ui/src/components/SeverityWithCvssDisplay/severity-with-cvss-display.scss index b1a4f85e71..3a9e67c1c6 100644 --- a/ui/src/components/SeverityWithCvssDisplay/severity-with-cvss-display.scss +++ b/ui/src/components/SeverityWithCvssDisplay/severity-with-cvss-display.scss @@ -1,10 +1,10 @@ .severity-with-cvss-display { - display: flex; - align-items: center; + display: flex; + align-items: center; - .cvss-score-display { - margin-left: 3px; - font-size: 10px; - line-height: 18px; - } -} \ No newline at end of file + .cvss-score-display { + margin-left: 3px; + font-size: 10px; + line-height: 18px; + } +} diff --git a/ui/src/components/SystemFiltersBanner/index.jsx b/ui/src/components/SystemFiltersBanner/index.jsx index 37507d5252..df668d8c72 100644 --- a/ui/src/components/SystemFiltersBanner/index.jsx +++ b/ui/src/components/SystemFiltersBanner/index.jsx @@ -1,25 +1,34 @@ -import React from 'react'; -import { useNavigate } from 'react-router-dom'; -import CloseButton from 'components/CloseButton'; -import Arrow, { ARROW_NAMES } from 'components/Arrow'; +import React from "react"; +import { useNavigate } from "react-router-dom"; +import CloseButton from "components/CloseButton"; +import Arrow, { ARROW_NAMES } from "components/Arrow"; -import './system-filter-banner.scss'; +import "./system-filter-banner.scss"; -const SystemFilterBanner = ({onClose, displayText, backPath, customDisplay: CustomDisplay}) => { - const navigate = useNavigate(); +const SystemFilterBanner = ({ + onClose, + displayText, + backPath, + customDisplay: CustomDisplay, +}) => { + const navigate = useNavigate(); - return ( -
-
- navigate(backPath)} /> -
{displayText}
-
-
- {!!CustomDisplay && } - -
-
- ) -} + return ( +
+
+ navigate(backPath)} + /> +
{displayText}
+
+
+ {!!CustomDisplay && } + +
+
+ ); +}; -export default SystemFilterBanner; \ No newline at end of file +export default SystemFilterBanner; diff --git a/ui/src/components/SystemFiltersBanner/system-filter-banner.scss b/ui/src/components/SystemFiltersBanner/system-filter-banner.scss index c5f6c41ef7..1779ed4709 100644 --- a/ui/src/components/SystemFiltersBanner/system-filter-banner.scss +++ b/ui/src/components/SystemFiltersBanner/system-filter-banner.scss @@ -1,31 +1,31 @@ -@import 'utils/scss_variables.module.scss'; +@import "utils/scss_variables.module.scss"; .system-filter-banner { - background-color: $color-blue; - box-shadow: 0px 5px 10px rgba(34, 43, 54, 0.11); - height: 50px; - width: 100%; - padding: 0 10px; - box-sizing: border-box; + background-color: $color-blue; + box-shadow: 0px 5px 10px rgba(34, 43, 54, 0.11); + height: 50px; + width: 100%; + padding: 0 10px; + box-sizing: border-box; + display: flex; + align-items: center; + justify-content: space-between; + font-weight: 400; + font-size: 12px; + position: fixed; + top: $top-bar-height; + left: $side-bar-width; + width: calc(100% - #{$side-bar-width}); + z-index: 1; + + .system-filter-content { display: flex; align-items: center; - justify-content: space-between; - font-weight: 400; - font-size: 12px; - position: fixed; - top: $top-bar-height; - left: $side-bar-width; - width: calc(100% - #{$side-bar-width}); - z-index: 1; - - .system-filter-content { - display: flex; - align-items: center; - white-space: nowrap; - overflow: hidden; + white-space: nowrap; + overflow: hidden; - .icon { - margin-right: 10px; - } + .icon { + margin-right: 10px; } -} \ No newline at end of file + } +} diff --git a/ui/src/components/TabbedPage/index.jsx b/ui/src/components/TabbedPage/index.jsx index aec4a050dc..062a7e0b71 100644 --- a/ui/src/components/TabbedPage/index.jsx +++ b/ui/src/components/TabbedPage/index.jsx @@ -1,46 +1,85 @@ -import React from 'react'; -import { Routes, Route, useNavigate, Outlet, useLocation, useParams, Navigate } from 'react-router-dom'; -import classnames from 'classnames'; -import Tabs from 'components/Tabs'; - -import './tabbed-page.scss'; - -const TabbedPage= ({items, redirectTo, basePath, headerCustomDisplay, withInnerPadding=true, withStickyTabs=false}) => { - const navigate = useNavigate(); - - const {pathname} = useLocation(); - const params = useParams(); - - const tabInnerPath = params["*"]; - const cleanPath = !!basePath ? basePath : (!!tabInnerPath ? pathname.replace(`/${tabInnerPath}`, "") : pathname); - - const checkIsActive = ({isIndex, path}) => (isIndex && pathname === cleanPath) || path === pathname.replace(`${cleanPath}/`, ""); - - const isInTab = items.find(({path, isIndex}) => checkIsActive({isIndex, path})); - - return ( -
- {isInTab && - navigate(isIndex ? cleanPath : path)} - headerCustomDisplay={headerCustomDisplay} - withStickyTabs={withStickyTabs} - /> - } - -
}> - { - items.map(({id, path, isIndex, component: Component}) => ( - } /> - )) - } - {!!redirectTo && } />} - - -
- ); -} +import React from "react"; +import { + Routes, + Route, + useNavigate, + Outlet, + useLocation, + useParams, + Navigate, +} from "react-router-dom"; +import classnames from "classnames"; +import Tabs from "components/Tabs"; + +import "./tabbed-page.scss"; + +const TabbedPage = ({ + items, + redirectTo, + basePath, + headerCustomDisplay, + withInnerPadding = true, + withStickyTabs = false, +}) => { + const navigate = useNavigate(); + + const { pathname } = useLocation(); + const params = useParams(); + + const tabInnerPath = params["*"]; + const cleanPath = !!basePath + ? basePath + : !!tabInnerPath + ? pathname.replace(`/${tabInnerPath}`, "") + : pathname; + + const checkIsActive = ({ isIndex, path }) => + (isIndex && pathname === cleanPath) || + path === pathname.replace(`${cleanPath}/`, ""); + + const isInTab = items.find(({ path, isIndex }) => + checkIsActive({ isIndex, path }), + ); + + return ( +
+ {isInTab && ( + navigate(isIndex ? cleanPath : path)} + headerCustomDisplay={headerCustomDisplay} + withStickyTabs={withStickyTabs} + /> + )} + + + +
+ } + > + {items.map(({ id, path, isIndex, component: Component }) => ( + } + /> + ))} + {!!redirectTo && ( + } /> + )} + + +
+ ); +}; export default TabbedPage; diff --git a/ui/src/components/TabbedPage/tabbed-page.scss b/ui/src/components/TabbedPage/tabbed-page.scss index d0aabc9d6b..773f070c17 100644 --- a/ui/src/components/TabbedPage/tabbed-page.scss +++ b/ui/src/components/TabbedPage/tabbed-page.scss @@ -1,20 +1,20 @@ -@import 'utils/scss_variables.module.scss'; +@import "utils/scss_variables.module.scss"; -.tabbed-page-container{ +.tabbed-page-container { + display: flex; + flex-direction: column; + flex-grow: 1; + + .tab-content { display: flex; flex-direction: column; flex-grow: 1; - .tab-content { - display: flex; - flex-direction: column; - flex-grow: 1; - - > * { - flex-grow: 1; - } - &.with-padding { - padding: $main-content-padding; - } + > * { + flex-grow: 1; + } + &.with-padding { + padding: $main-content-padding; } -} \ No newline at end of file + } +} diff --git a/ui/src/components/Table/Pagination/index.jsx b/ui/src/components/Table/Pagination/index.jsx index 034b75441c..8de248c6c2 100644 --- a/ui/src/components/Table/Pagination/index.jsx +++ b/ui/src/components/Table/Pagination/index.jsx @@ -1,76 +1,136 @@ -import React from 'react'; -import classnames from 'classnames'; -import Icon, { ICON_NAMES } from 'components/Icon'; +import React from "react"; +import classnames from "classnames"; +import Icon, { ICON_NAMES } from "components/Icon"; -import './pagination.scss'; +import "./pagination.scss"; const FIRST_PAGE_NUMBER = 1; -const PaginationItem = ({children, className, isActive=false, onClick}) => ( -
{children}
+const PaginationItem = ({ children, className, isActive = false, onClick }) => ( +
+ {children} +
); -const PaginationNumber = ({page, isActive, onClick}) => ( - {String(page)} +const PaginationNumber = ({ page, isActive, onClick }) => ( + + {String(page)} + ); -const PaginationArrow = ({isLeft=false, isDouble=false, onClick, disabled}) => ( - - - +const PaginationArrow = ({ + isLeft = false, + isDouble = false, + onClick, + disabled, +}) => ( + + + ); const PaginationDots = () => ( - ... + ... ); -const Pagination = ({canPreviousPage, previousPage, nextPage, pageIndex, pageSize, gotoPage, page, total, loading, displayName="items"}) => { - if (total === 0 || loading) { - return
; - } - - const startPageItem = (pageIndex * pageSize) + 1; - const endPageItem = page.length + (pageIndex * pageSize); - const lastPageNumber = Math.ceil(total / pageSize) || 1; - const activePageNumber = pageIndex + 1; - const canNextPage = lastPageNumber > activePageNumber; - - const prevPageNumber = activePageNumber - 1; - const nextPageNumber = activePageNumber + 1; - const displayPageNumbers = [...new Set([ - prevPageNumber < FIRST_PAGE_NUMBER ? FIRST_PAGE_NUMBER : prevPageNumber, - activePageNumber, - nextPageNumber > lastPageNumber ? lastPageNumber : nextPageNumber - ])].filter(page => page !== FIRST_PAGE_NUMBER && page !== lastPageNumber); - - const goToPageNumber = page => gotoPage(page - 1); - const goToFirstPage = () => goToPageNumber(FIRST_PAGE_NUMBER); - const goToLastPage = () => goToPageNumber(lastPageNumber); +const Pagination = ({ + canPreviousPage, + previousPage, + nextPage, + pageIndex, + pageSize, + gotoPage, + page, + total, + loading, + displayName = "items", +}) => { + if (total === 0 || loading) { + return
; + } - return ( -
-
- {`Showing ${startPageItem}-${endPageItem} of ${total} ${displayName}`} -
-
- - - - {displayPageNumbers[0] > (FIRST_PAGE_NUMBER + 1) && } - { - displayPageNumbers.map(page => ( - goToPageNumber(page)} isActive={page === activePageNumber} /> - )) - } - {displayPageNumbers[displayPageNumbers.length - 1] < (lastPageNumber - 1) && } - {FIRST_PAGE_NUMBER !== lastPageNumber && - - } - - -
-
- ); -} + const startPageItem = pageIndex * pageSize + 1; + const endPageItem = page.length + pageIndex * pageSize; + const lastPageNumber = Math.ceil(total / pageSize) || 1; + const activePageNumber = pageIndex + 1; + const canNextPage = lastPageNumber > activePageNumber; -export default Pagination; \ No newline at end of file + const prevPageNumber = activePageNumber - 1; + const nextPageNumber = activePageNumber + 1; + const displayPageNumbers = [ + ...new Set([ + prevPageNumber < FIRST_PAGE_NUMBER ? FIRST_PAGE_NUMBER : prevPageNumber, + activePageNumber, + nextPageNumber > lastPageNumber ? lastPageNumber : nextPageNumber, + ]), + ].filter((page) => page !== FIRST_PAGE_NUMBER && page !== lastPageNumber); + + const goToPageNumber = (page) => gotoPage(page - 1); + const goToFirstPage = () => goToPageNumber(FIRST_PAGE_NUMBER); + const goToLastPage = () => goToPageNumber(lastPageNumber); + + return ( +
+
+ {`Showing ${startPageItem}-${endPageItem} of ${total} ${displayName}`} +
+
+ + + + {displayPageNumbers[0] > FIRST_PAGE_NUMBER + 1 && } + {displayPageNumbers.map((page) => ( + goToPageNumber(page)} + isActive={page === activePageNumber} + /> + ))} + {displayPageNumbers[displayPageNumbers.length - 1] < + lastPageNumber - 1 && } + {FIRST_PAGE_NUMBER !== lastPageNumber && ( + + )} + + +
+
+ ); +}; + +export default Pagination; diff --git a/ui/src/components/Table/Pagination/pagination.scss b/ui/src/components/Table/Pagination/pagination.scss index 6044ee2e0f..3132447f09 100644 --- a/ui/src/components/Table/Pagination/pagination.scss +++ b/ui/src/components/Table/Pagination/pagination.scss @@ -1,58 +1,57 @@ -@import 'utils/scss_variables.module.scss'; +@import "utils/scss_variables.module.scss"; $box-size: 30px; .pagination-container { - height: $box-size; - padding: 8px 20px; - font-size: 14px; + height: $box-size; + padding: 8px 20px; + font-size: 14px; + color: $color-grey-black; + display: flex; + align-items: center; + justify-content: flex-end; + border-bottom: 3px solid $color-grey-lighter; + + .pagination-results { + font-weight: 400; color: $color-grey-black; + } + .pagination-navigation { + margin-left: 15px; display: flex; - align-items: center; - justify-content: flex-end; - border-bottom: 3px solid $color-grey-lighter; - .pagination-results { - font-weight: 400; - color: $color-grey-black; - } - .pagination-navigation { - margin-left: 15px; - display: flex; + .pagination-item { + min-width: $box-size; + height: $box-size; + box-sizing: border-box; + padding: 0 5px; + background-color: $color-grey-lighter; + display: flex; + align-items: center; + justify-content: space-around; + cursor: pointer; - .pagination-item { - min-width: $box-size; - height: $box-size; - box-sizing: border-box; - padding: 0 5px; - background-color: $color-grey-lighter; - display: flex; - align-items: center; - justify-content: space-around; - cursor: pointer; - - &:not(:last-child) { - margin-right: 4px; - } - &.pagination-arrow { - color: $color-grey; + &:not(:last-child) { + margin-right: 4px; + } + &.pagination-arrow { + color: $color-grey; - &.left .icon { - transform: rotate(180deg); - } - &.disabled { - cursor: not-allowed; - color: $color-grey; - } - } - &.pagination-dots { - background-color: transparent; - cursor: auto; - } - &.is-active { - background-color: $color-main-light; - } + &.left .icon { + transform: rotate(180deg); + } + &.disabled { + cursor: not-allowed; + color: $color-grey; } + } + &.pagination-dots { + background-color: transparent; + cursor: auto; + } + &.is-active { + background-color: $color-main-light; + } } - -} \ No newline at end of file + } +} diff --git a/ui/src/components/Table/index.jsx b/ui/src/components/Table/index.jsx index a7e5fe1843..a8c1651350 100644 --- a/ui/src/components/Table/index.jsx +++ b/ui/src/components/Table/index.jsx @@ -1,325 +1,398 @@ -import React, { useMemo, useCallback, useEffect, useState } from 'react'; -import classnames from 'classnames'; -import { isEmpty, isEqual, pickBy, isNull, isUndefined } from 'lodash'; -import { useTable, usePagination, useResizeColumns, useFlexLayout, useRowSelect } from 'react-table'; -import Icon, { ICON_NAMES } from 'components/Icon'; -import Loader from 'components/Loader'; -import { useFetch, usePrevious } from 'hooks'; -import Pagination from './Pagination'; -import * as utils from './utils'; - -import './table.scss'; -import { ValueWithFallback } from 'components/ValueWithFallback'; - -export { - utils -} +import React, { useMemo, useCallback, useEffect, useState } from "react"; +import classnames from "classnames"; +import { isEmpty, isEqual, pickBy, isNull, isUndefined } from "lodash"; +import { + useTable, + usePagination, + useResizeColumns, + useFlexLayout, + useRowSelect, +} from "react-table"; +import Icon, { ICON_NAMES } from "components/Icon"; +import Loader from "components/Loader"; +import { useFetch, usePrevious } from "hooks"; +import Pagination from "./Pagination"; +import * as utils from "./utils"; + +import "./table.scss"; +import { ValueWithFallback } from "components/ValueWithFallback"; + +export { utils }; const ACTIONS_COLUMN_ID = "ACTIONS"; const STATIC_COLUMN_IDS = [ACTIONS_COLUMN_ID]; -const Table = props => { - const {columns, defaultSortBy, onSortChnage, onLineClick, paginationItemsName, url, formatFetchedData, filters, defaultPageIndex=0, defaultPageSize=50, - onPageChange, noResultsTitle="items", refreshTimestamp, withPagination=true, data: externalData, onRowSelect, - actionsComponent: ActionsComponent, customEmptyResultsDisplay: CustomEmptyResultsDisplay, showCustomEmptyDisplay=true, - actionsColumnWidth=80} = props; - - const [sortBy, setSortBy] = useState(defaultSortBy || {}); - const prevSortBy = usePrevious(sortBy); - - useEffect(() => { - if (!!onSortChnage && !isEqual(prevSortBy, sortBy)) { - onSortChnage(sortBy); - } - }, [prevSortBy, sortBy, onSortChnage]); - - - const defaultColumn = React.useMemo(() => ({ - minWidth: 30, - width: 100 - }), []); - - const [{loading, data, error}, fetchData] = useFetch(url, {loadOnMount: false, formatFetchedData}); - const prevLoading = usePrevious(loading); - const tableData = !!url ? data : externalData; - const {items, count} = tableData || {}; - const tableItems = useMemo(() => items || [], [items]); - - const withRowActions = !!ActionsComponent; - - const [inititalLoaded, setInititalLoaded] = useState(!!externalData); - - const { - getTableProps, - getTableBodyProps, - headerGroups, - prepareRow, - page, - canPreviousPage, - nextPage, - previousPage, - gotoPage, - state: { - pageIndex, - pageSize, - selectedRowIds - } - } = useTable({ - columns, - getRowId: (rowData, rowIndex) => !!rowData.id ? rowData.id : rowIndex, - data: tableItems, - defaultColumn, - initialState: { - pageIndex: defaultPageIndex, - pageSize: defaultPageSize, - selectedRowIds: {} - }, - manualPagination: true, - pageCount: -1, - disableMultiSort: true - }, - useResizeColumns, - useFlexLayout, - usePagination, - useRowSelect, - hooks => { - hooks.useInstanceBeforeDimensions.push(({headerGroups}) => { - // fix the parent group of the framework columns to not be resizable - headerGroups[0].headers.forEach(header => { - if (STATIC_COLUMN_IDS.includes(header.originalId)) { - header.canResize = false; - } - }); - }); - - hooks.visibleColumns.push(columns => { - const updatedColumns = columns; - - if (withRowActions) { - updatedColumns.push({ - Header: () => null, // No header - id: ACTIONS_COLUMN_ID, - accessor: original => ( -
- {!!ActionsComponent && } -
- ), - disableResizing: true, - minWidth: actionsColumnWidth, - width: actionsColumnWidth, - maxWidth: actionsColumnWidth - }); - } - - return updatedColumns; - }) - } - ); - - const updatePage = useCallback(pageIndex => { - if (!!onPageChange) { - onPageChange(pageIndex); - } - - gotoPage(pageIndex); - }, [gotoPage, onPageChange]); - - const cleanFilters = pickBy(filters, value => !isNull(value) && value !== ""); - const prevCleanFilters = usePrevious(cleanFilters); - const filtersChanged = !isEqual(cleanFilters, prevCleanFilters) && !isUndefined(prevCleanFilters); - - const prevPageIndex = usePrevious(pageIndex); - - const {sortIds: sortKeys, desc: sortDesc} = sortBy || {}; - const prevSortKeys = usePrevious(sortKeys); - const prevSortDesc = usePrevious(sortDesc); - const sortingChanged = !isEqual(sortKeys, prevSortKeys) || sortDesc !== prevSortDesc; - - const prevRefreshTimestamp = usePrevious(refreshTimestamp); - - const getQueryParams = useCallback(() => { - const queryParams = { - ...cleanFilters, - "$count": true - } - - if (withPagination) { - queryParams["$skip"] = pageIndex * pageSize; - queryParams["$top"] = pageSize; - } - - if (!isEmpty(sortKeys)) { - queryParams["$orderby"] = sortKeys.map(sortKey => `${sortKey} ${sortDesc ? "desc" : "asc"}`); - } - - return queryParams; - }, [pageIndex, pageSize, sortKeys, sortDesc, cleanFilters, withPagination]); - - const doFetchWithQueryParams = useCallback(() => { - if (loading) { - return; - } - - fetchData({queryParams: {...getQueryParams()}}); - }, [fetchData, getQueryParams, loading]) - - useEffect(() => { - if (!filtersChanged && pageIndex === prevPageIndex && !sortingChanged && prevRefreshTimestamp === refreshTimestamp) { - return; +const Table = (props) => { + const { + columns, + defaultSortBy, + onSortChnage, + onLineClick, + paginationItemsName, + url, + formatFetchedData, + filters, + defaultPageIndex = 0, + defaultPageSize = 50, + onPageChange, + noResultsTitle = "items", + refreshTimestamp, + withPagination = true, + data: externalData, + onRowSelect, + actionsComponent: ActionsComponent, + customEmptyResultsDisplay: CustomEmptyResultsDisplay, + showCustomEmptyDisplay = true, + actionsColumnWidth = 80, + } = props; + + const [sortBy, setSortBy] = useState(defaultSortBy || {}); + const prevSortBy = usePrevious(sortBy); + + useEffect(() => { + if (!!onSortChnage && !isEqual(prevSortBy, sortBy)) { + onSortChnage(sortBy); + } + }, [prevSortBy, sortBy, onSortChnage]); + + const defaultColumn = React.useMemo( + () => ({ + minWidth: 30, + width: 100, + }), + [], + ); + + const [{ loading, data, error }, fetchData] = useFetch(url, { + loadOnMount: false, + formatFetchedData, + }); + const prevLoading = usePrevious(loading); + const tableData = !!url ? data : externalData; + const { items, count } = tableData || {}; + const tableItems = useMemo(() => items || [], [items]); + + const withRowActions = !!ActionsComponent; + + const [inititalLoaded, setInititalLoaded] = useState(!!externalData); + + const { + getTableProps, + getTableBodyProps, + headerGroups, + prepareRow, + page, + canPreviousPage, + nextPage, + previousPage, + gotoPage, + state: { pageIndex, pageSize, selectedRowIds }, + } = useTable( + { + columns, + getRowId: (rowData, rowIndex) => (!!rowData.id ? rowData.id : rowIndex), + data: tableItems, + defaultColumn, + initialState: { + pageIndex: defaultPageIndex, + pageSize: defaultPageSize, + selectedRowIds: {}, + }, + manualPagination: true, + pageCount: -1, + disableMultiSort: true, + }, + useResizeColumns, + useFlexLayout, + usePagination, + useRowSelect, + (hooks) => { + hooks.useInstanceBeforeDimensions.push(({ headerGroups }) => { + // fix the parent group of the framework columns to not be resizable + headerGroups[0].headers.forEach((header) => { + if (STATIC_COLUMN_IDS.includes(header.originalId)) { + header.canResize = false; + } + }); + }); + + hooks.visibleColumns.push((columns) => { + const updatedColumns = columns; + + if (withRowActions) { + updatedColumns.push({ + Header: () => null, // No header + id: ACTIONS_COLUMN_ID, + accessor: (original) => ( +
+ {!!ActionsComponent && } +
+ ), + disableResizing: true, + minWidth: actionsColumnWidth, + width: actionsColumnWidth, + maxWidth: actionsColumnWidth, + }); } - if (filtersChanged && pageIndex !== 0) { - updatePage(0); + return updatedColumns; + }); + }, + ); + + const updatePage = useCallback( + (pageIndex) => { + if (!!onPageChange) { + onPageChange(pageIndex); + } + + gotoPage(pageIndex); + }, + [gotoPage, onPageChange], + ); + + const cleanFilters = pickBy( + filters, + (value) => !isNull(value) && value !== "", + ); + const prevCleanFilters = usePrevious(cleanFilters); + const filtersChanged = + !isEqual(cleanFilters, prevCleanFilters) && !isUndefined(prevCleanFilters); + + const prevPageIndex = usePrevious(pageIndex); + + const { sortIds: sortKeys, desc: sortDesc } = sortBy || {}; + const prevSortKeys = usePrevious(sortKeys); + const prevSortDesc = usePrevious(sortDesc); + const sortingChanged = + !isEqual(sortKeys, prevSortKeys) || sortDesc !== prevSortDesc; + + const prevRefreshTimestamp = usePrevious(refreshTimestamp); + + const getQueryParams = useCallback(() => { + const queryParams = { + ...cleanFilters, + $count: true, + }; + + if (withPagination) { + queryParams["$skip"] = pageIndex * pageSize; + queryParams["$top"] = pageSize; + } - return; - } + if (!isEmpty(sortKeys)) { + queryParams["$orderby"] = sortKeys.map( + (sortKey) => `${sortKey} ${sortDesc ? "desc" : "asc"}`, + ); + } - if (!!url) { - doFetchWithQueryParams(); - } - }, [filtersChanged, pageIndex, prevPageIndex, doFetchWithQueryParams, updatePage, sortingChanged, refreshTimestamp, prevRefreshTimestamp, url]); + return queryParams; + }, [pageIndex, pageSize, sortKeys, sortDesc, cleanFilters, withPagination]); - const selectedRows = Object.keys(selectedRowIds); - const prevSelectedRows = usePrevious(selectedRows); + const doFetchWithQueryParams = useCallback(() => { + if (loading) { + return; + } - useEffect(() => { - if (!!onRowSelect && !isEqual(selectedRows, prevSelectedRows)) { - onRowSelect(selectedRows); - } - }, [prevSelectedRows, selectedRows, onRowSelect]); + fetchData({ queryParams: { ...getQueryParams() } }); + }, [fetchData, getQueryParams, loading]); + + useEffect(() => { + if ( + !filtersChanged && + pageIndex === prevPageIndex && + !sortingChanged && + prevRefreshTimestamp === refreshTimestamp + ) { + return; + } - useEffect(() => { - if (prevLoading && !loading && !inititalLoaded) { - setInititalLoaded(true); - } - }, [prevLoading, loading, inititalLoaded]); + if (filtersChanged && pageIndex !== 0) { + updatePage(0); - if (!!error) { - return null; + return; } - if (!inititalLoaded) { - return ( - - ) + if (!!url) { + doFetchWithQueryParams(); } - - if (isEmpty(page) && !loading && showCustomEmptyDisplay && !!CustomEmptyResultsDisplay) { - return ( - - ) + }, [ + filtersChanged, + pageIndex, + prevPageIndex, + doFetchWithQueryParams, + updatePage, + sortingChanged, + refreshTimestamp, + prevRefreshTimestamp, + url, + ]); + + const selectedRows = Object.keys(selectedRowIds); + const prevSelectedRows = usePrevious(selectedRows); + + useEffect(() => { + if (!!onRowSelect && !isEqual(selectedRows, prevSelectedRows)) { + onRowSelect(selectedRows); } + }, [prevSelectedRows, selectedRows, onRowSelect]); - return ( -
- {!withPagination ?
{`Showing ${count} entries`}
: - - } -
-
- { - headerGroups.map(headerGroup => { - return ( -
- { - headerGroup.headers.map(column => { - const {sortIds} = column; - const isSorted = isEqual(sortIds, sortKeys); - - return ( -
- {column.render('Header')} - {!isEmpty(sortIds) && - setSortBy(({sortIds, desc}) => - ({sortIds: column.sortIds, desc: isEqual(column.sortIds, sortIds) ? !desc : false}) - )} - /> - } - {column.canResize && -
- } -
- ) - }) - } -
- ) - }) - } -
-
- {isEmpty(page) && !loading &&
{`No results available for ${noResultsTitle}`}
} - {loading &&
} - { - page.map((row) => { - prepareRow(row); - - return ( - -
{ - if (!!onLineClick) { - onLineClick(row.original); - } - }} - > - { - row.cells.map(cell => { - const {className, alignToTop} = cell.column; - const cellClassName = classnames( - "table-td", - {"align-to-top": alignToTop}, - {[className]: className} - ); - - const isTextValue = !!cell.column.accessor; - - return ( -
- - {isTextValue ? cell.value : cell.render('Cell')} - -
- ) - }) - } -
-
- ) - }) + useEffect(() => { + if (prevLoading && !loading && !inititalLoaded) { + setInititalLoaded(true); + } + }, [prevLoading, loading, inititalLoaded]); + + if (!!error) { + return null; + } + + if (!inititalLoaded) { + return ; + } + + if ( + isEmpty(page) && + !loading && + showCustomEmptyDisplay && + !!CustomEmptyResultsDisplay + ) { + return ; + } + + return ( +
+ {!withPagination ? ( +
{`Showing ${count} entries`}
+ ) : ( + + )} +
+
+ {headerGroups.map((headerGroup) => { + return ( +
+ {headerGroup.headers.map((column) => { + const { sortIds } = column; + const isSorted = isEqual(sortIds, sortKeys); + + return ( +
+ + {column.render("Header")} + + {!isEmpty(sortIds) && ( + + setSortBy(({ sortIds, desc }) => ({ + sortIds: column.sortIds, + desc: isEqual(column.sortIds, sortIds) + ? !desc + : false, + })) + } + /> + )} + {column.canResize && ( +
+ )} +
+ ); + })} +
+ ); + })} +
+
+ {isEmpty(page) && !loading && ( +
{`No results available for ${noResultsTitle}`}
+ )} + {loading && ( +
+ +
+ )} + {page.map((row) => { + prepareRow(row); + + return ( + +
{ + if (!!onLineClick) { + onLineClick(row.original); } + }} + > + {row.cells.map((cell) => { + const { className, alignToTop } = cell.column; + const cellClassName = classnames( + "table-td", + { "align-to-top": alignToTop }, + { [className]: className }, + ); + + const isTextValue = !!cell.column.accessor; + + return ( +
+ + {isTextValue ? cell.value : cell.render("Cell")} + +
+ ); + })}
-
- + + ); + })}
- ) -} +
+
+ ); +}; export default React.memo(Table, (prevProps, nextProps) => { - const {filters: prevFilters, refreshTimestamp: prevRefreshTimestamp, data: prevData} = prevProps; - const {filters, refreshTimestamp, data} = nextProps; - - const shouldRefresh = !isEqual(prevFilters, filters) || prevRefreshTimestamp !== refreshTimestamp || !isEqual(prevData, data); - - return !shouldRefresh; + const { + filters: prevFilters, + refreshTimestamp: prevRefreshTimestamp, + data: prevData, + } = prevProps; + const { filters, refreshTimestamp, data } = nextProps; + + const shouldRefresh = + !isEqual(prevFilters, filters) || + prevRefreshTimestamp !== refreshTimestamp || + !isEqual(prevData, data); + + return !shouldRefresh; }); diff --git a/ui/src/components/Table/table.scss b/ui/src/components/Table/table.scss index 470a1563ec..86dab6f31f 100644 --- a/ui/src/components/Table/table.scss +++ b/ui/src/components/Table/table.scss @@ -1,127 +1,127 @@ -@import 'utils/scss_variables.module.scss'; +@import "utils/scss_variables.module.scss"; $cell-padding: 10px; .table-wrapper { - .no-pagination-results-total { - font-size: 14px; - line-height: 19px; - color: $color-grey-black; - display: flex; - justify-content: flex-end; - } - .table { - color: $color-grey-black; - overflow-x: hidden; - padding: 0 18px; + .no-pagination-results-total { + font-size: 14px; + line-height: 19px; + color: $color-grey-black; + display: flex; + justify-content: flex-end; + } + .table { + color: $color-grey-black; + overflow-x: hidden; + padding: 0 18px; - .table-head { - font-weight: 700; - font-size: 14px; - line-height: 18px; - letter-spacing: 0.27px; - overflow-y: hidden; + .table-head { + font-weight: 700; + font-size: 14px; + line-height: 18px; + letter-spacing: 0.27px; + overflow-y: hidden; - .table-tr { - border-bottom: 2px solid $color-grey-lighter; - - .table-th { - padding: $cell-padding; - margin: auto 0; - display: flex; - align-items: center; + .table-tr { + border-bottom: 2px solid $color-grey-lighter; - .resizer { - right: 0; - width: 10px; - height: 100%; - position: absolute; - top: 0; - touch-action: none; - } - .table-sort-icon { - margin-left: 5px; + .table-th { + padding: $cell-padding; + margin: auto 0; + display: flex; + align-items: center; - &:not(.sorted) { - display: none; - } - &.rotate { - transform: rotate(180deg); - } - } - &:hover { - .table-sort-icon { - display: inline-block; - } - } - } - } - } - .table-body { - font-size: 14px; - line-height: 18px; - overflow-y: hidden; + .resizer { + right: 0; + width: 10px; + height: 100%; + position: absolute; + top: 0; + touch-action: none; + } + .table-sort-icon { + margin-left: 5px; - .empty-results-display-wrapper { - margin: 30px 10px; + &:not(.sorted) { + display: none; } - .table-loading { - position: relative; - height: 200px; + &.rotate { + transform: rotate(180deg); } - &:not(:hover) { - .table-tr.with-row-actions:first-child { - background-color: $color-blue-light; - - .table-td .actions-column-container { - visibility: visible; - } - } + } + &:hover { + .table-sort-icon { + display: inline-block; } - .table-tr { - border-bottom: 1px solid $color-grey-lighter; + } + } + } + } + .table-body { + font-size: 14px; + line-height: 18px; + overflow-y: hidden; - &:hover, - &.marked { - background-color: $color-blue-light; + .empty-results-display-wrapper { + margin: 30px 10px; + } + .table-loading { + position: relative; + height: 200px; + } + &:not(:hover) { + .table-tr.with-row-actions:first-child { + background-color: $color-blue-light; - &.clickable { - cursor: pointer; - } - } - &:hover { - .actions-column-container { - visibility: visible !important; - } - } - .table-td { - padding: $cell-padding; - display: flex; - flex-direction: column; - justify-content: center; - overflow-x: hidden; + .table-td .actions-column-container { + visibility: visible; + } + } + } + .table-tr { + border-bottom: 1px solid $color-grey-lighter; + + &:hover, + &.marked { + background-color: $color-blue-light; - &.align-to-top { - align-self: flex-start; - } - .actions-column-container { - visibility: hidden; + &.clickable { + cursor: pointer; + } + } + &:hover { + .actions-column-container { + visibility: visible !important; + } + } + .table-td { + padding: $cell-padding; + display: flex; + flex-direction: column; + justify-content: center; + overflow-x: hidden; - .icon { - color: $color-main; + &.align-to-top { + align-self: flex-start; + } + .actions-column-container { + visibility: hidden; - &.disabled { - color: $color-grey; - } - } - } - } - } + .icon { + color: $color-main; - //----- table utils ----- - .table-empty-value { + &.disabled { color: $color-grey; + } } + } } + } + + //----- table utils ----- + .table-empty-value { + color: $color-grey; + } } -} \ No newline at end of file + } +} diff --git a/ui/src/components/Table/utils.jsx b/ui/src/components/Table/utils.jsx index 48a7a9646c..3d66a7ab66 100644 --- a/ui/src/components/Table/utils.jsx +++ b/ui/src/components/Table/utils.jsx @@ -1,6 +1,6 @@ -import React from 'react'; -import Icon, { ICON_NAMES } from 'components/Icon'; +import React from "react"; +import Icon, { ICON_NAMES } from "components/Icon"; export const EmptyValue = () => ( - -); \ No newline at end of file + +); diff --git a/ui/src/components/TablePage/index.jsx b/ui/src/components/TablePage/index.jsx index c74fa7d472..81cdb072b9 100644 --- a/ui/src/components/TablePage/index.jsx +++ b/ui/src/components/TablePage/index.jsx @@ -1,130 +1,221 @@ -import React, { useEffect } from 'react'; -import { useNavigate, useLocation, useSearchParams } from 'react-router-dom'; -import { isEmpty } from 'lodash'; -import { ErrorBoundary } from 'react-error-boundary'; -import ContentContainer from 'components/ContentContainer'; -import Table from 'components/Table'; -import SystemFilterBanner from 'components/SystemFiltersBanner'; -import Filter, { formatFiltersToOdataItems } from 'components/Filter'; -import Loader from 'components/Loader'; -import { toCapitalized, BoldText } from 'utils/utils'; -import { useFilterState, useFilterDispatch, resetSystemFilters, setPage, setSort, setFilters, initializeFilters } from 'context/FiltersProvider'; +import React, { useEffect } from "react"; +import { useNavigate, useLocation, useSearchParams } from "react-router-dom"; +import { isEmpty } from "lodash"; +import { ErrorBoundary } from "react-error-boundary"; +import ContentContainer from "components/ContentContainer"; +import Table from "components/Table"; +import SystemFilterBanner from "components/SystemFiltersBanner"; +import Filter, { formatFiltersToOdataItems } from "components/Filter"; +import Loader from "components/Loader"; +import { toCapitalized, BoldText } from "utils/utils"; +import { + useFilterState, + useFilterDispatch, + resetSystemFilters, + setPage, + setSort, + setFilters, + initializeFilters, +} from "context/FiltersProvider"; const TablePage = (props) => { - const {tableTitle, filterType, systemFilterType, filters, expand, select, withMargin, defaultSortBy: initialSortBy, - filtersConfig, customHeaderDisplay: CustomHeaderDisplay, ...tableProps} = props; - - const [searchParams, setSearchParams] = useSearchParams(); - - const navigate = useNavigate(); - const {pathname} = useLocation(); - - const filtersDispatch = useFilterDispatch(); - const filtersState = useFilterState(); - - const {initialized} = filtersState; - - const {systemFilters} = filtersState[systemFilterType || filterType]; - const {name: systemFilterName, suffix: systemSuffix, backPath: systemFilterBackPath, filter: systemFilter, customDisplay} = systemFilters; - - const {tableFilters, customFilters, selectedPageIndex, tableSort} = filtersState[filterType]; - - const setTableFilters = (filters) => setFilters(filtersDispatch, {type: filterType, filters, isSystem: false}); - - const resetSystemFilter = () => resetSystemFilters(filtersDispatch, systemFilterType || filterType); - - const fitlersList = [ - ...(!!filters ? [filters] : []), - ...(!!tableFilters ? formatFiltersToOdataItems(tableFilters) : []), - ...(!!systemFilter ? [systemFilter] : []) - ]; - - useEffect(() => { - if (!initialized) { - try { - const {filterType, systemFilterType, tableFilters, systemFilters, customFilters} = JSON.parse(searchParams.get("filters") || {}); - - initializeFilters(filtersDispatch, {filterType, systemFilterType, tableFilters, systemFilters, customFilters}); - } catch(error) { - console.log("invalid filters"); - - setSearchParams({}, { replace: true }); - } - } - }, [initialized, searchParams, filtersDispatch, setSearchParams]); + const { + tableTitle, + filterType, + systemFilterType, + filters, + expand, + select, + withMargin, + defaultSortBy: initialSortBy, + filtersConfig, + customHeaderDisplay: CustomHeaderDisplay, + ...tableProps + } = props; + + const [searchParams, setSearchParams] = useSearchParams(); + + const navigate = useNavigate(); + const { pathname } = useLocation(); + + const filtersDispatch = useFilterDispatch(); + const filtersState = useFilterState(); + + const { initialized } = filtersState; + + const { systemFilters } = filtersState[systemFilterType || filterType]; + const { + name: systemFilterName, + suffix: systemSuffix, + backPath: systemFilterBackPath, + filter: systemFilter, + customDisplay, + } = systemFilters; + + const { tableFilters, customFilters, selectedPageIndex, tableSort } = + filtersState[filterType]; + + const setTableFilters = (filters) => + setFilters(filtersDispatch, { type: filterType, filters, isSystem: false }); + + const resetSystemFilter = () => + resetSystemFilters(filtersDispatch, systemFilterType || filterType); - useEffect(() => { - const {customDisplay, ...cleanSystemFilters} = systemFilters; - setSearchParams({filters: JSON.stringify({filterType, systemFilterType, tableFilters, systemFilters: cleanSystemFilters, customFilters})}, { replace: true }); - }, [filterType, systemFilterType, tableFilters, systemFilters, customFilters, setSearchParams]); + const fitlersList = [ + ...(!!filters ? [filters] : []), + ...(!!tableFilters ? formatFiltersToOdataItems(tableFilters) : []), + ...(!!systemFilter ? [systemFilter] : []), + ]; + useEffect(() => { if (!initialized) { - return ; + try { + const { + filterType, + systemFilterType, + tableFilters, + systemFilters, + customFilters, + } = JSON.parse(searchParams.get("filters") || {}); + + initializeFilters(filtersDispatch, { + filterType, + systemFilterType, + tableFilters, + systemFilters, + customFilters, + }); + } catch (error) { + console.log("invalid filters"); + + setSearchParams({}, { replace: true }); + } } - - return ( -
- {!!systemFilterName && - { - if (isEmpty(systemFilters)) { - resetErrorBoundary(); - } - - return null; - }} - onError={resetSystemFilter} - > - {`${toCapitalized(tableTitle)} for `}{systemFilterName}{` ${systemSuffix}`}} - onClose={resetSystemFilter} - backPath={systemFilterBackPath} - customDisplay={customDisplay} - /> - + }, [initialized, searchParams, filtersDispatch, setSearchParams]); + + useEffect(() => { + const { customDisplay, ...cleanSystemFilters } = systemFilters; + setSearchParams( + { + filters: JSON.stringify({ + filterType, + systemFilterType, + tableFilters, + systemFilters: cleanSystemFilters, + customFilters, + }), + }, + { replace: true }, + ); + }, [ + filterType, + systemFilterType, + tableFilters, + systemFilters, + customFilters, + setSearchParams, + ]); + + if (!initialized) { + return ; + } + + return ( +
+ {!!systemFilterName && ( + { + if (isEmpty(systemFilters)) { + resetErrorBoundary(); + } + + return null; + }} + onError={resetSystemFilter} + > + + {`${toCapitalized(tableTitle)} for `} + {systemFilterName} + {` ${systemSuffix}`} + + } + onClose={resetSystemFilter} + backPath={systemFilterBackPath} + customDisplay={customDisplay} + /> + + )} +
+ {!!filtersConfig && ( + { + if (isEmpty(tableFilters)) { + resetErrorBoundary(); + } + + return null; + }} + onError={() => + setFilters(filtersDispatch, { + type: filterType, + filters: [], + isSystem: false, + }) } -
- {!!filtersConfig && - { - if (isEmpty(tableFilters)) { - resetErrorBoundary(); - } - - return null; - }} - onError={() => setFilters(filtersDispatch, {type: filterType, filters: [], isSystem: false})} - > - setTableFilters(filters)} - filtersConfig={filtersConfig} - filtersOnCopyText={window.location.href} - /> - - } - {!!CustomHeaderDisplay &&
} -
- - 0 ? {"$filter": fitlersList.join(" and ")} : {}) - }} - noResultsTitle={tableTitle} - onLineClick={({id}) => navigate(`${pathname}/${id}`)} - defaultPageIndex={selectedPageIndex} - onPageChange={pageIndex => setPage(filtersDispatch, {type: filterType, pageIndex})} - defaultSortBy={isEmpty(tableSort) ? initialSortBy : tableSort} - onSortChnage={tableSort => setSort(filtersDispatch, {type: filterType, tableSort})} - showCustomEmptyDisplay={isEmpty(tableFilters)} - {...tableProps} - /> - - - ) -} + > + setTableFilters(filters)} + filtersConfig={filtersConfig} + filtersOnCopyText={window.location.href} + /> + + )} + {!!CustomHeaderDisplay && ( +
+ +
+ )} + + +
0 + ? { $filter: fitlersList.join(" and ") } + : {}), + }} + noResultsTitle={tableTitle} + onLineClick={({ id }) => navigate(`${pathname}/${id}`)} + defaultPageIndex={selectedPageIndex} + onPageChange={(pageIndex) => + setPage(filtersDispatch, { type: filterType, pageIndex }) + } + defaultSortBy={isEmpty(tableSort) ? initialSortBy : tableSort} + onSortChnage={(tableSort) => + setSort(filtersDispatch, { type: filterType, tableSort }) + } + showCustomEmptyDisplay={isEmpty(tableFilters)} + {...tableProps} + /> + + + ); +}; export default TablePage; diff --git a/ui/src/components/Tabs/index.jsx b/ui/src/components/Tabs/index.jsx index 80802dc92c..85cfcda74c 100644 --- a/ui/src/components/Tabs/index.jsx +++ b/ui/src/components/Tabs/index.jsx @@ -1,36 +1,63 @@ -import React from 'react'; -import classnames from 'classnames'; -import { TooltipWrapper } from 'components/Tooltip'; +import React from "react"; +import classnames from "classnames"; +import { TooltipWrapper } from "components/Tooltip"; -import './tabs.scss'; +import "./tabs.scss"; -const Tabs = ({items, checkIsActive, onClick, headerCustomDisplay: HeaderCustomDisplay, withStickyTabs=false, tabItemPadding=20}) => ( -
- { - items.map((item) => { - const {id, title, disabled, tabTooltip, customTitle: CustomTitle} = item; - const isActive = checkIsActive(item); +const Tabs = ({ + items, + checkIsActive, + onClick, + headerCustomDisplay: HeaderCustomDisplay, + withStickyTabs = false, + tabItemPadding = 20, +}) => ( +
+ {items.map((item) => { + const { + id, + title, + disabled, + tabTooltip, + customTitle: CustomTitle, + } = item; + const isActive = checkIsActive(item); - const onTabClick = () => { - if (disabled) { - return; - } - - onClick(item); - } - - const WrapperElement = !!tabTooltip ? TooltipWrapper : "div"; - const wrapperProps = !!tabTooltip ? {tooltipId: `tab-disabled-tooltip-${id}`, tooltipText: tabTooltip} : {}; - - return ( - - {!!CustomTitle ? : title} - - ) - }) + const onTabClick = () => { + if (disabled) { + return; } - {!!HeaderCustomDisplay &&
} -
+ + onClick(item); + }; + + const WrapperElement = !!tabTooltip ? TooltipWrapper : "div"; + const wrapperProps = !!tabTooltip + ? { tooltipId: `tab-disabled-tooltip-${id}`, tooltipText: tabTooltip } + : {}; + + return ( + + {!!CustomTitle ? ( + + ) : ( + title + )} + + ); + })} + {!!HeaderCustomDisplay && ( +
+ +
+ )} +
); -export default Tabs; \ No newline at end of file +export default Tabs; diff --git a/ui/src/components/Tabs/tabs.scss b/ui/src/components/Tabs/tabs.scss index 18f2b31bff..2e296a8810 100644 --- a/ui/src/components/Tabs/tabs.scss +++ b/ui/src/components/Tabs/tabs.scss @@ -1,38 +1,38 @@ -@import 'utils/scss_variables.module.scss'; +@import "utils/scss_variables.module.scss"; .tabs-container { - display: flex; - height: 44px; - border-bottom: 2px solid $color-grey-lighter; - background-color: white; - position: relative; + display: flex; + height: 44px; + border-bottom: 2px solid $color-grey-lighter; + background-color: white; + position: relative; - &.sticky { - position: sticky; - top: 0; - z-index: 1; - } - .tab-item { - display: inline-flex; - align-items: center; - font-size: 14px; - line-height: 16px; - text-align: center; - cursor: pointer; + &.sticky { + position: sticky; + top: 0; + z-index: 1; + } + .tab-item { + display: inline-flex; + align-items: center; + font-size: 14px; + line-height: 16px; + text-align: center; + cursor: pointer; - &.active { - font-weight: bold; - border-bottom: 2px solid $color-main-light; - } - &.disabled { - color: $color-grey-light; - cursor: not-allowed; - } + &.active { + font-weight: bold; + border-bottom: 2px solid $color-main-light; } - .tabs-header-custom-content-wrapper { - position: absolute; - top: 50%; - right: 10px; - transform: translateY(-50%); + &.disabled { + color: $color-grey-light; + cursor: not-allowed; } -} \ No newline at end of file + } + .tabs-header-custom-content-wrapper { + position: absolute; + top: 50%; + right: 10px; + transform: translateY(-50%); + } +} diff --git a/ui/src/components/Tag/index.jsx b/ui/src/components/Tag/index.jsx index 962d96cd1f..dc4d25a2ef 100644 --- a/ui/src/components/Tag/index.jsx +++ b/ui/src/components/Tag/index.jsx @@ -1,19 +1,26 @@ -import React from 'react'; -import classnames from 'classnames'; +import React from "react"; +import classnames from "classnames"; -import './tag.scss'; -import { ValueWithFallback } from 'components/ValueWithFallback'; +import "./tag.scss"; +import { ValueWithFallback } from "components/ValueWithFallback"; -const Tag = ({children, onClick}) => ( -
{children}
-) +const Tag = ({ children, onClick }) => ( +
+ {children} +
+); -export const TagsList = ({items}) => ( -
- - {items?.map((tag, index) => {tag})} - -
-) +export const TagsList = ({ items }) => ( +
+ + {items?.map((tag, index) => ( + {tag} + ))} + +
+); export default Tag; diff --git a/ui/src/components/Tag/tag.scss b/ui/src/components/Tag/tag.scss index 527ca86ea0..d428b778a1 100644 --- a/ui/src/components/Tag/tag.scss +++ b/ui/src/components/Tag/tag.scss @@ -1,25 +1,25 @@ -@import 'utils/scss_variables.module.scss'; +@import "utils/scss_variables.module.scss"; .clarity-tag { - display: inline-block; - padding: 5px 10px; - background-color: $color-blue-light; - border: 1px solid $color-blue; - font-weight: 400; - font-size: 10px; - line-height: 14px; + display: inline-block; + padding: 5px 10px; + background-color: $color-blue-light; + border: 1px solid $color-blue; + font-weight: 400; + font-size: 10px; + line-height: 14px; - &.clickable { - cursor: pointer; - } + &.clickable { + cursor: pointer; + } } .clarity-tags-list { - .clarity-tag { - margin-bottom: 5px; - - &:not(:last-child) { - margin-right: 10px; - } + .clarity-tag { + margin-bottom: 5px; + + &:not(:last-child) { + margin-right: 10px; } -} \ No newline at end of file + } +} diff --git a/ui/src/components/Title/index.jsx b/ui/src/components/Title/index.jsx index 7dfeec5b65..d1d86195c8 100644 --- a/ui/src/components/Title/index.jsx +++ b/ui/src/components/Title/index.jsx @@ -1,12 +1,27 @@ -import React from 'react'; -import classnames from 'classnames'; +import React from "react"; +import classnames from "classnames"; -import './title.scss'; +import "./title.scss"; -const Title = ({children, className, medium, onClick, removeMargin=false}) => ( -
- {children} -
-) +const Title = ({ + children, + className, + medium, + onClick, + removeMargin = false, +}) => ( +
+ {children} +
+); -export default Title; \ No newline at end of file +export default Title; diff --git a/ui/src/components/Title/title.scss b/ui/src/components/Title/title.scss index 4114e1a8c5..74e3d78225 100644 --- a/ui/src/components/Title/title.scss +++ b/ui/src/components/Title/title.scss @@ -1,22 +1,22 @@ -@import 'utils/scss_variables.module.scss'; +@import "utils/scss_variables.module.scss"; .clarity-title { - color: $color-grey-black; - font-weight: 300; - font-size: 35px; - line-height: 48px; + color: $color-grey-black; + font-weight: 300; + font-size: 35px; + line-height: 48px; - &:not(.no-margin) { - margin-bottom: 30px; - } - &.clickable { - color: $color-main; - text-decoration: underline; - cursor: pointer; - } - &.medium { - font-weight: 350; - font-size: 26px; - line-height: 30px; - } -} \ No newline at end of file + &:not(.no-margin) { + margin-bottom: 30px; + } + &.clickable { + color: $color-main; + text-decoration: underline; + cursor: pointer; + } + &.medium { + font-weight: 350; + font-size: 26px; + line-height: 30px; + } +} diff --git a/ui/src/components/TitleValueDisplay/index.jsx b/ui/src/components/TitleValueDisplay/index.jsx index b2f6843d88..6df725375d 100644 --- a/ui/src/components/TitleValueDisplay/index.jsx +++ b/ui/src/components/TitleValueDisplay/index.jsx @@ -1,39 +1,70 @@ -import React, { useState } from 'react'; -import classnames from 'classnames'; -import Arrow, { ARROW_NAMES } from 'components/Arrow'; -import { isEmpty } from 'lodash'; +import React, { useState } from "react"; +import classnames from "classnames"; +import Arrow, { ARROW_NAMES } from "components/Arrow"; +import { isEmpty } from "lodash"; -import './title-value-display.scss'; -import { ValueWithFallback } from 'components/ValueWithFallback'; +import "./title-value-display.scss"; +import { ValueWithFallback } from "components/ValueWithFallback"; -export const TitleValueDisplayRow = ({children}) => ( -
{children}
+export const TitleValueDisplayRow = ({ children }) => ( +
{children}
); -export const TitleValueDisplayColumn = ({children}) => ( -
{children}
+export const TitleValueDisplayColumn = ({ children }) => ( +
{children}
); -export const ValuesListDisplay = ({values}) => ( -
- {values?.map((value, index) =>
{value}
)} -
-) +export const ValuesListDisplay = ({ values }) => ( +
+ {values?.map((value, index) => ( +
+ {value} +
+ ))} +
+); -const TitleValueDisplay = ({title, children, className, withOpen=false, defaultOpen=false, isSubItem=false, isLargeTitle=false}) => { - const [isOpen, setIsOpen] = useState(defaultOpen); +const TitleValueDisplay = ({ + title, + children, + className, + withOpen = false, + defaultOpen = false, + isSubItem = false, + isLargeTitle = false, +}) => { + const [isOpen, setIsOpen] = useState(defaultOpen); - return ( -
-
setIsOpen(!isOpen)}> -
{title}
- {withOpen && } -
- {(!withOpen || isOpen) &&
- {children} -
} + return ( +
+
setIsOpen(!isOpen)} + > +
+ {title}
- ); -} + {withOpen && ( + + )} +
+ {(!withOpen || isOpen) && ( +
+ {children} +
+ )} +
+ ); +}; -export default TitleValueDisplay +export default TitleValueDisplay; diff --git a/ui/src/components/TitleValueDisplay/title-value-display.scss b/ui/src/components/TitleValueDisplay/title-value-display.scss index 99f6de9b8e..eaa1a2556f 100644 --- a/ui/src/components/TitleValueDisplay/title-value-display.scss +++ b/ui/src/components/TitleValueDisplay/title-value-display.scss @@ -1,91 +1,91 @@ -@import 'utils/scss_variables.module.scss'; +@import "utils/scss_variables.module.scss"; $open-icon-size: 14px; .title-value-display-wrapper { - min-width: 220px; + min-width: 220px; - &.sub-item { - .title-value-display-title-wrapper .title-value-display-title { - color: $color-grey-dark; - margin-bottom: 5px; - } + &.sub-item { + .title-value-display-title-wrapper .title-value-display-title { + color: $color-grey-dark; + margin-bottom: 5px; } - .title-value-display-title-wrapper { - display: flex; - align-items: center; - justify-content: space-between; - - &.with-open { - border-bottom: 1px solid $color-grey-lighter; - margin-bottom: 10px; - cursor: pointer; - } - .title-value-display-title { - font-weight: bold; - font-size: 10px; - line-height: 14px; - letter-spacing: 0.27px; - color: $color-grey-black; - text-transform: uppercase; - margin-bottom: 10px; + } + .title-value-display-title-wrapper { + display: flex; + align-items: center; + justify-content: space-between; - &.large { - font-size: 12px; - } - } + &.with-open { + border-bottom: 1px solid $color-grey-lighter; + margin-bottom: 10px; + cursor: pointer; } - .title-value-display-content { + .title-value-display-title { + font-weight: bold; + font-size: 10px; + line-height: 14px; + letter-spacing: 0.27px; + color: $color-grey-black; + text-transform: uppercase; + margin-bottom: 10px; + + &.large { font-size: 12px; - line-height: 16px; - color: $color-grey-dark; - word-wrap: break-word; + } + } + } + .title-value-display-content { + font-size: 12px; + line-height: 16px; + color: $color-grey-dark; + word-wrap: break-word; - a { - color: $color-main; - font-weight: bold; - font-size: 12px; - line-height: 16px; - text-decoration: none; + a { + color: $color-main; + font-weight: bold; + font-size: 12px; + line-height: 16px; + text-decoration: none; - &:hover { - text-decoration: underline; - } - } + &:hover { + text-decoration: underline; + } + } - .empty { - color: $color-grey; - } + .empty { + color: $color-grey; } + } } .title-value-display-row { - display: flex; - margin-bottom: 30px; + display: flex; + margin-bottom: 30px; - > * { - width: 100%; - } - .title-value-display-wrapper:not(:last-child) { - margin-right: 10px; - } + > * { + width: 100%; + } + .title-value-display-wrapper:not(:last-child) { + margin-right: 10px; + } } .title-value-display-column { - padding-right: 30px; - flex-grow: 1; + padding-right: 30px; + flex-grow: 1; - .title-value-display-wrapper { - margin-bottom: 30px; + .title-value-display-wrapper { + margin-bottom: 30px; - &.sub-item { - margin-bottom: 20px; - } + &.sub-item { + margin-bottom: 20px; } + } } .title-value-values-list { - .title-value-values-list-item:not(:last-child) { - margin-bottom: 10px; - } + .title-value-values-list-item:not(:last-child) { + margin-bottom: 10px; + } } diff --git a/ui/src/components/ToggleButton/index.jsx b/ui/src/components/ToggleButton/index.jsx index 7df1e7012e..d97d5b4873 100644 --- a/ui/src/components/ToggleButton/index.jsx +++ b/ui/src/components/ToggleButton/index.jsx @@ -1,18 +1,18 @@ -import React from 'react'; -import Toggle from 'react-toggle'; +import React from "react"; +import Toggle from "react-toggle"; -import 'react-toggle/style.css'; -import './toggle-button.scss'; +import "react-toggle/style.css"; +import "./toggle-button.scss"; -const ToggleButton = ({title, checked, onChange}) => ( -
- onChange(target.checked)} - /> -
{title}
-
-) +const ToggleButton = ({ title, checked, onChange }) => ( +
+ onChange(target.checked)} + /> +
{title}
+
+); -export default ToggleButton; \ No newline at end of file +export default ToggleButton; diff --git a/ui/src/components/ToggleButton/toggle-button.scss b/ui/src/components/ToggleButton/toggle-button.scss index 0c83c484e6..f8d415c7f2 100644 --- a/ui/src/components/ToggleButton/toggle-button.scss +++ b/ui/src/components/ToggleButton/toggle-button.scss @@ -1,45 +1,45 @@ -@import 'utils/scss_variables.module.scss'; +@import "utils/scss_variables.module.scss"; .toggle-button { - display: flex; - align-items: center; - font-weight: 400; - font-size: 14px; - color: $color-grey-black; + display: flex; + align-items: center; + font-weight: 400; + font-size: 14px; + color: $color-grey-black; - .react-toggle { - margin-right: 13px; + .react-toggle { + margin-right: 13px; - &.react-toggle--focus, - &:active { - .react-toggle-thumb { - box-shadow: none; - outline: none; - } - } - &:hover { - .react-toggle-track { - background-color: $color-grey; - } - } - &.react-toggle--checked { - .react-toggle-track { - background-color: $color-main-light; - } - .react-toggle-thumb { - left: 18px; - border-color: $color-main-light; - } - } - .react-toggle-track { - height: 12px; - width: 40px; - background-color: $color-grey; - } - .react-toggle-thumb { - top: -5px; - left: 0; - border-color: $color-grey; - } + &.react-toggle--focus, + &:active { + .react-toggle-thumb { + box-shadow: none; + outline: none; + } } -} \ No newline at end of file + &:hover { + .react-toggle-track { + background-color: $color-grey; + } + } + &.react-toggle--checked { + .react-toggle-track { + background-color: $color-main-light; + } + .react-toggle-thumb { + left: 18px; + border-color: $color-main-light; + } + } + .react-toggle-track { + height: 12px; + width: 40px; + background-color: $color-grey; + } + .react-toggle-thumb { + top: -5px; + left: 0; + border-color: $color-grey; + } + } +} diff --git a/ui/src/components/Tooltip/index.jsx b/ui/src/components/Tooltip/index.jsx index 70d4e432de..8ddd4c6ab8 100644 --- a/ui/src/components/Tooltip/index.jsx +++ b/ui/src/components/Tooltip/index.jsx @@ -1,27 +1,39 @@ -import React from 'react'; -import classnames from 'classnames'; -import ReactTooltip from 'react-tooltip'; +import React from "react"; +import classnames from "classnames"; +import ReactTooltip from "react-tooltip"; -import './tooltip.scss'; +import "./tooltip.scss"; -const Tooltip = ({id, text, placement="top"}) => ( - - {text} - -) +const Tooltip = ({ id, text, placement = "top" }) => ( + + {text} + +); export default Tooltip; -export const TooltipWrapper = ({children, className, tooltipId, tooltipText}) => ( - -
{children}
- -
-) \ No newline at end of file +export const TooltipWrapper = ({ + children, + className, + tooltipId, + tooltipText, +}) => ( + +
+ {children} +
+ +
+); diff --git a/ui/src/components/Tooltip/tooltip.scss b/ui/src/components/Tooltip/tooltip.scss index 352897358c..5e3ead57e3 100644 --- a/ui/src/components/Tooltip/tooltip.scss +++ b/ui/src/components/Tooltip/tooltip.scss @@ -1,12 +1,12 @@ -@import 'utils/scss_variables.module.scss'; +@import "utils/scss_variables.module.scss"; .__react_component_tooltip.clarity-tooltip { - font-weight: 400; - font-size: 14px; - line-height: 21px; - padding: 10px 15px; + font-weight: 400; + font-size: 14px; + line-height: 21px; + padding: 10px 15px; - &.place-top::before { - height: 0; - } -} \ No newline at end of file + &.place-top::before { + height: 0; + } +} diff --git a/ui/src/components/ValueWithFallback/index.jsx b/ui/src/components/ValueWithFallback/index.jsx index 631d9fc6d3..eba8ac9aa4 100644 --- a/ui/src/components/ValueWithFallback/index.jsx +++ b/ui/src/components/ValueWithFallback/index.jsx @@ -1,12 +1,11 @@ +import React from "react"; +import { isEmpty } from "lodash"; -import React from 'react'; -import { isEmpty } from 'lodash'; - -import './value-with-fallback.scss'; +import "./value-with-fallback.scss"; export const ValueWithFallback = ({ - children, - fallback = - + children, + fallback = -, }) => { - return isEmpty(children) ? fallback : <>{children}; -} + return isEmpty(children) ? fallback : <>{children}; +}; diff --git a/ui/src/components/ValueWithFallback/value-with-fallback.scss b/ui/src/components/ValueWithFallback/value-with-fallback.scss index 3e869ec911..8c030819bf 100644 --- a/ui/src/components/ValueWithFallback/value-with-fallback.scss +++ b/ui/src/components/ValueWithFallback/value-with-fallback.scss @@ -1,5 +1,5 @@ -@import 'utils/scss_variables.module.scss'; +@import "utils/scss_variables.module.scss"; .value-with-fallback-empty { - color: $color-grey; + color: $color-grey; } diff --git a/ui/src/components/VulnerabilitiesDisplay/index.jsx b/ui/src/components/VulnerabilitiesDisplay/index.jsx index 6b7410079c..eea323f0d1 100644 --- a/ui/src/components/VulnerabilitiesDisplay/index.jsx +++ b/ui/src/components/VulnerabilitiesDisplay/index.jsx @@ -1,128 +1,177 @@ -import React from 'react'; -import { TooltipWrapper } from 'components/Tooltip'; -import Icon from 'components/Icon'; -import { SEVERITY_ITEMS } from 'components/SeverityDisplay'; -import { VULNERABIITY_FINDINGS_ITEM } from 'utils/systemConsts'; -import { formatNumber } from 'utils/utils'; +import React from "react"; +import { TooltipWrapper } from "components/Tooltip"; +import Icon from "components/Icon"; +import { SEVERITY_ITEMS } from "components/SeverityDisplay"; +import { VULNERABIITY_FINDINGS_ITEM } from "utils/systemConsts"; +import { formatNumber } from "utils/utils"; -import COLORS from 'utils/scss_variables.module.scss'; +import COLORS from "utils/scss_variables.module.scss"; -import './vulnerabilities-display.scss'; +import "./vulnerabilities-display.scss"; -export const getTotlalVulnerabilitiesFromCounters = (counters={}) => ( - Object.values(VULNERABILITY_SEVERITY_ITEMS).reduce((acc, curr) => { - return acc + (counters[curr.totalKey] || 0); - }, 0) -) +export const getTotlalVulnerabilitiesFromCounters = (counters = {}) => + Object.values(VULNERABILITY_SEVERITY_ITEMS).reduce((acc, curr) => { + return acc + (counters[curr.totalKey] || 0); + }, 0); export const VULNERABILITY_SEVERITY_ITEMS = { - totalCriticalVulnerabilities: { - totalKey: "totalCriticalVulnerabilities", - countKey: "criticalVulnerabilitiesCount", - title: "Critical", - color: SEVERITY_ITEMS.CRITICAL.color, - innerTextColor: "white" - }, - totalHighVulnerabilities: { - totalKey: "totalHighVulnerabilities", - countKey: "highVulnerabilitiesCount", - title: "High", - color: SEVERITY_ITEMS.HIGH.color, - innerTextColor: "white" - }, - totalMediumVulnerabilities: { - totalKey: "totalMediumVulnerabilities", - countKey: "mediumVulnerabilitiesCount", - title: "Medium", - color: SEVERITY_ITEMS.MEDIUM.color, - innerTextColor: COLORS["color-grey-black"] - }, - totalLowVulnerabilities: { - totalKey: "totalLowVulnerabilities", - countKey: "lowVulnerabilitiesCount", - title: "Low", - color: SEVERITY_ITEMS.LOW.color, - innerTextColor: COLORS["color-grey-black"] - }, - totalNegligibleVulnerabilities: { - totalKey: "totalNegligibleVulnerabilities", - countKey: "negligibleVulnerabilitiesCount", - title: "Negligible", - color: SEVERITY_ITEMS.NEGLIGIBLE.color, - innerTextColor: COLORS["color-grey-black"], - backgroundColor: "transparent" - } -} + totalCriticalVulnerabilities: { + totalKey: "totalCriticalVulnerabilities", + countKey: "criticalVulnerabilitiesCount", + title: "Critical", + color: SEVERITY_ITEMS.CRITICAL.color, + innerTextColor: "white", + }, + totalHighVulnerabilities: { + totalKey: "totalHighVulnerabilities", + countKey: "highVulnerabilitiesCount", + title: "High", + color: SEVERITY_ITEMS.HIGH.color, + innerTextColor: "white", + }, + totalMediumVulnerabilities: { + totalKey: "totalMediumVulnerabilities", + countKey: "mediumVulnerabilitiesCount", + title: "Medium", + color: SEVERITY_ITEMS.MEDIUM.color, + innerTextColor: COLORS["color-grey-black"], + }, + totalLowVulnerabilities: { + totalKey: "totalLowVulnerabilities", + countKey: "lowVulnerabilitiesCount", + title: "Low", + color: SEVERITY_ITEMS.LOW.color, + innerTextColor: COLORS["color-grey-black"], + }, + totalNegligibleVulnerabilities: { + totalKey: "totalNegligibleVulnerabilities", + countKey: "negligibleVulnerabilitiesCount", + title: "Negligible", + color: SEVERITY_ITEMS.NEGLIGIBLE.color, + innerTextColor: COLORS["color-grey-black"], + backgroundColor: "transparent", + }, +}; -const TooltipContentDisplay = ({total, counters}) => ( -
-
{`Vulnerabilities: ${formatNumber(total || 0)}`}
-
- { - Object.values(VULNERABILITY_SEVERITY_ITEMS).map(({totalKey, color}) => ( -
- {formatNumber(counters[totalKey] || 0)} -
- )) - } -
+const TooltipContentDisplay = ({ total, counters }) => ( +
+
{`Vulnerabilities: ${formatNumber(total || 0)}`}
+
+ {Object.values(VULNERABILITY_SEVERITY_ITEMS).map( + ({ totalKey, color }) => ( +
+ + {formatNumber(counters[totalKey] || 0)} +
+ ), + )}
-) +
+); -const MinimizedVulnerabilitiesDisplay = ({id, highestSeverity, total, counters}) => { - const {color, innerTextColor, backgroundColor} = VULNERABILITY_SEVERITY_ITEMS[highestSeverity]; +const MinimizedVulnerabilitiesDisplay = ({ + id, + highestSeverity, + total, + counters, +}) => { + const { color, innerTextColor, backgroundColor } = + VULNERABILITY_SEVERITY_ITEMS[highestSeverity]; - return ( -
- }> -
- {formatNumber(counters[highestSeverity] || 0)} -
-
+ return ( +
+ + } + > +
+ {formatNumber(counters[highestSeverity] || 0)}
- ) -} +
+
+ ); +}; -const CounterItemDisplay = ({count, title, color}) => ( -
-
{count || 0}
-
{title}
+const CounterItemDisplay = ({ count, title, color }) => ( +
+
+ {count || 0}
-) +
{title}
+
+); -const VulnerabilitiesDisplay = ({highestSeverity, total, counters}) => { - const {color} = VULNERABILITY_SEVERITY_ITEMS[highestSeverity]; - const {color: vulnerabilitiesColor, title: vulnerabilitiesTitle, icon: vulnerabilitiesIcon} = VULNERABIITY_FINDINGS_ITEM; +const VulnerabilitiesDisplay = ({ highestSeverity, total, counters }) => { + const { color } = VULNERABILITY_SEVERITY_ITEMS[highestSeverity]; + const { + color: vulnerabilitiesColor, + title: vulnerabilitiesTitle, + icon: vulnerabilitiesIcon, + } = VULNERABIITY_FINDINGS_ITEM; - return ( -
-
- - -
-
- { - Object.values(VULNERABILITY_SEVERITY_ITEMS).map(({totalKey, title, color}) => ( - - )) - } -
-
- ) -} + return ( +
+
+ + +
+
+ {Object.values(VULNERABILITY_SEVERITY_ITEMS).map( + ({ totalKey, title, color }) => ( + + ), + )} +
+
+ ); +}; + +const VulnerabilitiesDisplayWrapper = ({ + counters = {}, + isMinimized = false, + minimizedTooltipId = null, +}) => { + const total = getTotlalVulnerabilitiesFromCounters(counters); + const highestSeverity = ( + Object.values(VULNERABILITY_SEVERITY_ITEMS).find( + ({ totalKey }) => !!counters[totalKey] && counters[totalKey] > 0, + ) || VULNERABILITY_SEVERITY_ITEMS.totalNegligibleVulnerabilities + ).totalKey; -const VulnerabilitiesDisplayWrapper = ({counters={}, isMinimized=false, minimizedTooltipId=null}) => { - const total = getTotlalVulnerabilitiesFromCounters(counters); - - const highestSeverity = (Object.values(VULNERABILITY_SEVERITY_ITEMS).find(({totalKey}) => - !!counters[totalKey] && counters[totalKey] > 0) || VULNERABILITY_SEVERITY_ITEMS.totalNegligibleVulnerabilities).totalKey; + const DisplayComponent = isMinimized + ? MinimizedVulnerabilitiesDisplay + : VulnerabilitiesDisplay; - const DisplayComponent = isMinimized ? MinimizedVulnerabilitiesDisplay : VulnerabilitiesDisplay; - - return ( - - ) -} + return ( + + ); +}; export default VulnerabilitiesDisplayWrapper; diff --git a/ui/src/components/VulnerabilitiesDisplay/vulnerabilities-display.scss b/ui/src/components/VulnerabilitiesDisplay/vulnerabilities-display.scss index 1aad79213e..8d5e49b165 100644 --- a/ui/src/components/VulnerabilitiesDisplay/vulnerabilities-display.scss +++ b/ui/src/components/VulnerabilitiesDisplay/vulnerabilities-display.scss @@ -1,69 +1,69 @@ -@import 'utils/scss_variables.module.scss'; +@import "utils/scss_variables.module.scss"; $inner-padding: 21px; .vulnerabilities-minimized-display-wrapper { - display: inline-block; - text-align: left; + display: inline-block; + text-align: left; - .vulnerabilities-minimized-display-summary-item { - padding: 2px 5px; - font-weight: 400; - font-size: 14px; - line-height: 18px; - } + .vulnerabilities-minimized-display-summary-item { + padding: 2px 5px; + font-weight: 400; + font-size: 14px; + line-height: 18px; + } } .vulnerabilities-minimized-tooltip-content { - .vulnerabilities-tooltip-counters { - display: flex; - border-top: 1px solid $color-grey-dark; - padding-top: 5px; - margin-top: 5px; + .vulnerabilities-tooltip-counters { + display: flex; + border-top: 1px solid $color-grey-dark; + padding-top: 5px; + margin-top: 5px; - .vulnerabilities-tooltip-counters-item { - display: flex; - align-items: center; + .vulnerabilities-tooltip-counters-item { + display: flex; + align-items: center; - &:not(:last-child) { - margin-right: 10px; - } - .icon { - margin-right: 2px; - } - } + &:not(:last-child) { + margin-right: 10px; + } + .icon { + margin-right: 2px; + } } + } } .vulnerabilities-display-wrapper { + display: flex; + align-items: center; + + .icon { + margin-right: 5px; + } + .vulnerabilities-display-summary { + display: flex; + align-items: center; + border-right: 1px solid $color-grey-light; + padding-right: $inner-padding; + } + .vulnerabilities-display-counters { display: flex; align-items: center; - .icon { - margin-right: 5px; + .vulnerabilities-display-counter-item { + padding-left: $inner-padding; } - .vulnerabilities-display-summary { - display: flex; - align-items: center; - border-right: 1px solid $color-grey-light; - padding-right: $inner-padding; + } + .vulnerabilities-display-counter-item { + min-width: 42px; + + .vulnerabilities-counter-item-count { + font-size: 22px; } - .vulnerabilities-display-counters { - display: flex; - align-items: center; - - .vulnerabilities-display-counter-item { - padding-left: $inner-padding; - } + .vulnerabilities-counter-item-title { + font-size: 11px; } - .vulnerabilities-display-counter-item { - min-width: 42px; - - .vulnerabilities-counter-item-count { - font-size: 22px; - } - .vulnerabilities-counter-item-title { - font-size: 11px; - } - } -} \ No newline at end of file + } +} diff --git a/ui/src/components/WizardModal/index.jsx b/ui/src/components/WizardModal/index.jsx index f98d617a33..be6afb6aa9 100644 --- a/ui/src/components/WizardModal/index.jsx +++ b/ui/src/components/WizardModal/index.jsx @@ -1,122 +1,172 @@ -import React, { useState, useEffect } from 'react'; -import classnames from 'classnames'; -import { Formik, Form, useFormikContext } from 'formik'; -import { cloneDeep, isNull, isEmpty } from 'lodash'; -import { useFetch, FETCH_METHODS, usePrevious } from 'hooks'; -import Button from 'components/Button'; -import Modal from 'components/Modal'; -import Loader from 'components/Loader'; -import Title from 'components/Title'; -import Arrow, { ARROW_NAMES } from 'components/Arrow'; - -import './wizard-modal.scss'; - -const Wizard = ({steps, onClose, submitUrl, onSubmitSuccess, getSubmitParams}) => { - const {values, isSubmitting, isValidating, setSubmitting, status, setStatus, isValid, setErrors, validateForm} = useFormikContext(); - - const [activeStepId, setActiveStepId] = useState(steps[0].id); - - const activeStepIndex = steps.findIndex(({id}) => id === activeStepId); - const {component: ActiveStepComponent, title: activeTitle} = steps[activeStepIndex]; - const {title: nextStepTitle, id: nextStepId} = steps[activeStepIndex + 1] || {}; - - const disableStepDone = isSubmitting || isValidating || !isValid; - - const [{loading, data, error}, submitFormData] = useFetch(submitUrl, {loadOnMount: false}); - const prevLoading = usePrevious(loading); - - const onStepClick = (stepId) => { - if (disableStepDone || stepId === activeStepId) { - return; - } +import React, { useState, useEffect } from "react"; +import classnames from "classnames"; +import { Formik, Form, useFormikContext } from "formik"; +import { cloneDeep, isNull, isEmpty } from "lodash"; +import { useFetch, FETCH_METHODS, usePrevious } from "hooks"; +import Button from "components/Button"; +import Modal from "components/Modal"; +import Loader from "components/Loader"; +import Title from "components/Title"; +import Arrow, { ARROW_NAMES } from "components/Arrow"; + +import "./wizard-modal.scss"; + +const Wizard = ({ + steps, + onClose, + submitUrl, + onSubmitSuccess, + getSubmitParams, +}) => { + const { + values, + isSubmitting, + isValidating, + setSubmitting, + status, + setStatus, + isValid, + setErrors, + validateForm, + } = useFormikContext(); + + const [activeStepId, setActiveStepId] = useState(steps[0].id); + + const activeStepIndex = steps.findIndex(({ id }) => id === activeStepId); + const { component: ActiveStepComponent, title: activeTitle } = + steps[activeStepIndex]; + const { title: nextStepTitle, id: nextStepId } = + steps[activeStepIndex + 1] || {}; - setActiveStepId(stepId); + const disableStepDone = isSubmitting || isValidating || !isValid; + + const [{ loading, data, error }, submitFormData] = useFetch(submitUrl, { + loadOnMount: false, + }); + const prevLoading = usePrevious(loading); + + const onStepClick = (stepId) => { + if (disableStepDone || stepId === activeStepId) { + return; } - const handleSubmit = () => { - const submitQueryParams = !!getSubmitParams ? getSubmitParams(cloneDeep(values)) : {}; - submitFormData({method: FETCH_METHODS.POST, submitData: values, ...submitQueryParams}); + setActiveStepId(stepId); + }; + + const handleSubmit = () => { + const submitQueryParams = !!getSubmitParams + ? getSubmitParams(cloneDeep(values)) + : {}; + submitFormData({ + method: FETCH_METHODS.POST, + submitData: values, + ...submitQueryParams, + }); + }; + + useEffect(() => { + validateForm(); + }, [activeStepId, validateForm]); + + useEffect(() => { + if (prevLoading && !loading) { + setSubmitting(false); + setStatus(null); + + if (isNull(error)) { + if (!!onSubmitSuccess) { + onSubmitSuccess(data); + } + } else { + const { message, errors } = error; + + if (!!message) { + setStatus(message); + } + + if (!isEmpty(errors)) { + setErrors(errors); + } + } } + }, [ + prevLoading, + loading, + error, + data, + setSubmitting, + setStatus, + onSubmitSuccess, + setErrors, + ]); - useEffect(() => { - validateForm(); - }, [activeStepId, validateForm]); - - useEffect(() => { - if (prevLoading && !loading) { - setSubmitting(false); - setStatus(null); - - if (isNull(error)) { - if (!!onSubmitSuccess) { - onSubmitSuccess(data); - } - } else { - const {message, errors} = error; - - if (!!message) { - setStatus(message); - } - - if (!isEmpty(errors)) { - setErrors(errors); - } - } - } - }, [prevLoading, loading, error, data, setSubmitting, setStatus, onSubmitSuccess, setErrors]); - - if (isSubmitting) { - return ; - } - - return ( -
-
-
    - { - steps.map(({id, title}) => ( -
  • onStepClick(id)} - >{title}
  • - )) - } -
-
- {!!status &&
{status}
} - {activeTitle} - - {!!nextStepTitle && -
onStepClick(nextStepId)}> -
{`Go to ${nextStepTitle}`}
- -
- } -
-
-
- - + if (isSubmitting) { + return ; + } + + return ( + +
+
    + {steps.map(({ id, title }) => ( +
  • onStepClick(id)} + > + {title} +
  • + ))} +
+
+ {!!status &&
{status}
} + {activeTitle} + + {!!nextStepTitle && ( +
onStepClick(nextStepId) + } + > +
{`Go to ${nextStepTitle}`}
+
- - ) -} - -const WizardModal = ({title, initialValues, onClose, validate, ...props}) => ( - - - - - -) - -export default WizardModal; \ No newline at end of file + )} +
+
+
+ + +
+ + ); +}; + +const WizardModal = ({ title, initialValues, onClose, validate, ...props }) => ( + + + + + +); + +export default WizardModal; diff --git a/ui/src/components/WizardModal/wizard-modal.scss b/ui/src/components/WizardModal/wizard-modal.scss index 94c2c71ca8..56b0c95d37 100644 --- a/ui/src/components/WizardModal/wizard-modal.scss +++ b/ui/src/components/WizardModal/wizard-modal.scss @@ -1,4 +1,4 @@ -@import 'utils/scss_variables.module.scss'; +@import "utils/scss_variables.module.scss"; $actions-panel-height: 49px; $top-padding: 40px; @@ -6,86 +6,89 @@ $inner-padding: 25px; $navigation-width: 180px; .wizard-modal { - padding-top: $top-padding; + padding-top: $top-padding; - .modal-title { - padding: 0 $inner-padding; - } - .wizard-wrapper { - .wizard-content { - display: flex; - padding: 0 $inner-padding; - - .wizard-navigation { - margin: 0; - padding: 0 20px; - width: $navigation-width; - border-right: 1px dashed $color-grey-dark; - - .wizard-navigation-item { - cursor: pointer; - color: $color-main; - - &.is-active { - font-weight: bold; - cursor: auto; - } - &.disabled { - cursor: not-allowed; - } - &:not(.is-active) { - text-decoration: underline; - } - } - } - .wizard-step-display { - padding: 0 20px; - width: calc(100% - #{$navigation-width}); - height: calc(100vh - #{$top-bar-height} - #{$actions-panel-height} - #{$top-padding} - 85px); - overflow-y: auto; + .modal-title { + padding: 0 $inner-padding; + } + .wizard-wrapper { + .wizard-content { + display: flex; + padding: 0 $inner-padding; + + .wizard-navigation { + margin: 0; + padding: 0 20px; + width: $navigation-width; + border-right: 1px dashed $color-grey-dark; + + .wizard-navigation-item { + cursor: pointer; + color: $color-main; + + &.is-active { + font-weight: bold; + cursor: auto; + } + &.disabled { + cursor: not-allowed; + } + &:not(.is-active) { + text-decoration: underline; + } + } + } + .wizard-step-display { + padding: 0 20px; + width: calc(100% - #{$navigation-width}); + height: calc( + 100vh - #{$top-bar-height} - #{$actions-panel-height} - #{$top-padding} - + 85px + ); + overflow-y: auto; - .main-error-message { - background-color: $color-error-light; - padding: 5px 10px; - margin-bottom: 27px; - font-weight: 400; - font-size: 12px; - line-height: 18px; - } - .form-field-label-wrapper{ - margin-top: 30px; - } - .wizard-next-step-wrapper { - display: flex; - align-items: center; - justify-content: flex-end; - margin: 40px 0; - color: $color-main; - cursor: pointer; - - &.disabled { - cursor: not-allowed; - } - .wizard-next-step-title { - font-weight: 400; - font-size: 14px; - text-decoration: underline; - margin-right: 5px; - } - } - } + .main-error-message { + background-color: $color-error-light; + padding: 5px 10px; + margin-bottom: 27px; + font-weight: 400; + font-size: 12px; + line-height: 18px; } - .wizard-action-buttons { - height: $actions-panel-height; - border-top: 3px solid $color-grey-lighter; - padding: 0 $inner-padding; - display: flex; - align-items: center; - justify-content: flex-end; - - .clarity-button:not(:last-child) { - margin-right: 10px; - } + .form-field-label-wrapper { + margin-top: 30px; } + .wizard-next-step-wrapper { + display: flex; + align-items: center; + justify-content: flex-end; + margin: 40px 0; + color: $color-main; + cursor: pointer; + + &.disabled { + cursor: not-allowed; + } + .wizard-next-step-title { + font-weight: 400; + font-size: 14px; + text-decoration: underline; + margin-right: 5px; + } + } + } + } + .wizard-action-buttons { + height: $actions-panel-height; + border-top: 3px solid $color-grey-lighter; + padding: 0 $inner-padding; + display: flex; + align-items: center; + justify-content: flex-end; + + .clarity-button:not(:last-child) { + margin-right: 10px; + } } -} \ No newline at end of file + } +} diff --git a/ui/src/components/WrappingTextBoxWithEllipsis/index.jsx b/ui/src/components/WrappingTextBoxWithEllipsis/index.jsx index 98ae902d3a..de4e7c0f7c 100644 --- a/ui/src/components/WrappingTextBoxWithEllipsis/index.jsx +++ b/ui/src/components/WrappingTextBoxWithEllipsis/index.jsx @@ -1,36 +1,50 @@ import React, { useEffect, useRef, useState } from "react"; -import Arrow, { ARROW_NAMES } from 'components/Arrow'; +import Arrow, { ARROW_NAMES } from "components/Arrow"; import { TooltipWrapper } from "components/Tooltip"; -import './wrapping-text-box-with-ellipsis.scss'; +import "./wrapping-text-box-with-ellipsis.scss"; -export const WrappingTextBoxWithEllipsis = ({ children, numberOfLines = 1 }) => { - const [isExpanded, setIsExpanded] = useState(false); - const [showExpandButton, setShowExpandButton] = useState(false); - const ref = useRef(); +export const WrappingTextBoxWithEllipsis = ({ + children, + numberOfLines = 1, +}) => { + const [isExpanded, setIsExpanded] = useState(false); + const [showExpandButton, setShowExpandButton] = useState(false); + const ref = useRef(); - useEffect(() => { - setShowExpandButton(ref.current && (ref.current.offsetHeight < ref.current.scrollHeight || ref.current.offsetWidth < ref.current.scrollWidth)); - }, [children]); + useEffect(() => { + setShowExpandButton( + ref.current && + (ref.current.offsetHeight < ref.current.scrollHeight || + ref.current.offsetWidth < ref.current.scrollWidth), + ); + }, [children]); - return ( -
-
- {children} -
- {showExpandButton && -
- - setIsExpanded(!isExpanded)} - small - /> - -
- } + return ( +
+
+ {children} +
+ {showExpandButton && ( +
+ + setIsExpanded(!isExpanded)} + small + /> +
- ) -} + )} +
+ ); +}; diff --git a/ui/src/components/WrappingTextBoxWithEllipsis/wrapping-text-box-with-ellipsis.scss b/ui/src/components/WrappingTextBoxWithEllipsis/wrapping-text-box-with-ellipsis.scss index d2a931f1fe..d348693a01 100644 --- a/ui/src/components/WrappingTextBoxWithEllipsis/wrapping-text-box-with-ellipsis.scss +++ b/ui/src/components/WrappingTextBoxWithEllipsis/wrapping-text-box-with-ellipsis.scss @@ -1,14 +1,14 @@ .wrapping-text-box-with-ellipsis-wrapper { - display: flex; - align-items: end; + display: flex; + align-items: end; - .wrapping-text-box-with-ellipsis-content { - flex: 1; - display: -webkit-box; - -webkit-box-orient: vertical; - white-space: normal; - overflow-wrap: break-word; - overflow: hidden; - text-overflow: ellipsis; - } + .wrapping-text-box-with-ellipsis-content { + flex: 1; + display: -webkit-box; + -webkit-box-orient: vertical; + white-space: normal; + overflow-wrap: break-word; + overflow: hidden; + text-overflow: ellipsis; + } } diff --git a/ui/src/context/FiltersProvider.js b/ui/src/context/FiltersProvider.js index ea8da3345d..f62c0f22e2 100644 --- a/ui/src/context/FiltersProvider.js +++ b/ui/src/context/FiltersProvider.js @@ -1,186 +1,235 @@ -import { isArray, isObject } from 'lodash'; -import { create } from './utils'; +import { isArray, isObject } from "lodash"; +import { create } from "./utils"; export const FILTER_TYPES = { - ASSETS: "ASSETS", - ASSET_SCANS: "ASSET_SCANS", - SCANS: "SCANS", - SCAN_CONFIGURATIONS: "SCAN_CONFIGURATIONS", - FINDINGS_GENERAL: "FINDINGS_GENERAL", - FINDINGS_VULNERABILITIES: "FINDINGS_GENERAL", - FINDINGS_EXPLOITS: "FINDINGS_EXPLOITS", - FINDINGS_MISCONFIGURATIONS: "FINDINGS_MISCONFIGURATIONS", - FINDINGS_SECRETS: "FINDINGS_SECRETS", - FINDINGS_MALWARE: "FINDINGS_MALWARE", - FINDINGS_ROOTKITS: "FINDINGS_ROOTKITS", - FINDINGS_PACKAGES: "FINDINGS_PACKAGES" -} + ASSETS: "ASSETS", + ASSET_SCANS: "ASSET_SCANS", + SCANS: "SCANS", + SCAN_CONFIGURATIONS: "SCAN_CONFIGURATIONS", + FINDINGS_GENERAL: "FINDINGS_GENERAL", + FINDINGS_VULNERABILITIES: "FINDINGS_GENERAL", + FINDINGS_EXPLOITS: "FINDINGS_EXPLOITS", + FINDINGS_MISCONFIGURATIONS: "FINDINGS_MISCONFIGURATIONS", + FINDINGS_SECRETS: "FINDINGS_SECRETS", + FINDINGS_MALWARE: "FINDINGS_MALWARE", + FINDINGS_ROOTKITS: "FINDINGS_ROOTKITS", + FINDINGS_PACKAGES: "FINDINGS_PACKAGES", +}; const initialState = { - ...Object.keys(FILTER_TYPES).reduce((acc, curr) => ({ - ...acc, - [curr]: { - tableFilters: [], - systemFilters: {}, - customFilters: {}, - selectedPageIndex: 0, - tableSort: {} - } - }), {}), - initialized: false + ...Object.keys(FILTER_TYPES).reduce( + (acc, curr) => ({ + ...acc, + [curr]: { + tableFilters: [], + systemFilters: {}, + customFilters: {}, + selectedPageIndex: 0, + tableSort: {}, + }, + }), + {}, + ), + initialized: false, }; const FITLER_ACTIONS = { - SET_TABLE_FILTERS_BY_KEY: "SET_TABLE_FILTERS_BY_KEY", - SET_SYSTEM_FILTERS_BY_KEY: "SET_SYSTEM_FILTERS_BY_KEY", - SET_CUSTOM_FILTERS_BY_KEY: "SET_CUSTOM_FILTERS_BY_KEY", - SET_TABLE_PAGE_BY_KEY: "SET_TABLE_PAGE_BY_KEY", - SET_TABLE_SORT_BY_KEY: "SET_TABLE_SORT_BY_KEY", - RESET_ALL_FILTERS: "RESET_ALL_FILTERS", - RESET_FILTERS_BY_KEY: "RESET_FILTERS_BY_KEY", - INITIALIZE_FILTERS: "INITIALIZE_FILTERS" -} + SET_TABLE_FILTERS_BY_KEY: "SET_TABLE_FILTERS_BY_KEY", + SET_SYSTEM_FILTERS_BY_KEY: "SET_SYSTEM_FILTERS_BY_KEY", + SET_CUSTOM_FILTERS_BY_KEY: "SET_CUSTOM_FILTERS_BY_KEY", + SET_TABLE_PAGE_BY_KEY: "SET_TABLE_PAGE_BY_KEY", + SET_TABLE_SORT_BY_KEY: "SET_TABLE_SORT_BY_KEY", + RESET_ALL_FILTERS: "RESET_ALL_FILTERS", + RESET_FILTERS_BY_KEY: "RESET_FILTERS_BY_KEY", + INITIALIZE_FILTERS: "INITIALIZE_FILTERS", +}; const reducer = (state, action) => { - switch (action.type) { - case FITLER_ACTIONS.SET_TABLE_FILTERS_BY_KEY: { - const {filterType, filterData} = action.payload; + switch (action.type) { + case FITLER_ACTIONS.SET_TABLE_FILTERS_BY_KEY: { + const { filterType, filterData } = action.payload; - return { - ...state, - [filterType]: { - ...state[filterType], - tableFilters: filterData, - selectedPageIndex: 0 - } - }; - } - case FITLER_ACTIONS.SET_SYSTEM_FILTERS_BY_KEY: { - const {filterType, filterData} = action.payload; - - return { - ...state, - [filterType]: { - ...state[filterType], - tableFilters: [...initialState[filterType].tableFilters], - systemFilters: filterData, - selectedPageIndex: 0 - } - }; - } - case FITLER_ACTIONS.SET_CUSTOM_FILTERS_BY_KEY: { - const {filterType, filterData} = action.payload; - - return { - ...state, - [filterType]: { - ...state[filterType], - customFilters: filterData, - selectedPageIndex: 0 - } - }; - } - case FITLER_ACTIONS.SET_TABLE_PAGE_BY_KEY: { - const {filterType, pageIndex} = action.payload; + return { + ...state, + [filterType]: { + ...state[filterType], + tableFilters: filterData, + selectedPageIndex: 0, + }, + }; + } + case FITLER_ACTIONS.SET_SYSTEM_FILTERS_BY_KEY: { + const { filterType, filterData } = action.payload; - return { - ...state, - [filterType]: { - ...state[filterType], - selectedPageIndex: pageIndex - } - }; - } - case FITLER_ACTIONS.SET_TABLE_SORT_BY_KEY: { - const {filterType, tableSort} = action.payload; + return { + ...state, + [filterType]: { + ...state[filterType], + tableFilters: [...initialState[filterType].tableFilters], + systemFilters: filterData, + selectedPageIndex: 0, + }, + }; + } + case FITLER_ACTIONS.SET_CUSTOM_FILTERS_BY_KEY: { + const { filterType, filterData } = action.payload; - return { - ...state, - [filterType]: { - ...state[filterType], - tableSort - } - }; - } - case FITLER_ACTIONS.RESET_ALL_FILTERS: { - return Object.keys(initialState).reduce((acc, curr) => ({ - ...acc, - [curr]: { - ...initialState[curr], - tableSort: state[curr].tableSort - } - }), {}); - } - case FITLER_ACTIONS.RESET_FILTERS_BY_KEY: { - const {filterTypes} = action.payload; - - return { - ...state, - ...filterTypes.reduce((acc, curr) => ({ - ...acc, - [curr]: { - ...initialState[curr], - tableSort: state[curr].tableSort - } - }), {}) - }; - } - case FITLER_ACTIONS.INITIALIZE_FILTERS: { - const {filterType, systemFilterType, tableFilters, systemFilters, customFilters} = action.payload; - - if (!Object.values(FILTER_TYPES).includes(filterType) || (!Object.values(FILTER_TYPES).includes(systemFilterType) && !!systemFilterType) - || !isArray(tableFilters || {}) || !isObject(systemFilters || {}) || !isObject(customFilters || {})) { - return { - ...state, - initialized: true - } - } + return { + ...state, + [filterType]: { + ...state[filterType], + customFilters: filterData, + selectedPageIndex: 0, + }, + }; + } + case FITLER_ACTIONS.SET_TABLE_PAGE_BY_KEY: { + const { filterType, pageIndex } = action.payload; - return { - ...state, - [filterType]: { - ...state[filterType], - tableFilters, - systemFilters, - customFilters, - selectedPageIndex: 0 - }, - ...(!systemFilterType ? {} : { - [systemFilterType]: { - ...state[systemFilterType], - systemFilters - } - }), - initialized: true - }; - } - default: - return state; + return { + ...state, + [filterType]: { + ...state[filterType], + selectedPageIndex: pageIndex, + }, + }; } -} + case FITLER_ACTIONS.SET_TABLE_SORT_BY_KEY: { + const { filterType, tableSort } = action.payload; -const [FiltersProvider, useFilterState, useFilterDispatch] = create(reducer, initialState); + return { + ...state, + [filterType]: { + ...state[filterType], + tableSort, + }, + }; + } + case FITLER_ACTIONS.RESET_ALL_FILTERS: { + return Object.keys(initialState).reduce( + (acc, curr) => ({ + ...acc, + [curr]: { + ...initialState[curr], + tableSort: state[curr].tableSort, + }, + }), + {}, + ); + } + case FITLER_ACTIONS.RESET_FILTERS_BY_KEY: { + const { filterTypes } = action.payload; -const setFilters = (dispatch, {type, filters, isSystem=false, isCustom=false}) => dispatch({ - type: isSystem ? FITLER_ACTIONS.SET_SYSTEM_FILTERS_BY_KEY : (isCustom ? FITLER_ACTIONS.SET_CUSTOM_FILTERS_BY_KEY : FITLER_ACTIONS.SET_TABLE_FILTERS_BY_KEY), - payload: {filterType: type, filterData: filters} -}); -const setPage = (dispatch, {type, pageIndex}) => dispatch({type: FITLER_ACTIONS.SET_TABLE_PAGE_BY_KEY, payload: {filterType: type, pageIndex}}); -const setSort = (dispatch, {type, tableSort}) => dispatch({type: FITLER_ACTIONS.SET_TABLE_SORT_BY_KEY, payload: {filterType: type, tableSort}}); -const resetAllFilters = (dispatch) => dispatch({type: FITLER_ACTIONS.RESET_ALL_FILTERS}); -const resetFilters = (dispatch, filterTypes) => dispatch({type: FITLER_ACTIONS.RESET_FILTERS_BY_KEY, payload: {filterTypes}}); -const resetSystemFilters = (dispatch, type) => setFilters(dispatch, {type, filters: {}, isSystem: true}); -const initializeFilters = (dispatch, filtersData) => dispatch({type: FITLER_ACTIONS.INITIALIZE_FILTERS, payload: filtersData}); + return { + ...state, + ...filterTypes.reduce( + (acc, curr) => ({ + ...acc, + [curr]: { + ...initialState[curr], + tableSort: state[curr].tableSort, + }, + }), + {}, + ), + }; + } + case FITLER_ACTIONS.INITIALIZE_FILTERS: { + const { + filterType, + systemFilterType, + tableFilters, + systemFilters, + customFilters, + } = action.payload; + + if ( + !Object.values(FILTER_TYPES).includes(filterType) || + (!Object.values(FILTER_TYPES).includes(systemFilterType) && + !!systemFilterType) || + !isArray(tableFilters || {}) || + !isObject(systemFilters || {}) || + !isObject(customFilters || {}) + ) { + return { + ...state, + initialized: true, + }; + } + + return { + ...state, + [filterType]: { + ...state[filterType], + tableFilters, + systemFilters, + customFilters, + selectedPageIndex: 0, + }, + ...(!systemFilterType + ? {} + : { + [systemFilterType]: { + ...state[systemFilterType], + systemFilters, + }, + }), + initialized: true, + }; + } + default: + return state; + } +}; + +const [FiltersProvider, useFilterState, useFilterDispatch] = create( + reducer, + initialState, +); + +const setFilters = ( + dispatch, + { type, filters, isSystem = false, isCustom = false }, +) => + dispatch({ + type: isSystem + ? FITLER_ACTIONS.SET_SYSTEM_FILTERS_BY_KEY + : isCustom + ? FITLER_ACTIONS.SET_CUSTOM_FILTERS_BY_KEY + : FITLER_ACTIONS.SET_TABLE_FILTERS_BY_KEY, + payload: { filterType: type, filterData: filters }, + }); +const setPage = (dispatch, { type, pageIndex }) => + dispatch({ + type: FITLER_ACTIONS.SET_TABLE_PAGE_BY_KEY, + payload: { filterType: type, pageIndex }, + }); +const setSort = (dispatch, { type, tableSort }) => + dispatch({ + type: FITLER_ACTIONS.SET_TABLE_SORT_BY_KEY, + payload: { filterType: type, tableSort }, + }); +const resetAllFilters = (dispatch) => + dispatch({ type: FITLER_ACTIONS.RESET_ALL_FILTERS }); +const resetFilters = (dispatch, filterTypes) => + dispatch({ + type: FITLER_ACTIONS.RESET_FILTERS_BY_KEY, + payload: { filterTypes }, + }); +const resetSystemFilters = (dispatch, type) => + setFilters(dispatch, { type, filters: {}, isSystem: true }); +const initializeFilters = (dispatch, filtersData) => + dispatch({ type: FITLER_ACTIONS.INITIALIZE_FILTERS, payload: filtersData }); export { - FiltersProvider, - useFilterState, - useFilterDispatch, - setFilters, - setPage, - setSort, - resetAllFilters, - resetFilters, - resetSystemFilters, - initializeFilters -}; \ No newline at end of file + FiltersProvider, + useFilterState, + useFilterDispatch, + setFilters, + setPage, + setSort, + resetAllFilters, + resetFilters, + resetSystemFilters, + initializeFilters, +}; diff --git a/ui/src/context/NotificationProvider.js b/ui/src/context/NotificationProvider.js index a6010a8a90..24974db15a 100644 --- a/ui/src/context/NotificationProvider.js +++ b/ui/src/context/NotificationProvider.js @@ -1,50 +1,55 @@ -import { create } from './utils'; +import { create } from "./utils"; const initialState = { - message: null, - type: null + message: null, + type: null, }; const NOTIFICATION_ACTIONS = { - SHOW_NOTIFICATION: "SHOW_NOTIFICATION", - REMOVE_NOTIFICATION: "REMOVE_NOTIFICATION" + SHOW_NOTIFICATION: "SHOW_NOTIFICATION", + REMOVE_NOTIFICATION: "REMOVE_NOTIFICATION", }; const reducer = (state, action) => { - switch (action.type) { - case NOTIFICATION_ACTIONS.SHOW_NOTIFICATION: { - const {type, message} = action.payload; - - return { - ...state, - message, - type - }; - } - case NOTIFICATION_ACTIONS.REMOVE_NOTIFICATION: { - return { - ...state, - message: null, - type: null - } - } - default: - return state; + switch (action.type) { + case NOTIFICATION_ACTIONS.SHOW_NOTIFICATION: { + const { type, message } = action.payload; + + return { + ...state, + message, + type, + }; + } + case NOTIFICATION_ACTIONS.REMOVE_NOTIFICATION: { + return { + ...state, + message: null, + type: null, + }; } -} + default: + return state; + } +}; -const [NotificationProvider, useNotificationState, useNotificationDispatch] = create(reducer, initialState); +const [NotificationProvider, useNotificationState, useNotificationDispatch] = + create(reducer, initialState); -const removeNotification = (dispatch) => dispatch({type: NOTIFICATION_ACTIONS.REMOVE_NOTIFICATION}); -const showNotification = (dispatch, {type, message}) => { - dispatch({type: NOTIFICATION_ACTIONS.SHOW_NOTIFICATION, payload: {type, message}}); - setTimeout(() => removeNotification(dispatch), 6000); +const removeNotification = (dispatch) => + dispatch({ type: NOTIFICATION_ACTIONS.REMOVE_NOTIFICATION }); +const showNotification = (dispatch, { type, message }) => { + dispatch({ + type: NOTIFICATION_ACTIONS.SHOW_NOTIFICATION, + payload: { type, message }, + }); + setTimeout(() => removeNotification(dispatch), 6000); }; export { - NotificationProvider, - useNotificationState, - useNotificationDispatch, - showNotification, - removeNotification -}; \ No newline at end of file + NotificationProvider, + useNotificationState, + useNotificationDispatch, + showNotification, + removeNotification, +}; diff --git a/ui/src/context/utils.jsx b/ui/src/context/utils.jsx index f07ff7016e..f669cfb8c6 100644 --- a/ui/src/context/utils.jsx +++ b/ui/src/context/utils.jsx @@ -1,41 +1,41 @@ -import React from 'react'; -import { isUndefined } from 'lodash'; +import React from "react"; +import { isUndefined } from "lodash"; export const create = (reducer, initialState) => { - const StateContext = React.createContext(); - const DispatchContext = React.createContext(); - - const Provider = ({children}) => { - const [state, dispatch] = React.useReducer(reducer, initialState); - - return ( - - - {children} - - - ) + const StateContext = React.createContext(); + const DispatchContext = React.createContext(); + + const Provider = ({ children }) => { + const [state, dispatch] = React.useReducer(reducer, initialState); + + return ( + + + {children} + + + ); + }; + + const useState = () => { + const context = React.useContext(StateContext); + + if (isUndefined(context)) { + throw Error("useState is not within the related provider"); } - const useState = () => { - const context = React.useContext(StateContext); + return context; + }; - if (isUndefined(context)) { - throw Error("useState is not within the related provider") - } + const useDispatch = () => { + const context = React.useContext(DispatchContext); - return context; + if (isUndefined(context)) { + throw Error("useDispatch is not within the related provider"); } - const useDispatch = () => { - const context = React.useContext(DispatchContext); + return context; + }; - if (isUndefined(context)) { - throw Error("useDispatch is not within the related provider") - } - - return context; - } - - return [Provider, useState, useDispatch]; -} \ No newline at end of file + return [Provider, useState, useDispatch]; +}; diff --git a/ui/src/hooks/index.js b/ui/src/hooks/index.js index 3e701b814d..141414f1ef 100644 --- a/ui/src/hooks/index.js +++ b/ui/src/hooks/index.js @@ -1,14 +1,14 @@ -import useFetch, { FETCH_METHODS } from './useFetch'; -import useMultiFetch from './useMultiFetch'; -import useMountMultiFetch from './useMountMultiFetch'; -import useDelete from './useDelete'; -import usePrevious from './usePrevious'; +import useFetch, { FETCH_METHODS } from "./useFetch"; +import useMultiFetch from "./useMultiFetch"; +import useMountMultiFetch from "./useMountMultiFetch"; +import useDelete from "./useDelete"; +import usePrevious from "./usePrevious"; export { - useFetch, - FETCH_METHODS, - useMultiFetch, - useMountMultiFetch, - useDelete, - usePrevious -} \ No newline at end of file + useFetch, + FETCH_METHODS, + useMultiFetch, + useMountMultiFetch, + useDelete, + usePrevious, +}; diff --git a/ui/src/hooks/useDelete.js b/ui/src/hooks/useDelete.js index bde759e02c..b20c6ba3e4 100644 --- a/ui/src/hooks/useDelete.js +++ b/ui/src/hooks/useDelete.js @@ -1,109 +1,125 @@ -import { useReducer, useEffect, useRef, useCallback } from 'react'; -import { useNotificationDispatch, showNotification } from 'context/NotificationProvider'; -import { NOTIFICATION_TYPES } from 'components/Notification'; -import { formatFetchOptions, FETCH_METHODS } from './useFetch'; +import { useReducer, useEffect, useRef, useCallback } from "react"; +import { + useNotificationDispatch, + showNotification, +} from "context/NotificationProvider"; +import { NOTIFICATION_TYPES } from "components/Notification"; +import { formatFetchOptions, FETCH_METHODS } from "./useFetch"; const DELETE_ACTIONS = { - DELETING: "DELETING", - DELETE_SUCCESS: "DELETE_SUCCESS", - DELETE_ERROR: "DELETE_ERROR", - UPDATE_DELETE_PARAMS: "UPDATE_DELETE_PARAMS", - DELETE_GENERAL_ERROR_MESSAGE: "DELETE_GENERAL_ERROR_MESSAGE" -} + DELETING: "DELETING", + DELETE_SUCCESS: "DELETE_SUCCESS", + DELETE_ERROR: "DELETE_ERROR", + UPDATE_DELETE_PARAMS: "UPDATE_DELETE_PARAMS", + DELETE_GENERAL_ERROR_MESSAGE: "DELETE_GENERAL_ERROR_MESSAGE", +}; function reducer(state, action) { - switch (action.type) { - case DELETE_ACTIONS.DELETING: - return {...state, deleting: true, error: null, callDelete: false}; - case DELETE_ACTIONS.DELETE_SUCCESS: - return {...state, deleting: false, callDelete: false}; - case DELETE_ACTIONS.DELETE_ERROR: - return {...state, deleting: false, error: action.payload, callDelete: false}; - case DELETE_ACTIONS.UPDATE_DELETE_PARAMS: - return { - ...state, - callDelete: true, - formattedUrl: `/api/${state.baseUrl}/${action.payload}${state.urlSuffix}` - }; - default: - return {...state}; - } + switch (action.type) { + case DELETE_ACTIONS.DELETING: + return { ...state, deleting: true, error: null, callDelete: false }; + case DELETE_ACTIONS.DELETE_SUCCESS: + return { ...state, deleting: false, callDelete: false }; + case DELETE_ACTIONS.DELETE_ERROR: + return { + ...state, + deleting: false, + error: action.payload, + callDelete: false, + }; + case DELETE_ACTIONS.UPDATE_DELETE_PARAMS: + return { + ...state, + callDelete: true, + formattedUrl: `/api/${state.baseUrl}/${action.payload}${state.urlSuffix}`, + }; + default: + return { ...state }; + } } const useDelete = (url, options) => { - const notificationDispatch = useNotificationDispatch(); + const notificationDispatch = useNotificationDispatch(); + + const { urlSuffix, showServerError } = options || {}; + const [state, dispatch] = useReducer(reducer, { + deleting: false, + error: null, + baseUrl: url, + formattedUrl: null, + urlSuffix: urlSuffix || "", + callDelete: false, + }); + + const mounted = useRef(true); + + useEffect(() => { + return function cleanup() { + mounted.current = false; + }; + }, []); + + const { error, deleting, formattedUrl, callDelete } = state; + + const doDelete = useCallback(async () => { + const options = formatFetchOptions({ method: FETCH_METHODS.DELETE }); + + dispatch({ type: DELETE_ACTIONS.DELETING }); + + let isError = false; + const showErrorMessage = (message) => + showNotification(notificationDispatch, { + message: + showServerError && !!message + ? message + : DELETE_ACTIONS.DELETE_GENERAL_ERROR_MESSAGE, + type: NOTIFICATION_TYPES.ERROR, + }); + + fetch(formattedUrl, options) + .then((response) => { + isError = !response.ok; + + return response; + }) + .then((response) => (response.status === 204 ? {} : response.json())) + .then((data) => { + if (isError) { + dispatch({ type: DELETE_ACTIONS.DELETE_ERROR, payload: data }); + + showErrorMessage(data.message); + + return; + } - const {urlSuffix, showServerError} = options || {}; - const [state, dispatch] = useReducer(reducer, { - deleting: false, - error: null, - baseUrl: url, - formattedUrl: null, - urlSuffix: urlSuffix || "", - callDelete: false - }); - - const mounted = useRef(true); - - useEffect(() => { - return function cleanup() { - mounted.current = false; - }; - }, []); - - const {error, deleting, formattedUrl, callDelete} = state; - - const doDelete = useCallback(async () => { - const options = formatFetchOptions({method: FETCH_METHODS.DELETE}); - - dispatch({type: DELETE_ACTIONS.DELETING}); - - let isError = false; - const showErrorMessage = (message) => showNotification(notificationDispatch, {message: showServerError && !!message ? message : DELETE_ACTIONS.DELETE_GENERAL_ERROR_MESSAGE, type: NOTIFICATION_TYPES.ERROR}); - - fetch(formattedUrl, options) - .then(response => { - isError = !response.ok; - - return response; - }) - .then(response => response.status === 204 ? {} : response.json()) - .then(data => { - if (isError) { - dispatch({type: DELETE_ACTIONS.DELETE_ERROR, payload: data}); - - showErrorMessage(data.message); - - return; - } - - if (!mounted.current) { - return; - } - - dispatch({type: DELETE_ACTIONS.DELETE_SUCCESS, payload: data}); - }) - .catch(error => { - if (!mounted.current) { - return; - } - - showErrorMessage(); - - dispatch({type: DELETE_ACTIONS.DELETE_ERROR, payload: error}); - }); - }, [formattedUrl, showServerError, notificationDispatch]); - - useEffect(() => { - if (!formattedUrl || !callDelete) { - return; + if (!mounted.current) { + return; } - doDelete(); - }, [doDelete, formattedUrl, callDelete]); + dispatch({ type: DELETE_ACTIONS.DELETE_SUCCESS, payload: data }); + }) + .catch((error) => { + if (!mounted.current) { + return; + } - const deleteData = deleteId => dispatch({type: DELETE_ACTIONS.UPDATE_DELETE_PARAMS, payload: deleteId}); - - return [{error, deleting}, deleteData]; -} + showErrorMessage(); + + dispatch({ type: DELETE_ACTIONS.DELETE_ERROR, payload: error }); + }); + }, [formattedUrl, showServerError, notificationDispatch]); + + useEffect(() => { + if (!formattedUrl || !callDelete) { + return; + } + + doDelete(); + }, [doDelete, formattedUrl, callDelete]); + + const deleteData = (deleteId) => + dispatch({ type: DELETE_ACTIONS.UPDATE_DELETE_PARAMS, payload: deleteId }); + + return [{ error, deleting }, deleteData]; +}; -export default useDelete; \ No newline at end of file +export default useDelete; diff --git a/ui/src/hooks/useFetch.js b/ui/src/hooks/useFetch.js index 81a78b0a9b..742c54d753 100644 --- a/ui/src/hooks/useFetch.js +++ b/ui/src/hooks/useFetch.js @@ -1,156 +1,217 @@ -import { useReducer, useEffect, useRef, useCallback } from 'react'; -import { isUndefined, isNull, isEmpty } from 'lodash'; -import { useNotificationDispatch, showNotification } from 'context/NotificationProvider'; -import { NOTIFICATION_TYPES } from 'components/Notification'; +import { useReducer, useEffect, useRef, useCallback } from "react"; +import { isUndefined, isNull, isEmpty } from "lodash"; +import { + useNotificationDispatch, + showNotification, +} from "context/NotificationProvider"; +import { NOTIFICATION_TYPES } from "components/Notification"; export const FETCH_METHODS = { - GET: "GET", - POST: "POST", - PUT: "PUT", - DELETE: "DELETE", - PATCH: "PATCH" -} + GET: "GET", + POST: "POST", + PUT: "PUT", + DELETE: "DELETE", + PATCH: "PATCH", +}; const FETCH_ACTIONS = { - LOADING_DATA: "LOADING_DATA", - LOAD_DATA_SUCCESS: "LOAD_DATA_SUCCESS", - LOAD_DATA_ERROR: "LOAD_DATA_ERROR", - UPDATE_FETCH_PARAMS: "UPDATE_FETCH_PARAMS" -} - -const queryString = (params) => Object.keys(params).map((key) => { - return encodeURIComponent(key) + '=' + encodeURIComponent(params[key]) -}).join('&'); - -export const formatFetchUrl = ({url, queryParams, formatUrl, urlPrefix}) => { - const formattedUrl = !!formatUrl ? formatUrl(url) : url; - const formattedPrefix = !!urlPrefix ? `${urlPrefix}/api` : "api"; + LOADING_DATA: "LOADING_DATA", + LOAD_DATA_SUCCESS: "LOAD_DATA_SUCCESS", + LOAD_DATA_ERROR: "LOAD_DATA_ERROR", + UPDATE_FETCH_PARAMS: "UPDATE_FETCH_PARAMS", +}; + +const queryString = (params) => + Object.keys(params) + .map((key) => { + return encodeURIComponent(key) + "=" + encodeURIComponent(params[key]); + }) + .join("&"); + +export const formatFetchUrl = ({ url, queryParams, formatUrl, urlPrefix }) => { + const formattedUrl = !!formatUrl ? formatUrl(url) : url; + const formattedPrefix = !!urlPrefix ? `${urlPrefix}/api` : "api"; + + return isEmpty(queryParams) + ? `/${formattedPrefix}/${formattedUrl}` + : `/${formattedPrefix}/${formattedUrl}?${queryString(queryParams)}`; +}; + +export const formatFetchOptions = ({ method, stringifiedSubmitData }) => { + const options = { + credentials: "include", + method, + }; + + if ( + [FETCH_METHODS.POST, FETCH_METHODS.PUT, FETCH_METHODS.PATCH].includes( + method, + ) + ) { + options.headers = { "content-type": "application/json" }; + options.body = stringifiedSubmitData; + } + + return options; +}; + +const getErrorMessage = (method) => + `An error occurred when trying to ${method === FETCH_METHODS.GET ? "fetch" : "submit"} data`; - return isEmpty(queryParams) ? `/${formattedPrefix}/${formattedUrl}` : `/${formattedPrefix}/${formattedUrl}?${queryString(queryParams)}`; +function reducer(state, action) { + switch (action.type) { + case FETCH_ACTIONS.LOADING_DATA: + return { ...state, loading: true, error: null, loadData: false }; + case FETCH_ACTIONS.LOAD_DATA_SUCCESS: + return { + ...state, + loading: false, + data: action.payload, + loadData: false, + }; + case FETCH_ACTIONS.LOAD_DATA_ERROR: + return { + ...state, + loading: false, + error: action.payload, + loadData: false, + data: null, + }; + case FETCH_ACTIONS.UPDATE_FETCH_PARAMS: + const { + queryParams, + method = FETCH_METHODS.GET, + submitData, + formatUrl, + urlPrefix, + } = action.payload || {}; + + return { + ...state, + url: formatFetchUrl({ + url: state.baseUrl, + queryParams, + formatUrl, + urlPrefix, + }), + method: method.toUpperCase(), + submitData: !!submitData ? JSON.stringify(submitData) : null, + loadData: true, + data: undefined, + }; + default: + return { ...state }; + } } -export const formatFetchOptions = ({method, stringifiedSubmitData}) => { - const options = { - credentials: 'include', - method +function useFetch(baseUrl, options) { + const { + queryParams, + method: initialMethod, + submitData: inititalSubmitData, + formatUrl, + loadOnMount = true, + urlPrefix, + } = options || {}; + + const [state, dispatch] = useReducer(reducer, { + loading: false, + error: null, + data: loadOnMount ? undefined : null, + baseUrl, + url: formatFetchUrl({ url: baseUrl, queryParams, formatUrl, urlPrefix }), + method: !!initialMethod ? initialMethod.toUpperCase() : FETCH_METHODS.GET, + submitData: !!inititalSubmitData + ? JSON.stringify(inititalSubmitData) + : null, + loadData: loadOnMount || false, + }); + + const mounted = useRef(true); + + useEffect(() => { + return function cleanup() { + mounted.current = false; }; + }, []); - if ([FETCH_METHODS.POST, FETCH_METHODS.PUT, FETCH_METHODS.PATCH].includes(method)) { - options.headers = {'content-type': 'application/json'}; - options.body = stringifiedSubmitData; - } + const notificationDispatch = useNotificationDispatch(); - return options; -} + const { url, method, submitData, loadData, data, error, loading } = state; -const getErrorMessage = (method) => `An error occurred when trying to ${method === FETCH_METHODS.GET ? "fetch" : "submit"} data`; + const doFetch = useCallback(async () => { + const options = formatFetchOptions({ + method, + stringifiedSubmitData: submitData, + }); -function reducer(state, action) { - switch (action.type) { - case FETCH_ACTIONS.LOADING_DATA: - return {...state, loading: true, error: null, loadData: false}; - case FETCH_ACTIONS.LOAD_DATA_SUCCESS: - return {...state, loading: false, data: action.payload, loadData: false}; - case FETCH_ACTIONS.LOAD_DATA_ERROR: - return {...state, loading: false, error: action.payload, loadData: false, data: null}; - case FETCH_ACTIONS.UPDATE_FETCH_PARAMS: - const {queryParams, method=FETCH_METHODS.GET, submitData, formatUrl, urlPrefix} = action.payload || {}; - - return { - ...state, - url: formatFetchUrl({url: state.baseUrl, queryParams, formatUrl, urlPrefix}), - method: method.toUpperCase(), - submitData: !!submitData ? JSON.stringify(submitData) : null, - loadData: true, - data: undefined - }; - default: - return {...state}; - } -} + dispatch({ type: FETCH_ACTIONS.LOADING_DATA }); -function useFetch(baseUrl, options){ - const {queryParams, method: initialMethod, submitData: inititalSubmitData, formatUrl, loadOnMount=true, urlPrefix} = options || {}; + let isError = false; + const showErrorMessage = () => + showNotification(notificationDispatch, { + message: getErrorMessage(method), + type: NOTIFICATION_TYPES.ERROR, + }); - const [state, dispatch] = useReducer(reducer, { - loading: false, - error: null, - data: loadOnMount ? undefined : null, - baseUrl, - url: formatFetchUrl({url: baseUrl, queryParams, formatUrl, urlPrefix}), - method: !!initialMethod ? initialMethod.toUpperCase() : FETCH_METHODS.GET, - submitData: !!inititalSubmitData ? JSON.stringify(inititalSubmitData) : null, - loadData: loadOnMount || false - }); + fetch(url, options) + .then((response) => { + isError = !response.ok; - const mounted = useRef(true); - - useEffect(() => { - return function cleanup() { - mounted.current = false; - }; - }, []); - - const notificationDispatch = useNotificationDispatch(); - - const {url, method, submitData, loadData, data, error, loading} = state; - - const doFetch = useCallback(async () => { - const options = formatFetchOptions({method, stringifiedSubmitData: submitData}); - - dispatch({type: FETCH_ACTIONS.LOADING_DATA}); - - let isError = false; - const showErrorMessage = () => showNotification(notificationDispatch, {message: getErrorMessage(method), type: NOTIFICATION_TYPES.ERROR}); - - fetch(url, options) - .then(response => { - isError = !response.ok; - - return response; - }) - .then(response => response.status === 204 ? {} : response.json()) - .then(data => { - if (!mounted.current) { - return; - } - - if (isError) { - dispatch({type: FETCH_ACTIONS.LOAD_DATA_ERROR, payload: data}); - - showErrorMessage(); - - return; - } - - dispatch({type: FETCH_ACTIONS.LOAD_DATA_SUCCESS, payload: data}); - }) - .catch(error => { - if (!mounted.current) { - return; - } - - showErrorMessage(); - - dispatch({type: FETCH_ACTIONS.LOAD_DATA_ERROR, payload: error}); - }); - }, [url, method, submitData, notificationDispatch]); - - useEffect(() => { + return response; + }) + .then((response) => (response.status === 204 ? {} : response.json())) + .then((data) => { if (!mounted.current) { - return; + return; } - - if (!loadData) { - return; + + if (isError) { + dispatch({ type: FETCH_ACTIONS.LOAD_DATA_ERROR, payload: data }); + + showErrorMessage(); + + return; } - doFetch(); - }, [doFetch, loadOnMount, loadData]); + dispatch({ type: FETCH_ACTIONS.LOAD_DATA_SUCCESS, payload: data }); + }) + .catch((error) => { + if (!mounted.current) { + return; + } + + showErrorMessage(); + + dispatch({ type: FETCH_ACTIONS.LOAD_DATA_ERROR, payload: error }); + }); + }, [url, method, submitData, notificationDispatch]); + + useEffect(() => { + if (!mounted.current) { + return; + } + + if (!loadData) { + return; + } - const fetchData = useCallback(fetchParams => dispatch({type: FETCH_ACTIONS.UPDATE_FETCH_PARAMS, payload: fetchParams}), []); - - return [{data, error, loading: loading || (isUndefined(data) && isNull(error))}, fetchData]; + doFetch(); + }, [doFetch, loadOnMount, loadData]); + + const fetchData = useCallback( + (fetchParams) => + dispatch({ + type: FETCH_ACTIONS.UPDATE_FETCH_PARAMS, + payload: fetchParams, + }), + [], + ); + + return [ + { data, error, loading: loading || (isUndefined(data) && isNull(error)) }, + fetchData, + ]; } -export default useFetch; \ No newline at end of file +export default useFetch; diff --git a/ui/src/hooks/useMountMultiFetch.js b/ui/src/hooks/useMountMultiFetch.js index 3e5e48863c..86a9442e0d 100644 --- a/ui/src/hooks/useMountMultiFetch.js +++ b/ui/src/hooks/useMountMultiFetch.js @@ -1,20 +1,20 @@ -import { useEffect } from 'react'; -import { isEqual } from 'lodash'; -import { usePrevious } from 'hooks'; -import useMultiFetch from './useMultiFetch'; +import { useEffect } from "react"; +import { isEqual } from "lodash"; +import { usePrevious } from "hooks"; +import useMultiFetch from "./useMultiFetch"; const useMountMultiFetch = (urlsData) => { - const [state, fetchData] = useMultiFetch({initialLoading: true}); - - const prevUrlData = usePrevious(urlsData); + const [state, fetchData] = useMultiFetch({ initialLoading: true }); - useEffect(() => { - if (!isEqual(urlsData, prevUrlData)) { - fetchData(urlsData); - } - }, [urlsData, prevUrlData, fetchData]); + const prevUrlData = usePrevious(urlsData); - return [state, () => fetchData(urlsData)]; -} + useEffect(() => { + if (!isEqual(urlsData, prevUrlData)) { + fetchData(urlsData); + } + }, [urlsData, prevUrlData, fetchData]); -export default useMountMultiFetch; \ No newline at end of file + return [state, () => fetchData(urlsData)]; +}; + +export default useMountMultiFetch; diff --git a/ui/src/hooks/useMultiFetch.js b/ui/src/hooks/useMultiFetch.js index 30f46997bb..c303398756 100644 --- a/ui/src/hooks/useMultiFetch.js +++ b/ui/src/hooks/useMultiFetch.js @@ -1,92 +1,106 @@ -import { useReducer, useEffect, useRef } from 'react'; -import { useNotificationDispatch, showNotification } from 'context/NotificationProvider'; -import { NOTIFICATION_TYPES } from 'components/Notification'; -import { formatFetchUrl, formatFetchOptions, FETCH_METHODS } from './useFetch'; +import { useReducer, useEffect, useRef } from "react"; +import { + useNotificationDispatch, + showNotification, +} from "context/NotificationProvider"; +import { NOTIFICATION_TYPES } from "components/Notification"; +import { formatFetchUrl, formatFetchOptions, FETCH_METHODS } from "./useFetch"; const FETCH_ACTIONS = { - LOADING_DATA: "LOADING_DATA", - LOAD_DATA_SUCCESS: "LOAD_DATA_SUCCESS", - LOAD_DATA_ERROR: "LOAD_DATA_ERROR" -} + LOADING_DATA: "LOADING_DATA", + LOAD_DATA_SUCCESS: "LOAD_DATA_SUCCESS", + LOAD_DATA_ERROR: "LOAD_DATA_ERROR", +}; function reducer(state, action) { - switch (action.type) { - case FETCH_ACTIONS.LOADING_DATA: - return {...state, loading: true, error: null}; - case FETCH_ACTIONS.LOAD_DATA_SUCCESS: - return {...state, loading: false, data: action.payload}; - case FETCH_ACTIONS.LOAD_DATA_ERROR: - return {...state, loading: false, error: action.payload}; - default: - return {...state}; - } + switch (action.type) { + case FETCH_ACTIONS.LOADING_DATA: + return { ...state, loading: true, error: null }; + case FETCH_ACTIONS.LOAD_DATA_SUCCESS: + return { ...state, loading: false, data: action.payload }; + case FETCH_ACTIONS.LOAD_DATA_ERROR: + return { ...state, loading: false, error: action.payload }; + default: + return { ...state }; + } } function useMultiFetch(options) { - const notificationDispatch = useNotificationDispatch(); - - const {initialLoading=false} = options || {}; - const [state, dispatch] = useReducer(reducer, { - loading: initialLoading, - error: null, - data: null - }); - - const mounted = useRef(true); - - useEffect(() => { - return function cleanup() { - mounted.current = false; - }; - }, []); - - const fetchData = async (urlsData) => { - dispatch({type: FETCH_ACTIONS.LOADING_DATA}); - - try { - const response = await Promise.all( - urlsData.map(urlData => { - const {url, queryParams, data, method=FETCH_METHODS.GET, formatUrl} = urlData; - - const options = formatFetchOptions({ - method: method.toUpperCase(), - stringifiedSubmitData: JSON.stringify(data) - }); - - return fetch(formatFetchUrl({url, queryParams, formatUrl}), options) - .then(response => { - if (!response.ok) { - throw Error(response.statusText); - } - - return response; - }) - .then(response => response.json()) - }) - ); - - const data = response.map((item, index) => ({key: urlsData[index].key, data: item})).reduce((accumulator, curr) => { - accumulator[curr.key] = curr.data; - return accumulator - }, {}); - - if (!mounted.current) { - return; - } - - dispatch({type: FETCH_ACTIONS.LOAD_DATA_SUCCESS, payload: data}); - } catch (error) { - if (!mounted.current) { - return; - } - - showNotification(notificationDispatch, {message: "An error occurred when trying to fetch data", type: NOTIFICATION_TYPES.ERROR}); - - dispatch({type: FETCH_ACTIONS.LOAD_DATA_ERROR, payload: error}); - } + const notificationDispatch = useNotificationDispatch(); + + const { initialLoading = false } = options || {}; + const [state, dispatch] = useReducer(reducer, { + loading: initialLoading, + error: null, + data: null, + }); + + const mounted = useRef(true); + + useEffect(() => { + return function cleanup() { + mounted.current = false; }; + }, []); + + const fetchData = async (urlsData) => { + dispatch({ type: FETCH_ACTIONS.LOADING_DATA }); + + try { + const response = await Promise.all( + urlsData.map((urlData) => { + const { + url, + queryParams, + data, + method = FETCH_METHODS.GET, + formatUrl, + } = urlData; + + const options = formatFetchOptions({ + method: method.toUpperCase(), + stringifiedSubmitData: JSON.stringify(data), + }); + + return fetch(formatFetchUrl({ url, queryParams, formatUrl }), options) + .then((response) => { + if (!response.ok) { + throw Error(response.statusText); + } + + return response; + }) + .then((response) => response.json()); + }), + ); + + const data = response + .map((item, index) => ({ key: urlsData[index].key, data: item })) + .reduce((accumulator, curr) => { + accumulator[curr.key] = curr.data; + return accumulator; + }, {}); + + if (!mounted.current) { + return; + } + + dispatch({ type: FETCH_ACTIONS.LOAD_DATA_SUCCESS, payload: data }); + } catch (error) { + if (!mounted.current) { + return; + } + + showNotification(notificationDispatch, { + message: "An error occurred when trying to fetch data", + type: NOTIFICATION_TYPES.ERROR, + }); + + dispatch({ type: FETCH_ACTIONS.LOAD_DATA_ERROR, payload: error }); + } + }; - return [state, fetchData]; + return [state, fetchData]; } -export default useMultiFetch; \ No newline at end of file +export default useMultiFetch; diff --git a/ui/src/hooks/usePrevious.js b/ui/src/hooks/usePrevious.js index 137843e4cb..d1cd0bec20 100644 --- a/ui/src/hooks/usePrevious.js +++ b/ui/src/hooks/usePrevious.js @@ -1,13 +1,13 @@ -import { useRef, useEffect } from 'react'; +import { useRef, useEffect } from "react"; function usePrevious(value) { - const ref = useRef(); + const ref = useRef(); - useEffect(() => { - ref.current = value; - }); + useEffect(() => { + ref.current = value; + }); - return ref.current; + return ref.current; } -export default usePrevious; \ No newline at end of file +export default usePrevious; diff --git a/ui/src/index.tsx b/ui/src/index.tsx index 0dca02897b..746c9b8660 100644 --- a/ui/src/index.tsx +++ b/ui/src/index.tsx @@ -1,12 +1,12 @@ -import React from 'react'; -import { createRoot } from 'react-dom/client'; -import App from 'layout/App'; +import React from "react"; +import { createRoot } from "react-dom/client"; +import App from "layout/App"; -import 'utils/fonts/fonts.scss'; +import "utils/fonts/fonts.scss"; -const container = document.getElementById('root'); +const container = document.getElementById("root"); if (container === null) { - throw new Error('Root element not found'); + throw new Error("Root element not found"); } const root = createRoot(container); diff --git a/ui/src/layout/App/app.scss b/ui/src/layout/App/app.scss index 8a1b1a4bf5..5489609138 100644 --- a/ui/src/layout/App/app.scss +++ b/ui/src/layout/App/app.scss @@ -1,109 +1,108 @@ -@import 'utils/scss_variables.module.scss'; +@import "utils/scss_variables.module.scss"; $main-height: calc(100vh - #{$top-bar-height}); html body { - margin: 0; - font-family: CiscoSansTT, sans-serif; - color: $color-grey-black; - box-sizing: border-box; + margin: 0; + font-family: CiscoSansTT, sans-serif; + color: $color-grey-black; + box-sizing: border-box; - #root { - min-width: 1070px; + #root { + min-width: 1070px; - .app-wrapper { - position: relative; + .app-wrapper { + position: relative; - #main-wrapper { - .topbar-container { - position: fixed; - top: 0; - right: 0; - left: 0; - background-color: $color-main; - z-index: 1; - height: $top-bar-height; - display: flex; - align-items: center; - - img { - margin: 4px 15px; - } - .topbar-page-title { - display: flex; - align-items: center; - border-left: 1px solid $color-grey; - padding-left: 20px; - margin-left: 15px; - - .clarity-title { - color: $color-grey; - } - .icon { - color: $color-grey; - margin-left: 10px; - } - } - .topbar-menu-items { - display: flex; - flex-grow: 1; - align-items: center; - justify-content: flex-end; - margin-left: 30px; + #main-wrapper { + .topbar-container { + position: fixed; + top: 0; + right: 0; + left: 0; + background-color: $color-main; + z-index: 1; + height: $top-bar-height; + display: flex; + align-items: center; - .topbar-api-link { - display: flex; - align-items: center; - margin-right: 20px; - color: $color-grey; - text-decoration: none; - } - } - } - .sidebar-container { - background-color: $color-main-dark; - box-shadow: 0px 2px 43px rgba($color-shadow-main, 0.21); - position: fixed; - top: $top-bar-height; - bottom: 0; - left: 0; - width: $side-bar-width; - display: flex; - flex-direction: column; - align-items: center; - padding: 22px 0; - z-index: 2; + img { + margin: 4px 15px; + } + .topbar-page-title { + display: flex; + align-items: center; + border-left: 1px solid $color-grey; + padding-left: 20px; + margin-left: 15px; - .nav-item { - width: $side-bar-width; - height: 42px; - display: flex; - align-items: center; - justify-content: space-around; - margin-bottom: 10px; - cursor: pointer; + .clarity-title { + color: $color-grey; + } + .icon { + color: $color-grey; + margin-left: 10px; + } + } + .topbar-menu-items { + display: flex; + flex-grow: 1; + align-items: center; + justify-content: flex-end; + margin-left: 30px; - &:hover, - &.active { - background-color: $color-main; - } - .icon { - color: white; - } - } - } - [role="main"] { - margin-top: $top-bar-height; - margin-left: $side-bar-width; - background-color: $color-background; - height: $main-height; - max-height: $main-height; - overflow: auto; - width: calc(100% - $side-bar-width); - position: relative; - } + .topbar-api-link { + display: flex; + align-items: center; + margin-right: 20px; + color: $color-grey; + text-decoration: none; } + } } + .sidebar-container { + background-color: $color-main-dark; + box-shadow: 0px 2px 43px rgba($color-shadow-main, 0.21); + position: fixed; + top: $top-bar-height; + bottom: 0; + left: 0; + width: $side-bar-width; + display: flex; + flex-direction: column; + align-items: center; + padding: 22px 0; + z-index: 2; + + .nav-item { + width: $side-bar-width; + height: 42px; + display: flex; + align-items: center; + justify-content: space-around; + margin-bottom: 10px; + cursor: pointer; + + &:hover, + &.active { + background-color: $color-main; + } + .icon { + color: white; + } + } + } + [role="main"] { + margin-top: $top-bar-height; + margin-left: $side-bar-width; + background-color: $color-background; + height: $main-height; + max-height: $main-height; + overflow: auto; + width: calc(100% - $side-bar-width); + position: relative; + } + } } + } } - diff --git a/ui/src/layout/App/index.jsx b/ui/src/layout/App/index.jsx index 8ca37c0369..4c3f93bebc 100644 --- a/ui/src/layout/App/index.jsx +++ b/ui/src/layout/App/index.jsx @@ -1,170 +1,224 @@ -import React, { useState } from 'react'; -import { Route, Routes, BrowserRouter, Outlet, useNavigate, useMatch, useLocation} from 'react-router-dom'; -import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; -import classnames from 'classnames'; -import Icon, { ICON_NAMES } from 'components/Icon'; -import IconTemplates from 'components/Icon/IconTemplates'; -import Notification from 'components/Notification'; -import { TooltipWrapper } from 'components/Tooltip'; -import Title from 'components/Title'; -import { NotificationProvider, useNotificationState, useNotificationDispatch, removeNotification } from 'context/NotificationProvider'; -import { FiltersProvider, useFilterDispatch, resetFilters, resetAllFilters, FILTER_TYPES } from 'context/FiltersProvider'; -import { ROUTES } from 'utils/systemConsts'; -import Dashboard from 'layout/Dashboard'; -import Scans from 'layout/Scans'; -import Findings from 'layout/Findings'; -import Assets from 'layout/Assets'; -import AssetScans from 'layout/AssetScans'; - -import brandImage from 'utils/images/brand.svg'; - -import './app.scss'; +import React, { useState } from "react"; +import { + Route, + Routes, + BrowserRouter, + Outlet, + useNavigate, + useMatch, + useLocation, +} from "react-router-dom"; +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; +import classnames from "classnames"; +import Icon, { ICON_NAMES } from "components/Icon"; +import IconTemplates from "components/Icon/IconTemplates"; +import Notification from "components/Notification"; +import { TooltipWrapper } from "components/Tooltip"; +import Title from "components/Title"; +import { + NotificationProvider, + useNotificationState, + useNotificationDispatch, + removeNotification, +} from "context/NotificationProvider"; +import { + FiltersProvider, + useFilterDispatch, + resetFilters, + resetAllFilters, + FILTER_TYPES, +} from "context/FiltersProvider"; +import { ROUTES } from "utils/systemConsts"; +import Dashboard from "layout/Dashboard"; +import Scans from "layout/Scans"; +import Findings from "layout/Findings"; +import Assets from "layout/Assets"; +import AssetScans from "layout/AssetScans"; + +import brandImage from "utils/images/brand.svg"; + +import "./app.scss"; const queryClient = new QueryClient(); const ROUTES_CONFIG = [ - { - path: ROUTES.DEFAULT, - component: Dashboard, - icon: ICON_NAMES.DASHBOARD, - isIndex: true, - title: "Dashboard", - resetFilterAll: true - }, - { - path: ROUTES.ASSETS, - component: Assets, - icon: ICON_NAMES.ASSETS, - title: "Assets", - resetFilters: [FILTER_TYPES.ASSETS] - }, - { - path: ROUTES.ASSET_SCANS, - component: AssetScans, - icon: ICON_NAMES.ASSET_SCANS, - title: "Asset scans", - resetFilters: [FILTER_TYPES.ASSET_SCANS] - }, - { - path: ROUTES.FINDINGS, - component: Findings, - icon: ICON_NAMES.FINDINGS, - title: "Findings", - resetFilters: [ - FILTER_TYPES.FINDINGS_GENERAL, - FILTER_TYPES.FINDINGS_VULNERABILITIES, - FILTER_TYPES.FINDINGS_EXPLOITS, - FILTER_TYPES.FINDINGS_MISCONFIGURATIONS, - FILTER_TYPES.FINDINGS_SECRETS, - FILTER_TYPES.FINDINGS_MALWARE, - FILTER_TYPES.FINDINGS_ROOTKITS, - FILTER_TYPES.FINDINGS_PACKAGES - ] - }, - { - path: ROUTES.SCANS, - component: Scans, - icon: ICON_NAMES.SCANS, - title: "Scans", - resetFilters: [FILTER_TYPES.SCANS, FILTER_TYPES.SCAN_CONFIGURATIONS] - } + { + path: ROUTES.DEFAULT, + component: Dashboard, + icon: ICON_NAMES.DASHBOARD, + isIndex: true, + title: "Dashboard", + resetFilterAll: true, + }, + { + path: ROUTES.ASSETS, + component: Assets, + icon: ICON_NAMES.ASSETS, + title: "Assets", + resetFilters: [FILTER_TYPES.ASSETS], + }, + { + path: ROUTES.ASSET_SCANS, + component: AssetScans, + icon: ICON_NAMES.ASSET_SCANS, + title: "Asset scans", + resetFilters: [FILTER_TYPES.ASSET_SCANS], + }, + { + path: ROUTES.FINDINGS, + component: Findings, + icon: ICON_NAMES.FINDINGS, + title: "Findings", + resetFilters: [ + FILTER_TYPES.FINDINGS_GENERAL, + FILTER_TYPES.FINDINGS_VULNERABILITIES, + FILTER_TYPES.FINDINGS_EXPLOITS, + FILTER_TYPES.FINDINGS_MISCONFIGURATIONS, + FILTER_TYPES.FINDINGS_SECRETS, + FILTER_TYPES.FINDINGS_MALWARE, + FILTER_TYPES.FINDINGS_ROOTKITS, + FILTER_TYPES.FINDINGS_PACKAGES, + ], + }, + { + path: ROUTES.SCANS, + component: Scans, + icon: ICON_NAMES.SCANS, + title: "Scans", + resetFilters: [FILTER_TYPES.SCANS, FILTER_TYPES.SCAN_CONFIGURATIONS], + }, ]; const ConnectedNotification = () => { - const {message, type} = useNotificationState(); - const dispatch = useNotificationDispatch() - - if (!message) { - return null; - } + const { message, type } = useNotificationState(); + const dispatch = useNotificationDispatch(); - return removeNotification(dispatch)} /> -} + if (!message) { + return null; + } -const NavLinkItem = ({pathname, icon, resetFilterNames, resetFilterAll=false}) => { - const location = useLocation(); - const match = useMatch(`${pathname}/*`); - const isActive = pathname === location.pathname ? true : !!match; + return ( + removeNotification(dispatch)} + /> + ); +}; - const navigate = useNavigate(); - const filtersDispatch = useFilterDispatch(); +const NavLinkItem = ({ + pathname, + icon, + resetFilterNames, + resetFilterAll = false, +}) => { + const location = useLocation(); + const match = useMatch(`${pathname}/*`); + const isActive = pathname === location.pathname ? true : !!match; - const onClick = () => { - if (resetFilterAll) { - resetAllFilters(filtersDispatch); - } else if (!!resetFilterNames) { - resetFilters(filtersDispatch, resetFilterNames); - } + const navigate = useNavigate(); + const filtersDispatch = useFilterDispatch(); - navigate(pathname); + const onClick = () => { + if (resetFilterAll) { + resetAllFilters(filtersDispatch); + } else if (!!resetFilterNames) { + resetFilters(filtersDispatch, resetFilterNames); } - - return ( -
- -
- ) -} + + navigate(pathname); + }; + + return ( +
+ +
+ ); +}; const Layout = () => { - const {pathname} = useLocation(); - const mainPath = pathname.split("/").find(item => !!item); - const pageTitle = ROUTES_CONFIG.find(({path, isIndex}) => (isIndex && !mainPath) || path === `/${mainPath}`)?.title; - - const [refreshTimestamp, setRefreshTimestamp] = useState(Date.now()); - - return ( -
- -
- VMClarity - {!!pageTitle && -
- {pageTitle} - setRefreshTimestamp(Date.now())} /> -
- } -
- API Docs -
-
-
- { - ROUTES_CONFIG.map(({path, icon, title, resetFilters, resetFilterAll}) => ( - - - - )) - } -
-
- - - - -
-
+ const { pathname } = useLocation(); + const mainPath = pathname.split("/").find((item) => !!item); + const pageTitle = ROUTES_CONFIG.find( + ({ path, isIndex }) => (isIndex && !mainPath) || path === `/${mainPath}`, + )?.title; + + const [refreshTimestamp, setRefreshTimestamp] = useState(Date.now()); + + return ( +
+ +
+ VMClarity + {!!pageTitle && ( +
+ + {pageTitle} + + setRefreshTimestamp(Date.now())} + /> +
+ )} +
- ) -} +
+ {ROUTES_CONFIG.map( + ({ path, icon, title, resetFilters, resetFilterAll }) => ( + + + + ), + )} +
+
+ + + + +
+
+
+ ); +}; const App = () => ( -
- - - - }> - { - ROUTES_CONFIG.map(({path, component: Component, isIndex}) => ( - )} /> - )) - } - - - - - -
-) +
+ + + + }> + {ROUTES_CONFIG.map(({ path, component: Component, isIndex }) => ( + } + /> + ))} + + + + + +
+); export default App; diff --git a/ui/src/layout/AssetScans/AssetScanDetails.jsx b/ui/src/layout/AssetScans/AssetScanDetails.jsx index 4167d025a9..946f1b2aba 100644 --- a/ui/src/layout/AssetScans/AssetScanDetails.jsx +++ b/ui/src/layout/AssetScans/AssetScanDetails.jsx @@ -1,67 +1,67 @@ -import React from 'react'; -import { useLocation } from 'react-router-dom'; -import DetailsPageWrapper from 'components/DetailsPageWrapper'; -import TabbedPage from 'components/TabbedPage'; -import { APIS } from 'utils/systemConsts'; -import { formatDate, getScanName } from 'utils/utils'; -import { Findings } from 'layout/detail-displays'; -import TabAssetScanDetails from './TabAssetScanDetails'; +import React from "react"; +import { useLocation } from "react-router-dom"; +import DetailsPageWrapper from "components/DetailsPageWrapper"; +import TabbedPage from "components/TabbedPage"; +import { APIS } from "utils/systemConsts"; +import { formatDate, getScanName } from "utils/utils"; +import { Findings } from "layout/detail-displays"; +import TabAssetScanDetails from "./TabAssetScanDetails"; const ASSET_SCAN_DETAILS_PATHS = { - ASSET_SCAN_DETAILS: "", - FINDINGHS: "findings" -} + ASSET_SCAN_DETAILS: "", + FINDINGHS: "findings", +}; -const DetailsContent = ({data}) => { - const {pathname} = useLocation(); - - const {id, summary} = data; - - return ( - - }, - { - id: "findings", - title: "Findings", - path: ASSET_SCAN_DETAILS_PATHS.FINDINGHS, - component: () => ( - - ) - } - ]} - withInnerPadding={false} - /> - ) -} +const DetailsContent = ({ data }) => { + const { pathname } = useLocation(); -const AssetScanDetails = () => ( - { - const {startTime, name} = scan || {}; + const { id, summary } = data; - return ({ - title: asset?.assetInfo?.instanceID, - subTitle: `scanned by '${name}' on ${formatDate(startTime)}` - }) - }} - detailsContent={props => } - withPadding + return ( + , + }, + { + id: "findings", + title: "Findings", + path: ASSET_SCAN_DETAILS_PATHS.FINDINGHS, + component: () => ( + + ), + }, + ]} + withInnerPadding={false} /> -) + ); +}; + +const AssetScanDetails = () => ( + { + const { startTime, name } = scan || {}; + + return { + title: asset?.assetInfo?.instanceID, + subTitle: `scanned by '${name}' on ${formatDate(startTime)}`, + }; + }} + detailsContent={(props) => } + withPadding + /> +); export default AssetScanDetails; diff --git a/ui/src/layout/AssetScans/AssetScansTable.jsx b/ui/src/layout/AssetScans/AssetScansTable.jsx index e74f1d5e4b..521540e272 100644 --- a/ui/src/layout/AssetScans/AssetScansTable.jsx +++ b/ui/src/layout/AssetScans/AssetScansTable.jsx @@ -1,98 +1,122 @@ -import React, { useMemo } from 'react'; -import TablePage from 'components/TablePage'; -import { OPERATORS } from 'components/Filter'; -import { APIS } from 'utils/systemConsts'; -import { getFindingsColumnsConfigList, getVulnerabilitiesColumnConfigItem, formatDate, getAssetColumnsFiltersConfig, - findingsColumnsFiltersConfig, vulnerabilitiesCountersColumnsFiltersConfig, scanColumnsFiltersConfig, getAssetName } from 'utils/utils'; -import { FILTER_TYPES } from 'context/FiltersProvider'; -import StatusIndicator, { STATUS_MAPPING } from './StatusIndicator'; +import React, { useMemo } from "react"; +import TablePage from "components/TablePage"; +import { OPERATORS } from "components/Filter"; +import { APIS } from "utils/systemConsts"; +import { + getFindingsColumnsConfigList, + getVulnerabilitiesColumnConfigItem, + formatDate, + getAssetColumnsFiltersConfig, + findingsColumnsFiltersConfig, + vulnerabilitiesCountersColumnsFiltersConfig, + scanColumnsFiltersConfig, + getAssetName, +} from "utils/utils"; +import { FILTER_TYPES } from "context/FiltersProvider"; +import StatusIndicator, { STATUS_MAPPING } from "./StatusIndicator"; const TABLE_TITLE = "asset scans"; const NAME_SORT_IDS = [ - "asset.assetInfo.instanceID", - "asset.assetInfo.podName", - "asset.assetInfo.dirName", - "asset.assetInfo.imageID", - "asset.assetInfo.containerName" + "asset.assetInfo.instanceID", + "asset.assetInfo.podName", + "asset.assetInfo.dirName", + "asset.assetInfo.imageID", + "asset.assetInfo.containerName", ]; const SCAN_START_TIME_SORT_IDS = ["scan.startTime"]; -const FILTER_SCAN_STATUSES = Object.keys(STATUS_MAPPING).map(statusKey => ( - {value: statusKey, label: STATUS_MAPPING[statusKey]?.title} -)) +const FILTER_SCAN_STATUSES = Object.keys(STATUS_MAPPING).map((statusKey) => ({ + value: statusKey, + label: STATUS_MAPPING[statusKey]?.title, +})); const AssetScansTable = () => { - const columns = useMemo(() => [ - { - Header: "Asset name", - id: "name", - sortIds: NAME_SORT_IDS, - accessor: (assetScan) => getAssetName(assetScan.asset.assetInfo), - }, - { - Header: "Asset type", - id: "type", - sortIds: ["asset.assetInfo.objectType"], - accessor: "asset.assetInfo.objectType" - }, - { - Header: "Asset location", - id: "location", - sortIds: ["asset.assetInfo.location"], - accessor: (assetScan) => assetScan.asset.assetInfo.location || assetScan.asset.assetInfo.repoDigests?.[0], - }, - { - Header: "Scan name", - id: "scanName", - sortIds: ["scan.name"], - accessor: "scan.name" - }, - { - Header: "Scan start", - id: "startTime", - sortIds: SCAN_START_TIME_SORT_IDS, - accessor: original => formatDate(original.scan?.startTime) + const columns = useMemo( + () => [ + { + Header: "Asset name", + id: "name", + sortIds: NAME_SORT_IDS, + accessor: (assetScan) => getAssetName(assetScan.asset.assetInfo), + }, + { + Header: "Asset type", + id: "type", + sortIds: ["asset.assetInfo.objectType"], + accessor: "asset.assetInfo.objectType", + }, + { + Header: "Asset location", + id: "location", + sortIds: ["asset.assetInfo.location"], + accessor: (assetScan) => + assetScan.asset.assetInfo.location || + assetScan.asset.assetInfo.repoDigests?.[0], + }, + { + Header: "Scan name", + id: "scanName", + sortIds: ["scan.name"], + accessor: "scan.name", + }, + { + Header: "Scan start", + id: "startTime", + sortIds: SCAN_START_TIME_SORT_IDS, + accessor: (original) => formatDate(original.scan?.startTime), + }, + { + Header: "Scan status", + id: "status", + sortIds: ["status.state", "status.message"], + accessor: (original) => { + const { state, message } = original?.status || {}; + + return ( + + ); }, + }, + getVulnerabilitiesColumnConfigItem({ tableTile: TABLE_TITLE }), + ...getFindingsColumnsConfigList({ tableTile: TABLE_TITLE }), + ], + [], + ); + + return ( + { - const {state, message} = original?.status || {}; - - return ; - } + value: "status.state", + label: "Scan status", + operators: [ + { ...OPERATORS.eq, valueItems: FILTER_SCAN_STATUSES }, + { ...OPERATORS.ne, valueItems: FILTER_SCAN_STATUSES }, + ], }, - getVulnerabilitiesColumnConfigItem({tableTile: TABLE_TITLE}), - ...getFindingsColumnsConfigList({tableTile: TABLE_TITLE}) - ], []); - - return ( - - ) -} + ...vulnerabilitiesCountersColumnsFiltersConfig, + ...findingsColumnsFiltersConfig, + ]} + withMargin + /> + ); +}; export default AssetScansTable; diff --git a/ui/src/layout/AssetScans/StatusIndicator/index.jsx b/ui/src/layout/AssetScans/StatusIndicator/index.jsx index c1d0fd9143..63d4033312 100644 --- a/ui/src/layout/AssetScans/StatusIndicator/index.jsx +++ b/ui/src/layout/AssetScans/StatusIndicator/index.jsx @@ -1,46 +1,51 @@ -import React from 'react'; -import { isEmpty } from 'lodash'; -import { TooltipWrapper } from 'components/Tooltip'; +import React from "react"; +import { isEmpty } from "lodash"; +import { TooltipWrapper } from "components/Tooltip"; -import COLORS from 'utils/scss_variables.module.scss'; +import COLORS from "utils/scss_variables.module.scss"; -import './status-indicator.scss'; +import "./status-indicator.scss"; export const STATUS_MAPPING = { - Pending: {title: "Pending", color: COLORS["color-main"]}, - InProgress: {title: "In Progress", color: COLORS["color-main"]}, - Skipped: {title: "Skipped", color: COLORS["color-grey"]}, - Failed: {title: "Failed", color: COLORS["color-error"]}, - Done: {title: "Done", color: COLORS["color-success"]}, - - NotScanned: {title: "Not Scanned", color: COLORS["color-grey"]}, - Scheduled: {title: "Scheduled", color: COLORS["color-main"]}, - ReadyToScan: {title: "Ready To Scan", color: COLORS["color-main"]}, - Aborted: {title: "Aborted", color: COLORS["color-grey"]} -} - -const StatusIndicator = ({state, isError=false}) => { - const {title, color} = STATUS_MAPPING[state] || {}; - - return ( -
-
-
{title}
-
- ) -} - -const StatusIndicatorWrapper = ({state, errors, tooltipId}) => ( -
- { - (!isEmpty(errors) && !isEmpty(tooltipId)) ? ( - - - - ) : - } + Pending: { title: "Pending", color: COLORS["color-main"] }, + InProgress: { title: "In Progress", color: COLORS["color-main"] }, + Skipped: { title: "Skipped", color: COLORS["color-grey"] }, + Failed: { title: "Failed", color: COLORS["color-error"] }, + Done: { title: "Done", color: COLORS["color-success"] }, + + NotScanned: { title: "Not Scanned", color: COLORS["color-grey"] }, + Scheduled: { title: "Scheduled", color: COLORS["color-main"] }, + ReadyToScan: { title: "Ready To Scan", color: COLORS["color-main"] }, + Aborted: { title: "Aborted", color: COLORS["color-grey"] }, +}; + +const StatusIndicator = ({ state, isError = false }) => { + const { title, color } = STATUS_MAPPING[state] || {}; + + return ( +
+
+
{title}
- + ); +}; + +const StatusIndicatorWrapper = ({ state, errors, tooltipId }) => ( +
+ {!isEmpty(errors) && !isEmpty(tooltipId) ? ( + + + + ) : ( + + )} +
); export default StatusIndicatorWrapper; diff --git a/ui/src/layout/AssetScans/StatusIndicator/status-indicator.scss b/ui/src/layout/AssetScans/StatusIndicator/status-indicator.scss index d8357f7475..65616f5d32 100644 --- a/ui/src/layout/AssetScans/StatusIndicator/status-indicator.scss +++ b/ui/src/layout/AssetScans/StatusIndicator/status-indicator.scss @@ -1,18 +1,18 @@ -@import 'utils/scss_variables.module.scss'; +@import "utils/scss_variables.module.scss"; $indicator-size: 6px; .status-indicator-wrapper { - display: flex; - align-items: center; - gap: 5px; - font-size: 14px; - line-height: 18px; - color: $color-grey-black; + display: flex; + align-items: center; + gap: 5px; + font-size: 14px; + line-height: 18px; + color: $color-grey-black; - .status-indicator { - width: $indicator-size; - height: $indicator-size; - border-radius: 50px; - } -} \ No newline at end of file + .status-indicator { + width: $indicator-size; + height: $indicator-size; + border-radius: 50px; + } +} diff --git a/ui/src/layout/AssetScans/TabAssetScanDetails.jsx b/ui/src/layout/AssetScans/TabAssetScanDetails.jsx index 0adc276b22..75ddc99750 100644 --- a/ui/src/layout/AssetScans/TabAssetScanDetails.jsx +++ b/ui/src/layout/AssetScans/TabAssetScanDetails.jsx @@ -1,143 +1,196 @@ -import React, { useMemo } from 'react'; -import { useNavigate } from 'react-router-dom'; -import { isEmpty } from 'lodash'; -import TitleValueDisplay, { TitleValueDisplayRow } from 'components/TitleValueDisplay'; -import DoublePaneDisplay from 'components/DoublePaneDisplay'; -import Title from 'components/Title'; -import ErrorMessageDisplay from 'components/ErrorMessageDisplay'; -import { WrappingTextBoxWithEllipsis } from 'components/WrappingTextBoxWithEllipsis'; -import { ROUTES, FINDINGS_MAPPING, VULNERABIITY_FINDINGS_ITEM } from 'utils/systemConsts'; -import { formatDate, calculateDuration } from 'utils/utils'; -import { SCANS_PATHS } from 'layout/Scans'; -import StatusIndicator from './StatusIndicator'; - -import COLORS from 'utils/scss_variables.module.scss'; +import React, { useMemo } from "react"; +import { useNavigate } from "react-router-dom"; +import { isEmpty } from "lodash"; +import TitleValueDisplay, { + TitleValueDisplayRow, +} from "components/TitleValueDisplay"; +import DoublePaneDisplay from "components/DoublePaneDisplay"; +import Title from "components/Title"; +import ErrorMessageDisplay from "components/ErrorMessageDisplay"; +import { WrappingTextBoxWithEllipsis } from "components/WrappingTextBoxWithEllipsis"; +import { + ROUTES, + FINDINGS_MAPPING, + VULNERABIITY_FINDINGS_ITEM, +} from "utils/systemConsts"; +import { formatDate, calculateDuration } from "utils/utils"; +import { SCANS_PATHS } from "layout/Scans"; +import StatusIndicator from "./StatusIndicator"; + +import COLORS from "utils/scss_variables.module.scss"; const BORDER_COLOR = COLORS["color-grey-lighter"]; const STATUS_DISPLAY_ITEMS = [ - {dataKey: "sbom", title: "SBOM"}, - VULNERABIITY_FINDINGS_ITEM, - ...Object.values(FINDINGS_MAPPING).filter(({value}) => value !== FINDINGS_MAPPING.PACKAGES.value) -] - -const StatusDisplay = ({state, errors}) => ( - <> -
- -
- {!isEmpty(errors) && - - {errors.map((error, index) =>
{error}
)} -
- } - -) - -const TimeDataDisplayRow = ({startTime, endTime}) => ( - - {formatDate(startTime)} - {formatDate(endTime)} - {calculateDuration(startTime, endTime)} - -) - -const RootFsDisplay = ({ path, size, type }) => - - {path} - {size} - {type} - - -const CsvDisplay = ({path: input, type}) => - - - - {input} - - - {(input || '').split(',').length.toString()} - {type} - - -const StatsDisplay = ({scanTime, ...props}) => { - const {startTime, endTime} = scanTime || {}; - - const statsByType = useMemo(() => { - switch (props.type) { - case 'rootfs': - return - case 'csv': - return - default: - return - } - }, [props]); - - return ( -
- {statsByType} + { dataKey: "sbom", title: "SBOM" }, + VULNERABIITY_FINDINGS_ITEM, + ...Object.values(FINDINGS_MAPPING).filter( + ({ value }) => value !== FINDINGS_MAPPING.PACKAGES.value, + ), +]; + +const StatusDisplay = ({ state, errors }) => ( + <> +
+ +
+ {!isEmpty(errors) && ( + + {errors.map((error, index) => ( +
{error}
+ ))} +
+ )} + +); + +const TimeDataDisplayRow = ({ startTime, endTime }) => ( + + + {formatDate(startTime)} + + {formatDate(endTime)} + + {calculateDuration(startTime, endTime)} + + +); + +const RootFsDisplay = ({ path, size, type }) => ( + + {path} + {size} + {type} + +); + +const CsvDisplay = ({ path: input, type }) => ( + + + + {input} + + + + {(input || "").split(",").length.toString()} + + {type} + +); + +const StatsDisplay = ({ scanTime, ...props }) => { + const { startTime, endTime } = scanTime || {}; + + const statsByType = useMemo(() => { + switch (props.type) { + case "rootfs": + return ; + case "csv": + return ; + default: + return ; + } + }, [props]); + + return ( +
+ {statsByType} + +
+ ); +}; + +const TabAssetScanDetails = ({ data }) => { + const navigate = useNavigate(); + + const { scan, asset, status, stats } = data || {}; + const { id: assetId, assetInfo } = asset || {}; + const { instanceID, objectType, location } = assetInfo || {}; + const { id: scanId, startTime, endTime } = scan || {}; + const { state, message } = status || {}; + + const ITEM_MARGIN = "46px"; + + return ( + ( + <> + navigate(`${ROUTES.ASSETS}/${assetId}`)}> + Asset + + + {instanceID} + {objectType} + {location} + + + navigate(`${ROUTES.SCANS}/${SCANS_PATHS.SCANS}/${scanId}`) + } + > + Scan + + + + )} + rightPlaneDisplay={() => ( + <> + Asset scan details + + -
- ) -} - -const TabAssetScanDetails = ({data}) => { - const navigate = useNavigate(); - - const {scan, asset, status, stats} = data || {}; - const {id: assetId, assetInfo} = asset || {}; - const {instanceID, objectType, location} = assetInfo || {}; - const {id: scanId, startTime, endTime} = scan || {}; - const {state, message} = status || {}; - - const ITEM_MARGIN = "46px"; - - return ( - ( - <> - navigate(`${ROUTES.ASSETS}/${assetId}`)}>Asset - - {instanceID} - {objectType} - {location} - - navigate(`${ROUTES.SCANS}/${SCANS_PATHS.SCANS}/${scanId}`)}>Scan - - - )} - rightPlaneDisplay={() => ( - <> - Asset scan details - - - - -
- { - STATUS_DISPLAY_ITEMS.map(({dataKey, title}, index) => { - const typeStats = (stats || {})[dataKey] || []; - const {status: { state, message }} = (data || {})[dataKey] || {status: {}}; - - return ( -
- -
- {typeStats?.map((typeStats, index) => )} -
- {index + 1 < STATUS_DISPLAY_ITEMS.length && -
- } -
- ) - }) - } - - )} - /> - ) -} + +
+ {STATUS_DISPLAY_ITEMS.map(({ dataKey, title }, index) => { + const typeStats = (stats || {})[dataKey] || []; + const { + status: { state, message }, + } = (data || {})[dataKey] || { status: {} }; + + return ( +
+ +
+ +
+ {typeStats?.map((typeStats, index) => ( + + ))} +
+ {index + 1 < STATUS_DISPLAY_ITEMS.length && ( +
+ )} +
+ ); + })} + + )} + /> + ); +}; export default TabAssetScanDetails; diff --git a/ui/src/layout/AssetScans/index.jsx b/ui/src/layout/AssetScans/index.jsx index 5e7a2ff978..039cc18ec4 100644 --- a/ui/src/layout/AssetScans/index.jsx +++ b/ui/src/layout/AssetScans/index.jsx @@ -1,11 +1,13 @@ -import React from 'react'; -import ListAndDetailsRouter from 'components/ListAndDetailsRouter'; -import AssetScanDetails from './AssetScanDetails'; -import AssetScansTable from './AssetScansTable'; +import React from "react"; +import ListAndDetailsRouter from "components/ListAndDetailsRouter"; +import AssetScanDetails from "./AssetScanDetails"; +import AssetScansTable from "./AssetScansTable"; const AssetScans = () => ( - -) + +); - -export default AssetScans; \ No newline at end of file +export default AssetScans; diff --git a/ui/src/layout/Assets/AssetDetails.jsx b/ui/src/layout/Assets/AssetDetails.jsx index 2957508e1c..4ef0ead706 100644 --- a/ui/src/layout/Assets/AssetDetails.jsx +++ b/ui/src/layout/Assets/AssetDetails.jsx @@ -1,58 +1,63 @@ -import React from 'react'; -import { useLocation } from 'react-router-dom'; -import DetailsPageWrapper from 'components/DetailsPageWrapper'; -import TabbedPage from 'components/TabbedPage'; -import { APIS } from 'utils/systemConsts'; -import { AssetDetails as AssetDetailsTab, Findings } from 'layout/detail-displays'; +import React from "react"; +import { useLocation } from "react-router-dom"; +import DetailsPageWrapper from "components/DetailsPageWrapper"; +import TabbedPage from "components/TabbedPage"; +import { APIS } from "utils/systemConsts"; +import { + AssetDetails as AssetDetailsTab, + Findings, +} from "layout/detail-displays"; const ASSET_DETAILS_PATHS = { - ASSET_DETAILS: "", - FINDINGS: "findings" -} + ASSET_DETAILS: "", + FINDINGS: "findings", +}; -const DetailsContent = ({data}) => { - const {pathname} = useLocation(); - - const {id, assetInfo, summary} = data || {}; - - return ( - - }, - { - id: "findings", - title: "Findings", - path: ASSET_DETAILS_PATHS.FINDINGS, - component: () => ( - - ) - } - ]} - withInnerPadding={false} - /> - ) -} +const DetailsContent = ({ data }) => { + const { pathname } = useLocation(); -const AssetDetails = () => ( - ({title: assetInfo?.instanceID})} - detailsContent={props => } - withPadding + const { id, assetInfo, summary } = data || {}; + + return ( + ( + + ), + }, + { + id: "findings", + title: "Findings", + path: ASSET_DETAILS_PATHS.FINDINGS, + component: () => ( + + ), + }, + ]} + withInnerPadding={false} /> -) + ); +}; + +const AssetDetails = () => ( + ({ title: assetInfo?.instanceID })} + detailsContent={(props) => } + withPadding + /> +); export default AssetDetails; diff --git a/ui/src/layout/Assets/AssetsForFindingTable.jsx b/ui/src/layout/Assets/AssetsForFindingTable.jsx index 74dde6d6a1..f86822670d 100644 --- a/ui/src/layout/Assets/AssetsForFindingTable.jsx +++ b/ui/src/layout/Assets/AssetsForFindingTable.jsx @@ -1,18 +1,35 @@ -import React, { useMemo, useEffect, useCallback } from 'react'; -import { useNavigate } from 'react-router-dom'; -import { isUndefined } from 'lodash'; -import ExpandableList from 'components/ExpandableList'; -import ToggleButton from 'components/ToggleButton'; -import ContentContainer from 'components/ContentContainer'; -import Table from 'components/Table'; -import Loader from 'components/Loader'; -import { getFindingsColumnsConfigList, getVulnerabilitiesColumnConfigItem, formatTagsToStringsList, formatDate, getAssetName} from 'utils/utils'; -import { APIS } from 'utils/systemConsts'; -import { useFilterDispatch, useFilterState, setFilters, FILTER_TYPES } from 'context/FiltersProvider'; +import React, { useMemo, useEffect, useCallback } from "react"; +import { useNavigate } from "react-router-dom"; +import { isUndefined } from "lodash"; +import ExpandableList from "components/ExpandableList"; +import ToggleButton from "components/ToggleButton"; +import ContentContainer from "components/ContentContainer"; +import Table from "components/Table"; +import Loader from "components/Loader"; +import { + getFindingsColumnsConfigList, + getVulnerabilitiesColumnConfigItem, + formatTagsToStringsList, + formatDate, + getAssetName, +} from "utils/utils"; +import { APIS } from "utils/systemConsts"; +import { + useFilterDispatch, + useFilterState, + setFilters, + FILTER_TYPES, +} from "context/FiltersProvider"; const TABLE_TITLE = "assets"; -const NAME_SORT_IDS = ["asset.assetInfo.instanceID", "asset.assetInfo.podName", "asset.assetInfo.dirName", "asset.assetInfo.imageID", "asset.assetInfo.containerName"]; +const NAME_SORT_IDS = [ + "asset.assetInfo.instanceID", + "asset.assetInfo.podName", + "asset.assetInfo.dirName", + "asset.assetInfo.imageID", + "asset.assetInfo.containerName", +]; const LABEL_SORT_IDS = ["asset.assetInfo.tags", "asset.assetInfo.labels"]; const LOCATION_SORT_IDS = ["asset.assetInfo.location"]; @@ -20,137 +37,180 @@ const ASSETS_FILTER_TYPE = FILTER_TYPES.ASSETS; const FINDINGS_FILTER_TYPE = FILTER_TYPES.FINDINGS_GENERAL; const AssetsForFindingTable = (props) => { - const {findingId} = props; + const { findingId } = props; - const navigate = useNavigate(); - const filtersDispatch = useFilterDispatch(); - const filtersState = useFilterState(); + const navigate = useNavigate(); + const filtersDispatch = useFilterDispatch(); + const filtersState = useFilterState(); - const {customFilters: assetCustomFilters} = filtersState[ASSETS_FILTER_TYPE]; - const {hideTerminated} = assetCustomFilters; + const { customFilters: assetCustomFilters } = + filtersState[ASSETS_FILTER_TYPE]; + const { hideTerminated } = assetCustomFilters; - const setHideTerminated = useCallback(hideTerminated => setFilters(filtersDispatch, { + const setHideTerminated = useCallback( + (hideTerminated) => + setFilters(filtersDispatch, { type: ASSETS_FILTER_TYPE, - filters: {hideTerminated}, - isCustom: true - }), [filtersDispatch]); - - useEffect(() => { - if (isUndefined(hideTerminated)) { - setHideTerminated(true); - } - }, [hideTerminated, setHideTerminated]); + filters: { hideTerminated }, + isCustom: true, + }), + [filtersDispatch], + ); + + useEffect(() => { + if (isUndefined(hideTerminated)) { + setHideTerminated(true); + } + }, [hideTerminated, setHideTerminated]); - const {customFilters: findingCustomFilters} = filtersState[FINDINGS_FILTER_TYPE]; - const {showFindingCounts} = findingCustomFilters; + const { customFilters: findingCustomFilters } = + filtersState[FINDINGS_FILTER_TYPE]; + const { showFindingCounts } = findingCustomFilters; - const setShowFindingCounts = useCallback(showFindingCounts => setFilters(filtersDispatch, { + const setShowFindingCounts = useCallback( + (showFindingCounts) => + setFilters(filtersDispatch, { type: FINDINGS_FILTER_TYPE, - filters: {showFindingCounts}, - isCustom: true - }), [filtersDispatch]); - - useEffect(() => { - if (isUndefined(showFindingCounts)) { - setShowFindingCounts(false); - } - }, [showFindingCounts, setShowFindingCounts]); - - const columns = useMemo(() => [ - { - Header: "Name", - id: "instanceID", - sortIds: NAME_SORT_IDS, - accessor: (original) => getAssetName(original.asset.assetInfo), + filters: { showFindingCounts }, + isCustom: true, + }), + [filtersDispatch], + ); + + useEffect(() => { + if (isUndefined(showFindingCounts)) { + setShowFindingCounts(false); + } + }, [showFindingCounts, setShowFindingCounts]); + + const columns = useMemo( + () => [ + { + Header: "Name", + id: "instanceID", + sortIds: NAME_SORT_IDS, + accessor: (original) => getAssetName(original.asset.assetInfo), + }, + { + Header: "Labels", + id: "tags", + sortIds: LABEL_SORT_IDS, + Cell: ({ row }) => { + const { tags, labels } = row.original.asset.assetInfo; + + return ( + + ); }, - { - Header: "Labels", - id: "tags", - sortIds: LABEL_SORT_IDS, - Cell: ({row}) => { - const {tags, labels} = row.original.asset.assetInfo; - - return ( - - ) + alignToTop: true, + }, + { + Header: "Type", + id: "objectType", + sortIds: ["asset.assetInfo.objectType"], + accessor: "asset.assetInfo.objectType", + }, + { + Header: "Location", + id: "location", + sortIds: LOCATION_SORT_IDS, + accessor: (original) => + original.asset.assetInfo.location || + original.asset.assetInfo.repoDigests?.[0], + }, + { + Header: "Last Seen", + id: "lastSeen", + sortIds: ["asset.lastSeen"], + accessor: (original) => formatDate(original.asset.lastSeen), + }, + ...(hideTerminated + ? [] + : [ + { + Header: "Terminated On", + id: "terminatedOn", + sortIds: ["asset.terminatedOn"], + accessor: (original) => formatDate(original?.asset.terminatedOn), }, - alignToTop: true - }, - { - Header: "Type", - id: "objectType", - sortIds: ["asset.assetInfo.objectType"], - accessor: "asset.assetInfo.objectType" - }, - { - Header: "Location", - id: "location", - sortIds: LOCATION_SORT_IDS, - accessor: (original) => original.asset.assetInfo.location || original.asset.assetInfo.repoDigests?.[0], - }, - { - Header: "Last Seen", - id: "lastSeen", - sortIds: ["asset.lastSeen"], - accessor: original => formatDate(original.asset.lastSeen) - }, - ...(hideTerminated ? [] : [{ - Header: "Terminated On", - id: "terminatedOn", - sortIds: ["asset.terminatedOn"], - accessor: original => formatDate(original?.asset.terminatedOn) - }]), - ...(!showFindingCounts ? [] : [ - getVulnerabilitiesColumnConfigItem({tableTitle: TABLE_TITLE, withAssetPrefix: true}), - ...getFindingsColumnsConfigList({tableTitle: TABLE_TITLE, withAssetPrefix: true}) - ]), - ], [hideTerminated, showFindingCounts]); - - if (isUndefined(hideTerminated) || isUndefined(showFindingCounts)) { - return ; - } - - const expand = "asset" - let filtersList = [`(finding.id eq '${findingId}')`] - if (hideTerminated) { - filtersList.push("(asset.terminatedOn eq null)"); - } - let select = "asset.id,asset.assetInfo,asset.lastSeen" - if (!hideTerminated) { - select += ",asset.terminatedOn" - } - if (showFindingCounts) { - select += ",asset.summary" - } - - return ( -
-
- -
-
- -
-
- -
0 ? {"$filter": filtersList.join(" and ")} : {}) - }} - noResultsTitle={TABLE_TITLE} - onLineClick={({asset}) => navigate(`/${APIS.ASSETS}/${asset.id}`)} - columns={columns} - url={APIS.ASSET_FINDINGS} - defaultSortBy={{sortIds: ["asset.lastSeen", "asset.terminatedOn"], desc: true}} - defaultPageSize={10} - /> - - - - ) -} + ]), + ...(!showFindingCounts + ? [] + : [ + getVulnerabilitiesColumnConfigItem({ + tableTitle: TABLE_TITLE, + withAssetPrefix: true, + }), + ...getFindingsColumnsConfigList({ + tableTitle: TABLE_TITLE, + withAssetPrefix: true, + }), + ]), + ], + [hideTerminated, showFindingCounts], + ); + + if (isUndefined(hideTerminated) || isUndefined(showFindingCounts)) { + return ; + } + + const expand = "asset"; + let filtersList = [`(finding.id eq '${findingId}')`]; + if (hideTerminated) { + filtersList.push("(asset.terminatedOn eq null)"); + } + let select = "asset.id,asset.assetInfo,asset.lastSeen"; + if (!hideTerminated) { + select += ",asset.terminatedOn"; + } + if (showFindingCounts) { + select += ",asset.summary"; + } + + return ( +
+
+ +
+
+ +
+
+ +
0 + ? { $filter: filtersList.join(" and ") } + : {}), + }} + noResultsTitle={TABLE_TITLE} + onLineClick={({ asset }) => navigate(`/${APIS.ASSETS}/${asset.id}`)} + columns={columns} + url={APIS.ASSET_FINDINGS} + defaultSortBy={{ + sortIds: ["asset.lastSeen", "asset.terminatedOn"], + desc: true, + }} + defaultPageSize={10} + /> + + + + ); +}; export default AssetsForFindingTable; diff --git a/ui/src/layout/Assets/AssetsTable.jsx b/ui/src/layout/Assets/AssetsTable.jsx index e7268eb6c9..83d7c45093 100644 --- a/ui/src/layout/Assets/AssetsTable.jsx +++ b/ui/src/layout/Assets/AssetsTable.jsx @@ -1,116 +1,162 @@ -import React, { useMemo, useEffect, useCallback } from 'react'; -import { isUndefined } from 'lodash'; -import TablePage from 'components/TablePage'; -import ExpandableList from 'components/ExpandableList'; -import ToggleButton from 'components/ToggleButton'; -import Loader from 'components/Loader'; -import { APIS } from 'utils/systemConsts'; -import { getFindingsColumnsConfigList, getVulnerabilitiesColumnConfigItem, getAssetColumnsFiltersConfig, - findingsColumnsFiltersConfig, vulnerabilitiesCountersColumnsFiltersConfig, formatTagsToStringsList, formatDate} from 'utils/utils'; -import { useFilterDispatch, useFilterState, setFilters, FILTER_TYPES } from 'context/FiltersProvider'; -import { getAssetName } from 'utils/utils'; +import React, { useMemo, useEffect, useCallback } from "react"; +import { isUndefined } from "lodash"; +import TablePage from "components/TablePage"; +import ExpandableList from "components/ExpandableList"; +import ToggleButton from "components/ToggleButton"; +import Loader from "components/Loader"; +import { APIS } from "utils/systemConsts"; +import { + getFindingsColumnsConfigList, + getVulnerabilitiesColumnConfigItem, + getAssetColumnsFiltersConfig, + findingsColumnsFiltersConfig, + vulnerabilitiesCountersColumnsFiltersConfig, + formatTagsToStringsList, + formatDate, +} from "utils/utils"; +import { + useFilterDispatch, + useFilterState, + setFilters, + FILTER_TYPES, +} from "context/FiltersProvider"; +import { getAssetName } from "utils/utils"; const TABLE_TITLE = "assets"; -const NAME_SORT_IDS = ["assetInfo.instanceID", "assetInfo.podName", "assetInfo.dirName", "assetInfo.imageID", "assetInfo.containerName"]; +const NAME_SORT_IDS = [ + "assetInfo.instanceID", + "assetInfo.podName", + "assetInfo.dirName", + "assetInfo.imageID", + "assetInfo.containerName", +]; const LABEL_SORT_IDS = ["assetInfo.tags", "assetInfo.labels"]; const LOCATION_SORT_IDS = ["assetInfo.location"]; const ASSETS_FILTER_TYPE = FILTER_TYPES.ASSETS; const AssetsTable = () => { - const filtersDispatch = useFilterDispatch(); - const filtersState = useFilterState(); + const filtersDispatch = useFilterDispatch(); + const filtersState = useFilterState(); - const {customFilters} = filtersState[ASSETS_FILTER_TYPE]; - const {hideTerminated} = customFilters; + const { customFilters } = filtersState[ASSETS_FILTER_TYPE]; + const { hideTerminated } = customFilters; - const setHideTerminated = useCallback(hideTerminated => setFilters(filtersDispatch, { + const setHideTerminated = useCallback( + (hideTerminated) => + setFilters(filtersDispatch, { type: ASSETS_FILTER_TYPE, - filters: {hideTerminated}, - isCustom: true - }), [filtersDispatch]); - - useEffect(() => { - if (isUndefined(hideTerminated)) { - setHideTerminated(true); - } - }, [hideTerminated, setHideTerminated]); + filters: { hideTerminated }, + isCustom: true, + }), + [filtersDispatch], + ); - const columns = useMemo(() => [ - { - Header: "Name", - id: "instanceID", - sortIds: NAME_SORT_IDS, - accessor: (original) => getAssetName(original.assetInfo), - }, - { - Header: "Labels", - id: "tags", - sortIds: LABEL_SORT_IDS, - Cell: ({row}) => { - const {tags, labels} = row.original.assetInfo; - - return ( - - ) - }, - alignToTop: true - }, - { - Header: "Type", - id: "objectType", - sortIds: ["assetInfo.objectType"], - accessor: "assetInfo.objectType" - }, - { - Header: "Location", - id: "location", - sortIds: LOCATION_SORT_IDS, - accessor: (original) => original.assetInfo.location || original.assetInfo.repoDigests?.[0], - }, - { - Header: "Last Seen", - id: "lastSeen", - sortIds: ["lastSeen"], - accessor: original => formatDate(original.lastSeen) - }, - ...(hideTerminated ? [] : [{ - Header: "Terminated On", - id: "terminatedOn", - sortIds: ["terminatedOn"], - accessor: original => formatDate(original?.terminatedOn) - }]), - getVulnerabilitiesColumnConfigItem({tableTile: TABLE_TITLE}), - ...getFindingsColumnsConfigList({tableTile: TABLE_TITLE}) - ], [hideTerminated]); - + useEffect(() => { if (isUndefined(hideTerminated)) { - return ; + setHideTerminated(true); } + }, [hideTerminated, setHideTerminated]); - return ( -
-
- -
- [ + { + Header: "Name", + id: "instanceID", + sortIds: NAME_SORT_IDS, + accessor: (original) => getAssetName(original.assetInfo), + }, + { + Header: "Labels", + id: "tags", + sortIds: LABEL_SORT_IDS, + Cell: ({ row }) => { + const { tags, labels } = row.original.assetInfo; + + return ( + -
- ) -} + ); + }, + alignToTop: true, + }, + { + Header: "Type", + id: "objectType", + sortIds: ["assetInfo.objectType"], + accessor: "assetInfo.objectType", + }, + { + Header: "Location", + id: "location", + sortIds: LOCATION_SORT_IDS, + accessor: (original) => + original.assetInfo.location || original.assetInfo.repoDigests?.[0], + }, + { + Header: "Last Seen", + id: "lastSeen", + sortIds: ["lastSeen"], + accessor: (original) => formatDate(original.lastSeen), + }, + ...(hideTerminated + ? [] + : [ + { + Header: "Terminated On", + id: "terminatedOn", + sortIds: ["terminatedOn"], + accessor: (original) => formatDate(original?.terminatedOn), + }, + ]), + getVulnerabilitiesColumnConfigItem({ tableTile: TABLE_TITLE }), + ...getFindingsColumnsConfigList({ tableTile: TABLE_TITLE }), + ], + [hideTerminated], + ); + + if (isUndefined(hideTerminated)) { + return ; + } + + return ( +
+
+ +
+ +
+ ); +}; export default AssetsTable; diff --git a/ui/src/layout/Assets/index.jsx b/ui/src/layout/Assets/index.jsx index abb258f965..0a06d33d43 100644 --- a/ui/src/layout/Assets/index.jsx +++ b/ui/src/layout/Assets/index.jsx @@ -1,10 +1,13 @@ -import React from 'react'; -import ListAndDetailsRouter from 'components/ListAndDetailsRouter'; -import AssetDetails from './AssetDetails'; -import AssetsTable from './AssetsTable'; +import React from "react"; +import ListAndDetailsRouter from "components/ListAndDetailsRouter"; +import AssetDetails from "./AssetDetails"; +import AssetsTable from "./AssetsTable"; const Assets = () => ( - -) + +); -export default Assets; \ No newline at end of file +export default Assets; diff --git a/ui/src/layout/Dashboard/ChartTooltip/chart-tooltip.scss b/ui/src/layout/Dashboard/ChartTooltip/chart-tooltip.scss index 87ca9ee70b..ee5341fee0 100644 --- a/ui/src/layout/Dashboard/ChartTooltip/chart-tooltip.scss +++ b/ui/src/layout/Dashboard/ChartTooltip/chart-tooltip.scss @@ -1,25 +1,25 @@ -@import 'utils/scss_variables.module.scss'; +@import "utils/scss_variables.module.scss"; .widget-chart-tooltip { - .widget-chart-tooltip-header { - border-bottom: 1px solid $color-grey-dark; - padding-bottom: 5px; - margin-bottom: 5px; - } - .widget-chart-tooltip-content { - padding-right: 20px; + .widget-chart-tooltip-header { + border-bottom: 1px solid $color-grey-dark; + padding-bottom: 5px; + margin-bottom: 5px; + } + .widget-chart-tooltip-content { + padding-right: 20px; + + .widget-chart-content-item { + display: flex; + align-items: center; - .widget-chart-content-item { - display: flex; - align-items: center; - - &:not(:last-child) { - margin-bottom: 9px; - } - .widget-chart-content-item-text { - font-size: 11px; - margin-left: 6px; - } - } + &:not(:last-child) { + margin-bottom: 9px; + } + .widget-chart-content-item-text { + font-size: 11px; + margin-left: 6px; + } } + } } diff --git a/ui/src/layout/Dashboard/ChartTooltip/index.jsx b/ui/src/layout/Dashboard/ChartTooltip/index.jsx index 264c7a3127..00e9adb3fd 100644 --- a/ui/src/layout/Dashboard/ChartTooltip/index.jsx +++ b/ui/src/layout/Dashboard/ChartTooltip/index.jsx @@ -1,47 +1,60 @@ -import React from 'react'; -import Icon from 'components/Icon'; -import { BoldText, formatNumber } from 'utils/utils'; +import React from "react"; +import Icon from "components/Icon"; +import { BoldText, formatNumber } from "utils/utils"; -import './chart-tooltip.scss'; +import "./chart-tooltip.scss"; -const TooltipCountItem = ({color, icon, value, title}) => ( -
- -
{formatNumber(value)}{` ${title}`}
+const TooltipCountItem = ({ color, icon, value, title }) => ( +
+ +
+ {formatNumber(value)} + {` ${title}`}
-) - -const ChartTooltip = ({active, payload, widgetFindings, headerDisplay: HeaderDisplay, countKeyName}) => { - if (active && payload && payload.length) { - const data = payload[0].payload; - - return ( -
-
- -
-
- { - widgetFindings?.map(findingMap => { - const {dataKey, title, icon, color, darkColor} = findingMap; - const countKey = findingMap[countKeyName]; - const value = data[countKey] || 0; - - if (value === 0) { - return null; - } - - return ( - - ) - }) - } -
-
- ) - } - - return null; -} - -export default ChartTooltip; \ No newline at end of file +
+); + +const ChartTooltip = ({ + active, + payload, + widgetFindings, + headerDisplay: HeaderDisplay, + countKeyName, +}) => { + if (active && payload && payload.length) { + const data = payload[0].payload; + + return ( +
+
+ +
+
+ {widgetFindings?.map((findingMap) => { + const { dataKey, title, icon, color, darkColor } = findingMap; + const countKey = findingMap[countKeyName]; + const value = data[countKey] || 0; + + if (value === 0) { + return null; + } + + return ( + + ); + })} +
+
+ ); + } + + return null; +}; + +export default ChartTooltip; diff --git a/ui/src/layout/Dashboard/CounterDisplay/counter-display.scss b/ui/src/layout/Dashboard/CounterDisplay/counter-display.scss index d4cc77ff40..dc86a322b2 100644 --- a/ui/src/layout/Dashboard/CounterDisplay/counter-display.scss +++ b/ui/src/layout/Dashboard/CounterDisplay/counter-display.scss @@ -1,22 +1,22 @@ -@import 'utils/scss_variables.module.scss'; +@import "utils/scss_variables.module.scss"; .dashboard-counter { - box-sizing: border-box; - border: 1px solid $color-grey-light; - font-weight: 400; - font-size: 18px; - padding: 10px 30px; - overflow: hidden; + box-sizing: border-box; + border: 1px solid $color-grey-light; + font-weight: 400; + font-size: 18px; + padding: 10px 30px; + overflow: hidden; - .dashboard-counter-content { - display: flex; - align-items: center; - white-space: nowrap; + .dashboard-counter-content { + display: flex; + align-items: center; + white-space: nowrap; - .dashboard-counter-count { - font-weight: 350; - font-size: 26px; - margin-right: 10px; - } + .dashboard-counter-count { + font-weight: 350; + font-size: 26px; + margin-right: 10px; } -} \ No newline at end of file + } +} diff --git a/ui/src/layout/Dashboard/CounterDisplay/index.jsx b/ui/src/layout/Dashboard/CounterDisplay/index.jsx index 5cd5010bac..1bfbc5b3e1 100644 --- a/ui/src/layout/Dashboard/CounterDisplay/index.jsx +++ b/ui/src/layout/Dashboard/CounterDisplay/index.jsx @@ -1,47 +1,59 @@ -import React, {useMemo} from 'react'; -import {useFetch} from 'hooks'; -import {formatNumber} from 'utils/utils'; +import React, { useMemo } from "react"; +import { useFetch } from "hooks"; +import { formatNumber } from "utils/utils"; import COLORS from "../../../utils/scss_variables.module.scss"; -import {APIS} from "../../../utils/systemConsts"; +import { APIS } from "../../../utils/systemConsts"; -import './counter-display.scss'; +import "./counter-display.scss"; export const ScanCounterDisplay = () => { - const [{data, error, loading}] = useFetch( - APIS.SCANS, - { - queryParams: { - "$count": true, - "$top": 1, - "$select": "id", - "$filter": "status/state eq 'Aborted' or status/state eq 'Failed' or status/state eq 'Done'", - } - } - ); + const [{ data, error, loading }] = useFetch(APIS.SCANS, { + queryParams: { + $count: true, + $top: 1, + $select: "id", + $filter: + "status/state eq 'Aborted' or status/state eq 'Failed' or status/state eq 'Done'", + }, + }); - return ( -
- {loading || error ? "" : -
-
{formatNumber(data.count)}
- Completed scans -
- } + return ( +
+ {loading || error ? ( + "" + ) : ( +
+
+ {formatNumber(data.count)} +
+ Completed scans
- ) -} + )} +
+ ); +}; -export const CounterDisplay = ({url, title, background}) => { - const [{data, error, loading}] = useFetch(url, {queryParams: {"$count": true, "$top": 1, "$select": "id"}}); +export const CounterDisplay = ({ url, title, background }) => { + const [{ data, error, loading }] = useFetch(url, { + queryParams: { $count: true, $top: 1, $select: "id" }, + }); - return ( -
- {loading || error ? "" : -
-
{formatNumber(data.count)}
- {` ${title}`}
- } + return ( +
+ {loading || error ? ( + "" + ) : ( +
+
+ {formatNumber(data.count)} +
+ {` ${title}`}
- ) -} + )} +
+ ); +}; diff --git a/ui/src/layout/Dashboard/EmptyScansDisplay.jsx b/ui/src/layout/Dashboard/EmptyScansDisplay.jsx index 8de5059310..7d1f4415a9 100644 --- a/ui/src/layout/Dashboard/EmptyScansDisplay.jsx +++ b/ui/src/layout/Dashboard/EmptyScansDisplay.jsx @@ -1,31 +1,32 @@ -import React from 'react'; -import { useNavigate, createSearchParams } from 'react-router-dom'; -import EmptyDisplay from 'components/EmptyDisplay'; -import { ROUTES } from 'utils/systemConsts'; -import { SCANS_PATHS, OPEN_CONFIG_FORM_PARAM } from 'layout/Scans'; +import React from "react"; +import { useNavigate, createSearchParams } from "react-router-dom"; +import EmptyDisplay from "components/EmptyDisplay"; +import { ROUTES } from "utils/systemConsts"; +import { SCANS_PATHS, OPEN_CONFIG_FORM_PARAM } from "layout/Scans"; const EmptyScansDisplay = () => { - const navigate = useNavigate(); + const navigate = useNavigate(); - const goToScanCongifsPage = params => navigate({ - pathname: `${ROUTES.SCANS}/${SCANS_PATHS.CONFIGURATIONS}`, - search: createSearchParams(params).toString() + const goToScanCongifsPage = (params) => + navigate({ + pathname: `${ROUTES.SCANS}/${SCANS_PATHS.CONFIGURATIONS}`, + search: createSearchParams(params).toString(), }); - return ( - -
No scans detected.
-
Start your first scan to see your VM's issues.
- - )} - title="New scan configuration" - onClick={() => goToScanCongifsPage({[OPEN_CONFIG_FORM_PARAM]: true})} - subTitle="Start scan from config" - onSubClick={() => goToScanCongifsPage()} - /> - ) -} + return ( + +
No scans detected.
+
Start your first scan to see your VM's issues.
+ + } + title="New scan configuration" + onClick={() => goToScanCongifsPage({ [OPEN_CONFIG_FORM_PARAM]: true })} + subTitle="Start scan from config" + onSubClick={() => goToScanCongifsPage()} + /> + ); +}; -export default EmptyScansDisplay; \ No newline at end of file +export default EmptyScansDisplay; diff --git a/ui/src/layout/Dashboard/FindingsFilters/findings-filters.scss b/ui/src/layout/Dashboard/FindingsFilters/findings-filters.scss index fff045b33c..6253335e4b 100644 --- a/ui/src/layout/Dashboard/FindingsFilters/findings-filters.scss +++ b/ui/src/layout/Dashboard/FindingsFilters/findings-filters.scss @@ -1,16 +1,16 @@ -@import 'utils/scss_variables.module.scss'; +@import "utils/scss_variables.module.scss"; .findings-filters { - display: flex; - gap: 16px; + display: flex; + gap: 16px; - .findings-filter-button { - height: 30px; - box-sizing: border-box; - padding: 5px 10px; - border: 1px solid $color-grey-light; - border-radius: 50px; - background-color: $color-grey-off-white; - cursor: pointer; - } -} \ No newline at end of file + .findings-filter-button { + height: 30px; + box-sizing: border-box; + padding: 5px 10px; + border: 1px solid $color-grey-light; + border-radius: 50px; + background-color: $color-grey-off-white; + cursor: pointer; + } +} diff --git a/ui/src/layout/Dashboard/FindingsFilters/index.jsx b/ui/src/layout/Dashboard/FindingsFilters/index.jsx index ed89c8e7b6..c3da76c30e 100644 --- a/ui/src/layout/Dashboard/FindingsFilters/index.jsx +++ b/ui/src/layout/Dashboard/FindingsFilters/index.jsx @@ -1,51 +1,64 @@ -import React from 'react'; -import IconWithTooltip from 'components/IconWithTooltip'; +import React from "react"; +import IconWithTooltip from "components/IconWithTooltip"; -import COLORS from 'utils/scss_variables.module.scss'; +import COLORS from "utils/scss_variables.module.scss"; -import './findings-filters.scss'; +import "./findings-filters.scss"; -const FilterButton = ({widgetName, icon, title, color, selected, onClick}) => ( -
- -
-) +const FilterButton = ({ + widgetName, + icon, + title, + color, + selected, + onClick, +}) => ( +
+ +
+); -const FindingsFilters = ({widgetName, findingsItems, findingKeyName, selectedFilters, setSelectedFilters}) => ( -
- { - findingsItems.map((item) => { - const {color, icon, title} = item; - const findingKey = item[findingKeyName]; +const FindingsFilters = ({ + widgetName, + findingsItems, + findingKeyName, + selectedFilters, + setSelectedFilters, +}) => ( +
+ {findingsItems.map((item) => { + const { color, icon, title } = item; + const findingKey = item[findingKeyName]; - return (( - { - setSelectedFilters(selectedFilters => { - if (selectedFilters.includes(findingKey)) { - return selectedFilters.filter(selectedType => selectedType !== findingKey); - } else { - return [...selectedFilters, findingKey]; - } - }) - }} - /> - )) - }) - } -
-) + return ( + { + setSelectedFilters((selectedFilters) => { + if (selectedFilters.includes(findingKey)) { + return selectedFilters.filter( + (selectedType) => selectedType !== findingKey, + ); + } else { + return [...selectedFilters, findingKey]; + } + }); + }} + /> + ); + })} +
+); -export default FindingsFilters; \ No newline at end of file +export default FindingsFilters; diff --git a/ui/src/layout/Dashboard/FindingsImpactWidget/index.jsx b/ui/src/layout/Dashboard/FindingsImpactWidget/index.jsx index 0664255362..d4d1da121a 100644 --- a/ui/src/layout/Dashboard/FindingsImpactWidget/index.jsx +++ b/ui/src/layout/Dashboard/FindingsImpactWidget/index.jsx @@ -1,92 +1,94 @@ -import React from 'react'; -import SeverityWithCvssDisplay from 'components/SeverityWithCvssDisplay'; -import { getHigestVersionCvssData, formatNumber } from 'utils/utils'; -import { FINDINGS_MAPPING, VULNERABIITY_FINDINGS_ITEM, APIS } from 'utils/systemConsts'; -import FindingsTabsWidget from '../FindingsTabsWidget'; +import React from "react"; +import SeverityWithCvssDisplay from "components/SeverityWithCvssDisplay"; +import { getHigestVersionCvssData, formatNumber } from "utils/utils"; +import { + FINDINGS_MAPPING, + VULNERABIITY_FINDINGS_ITEM, + APIS, +} from "utils/systemConsts"; +import FindingsTabsWidget from "../FindingsTabsWidget"; -const FINDINGS_ITEMS = [VULNERABIITY_FINDINGS_ITEM, ...Object.values(FINDINGS_MAPPING)]; +const FINDINGS_ITEMS = [ + VULNERABIITY_FINDINGS_ITEM, + ...Object.values(FINDINGS_MAPPING), +]; const TABS_COLUMNS_MAPPING = { - [VULNERABIITY_FINDINGS_ITEM.dataKey]: { - headerItems: ["Name", "Severity"], - bodyItems: [ - {dataKey: "vulnerability.vulnerabilityName"}, - {customDisplay: ({vulnerability}) => { - const {severity, cvss, vulnerabilityName} = vulnerability || {}; - const {score, severity: cvssSeverity} = getHigestVersionCvssData(cvss); + [VULNERABIITY_FINDINGS_ITEM.dataKey]: { + headerItems: ["Name", "Severity"], + bodyItems: [ + { dataKey: "vulnerability.vulnerabilityName" }, + { + customDisplay: ({ vulnerability }) => { + const { severity, cvss, vulnerabilityName } = vulnerability || {}; + const { score, severity: cvssSeverity } = + getHigestVersionCvssData(cvss); - return ( - - ) - }} - ] - }, - [FINDINGS_MAPPING.EXPLOITS.dataKey]: { - headerItems: ["Vulnerability name", "URLs"], - bodyItems: [ - {dataKey: "exploit.cveID"}, - {dataKey: "exploit.urls"} - ] - }, - [FINDINGS_MAPPING.MISCONFIGURATIONS.dataKey]: { - headerItems: ["Message"], - bodyItems: [ - {dataKey: "misconfiguration.message"} - ] - }, - [FINDINGS_MAPPING.SECRETS.dataKey]: { - headerItems: ["Fingerprint"], - bodyItems: [ - {dataKey: "secret.fingerprint"} - ] - }, - [FINDINGS_MAPPING.MALWARE.dataKey]: { - headerItems: ["Malware name"], - bodyItems: [ - {dataKey: "malware.malwareName"} - ] - }, - [FINDINGS_MAPPING.ROOTKITS.dataKey]: { - headerItems: ["Rootkit name", "Message"], - bodyItems: [ - {dataKey: "rootkit.rootkitName"}, - {dataKey: "rootkit.message"} - ] - }, - [FINDINGS_MAPPING.PACKAGES.dataKey]: { - headerItems: ["Package name", "Version"], - bodyItems: [ - {dataKey: "package.name"}, - {dataKey: "package.version"} - ] - } -} + /> + ); + }, + }, + ], + }, + [FINDINGS_MAPPING.EXPLOITS.dataKey]: { + headerItems: ["Vulnerability name", "URLs"], + bodyItems: [{ dataKey: "exploit.cveID" }, { dataKey: "exploit.urls" }], + }, + [FINDINGS_MAPPING.MISCONFIGURATIONS.dataKey]: { + headerItems: ["Message"], + bodyItems: [{ dataKey: "misconfiguration.message" }], + }, + [FINDINGS_MAPPING.SECRETS.dataKey]: { + headerItems: ["Fingerprint"], + bodyItems: [{ dataKey: "secret.fingerprint" }], + }, + [FINDINGS_MAPPING.MALWARE.dataKey]: { + headerItems: ["Malware name"], + bodyItems: [{ dataKey: "malware.malwareName" }], + }, + [FINDINGS_MAPPING.ROOTKITS.dataKey]: { + headerItems: ["Rootkit name", "Message"], + bodyItems: [ + { dataKey: "rootkit.rootkitName" }, + { dataKey: "rootkit.message" }, + ], + }, + [FINDINGS_MAPPING.PACKAGES.dataKey]: { + headerItems: ["Package name", "Version"], + bodyItems: [{ dataKey: "package.name" }, { dataKey: "package.version" }], + }, +}; -const FindingsImpactWidget = ({className}) => ( - { - const {headerItems=[]} = TABS_COLUMNS_MAPPING[selectedId] || {}; +const FindingsImpactWidget = ({ className }) => ( + { + const { headerItems = [] } = TABS_COLUMNS_MAPPING[selectedId] || {}; - return ( - ([...headerItems, "Affected assets"]) - ) - }} - getBodyItems={(selectedId) => { - const {bodyItems=[]} = TABS_COLUMNS_MAPPING[selectedId] || {}; + return [...headerItems, "Affected assets"]; + }} + getBodyItems={(selectedId) => { + const { bodyItems = [] } = TABS_COLUMNS_MAPPING[selectedId] || {}; - return ([...bodyItems, {customDisplay: ({affectedAssetsCount}) => (formatNumber(affectedAssetsCount))}]) - }} - /> -) + return [ + ...bodyItems, + { + customDisplay: ({ affectedAssetsCount }) => + formatNumber(affectedAssetsCount), + }, + ]; + }} + /> +); -export default FindingsImpactWidget; \ No newline at end of file +export default FindingsImpactWidget; diff --git a/ui/src/layout/Dashboard/FindingsTabsWidget/findings-tabs-widget.scss b/ui/src/layout/Dashboard/FindingsTabsWidget/findings-tabs-widget.scss index 8ccaf507bc..a0f60443d6 100644 --- a/ui/src/layout/Dashboard/FindingsTabsWidget/findings-tabs-widget.scss +++ b/ui/src/layout/Dashboard/FindingsTabsWidget/findings-tabs-widget.scss @@ -1,38 +1,37 @@ -@import 'utils/scss_variables.module.scss'; +@import "utils/scss_variables.module.scss"; .findings-tabs-widget { - position: relative; - - .tabbed-widget-table-wrapper { - height: calc(100% - 90px); - overflow-y: auto; + position: relative; - .tabbed-widget-table { - font-size: 14px; - border-collapse: collapse; - width: 100%; - - thead { - font-size: 12px; - text-align: left; - text-transform: uppercase; - - } - tbody tr { - border-bottom: 1px solid $color-grey-light; - } - th { - padding-top: 10px; - white-space: nowrap; - } - td { - padding: 12px 0 5px 0; - overflow-wrap: anywhere; - } + .tabbed-widget-table-wrapper { + height: calc(100% - 90px); + overflow-y: auto; - .empty-results-display-wrapper { - margin: 20px 0px; - } - } + .tabbed-widget-table { + font-size: 14px; + border-collapse: collapse; + width: 100%; + + thead { + font-size: 12px; + text-align: left; + text-transform: uppercase; + } + tbody tr { + border-bottom: 1px solid $color-grey-light; + } + th { + padding-top: 10px; + white-space: nowrap; + } + td { + padding: 12px 0 5px 0; + overflow-wrap: anywhere; + } + + .empty-results-display-wrapper { + margin: 20px 0px; + } } + } } diff --git a/ui/src/layout/Dashboard/FindingsTabsWidget/index.jsx b/ui/src/layout/Dashboard/FindingsTabsWidget/index.jsx index 20bc1af38f..c5c0682d9c 100644 --- a/ui/src/layout/Dashboard/FindingsTabsWidget/index.jsx +++ b/ui/src/layout/Dashboard/FindingsTabsWidget/index.jsx @@ -1,90 +1,139 @@ -import React, { useState } from 'react'; -import classnames from 'classnames'; -import { get } from 'lodash'; -import { useFetch } from 'hooks'; -import Loader from 'components/Loader'; -import Tabs from 'components/Tabs'; -import IconWithTooltip from 'components/IconWithTooltip'; -import WidgetWrapper from '../WidgetWrapper'; +import React, { useState } from "react"; +import classnames from "classnames"; +import { get } from "lodash"; +import { useFetch } from "hooks"; +import Loader from "components/Loader"; +import Tabs from "components/Tabs"; +import IconWithTooltip from "components/IconWithTooltip"; +import WidgetWrapper from "../WidgetWrapper"; -import COLORS from 'utils/scss_variables.module.scss'; +import COLORS from "utils/scss_variables.module.scss"; -import './findings-tabs-widget.scss'; +import "./findings-tabs-widget.scss"; -const WidgetContent = ({data=[], getHeaderItems, getBodyItems, selectedId}) => { - const displayData = (data || []).slice(0, 5); +const WidgetContent = ({ + data = [], + getHeaderItems, + getBodyItems, + selectedId, +}) => { + const displayData = (data || []).slice(0, 5); - return ( -
- - - {getHeaderItems(selectedId).map((item, index, items) => ( - - ))} - - - - { - displayData.length > 0 ? - displayData.map((item, index) => { - return ( - - {getBodyItems(selectedId).map(({dataKey, customDisplay: CustomDisplay}, index, items) => ( - - ))} - - ) - }) - :
No results available
- } - -
{item}
- {!!CustomDisplay ? : get(item, dataKey)} -
- ) -} + return ( + + + + {getHeaderItems(selectedId).map((item, index, items) => ( + + ))} + + + + {displayData.length > 0 ? ( + displayData.map((item, index) => { + return ( + + {getBodyItems(selectedId).map( + ({ dataKey, customDisplay: CustomDisplay }, index, items) => ( + + ), + )} + + ); + }) + ) : ( + +
+ No results available +
+ + + )} + +
+ {item} +
+ {!!CustomDisplay ? ( + + ) : ( + get(item, dataKey) + )} +
+
+ ); +}; -const Tab = ({widgetName, title, icon, isActive}) => ( - -) +const Tab = ({ widgetName, title, icon, isActive }) => ( + +); -const FindingsTabsWidget = ({widgetName, findingsItems, className, title, url, getHeaderItems, getBodyItems}) => { - const [{data, error, loading}] = useFetch(url, {urlPrefix: "ui"}); - - const WIDGET_TAB_ITEMS = findingsItems.map(({dataKey, icon, title}) => ( - {id: dataKey, customTitle: ({isActive}) => } - )) +const FindingsTabsWidget = ({ + widgetName, + findingsItems, + className, + title, + url, + getHeaderItems, + getBodyItems, +}) => { + const [{ data, error, loading }] = useFetch(url, { urlPrefix: "ui" }); - const [selectedTabId, setSelectedTabId] = useState(WIDGET_TAB_ITEMS[0].id); + const WIDGET_TAB_ITEMS = findingsItems.map(({ dataKey, icon, title }) => ({ + id: dataKey, + customTitle: ({ isActive }) => ( + + ), + })); - return ( - - id === selectedTabId} - onClick={({id}) => setSelectedTabId(id)} - tabItemPadding={15} - /> -
- { - loading ? : (error ? null : - - ) - } -
-
- ) -} + const [selectedTabId, setSelectedTabId] = useState(WIDGET_TAB_ITEMS[0].id); + + return ( + + id === selectedTabId} + onClick={({ id }) => setSelectedTabId(id)} + tabItemPadding={15} + /> +
+ {loading ? ( + + ) : error ? null : ( + + )} +
+
+ ); +}; export default FindingsTabsWidget; diff --git a/ui/src/layout/Dashboard/FindingsTrendsWidget/findings-trends-widget.scss b/ui/src/layout/Dashboard/FindingsTrendsWidget/findings-trends-widget.scss index c0e5111cc9..b4e3629a84 100644 --- a/ui/src/layout/Dashboard/FindingsTrendsWidget/findings-trends-widget.scss +++ b/ui/src/layout/Dashboard/FindingsTrendsWidget/findings-trends-widget.scss @@ -1,24 +1,24 @@ -@import 'utils/scss_variables.module.scss'; +@import "utils/scss_variables.module.scss"; .findings-trends-widget { - position: relative; + position: relative; - .findings-trends-widget-header { - position: absolute; - top: 30px; - right: 30px; - display: flex; - align-items: center; - gap: 20px; + .findings-trends-widget-header { + position: absolute; + top: 30px; + right: 30px; + display: flex; + align-items: center; + gap: 20px; - .dropdown-select { - width: 140px; - } + .dropdown-select { + width: 140px; } - .findings-trends-widget-chart { - .recharts-cartesian-axis-line, - .recharts-cartesian-axis-tick-line { - stroke: $color-grey-lighter; - } + } + .findings-trends-widget-chart { + .recharts-cartesian-axis-line, + .recharts-cartesian-axis-tick-line { + stroke: $color-grey-lighter; } -} \ No newline at end of file + } +} diff --git a/ui/src/layout/Dashboard/FindingsTrendsWidget/index.jsx b/ui/src/layout/Dashboard/FindingsTrendsWidget/index.jsx index e9544c5837..fe7dbcdfbd 100644 --- a/ui/src/layout/Dashboard/FindingsTrendsWidget/index.jsx +++ b/ui/src/layout/Dashboard/FindingsTrendsWidget/index.jsx @@ -1,133 +1,208 @@ -import React, { useCallback, useEffect, useState } from 'react'; -import { LineChart, Line, XAxis, YAxis, CartesianGrid, ResponsiveContainer, Tooltip } from 'recharts'; -import classnames from 'classnames'; -import moment from 'moment'; -import { useFetch } from 'hooks'; -import Loader from 'components/Loader'; -import DropdownSelect from 'components/DropdownSelect'; -import { APIS, FINDINGS_MAPPING, VULNERABIITY_FINDINGS_ITEM } from 'utils/systemConsts'; -import { formatDate } from 'utils/utils'; -import WidgetWrapper from '../WidgetWrapper'; -import ChartTooltip from '../ChartTooltip'; -import FindingsFilters from '../FindingsFilters'; +import React, { useCallback, useEffect, useState } from "react"; +import { + LineChart, + Line, + XAxis, + YAxis, + CartesianGrid, + ResponsiveContainer, + Tooltip, +} from "recharts"; +import classnames from "classnames"; +import moment from "moment"; +import { useFetch } from "hooks"; +import Loader from "components/Loader"; +import DropdownSelect from "components/DropdownSelect"; +import { + APIS, + FINDINGS_MAPPING, + VULNERABIITY_FINDINGS_ITEM, +} from "utils/systemConsts"; +import { formatDate } from "utils/utils"; +import WidgetWrapper from "../WidgetWrapper"; +import ChartTooltip from "../ChartTooltip"; +import FindingsFilters from "../FindingsFilters"; -import COLORS from 'utils/scss_variables.module.scss'; +import COLORS from "utils/scss_variables.module.scss"; -import './findings-trends-widget.scss'; +import "./findings-trends-widget.scss"; -const WIDGET_FINDINGS_ITEMS = [VULNERABIITY_FINDINGS_ITEM, ...Object.values(FINDINGS_MAPPING)]; +const WIDGET_FINDINGS_ITEMS = [ + VULNERABIITY_FINDINGS_ITEM, + ...Object.values(FINDINGS_MAPPING), +]; -const calcRange = (unit, value) => ({endTime: moment().toISOString(), startTime: moment().subtract(unit, value).toISOString()}) +const calcRange = (unit, value) => ({ + endTime: moment().toISOString(), + startTime: moment().subtract(unit, value).toISOString(), +}); const TIME_RANGES = { - HOUR: { - value: "HOUR", - label: "Last hour", - calc: () => calcRange(1, 'hours') - }, - DAY: { - value: "DAY", - label: "Last day", - calc: () => calcRange(1, 'days') - }, - WEEK: { - value: "WEEK", - label: "Last week", - calc: () => calcRange(7, 'days') - }, - MONTH: { - value: "MONTH", - label: "Last month", - calc: () => calcRange(1, 'months') - }, - YEAR: { - value: "YEAR", - label: "Last year", - calc: () => calcRange(1, 'years') - } -} - -const TooltipHeader = ({data}) => (
{data.formattedTime}
) - -const WidgetChart = ({data, selectedFilters}) => { - const formattedData = data?.reduce((acc, curr) => { - const {findingType, trends} = curr; - - trends.forEach(({time, count}) => { - const accTimeIndex = acc.findIndex(({time: accTime}) => time === accTime); - const formattedTime = formatDate(time); - - acc = accTimeIndex < 0 ? [...acc, {time, formattedTime, [findingType]: count}] : - [ - ...acc.slice(0, accTimeIndex), - {...acc[accTimeIndex], [findingType]: count}, - ...acc.slice(accTimeIndex + 1) - ]; - }); - - return acc; - }, []); - - return ( -
- - - - - - } - wrapperStyle={{backgroundColor: "rgba(34, 37, 41, 0.95)", outline: "none", padding: "10px", color: "white", fontSize: "12px"}} - cursor={{fill: COLORS["color-grey-lighter"]}} - /> - { - WIDGET_FINDINGS_ITEMS.map(({color, typeKey}) => ( - selectedFilters.includes(typeKey) && - - )) - } - - -
- ) -} - -const FindingsTrendsWidget = ({className}) => { - const {value, label} = TIME_RANGES.WEEK; - const [selectedRange, setSelectedRange] = useState({value, label}); - - const [{data, error, loading}, fetchData] = useFetch(APIS.DASHBOARD_FINDINGS_TRENDS, {loadOnMount: false}); - const updateChartData = useCallback(({startTime, endTime}) => fetchData({urlPrefix: "ui", queryParams: {startTime, endTime}}), [fetchData]); - - useEffect(() => { - const {startTime, endTime} = TIME_RANGES[selectedRange.value].calc(); - updateChartData({startTime, endTime}); - }, [selectedRange.value, updateChartData]); - - const [selectedFilters, setSelectedFilters] = useState([ - ...WIDGET_FINDINGS_ITEMS.map(({typeKey}) => typeKey) - ]); - - return ( - -
- - ({value, label}))} - onChange={selectedItem => setSelectedRange(selectedItem)} + HOUR: { + value: "HOUR", + label: "Last hour", + calc: () => calcRange(1, "hours"), + }, + DAY: { + value: "DAY", + label: "Last day", + calc: () => calcRange(1, "days"), + }, + WEEK: { + value: "WEEK", + label: "Last week", + calc: () => calcRange(7, "days"), + }, + MONTH: { + value: "MONTH", + label: "Last month", + calc: () => calcRange(1, "months"), + }, + YEAR: { + value: "YEAR", + label: "Last year", + calc: () => calcRange(1, "years"), + }, +}; + +const TooltipHeader = ({ data }) =>
{data.formattedTime}
; + +const WidgetChart = ({ data, selectedFilters }) => { + const formattedData = data?.reduce((acc, curr) => { + const { findingType, trends } = curr; + + trends.forEach(({ time, count }) => { + const accTimeIndex = acc.findIndex( + ({ time: accTime }) => time === accTime, + ); + const formattedTime = formatDate(time); + + acc = + accTimeIndex < 0 + ? [...acc, { time, formattedTime, [findingType]: count }] + : [ + ...acc.slice(0, accTimeIndex), + { ...acc[accTimeIndex], [findingType]: count }, + ...acc.slice(accTimeIndex + 1), + ]; + }); + + return acc; + }, []); + + return ( +
+ + + + + + ( + + )} + wrapperStyle={{ + backgroundColor: "rgba(34, 37, 41, 0.95)", + outline: "none", + padding: "10px", + color: "white", + fontSize: "12px", + }} + cursor={{ fill: COLORS["color-grey-lighter"] }} + /> + {WIDGET_FINDINGS_ITEMS.map( + ({ color, typeKey }) => + selectedFilters.includes(typeKey) && ( + -
- {loading ? : (error ? null : )} - - ) -} + ), + )} + + +
+ ); +}; + +const FindingsTrendsWidget = ({ className }) => { + const { value, label } = TIME_RANGES.WEEK; + const [selectedRange, setSelectedRange] = useState({ value, label }); + + const [{ data, error, loading }, fetchData] = useFetch( + APIS.DASHBOARD_FINDINGS_TRENDS, + { loadOnMount: false }, + ); + const updateChartData = useCallback( + ({ startTime, endTime }) => + fetchData({ urlPrefix: "ui", queryParams: { startTime, endTime } }), + [fetchData], + ); + + useEffect(() => { + const { startTime, endTime } = TIME_RANGES[selectedRange.value].calc(); + updateChartData({ startTime, endTime }); + }, [selectedRange.value, updateChartData]); + + const [selectedFilters, setSelectedFilters] = useState([ + ...WIDGET_FINDINGS_ITEMS.map(({ typeKey }) => typeKey), + ]); + + return ( + +
+ + ({ + value, + label, + }))} + onChange={(selectedItem) => setSelectedRange(selectedItem)} + /> +
+ {loading ? ( + + ) : error ? null : ( + + )} +
+ ); +}; export default FindingsTrendsWidget; diff --git a/ui/src/layout/Dashboard/RiskiestAssetsWidget/index.jsx b/ui/src/layout/Dashboard/RiskiestAssetsWidget/index.jsx index 0239406e31..9124ce7abb 100644 --- a/ui/src/layout/Dashboard/RiskiestAssetsWidget/index.jsx +++ b/ui/src/layout/Dashboard/RiskiestAssetsWidget/index.jsx @@ -1,43 +1,59 @@ -import React from 'react'; -import VulnerabilitiesDisplay, { VULNERABILITY_SEVERITY_ITEMS } from 'components/VulnerabilitiesDisplay'; -import { APIS, VULNERABIITY_FINDINGS_ITEM, FINDINGS_MAPPING } from 'utils/systemConsts'; -import { formatNumber } from 'utils/utils'; -import FindingsTabsWidget from '../FindingsTabsWidget'; +import React from "react"; +import VulnerabilitiesDisplay, { + VULNERABILITY_SEVERITY_ITEMS, +} from "components/VulnerabilitiesDisplay"; +import { + APIS, + VULNERABIITY_FINDINGS_ITEM, + FINDINGS_MAPPING, +} from "utils/systemConsts"; +import { formatNumber } from "utils/utils"; +import FindingsTabsWidget from "../FindingsTabsWidget"; -const FINDINGS_ITEMS = [VULNERABIITY_FINDINGS_ITEM, ...Object.values(FINDINGS_MAPPING).filter(({value}) => value !== FINDINGS_MAPPING.PACKAGES.value)]; +const FINDINGS_ITEMS = [ + VULNERABIITY_FINDINGS_ITEM, + ...Object.values(FINDINGS_MAPPING).filter( + ({ value }) => value !== FINDINGS_MAPPING.PACKAGES.value, + ), +]; -const RiskiestAssetsWidget = ({className}) => ( - (["Name", "Type", "Findings"])} - getBodyItems={(selectedId) => ([ - {dataKey: "assetInfo.name"}, - {dataKey: "assetInfo.type"}, - {customDisplay: ({count, assetInfo, ...props}) => { - if (selectedId === VULNERABIITY_FINDINGS_ITEM.dataKey) { - const counters = Object.values(VULNERABILITY_SEVERITY_ITEMS).reduce((acc, curr) => { - const {totalKey, countKey} = curr; +const RiskiestAssetsWidget = ({ className }) => ( + ["Name", "Type", "Findings"]} + getBodyItems={(selectedId) => [ + { dataKey: "assetInfo.name" }, + { dataKey: "assetInfo.type" }, + { + customDisplay: ({ count, assetInfo, ...props }) => { + if (selectedId === VULNERABIITY_FINDINGS_ITEM.dataKey) { + const counters = Object.values(VULNERABILITY_SEVERITY_ITEMS).reduce( + (acc, curr) => { + const { totalKey, countKey } = curr; - return {...acc, [totalKey]: props[countKey]}; - }, {}); + return { ...acc, [totalKey]: props[countKey] }; + }, + {}, + ); - return ( - - ) - } - - return formatNumber(count); - }} - ])} - /> -) + return ( + + ); + } -export default RiskiestAssetsWidget; \ No newline at end of file + return formatNumber(count); + }, + }, + ]} + /> +); + +export default RiskiestAssetsWidget; diff --git a/ui/src/layout/Dashboard/RiskiestRegionsWidget/index.jsx b/ui/src/layout/Dashboard/RiskiestRegionsWidget/index.jsx index aa9e0dfa83..6f82a85cd6 100644 --- a/ui/src/layout/Dashboard/RiskiestRegionsWidget/index.jsx +++ b/ui/src/layout/Dashboard/RiskiestRegionsWidget/index.jsx @@ -1,88 +1,157 @@ -import React, { useState } from 'react'; -import classnames from 'classnames'; -import { orderBy } from 'lodash'; -import { BarChart, Bar, XAxis, YAxis, CartesianGrid, ResponsiveContainer, Tooltip } from 'recharts'; -import { useFetch } from 'hooks'; -import Loader from 'components/Loader'; -import { APIS, FINDINGS_MAPPING, VULNERABIITY_FINDINGS_ITEM } from 'utils/systemConsts'; -import { BoldText, formatNumber } from 'utils/utils'; -import WidgetWrapper from '../WidgetWrapper'; -import ChartTooltip from '../ChartTooltip'; -import FindingsFilters from '../FindingsFilters'; +import React, { useState } from "react"; +import classnames from "classnames"; +import { orderBy } from "lodash"; +import { + BarChart, + Bar, + XAxis, + YAxis, + CartesianGrid, + ResponsiveContainer, + Tooltip, +} from "recharts"; +import { useFetch } from "hooks"; +import Loader from "components/Loader"; +import { + APIS, + FINDINGS_MAPPING, + VULNERABIITY_FINDINGS_ITEM, +} from "utils/systemConsts"; +import { BoldText, formatNumber } from "utils/utils"; +import WidgetWrapper from "../WidgetWrapper"; +import ChartTooltip from "../ChartTooltip"; +import FindingsFilters from "../FindingsFilters"; -import COLORS from 'utils/scss_variables.module.scss'; +import COLORS from "utils/scss_variables.module.scss"; -import './reskiest-regions-widget.scss'; +import "./reskiest-regions-widget.scss"; const BAR_STACK_ID = 1; -const WIDGET_FINDINGS_ITEMS = [VULNERABIITY_FINDINGS_ITEM, ...Object.values(FINDINGS_MAPPING).filter(({value}) => value !== FINDINGS_MAPPING.PACKAGES.value)]; +const WIDGET_FINDINGS_ITEMS = [ + VULNERABIITY_FINDINGS_ITEM, + ...Object.values(FINDINGS_MAPPING).filter( + ({ value }) => value !== FINDINGS_MAPPING.PACKAGES.value, + ), +]; -const TooltipHeader = ({data}) => { - const {regionName, ...countData} = data; - - const total = Object.values(countData).reduce((acc, curr) => { - return acc + curr; - }, 0) +const TooltipHeader = ({ data }) => { + const { regionName, ...countData } = data; - return ( - <> - {regionName} -
{`Total findings: `}{formatNumber(total)}
- - ) -} + const total = Object.values(countData).reduce((acc, curr) => { + return acc + curr; + }, 0); -const WidgetContent = ({data}) => { - const [selectedFilters, setSelectedFilters] = useState([ - ...WIDGET_FINDINGS_ITEMS.map(({dataKey}) => dataKey) - ]); + return ( + <> + {regionName} +
+ {`Total findings: `} + {formatNumber(total)} +
+ + ); +}; - const formattedData = orderBy(data || [], ({findingsCount}) => { - return Object.keys(findingsCount || {}).reduce((acc, currFindingKey) => { - const count = findingsCount[currFindingKey] || 0; - - return acc + (selectedFilters.includes(currFindingKey) ? count : 0); - }, 0); - }, ["desc"]).map(({regionName, findingsCount}) => ({regionName, ...findingsCount})); +const WidgetContent = ({ data }) => { + const [selectedFilters, setSelectedFilters] = useState([ + ...WIDGET_FINDINGS_ITEMS.map(({ dataKey }) => dataKey), + ]); - return ( -
- - - - - - - } - wrapperStyle={{backgroundColor: "rgba(34, 37, 41, 0.95)", outline: "none", padding: "10px", color: "white", fontSize: "12px"}} - cursor={{fill: COLORS["color-grey-lighter"]}} - /> - { - WIDGET_FINDINGS_ITEMS.map(({dataKey, color}) => ( - selectedFilters.includes(dataKey) && - )) - } - - -
- ) -} + const formattedData = orderBy( + data || [], + ({ findingsCount }) => { + return Object.keys(findingsCount || {}).reduce((acc, currFindingKey) => { + const count = findingsCount[currFindingKey] || 0; -const RiskiestRegionsWidget = ({className}) => { - const [{data, error, loading}] = useFetch(APIS.DASHBOARD_RISKIEST_REGIONS, {urlPrefix: "ui"}); + return acc + (selectedFilters.includes(currFindingKey) ? count : 0); + }, 0); + }, + ["desc"], + ).map(({ regionName, findingsCount }) => ({ regionName, ...findingsCount })); - return ( - - {loading ? : (error ? null : )} - - ) -} + return ( +
+ + + + + + + ( + + )} + wrapperStyle={{ + backgroundColor: "rgba(34, 37, 41, 0.95)", + outline: "none", + padding: "10px", + color: "white", + fontSize: "12px", + }} + cursor={{ fill: COLORS["color-grey-lighter"] }} + /> + {WIDGET_FINDINGS_ITEMS.map( + ({ dataKey, color }) => + selectedFilters.includes(dataKey) && ( + + ), + )} + + +
+ ); +}; -export default RiskiestRegionsWidget; \ No newline at end of file +const RiskiestRegionsWidget = ({ className }) => { + const [{ data, error, loading }] = useFetch(APIS.DASHBOARD_RISKIEST_REGIONS, { + urlPrefix: "ui", + }); + + return ( + + {loading ? ( + + ) : error ? null : ( + + )} + + ); +}; + +export default RiskiestRegionsWidget; diff --git a/ui/src/layout/Dashboard/RiskiestRegionsWidget/reskiest-regions-widget.scss b/ui/src/layout/Dashboard/RiskiestRegionsWidget/reskiest-regions-widget.scss index 4ee00049e7..8040c0765c 100644 --- a/ui/src/layout/Dashboard/RiskiestRegionsWidget/reskiest-regions-widget.scss +++ b/ui/src/layout/Dashboard/RiskiestRegionsWidget/reskiest-regions-widget.scss @@ -1,10 +1,10 @@ -@import 'utils/scss_variables.module.scss'; +@import "utils/scss_variables.module.scss"; .riskiest-regions-widget { - position: relative; - - .recharts-cartesian-axis-line, - .recharts-cartesian-axis-tick-line { - stroke: $color-grey-lighter; - } -} \ No newline at end of file + position: relative; + + .recharts-cartesian-axis-line, + .recharts-cartesian-axis-tick-line { + stroke: $color-grey-lighter; + } +} diff --git a/ui/src/layout/Dashboard/WidgetWrapper/index.jsx b/ui/src/layout/Dashboard/WidgetWrapper/index.jsx index d3fdb25033..1facbf20a4 100644 --- a/ui/src/layout/Dashboard/WidgetWrapper/index.jsx +++ b/ui/src/layout/Dashboard/WidgetWrapper/index.jsx @@ -1,14 +1,18 @@ -import React from 'react'; -import classnames from 'classnames'; -import Title from 'components/Title'; +import React from "react"; +import classnames from "classnames"; +import Title from "components/Title"; -import './widget-wrapper.scss'; +import "./widget-wrapper.scss"; -const WidgetWrapper = ({className, title, children, titleMargin=20}) => ( -
-
{title}
- {children} +const WidgetWrapper = ({ className, title, children, titleMargin = 20 }) => ( +
+
+ + {title} +
-) + {children} +
+); -export default WidgetWrapper; \ No newline at end of file +export default WidgetWrapper; diff --git a/ui/src/layout/Dashboard/WidgetWrapper/widget-wrapper.scss b/ui/src/layout/Dashboard/WidgetWrapper/widget-wrapper.scss index 3ce35fd834..d94f69a5c6 100644 --- a/ui/src/layout/Dashboard/WidgetWrapper/widget-wrapper.scss +++ b/ui/src/layout/Dashboard/WidgetWrapper/widget-wrapper.scss @@ -1,9 +1,9 @@ -@import 'utils/scss_variables.module.scss'; +@import "utils/scss_variables.module.scss"; .dashboard-widget-wrapper { - background: linear-gradient(292.93deg, #F9F9F9 -0.56%, #FFFFFF 100%); - border: 1px solid $color-grey-light; - box-sizing: border-box; - padding: 30px; - overflow: hidden; -} \ No newline at end of file + background: linear-gradient(292.93deg, #f9f9f9 -0.56%, #ffffff 100%); + border: 1px solid $color-grey-light; + box-sizing: border-box; + padding: 30px; + overflow: hidden; +} diff --git a/ui/src/layout/Dashboard/dashboard.scss b/ui/src/layout/Dashboard/dashboard.scss index 1982ba0540..9256a9f509 100644 --- a/ui/src/layout/Dashboard/dashboard.scss +++ b/ui/src/layout/Dashboard/dashboard.scss @@ -1,28 +1,31 @@ -@import 'utils/scss_variables.module.scss'; +@import "utils/scss_variables.module.scss"; .dashboard-page-wrapper { - padding: $main-content-padding; - display: grid; - grid-template-areas: + padding: $main-content-padding; + display: grid; + grid-template-areas: ". . ." "riskiest-regions findings-trend findings-trend" "riskiest-regions riskiest-assets findings-impact"; - grid-template-rows: 50px minmax(320px, 1fr) minmax(320px, 1fr); - grid-template-columns: minmax(420px, 1fr) minmax(420px, 1fr) minmax(420px, 1fr); - gap: 20px; - height: calc(100vh - #{$top-bar-height} - 2 * #{$main-content-padding}); - overflow: auto; + grid-template-rows: 50px minmax(320px, 1fr) minmax(320px, 1fr); + grid-template-columns: minmax(420px, 1fr) minmax(420px, 1fr) minmax( + 420px, + 1fr + ); + gap: 20px; + height: calc(100vh - #{$top-bar-height} - 2 * #{$main-content-padding}); + overflow: auto; - .riskiest-regions { - grid-area: riskiest-regions; - } - .findings-trend { - grid-area: findings-trend; - } - .riskiest-assets { - grid-area: riskiest-assets; - } - .findings-impact { - grid-area: findings-impact; - } -} \ No newline at end of file + .riskiest-regions { + grid-area: riskiest-regions; + } + .findings-trend { + grid-area: findings-trend; + } + .riskiest-assets { + grid-area: riskiest-assets; + } + .findings-impact { + grid-area: findings-impact; + } +} diff --git a/ui/src/layout/Dashboard/index.jsx b/ui/src/layout/Dashboard/index.jsx index a6934d67e9..32a465d6f9 100644 --- a/ui/src/layout/Dashboard/index.jsx +++ b/ui/src/layout/Dashboard/index.jsx @@ -1,52 +1,63 @@ -import React from 'react'; -import { useFetch } from 'hooks'; -import Loader from 'components/Loader'; -import { APIS } from 'utils/systemConsts'; -import { CounterDisplay, ScanCounterDisplay } from './CounterDisplay'; -import FindingsTrendsWidget from './FindingsTrendsWidget'; -import RiskiestRegionsWidget from './RiskiestRegionsWidget'; -import RiskiestAssetsWidget from './RiskiestAssetsWidget'; -import FindingsImpactWidget from './FindingsImpactWidget'; -import EmptyScansDisplay from './EmptyScansDisplay'; +import React from "react"; +import { useFetch } from "hooks"; +import Loader from "components/Loader"; +import { APIS } from "utils/systemConsts"; +import { CounterDisplay, ScanCounterDisplay } from "./CounterDisplay"; +import FindingsTrendsWidget from "./FindingsTrendsWidget"; +import RiskiestRegionsWidget from "./RiskiestRegionsWidget"; +import RiskiestAssetsWidget from "./RiskiestAssetsWidget"; +import FindingsImpactWidget from "./FindingsImpactWidget"; +import EmptyScansDisplay from "./EmptyScansDisplay"; -import COLORS from 'utils/scss_variables.module.scss'; +import COLORS from "utils/scss_variables.module.scss"; -import './dashboard.scss'; +import "./dashboard.scss"; const COUNTERS_CONFIG = [ - {url: APIS.ASSETS, title: "Assets", background: COLORS["color-gradient-blue"]}, - {url: APIS.FINDINGS, title: "Findings", background: COLORS["color-gradient-yellow"]} + { + url: APIS.ASSETS, + title: "Assets", + background: COLORS["color-gradient-blue"], + }, + { + url: APIS.FINDINGS, + title: "Findings", + background: COLORS["color-gradient-yellow"], + }, ]; const Dashboard = () => { - const [{data, error, loading}] = useFetch(APIS.SCANS); - - if (loading) { - return ; - } - - if (error) { - return null; - } - - if (data.length === 0) { - return ; - } - - return ( -
- - { - COUNTERS_CONFIG.map(({url, title, background}, index) => ( - - )) - } - - - - -
- ) -} + const [{ data, error, loading }] = useFetch(APIS.SCANS); + + if (loading) { + return ; + } + + if (error) { + return null; + } + + if (data.length === 0) { + return ; + } + + return ( +
+ + {COUNTERS_CONFIG.map(({ url, title, background }, index) => ( + + ))} + + + + +
+ ); +}; export default Dashboard; diff --git a/ui/src/layout/Findings/AssetCountDisplay.jsx b/ui/src/layout/Findings/AssetCountDisplay.jsx index 44643bf116..4021fc583b 100644 --- a/ui/src/layout/Findings/AssetCountDisplay.jsx +++ b/ui/src/layout/Findings/AssetCountDisplay.jsx @@ -1,39 +1,48 @@ -import React from 'react'; -import { useFetch } from 'hooks'; -import { isUndefined } from 'lodash'; -import TitleValueDisplay, { TitleValueDisplayRow } from 'components/TitleValueDisplay'; -import Loader from 'components/Loader'; -import { APIS } from 'utils/systemConsts'; -import { formatNumber } from 'utils/utils'; +import React from "react"; +import { useFetch } from "hooks"; +import { isUndefined } from "lodash"; +import TitleValueDisplay, { + TitleValueDisplayRow, +} from "components/TitleValueDisplay"; +import Loader from "components/Loader"; +import { APIS } from "utils/systemConsts"; +import { formatNumber } from "utils/utils"; const AssetCountDisplay = (findingId) => { - const filter = `finding/id eq '${findingId}'`; - const [{loading, data, error}] = useFetch(APIS.ASSET_FINDINGS, { - queryParams: {"$expand": "asset", "$filter": filter, "$count": true, "$select": "count,asset/terminatedOn"} - }); + const filter = `finding/id eq '${findingId}'`; + const [{ loading, data, error }] = useFetch(APIS.ASSET_FINDINGS, { + queryParams: { + $expand: "asset", + $filter: filter, + $count: true, + $select: "count,asset/terminatedOn", + }, + }); - if (error) { - return null; - } + if (error) { + return null; + } - if (loading) { - return - } + if (loading) { + return ; + } - let notTerminated = 0; - if (data && data.items) { - data.items.forEach(item => { - if (isUndefined(item.asset.terminatedOn)) { - notTerminated++; - } - }); - } + let notTerminated = 0; + if (data && data.items) { + data.items.forEach((item) => { + if (isUndefined(item.asset.terminatedOn)) { + notTerminated++; + } + }); + } - return ( - - {formatNumber(notTerminated)} ({formatNumber(data?.count || 0)}) - - ) -} + return ( + + + {formatNumber(notTerminated)} ({formatNumber(data?.count || 0)}) + + + ); +}; export default AssetCountDisplay; diff --git a/ui/src/layout/Findings/Exploits/ExploitDetails.jsx b/ui/src/layout/Findings/Exploits/ExploitDetails.jsx index b40a30e41a..4e28bae8ec 100644 --- a/ui/src/layout/Findings/Exploits/ExploitDetails.jsx +++ b/ui/src/layout/Findings/Exploits/ExploitDetails.jsx @@ -1,49 +1,49 @@ -import React from 'react'; -import { useLocation } from 'react-router-dom'; -import TabbedPage from 'components/TabbedPage'; -import FindingsDetailsPage from '../FindingsDetailsPage'; -import TabExploitDetails from './TabExploitDetails'; -import AssetsForFindingTable from 'layout/Assets/AssetsForFindingTable'; +import React from "react"; +import { useLocation } from "react-router-dom"; +import TabbedPage from "components/TabbedPage"; +import FindingsDetailsPage from "../FindingsDetailsPage"; +import TabExploitDetails from "./TabExploitDetails"; +import AssetsForFindingTable from "layout/Assets/AssetsForFindingTable"; const EXPLOIT_DETAILS_PATHS = { - EXPLOIT_DETAILS: "", - ASSET_LIST: "assets", -} + EXPLOIT_DETAILS: "", + ASSET_LIST: "assets", +}; -const DetailsContent = ({data}) => { - const {pathname} = useLocation(); +const DetailsContent = ({ data }) => { + const { pathname } = useLocation(); - const {id} = data; + const { id } = data; - return ( - - }, - { - id: "assets", - title: "Assets", - path: EXPLOIT_DETAILS_PATHS.ASSET_LIST, - component: () => - } - ]} - withInnerPadding={false} - /> - ) -} + return ( + , + }, + { + id: "assets", + title: "Assets", + path: EXPLOIT_DETAILS_PATHS.ASSET_LIST, + component: () => , + }, + ]} + withInnerPadding={false} + /> + ); +}; const ExploitDetails = () => ( - ({title: findingInfo.name})} - detailsContent={DetailsContent} - /> -) + ({ title: findingInfo.name })} + detailsContent={DetailsContent} + /> +); export default ExploitDetails; diff --git a/ui/src/layout/Findings/Exploits/ExploitsTable.jsx b/ui/src/layout/Findings/Exploits/ExploitsTable.jsx index 1a43a8dfc2..bec0633ba5 100644 --- a/ui/src/layout/Findings/Exploits/ExploitsTable.jsx +++ b/ui/src/layout/Findings/Exploits/ExploitsTable.jsx @@ -1,64 +1,79 @@ -import React, { useMemo } from 'react'; -import { getScanColumnsConfigList } from 'layout/Findings/utils'; -import { FILTER_TYPES } from 'context/FiltersProvider'; -import { OPERATORS } from 'components/Filter'; -import FindingsTablePage from '../FindingsTablePage'; +import React, { useMemo } from "react"; +import { getScanColumnsConfigList } from "layout/Findings/utils"; +import { FILTER_TYPES } from "context/FiltersProvider"; +import { OPERATORS } from "components/Filter"; +import FindingsTablePage from "../FindingsTablePage"; const ExploitsTable = () => { - const columns = useMemo(() => [ + const columns = useMemo( + () => [ + { + Header: "Description", + id: "description", + sortIds: ["findingInfo.description"], + accessor: "findingInfo.description", + width: 300, + }, + { + Header: "Source DB", + id: "sourceDB", + sortIds: ["findingInfo.sourceDB"], + accessor: "findingInfo.sourceDB", + }, + { + Header: "Vulnerability name", + id: "cveID", + sortIds: ["findingInfo.cveID"], + accessor: "findingInfo.cveID", + }, + ...getScanColumnsConfigList(), + ], + [], + ); + + return ( + - ) -} + ]} + tableTitle="exploits" + findingsObjectType="Exploit" + /> + ); +}; export default ExploitsTable; diff --git a/ui/src/layout/Findings/Exploits/TabExploitDetails.jsx b/ui/src/layout/Findings/Exploits/TabExploitDetails.jsx index 0c517a365b..acf7c81ffc 100644 --- a/ui/src/layout/Findings/Exploits/TabExploitDetails.jsx +++ b/ui/src/layout/Findings/Exploits/TabExploitDetails.jsx @@ -1,32 +1,41 @@ -import React from 'react'; -import TitleValueDisplay, { TitleValueDisplayRow } from 'components/TitleValueDisplay'; -import DoublePaneDisplay from 'components/DoublePaneDisplay'; -import LinksDisplay from 'layout/Findings/LinkesDisplay'; -import { FindingsDetailsCommonFields } from '../utils'; -import AssetCountDisplay from '../AssetCountDisplay'; +import React from "react"; +import TitleValueDisplay, { + TitleValueDisplayRow, +} from "components/TitleValueDisplay"; +import DoublePaneDisplay from "components/DoublePaneDisplay"; +import LinksDisplay from "layout/Findings/LinkesDisplay"; +import { FindingsDetailsCommonFields } from "../utils"; +import AssetCountDisplay from "../AssetCountDisplay"; -const TabExploitDetails = ({data}) => { - const {id, findingInfo, firstSeen, lastSeen} = data; - const {cveID, description, sourceDB, urls} = findingInfo; +const TabExploitDetails = ({ data }) => { + const { id, findingInfo, firstSeen, lastSeen } = data; + const { cveID, description, sourceDB, urls } = findingInfo; - return ( - ( - <> - - {sourceDB} - {cveID} - - - {description} - - - - {AssetCountDisplay(id)} - - )} - /> - ) -} + return ( + ( + <> + + {sourceDB} + + {cveID} + + + + + {description} + + + + + {AssetCountDisplay(id)} + + )} + /> + ); +}; export default TabExploitDetails; diff --git a/ui/src/layout/Findings/Exploits/index.jsx b/ui/src/layout/Findings/Exploits/index.jsx index 0b82a3f586..10d765d99c 100644 --- a/ui/src/layout/Findings/Exploits/index.jsx +++ b/ui/src/layout/Findings/Exploits/index.jsx @@ -1,11 +1,13 @@ -import React from 'react'; -import ListAndDetailsRouter from 'components/ListAndDetailsRouter'; -import ExploitsTable from './ExploitsTable'; -import ExploitDetails from './ExploitDetails'; +import React from "react"; +import ListAndDetailsRouter from "components/ListAndDetailsRouter"; +import ExploitsTable from "./ExploitsTable"; +import ExploitDetails from "./ExploitDetails"; const Exploits = () => ( - -) - + +); export default Exploits; diff --git a/ui/src/layout/Findings/FindingsDetailsPage.jsx b/ui/src/layout/Findings/FindingsDetailsPage.jsx index d13d8969c6..316a0829b8 100644 --- a/ui/src/layout/Findings/FindingsDetailsPage.jsx +++ b/ui/src/layout/Findings/FindingsDetailsPage.jsx @@ -1,15 +1,19 @@ -import React from 'react'; -import DetailsPageWrapper from 'components/DetailsPageWrapper'; -import { APIS } from 'utils/systemConsts'; +import React from "react"; +import DetailsPageWrapper from "components/DetailsPageWrapper"; +import { APIS } from "utils/systemConsts"; -const FindingsDetailsPage = ({backTitle, getTitleData, detailsContent: DetailsContent}) => ( - } - /> -) +const FindingsDetailsPage = ({ + backTitle, + getTitleData, + detailsContent: DetailsContent, +}) => ( + } + /> +); export default FindingsDetailsPage; diff --git a/ui/src/layout/Findings/FindingsTablePage.jsx b/ui/src/layout/Findings/FindingsTablePage.jsx index f94fa7b2ff..f6fa466541 100644 --- a/ui/src/layout/Findings/FindingsTablePage.jsx +++ b/ui/src/layout/Findings/FindingsTablePage.jsx @@ -1,64 +1,97 @@ -import React, { useEffect, useCallback } from 'react'; -import { isUndefined } from 'lodash'; -import TablePage from 'components/TablePage'; -import { OPERATORS } from 'components/Filter'; -import ToggleButton from 'components/ToggleButton'; -import InfoIcon from 'components/InfoIcon'; -import Loader from 'components/Loader'; -import { APIS } from 'utils/systemConsts'; -import { useFilterDispatch, useFilterState, setFilters, FILTER_TYPES } from 'context/FiltersProvider'; +import React, { useEffect, useCallback } from "react"; +import { isUndefined } from "lodash"; +import TablePage from "components/TablePage"; +import { OPERATORS } from "components/Filter"; +import ToggleButton from "components/ToggleButton"; +import InfoIcon from "components/InfoIcon"; +import Loader from "components/Loader"; +import { APIS } from "utils/systemConsts"; +import { + useFilterDispatch, + useFilterState, + setFilters, + FILTER_TYPES, +} from "context/FiltersProvider"; -const FindingsTablePage = ({tableTitle, findingsObjectType, columns, filterType, filtersConfig}) => { - const filtersDispatch = useFilterDispatch(); - const filtersState = useFilterState(); +const FindingsTablePage = ({ + tableTitle, + findingsObjectType, + columns, + filterType, + filtersConfig, +}) => { + const filtersDispatch = useFilterDispatch(); + const filtersState = useFilterState(); - const {customFilters} = filtersState[filterType]; - const {hideHistory} = customFilters; + const { customFilters } = filtersState[filterType]; + const { hideHistory } = customFilters; - const setHideHistory = useCallback(hideHistory => setFilters(filtersDispatch, { + const setHideHistory = useCallback( + (hideHistory) => + setFilters(filtersDispatch, { type: filterType, - filters: {hideHistory}, - isCustom: true - }), [filterType, filtersDispatch]); - - useEffect(() => { - if (isUndefined(hideHistory)) { - setHideHistory(true); - } - }, [hideHistory, setHideHistory]); + filters: { hideHistory }, + isCustom: true, + }), + [filterType, filtersDispatch], + ); + useEffect(() => { if (isUndefined(hideHistory)) { - return ; + setHideHistory(true); } - - return ( -
-
- -
- -
-
- + }, [hideHistory, setHideHistory]); + + if (isUndefined(hideHistory)) { + return ; + } + + return ( +
+
+ +
+
- ) -} +
+ +
+ ); +}; export default FindingsTablePage; diff --git a/ui/src/layout/Findings/LinkesDisplay.jsx b/ui/src/layout/Findings/LinkesDisplay.jsx index 4e44c2e12c..0eb8b408c4 100644 --- a/ui/src/layout/Findings/LinkesDisplay.jsx +++ b/ui/src/layout/Findings/LinkesDisplay.jsx @@ -1,16 +1,20 @@ -import React from 'react'; -import TitleValueDisplay, { TitleValueDisplayRow } from 'components/TitleValueDisplay'; +import React from "react"; +import TitleValueDisplay, { + TitleValueDisplayRow, +} from "components/TitleValueDisplay"; -const LinksDisplay = ({title, links}) => ( - - - { - links?.map((link, index) => ( - - )) - } - - -) +const LinksDisplay = ({ title, links }) => ( + + + {links?.map((link, index) => ( + + ))} + + +); export default LinksDisplay; diff --git a/ui/src/layout/Findings/Malware/MalwareDetails.jsx b/ui/src/layout/Findings/Malware/MalwareDetails.jsx index 292c486f5b..844c170569 100644 --- a/ui/src/layout/Findings/Malware/MalwareDetails.jsx +++ b/ui/src/layout/Findings/Malware/MalwareDetails.jsx @@ -1,49 +1,49 @@ -import React from 'react'; -import { useLocation } from 'react-router-dom'; -import TabbedPage from 'components/TabbedPage'; -import FindingsDetailsPage from '../FindingsDetailsPage'; -import TabMalwareDetails from './TabMalwareDetails'; -import AssetsForFindingTable from 'layout/Assets/AssetsForFindingTable'; +import React from "react"; +import { useLocation } from "react-router-dom"; +import TabbedPage from "components/TabbedPage"; +import FindingsDetailsPage from "../FindingsDetailsPage"; +import TabMalwareDetails from "./TabMalwareDetails"; +import AssetsForFindingTable from "layout/Assets/AssetsForFindingTable"; const MALWARE_DETAILS_PATHS = { - MALWARE_DETAILS: "", - ASSET_LIST: "assets", -} + MALWARE_DETAILS: "", + ASSET_LIST: "assets", +}; -const DetailsContent = ({data}) => { - const {pathname} = useLocation(); - - const {id} = data; - - return ( - - }, - { - id: "assets", - title: "Assets", - path: MALWARE_DETAILS_PATHS.ASSET_LIST, - component: () => - } - ]} - withInnerPadding={false} - /> - ) -} +const DetailsContent = ({ data }) => { + const { pathname } = useLocation(); -const ExploitDetails = () => ( - ({title: findingInfo.malwareName})} - detailsContent={DetailsContent} + const { id } = data; + + return ( + , + }, + { + id: "assets", + title: "Assets", + path: MALWARE_DETAILS_PATHS.ASSET_LIST, + component: () => , + }, + ]} + withInnerPadding={false} /> -) + ); +}; + +const ExploitDetails = () => ( + ({ title: findingInfo.malwareName })} + detailsContent={DetailsContent} + /> +); export default ExploitDetails; diff --git a/ui/src/layout/Findings/Malware/MalwareTable.jsx b/ui/src/layout/Findings/Malware/MalwareTable.jsx index a7fee1f14e..5b8fc54379 100644 --- a/ui/src/layout/Findings/Malware/MalwareTable.jsx +++ b/ui/src/layout/Findings/Malware/MalwareTable.jsx @@ -1,65 +1,80 @@ -import React, { useMemo } from 'react'; -import { getScanColumnsConfigList } from 'layout/Findings/utils'; -import { FILTER_TYPES } from 'context/FiltersProvider'; -import { OPERATORS } from 'components/Filter'; -import FindingsTablePage from '../FindingsTablePage'; +import React, { useMemo } from "react"; +import { getScanColumnsConfigList } from "layout/Findings/utils"; +import { FILTER_TYPES } from "context/FiltersProvider"; +import { OPERATORS } from "components/Filter"; +import FindingsTablePage from "../FindingsTablePage"; const MalwareTable = () => { - const columns = useMemo(() => [ + const columns = useMemo( + () => [ + { + Header: "Malware name", + id: "name", + sortIds: ["findingInfo.malwareName"], + accessor: "findingInfo.malwareName", + }, + { + Header: "Rule name", + id: "ruleName", + sortIds: ["findingInfo.ruleName"], + accessor: "findingInfo.ruleName", + width: 200, + }, + { + Header: "File path", + id: "filePath", + sortIds: ["findingInfo.path"], + accessor: "findingInfo.path", + width: 200, + }, + ...getScanColumnsConfigList(), + ], + [], + ); + + return ( + - ) -} + ]} + tableTitle="malware" + findingsObjectType="Malware" + /> + ); +}; export default MalwareTable; diff --git a/ui/src/layout/Findings/Malware/TabMalwareDetails.jsx b/ui/src/layout/Findings/Malware/TabMalwareDetails.jsx index e3c90fac0c..83a2aaefb8 100644 --- a/ui/src/layout/Findings/Malware/TabMalwareDetails.jsx +++ b/ui/src/layout/Findings/Malware/TabMalwareDetails.jsx @@ -1,32 +1,39 @@ -import React from 'react'; -import TitleValueDisplay, { TitleValueDisplayRow } from 'components/TitleValueDisplay'; -import DoublePaneDisplay from 'components/DoublePaneDisplay'; -import { FindingsDetailsCommonFields } from '../utils'; -import AssetCountDisplay from '../AssetCountDisplay'; +import React from "react"; +import TitleValueDisplay, { + TitleValueDisplayRow, +} from "components/TitleValueDisplay"; +import DoublePaneDisplay from "components/DoublePaneDisplay"; +import { FindingsDetailsCommonFields } from "../utils"; +import AssetCountDisplay from "../AssetCountDisplay"; -const TabMalwareDetails = ({data}) => { - const {id, findingInfo, firstSeen, lastSeen} = data; - const {malwareName, path, ruleName} = findingInfo; +const TabMalwareDetails = ({ data }) => { + const { id, findingInfo, firstSeen, lastSeen } = data; + const { malwareName, path, ruleName } = findingInfo; - return ( - ( - <> - - {malwareName} - - - {path} - - - {ruleName} - - - {AssetCountDisplay(id)} - - )} - /> - ) -} + return ( + ( + <> + + + {malwareName} + + + + {path} + + + {ruleName} + + + {AssetCountDisplay(id)} + + )} + /> + ); +}; export default TabMalwareDetails; diff --git a/ui/src/layout/Findings/Malware/index.jsx b/ui/src/layout/Findings/Malware/index.jsx index e215a46e31..d3fc9fb611 100644 --- a/ui/src/layout/Findings/Malware/index.jsx +++ b/ui/src/layout/Findings/Malware/index.jsx @@ -1,11 +1,13 @@ -import React from 'react'; -import ListAndDetailsRouter from 'components/ListAndDetailsRouter'; -import MalwareTable from './MalwareTable'; -import MalwareDetails from './MalwareDetails'; +import React from "react"; +import ListAndDetailsRouter from "components/ListAndDetailsRouter"; +import MalwareTable from "./MalwareTable"; +import MalwareDetails from "./MalwareDetails"; const Malware = () => ( - -) - + +); export default Malware; diff --git a/ui/src/layout/Findings/Misconfigurations/MisconfigurationDetails.jsx b/ui/src/layout/Findings/Misconfigurations/MisconfigurationDetails.jsx index c548054dc0..7f9069c992 100644 --- a/ui/src/layout/Findings/Misconfigurations/MisconfigurationDetails.jsx +++ b/ui/src/layout/Findings/Misconfigurations/MisconfigurationDetails.jsx @@ -1,49 +1,49 @@ -import React from 'react'; -import { useLocation } from 'react-router-dom'; -import TabbedPage from 'components/TabbedPage'; -import FindingsDetailsPage from '../FindingsDetailsPage'; -import TabMisconfigurationDetails from './TabMisconfigurationDetails'; -import AssetsForFindingTable from 'layout/Assets/AssetsForFindingTable'; +import React from "react"; +import { useLocation } from "react-router-dom"; +import TabbedPage from "components/TabbedPage"; +import FindingsDetailsPage from "../FindingsDetailsPage"; +import TabMisconfigurationDetails from "./TabMisconfigurationDetails"; +import AssetsForFindingTable from "layout/Assets/AssetsForFindingTable"; const MISCONFIGURATION_DETAILS_PATHS = { - MISCONFIGURATION_DETAILS: "", - ASSET_LIST: "assets", -} + MISCONFIGURATION_DETAILS: "", + ASSET_LIST: "assets", +}; -const DetailsContent = ({data}) => { - const {pathname} = useLocation(); - - const {id} = data; - - return ( - - }, - { - id: "assets", - title: "Assets", - path: MISCONFIGURATION_DETAILS_PATHS.ASSET_LIST, - component: () => - } - ]} - withInnerPadding={false} - /> - ) -} +const DetailsContent = ({ data }) => { + const { pathname } = useLocation(); -const MisconfigurationDetails = () => ( - ({title: findingInfo.id})} - detailsContent={DetailsContent} + const { id } = data; + + return ( + , + }, + { + id: "assets", + title: "Assets", + path: MISCONFIGURATION_DETAILS_PATHS.ASSET_LIST, + component: () => , + }, + ]} + withInnerPadding={false} /> -) + ); +}; + +const MisconfigurationDetails = () => ( + ({ title: findingInfo.id })} + detailsContent={DetailsContent} + /> +); export default MisconfigurationDetails; diff --git a/ui/src/layout/Findings/Misconfigurations/MisconfigurationTable.jsx b/ui/src/layout/Findings/Misconfigurations/MisconfigurationTable.jsx index af53ca894d..8fff256012 100644 --- a/ui/src/layout/Findings/Misconfigurations/MisconfigurationTable.jsx +++ b/ui/src/layout/Findings/Misconfigurations/MisconfigurationTable.jsx @@ -1,65 +1,85 @@ -import React, { useMemo } from 'react'; -import { getScanColumnsConfigList } from 'layout/Findings/utils'; -import { FILTER_TYPES } from 'context/FiltersProvider'; -import { OPERATORS } from 'components/Filter'; -import FindingsTablePage from '../FindingsTablePage'; -import { MISCONFIGURATION_SEVERITY_MAP } from './utils'; +import React, { useMemo } from "react"; +import { getScanColumnsConfigList } from "layout/Findings/utils"; +import { FILTER_TYPES } from "context/FiltersProvider"; +import { OPERATORS } from "components/Filter"; +import FindingsTablePage from "../FindingsTablePage"; +import { MISCONFIGURATION_SEVERITY_MAP } from "./utils"; -const FILTER_SEVERITY_ITEMS = Object.keys(MISCONFIGURATION_SEVERITY_MAP) - .map(severityKey => ({value: severityKey, label: MISCONFIGURATION_SEVERITY_MAP[severityKey]})); +const FILTER_SEVERITY_ITEMS = Object.keys(MISCONFIGURATION_SEVERITY_MAP).map( + (severityKey) => ({ + value: severityKey, + label: MISCONFIGURATION_SEVERITY_MAP[severityKey], + }), +); const MisconfigurationsTable = () => { - const columns = useMemo(() => [ + const columns = useMemo( + () => [ + { + Header: "ID", + id: "id", + sortIds: ["findingInfo.id"], + accessor: "findingInfo.id", + }, + { + Header: "Severity", + id: "severity", + sortIds: ["findingInfo.severity"], + accessor: (original) => + MISCONFIGURATION_SEVERITY_MAP[original.findingInfo?.severity], + }, + { + Header: "Description", + id: "description", + sortIds: ["findingInfo.description"], + accessor: "findingInfo.description", + width: 200, + }, + ...getScanColumnsConfigList(), + ], + [], + ); + + return ( + MISCONFIGURATION_SEVERITY_MAP[original.findingInfo?.severity] + value: "findingInfo.severity", + label: "Severity", + operators: [ + { ...OPERATORS.eq, valueItems: FILTER_SEVERITY_ITEMS }, + { ...OPERATORS.ne, valueItems: FILTER_SEVERITY_ITEMS }, + ], }, { - Header: "Description", - id: "description", - sortIds: ["findingInfo.description"], - accessor: "findingInfo.description", - width: 200 + value: "findingInfo.description", + label: "Description", + operators: [ + { ...OPERATORS.eq, valueItems: [], creatable: true }, + { ...OPERATORS.ne, valueItems: [], creatable: true }, + { ...OPERATORS.startswith }, + { ...OPERATORS.endswith }, + { ...OPERATORS.contains, valueItems: [], creatable: true }, + ], }, - ...getScanColumnsConfigList() - ], []); - - return ( - - ) -} + ]} + tableTitle="misconfigurations" + findingsObjectType="Misconfiguration" + /> + ); +}; export default MisconfigurationsTable; diff --git a/ui/src/layout/Findings/Misconfigurations/TabMisconfigurationDetails.jsx b/ui/src/layout/Findings/Misconfigurations/TabMisconfigurationDetails.jsx index 07688e113b..f5f88ea4f3 100644 --- a/ui/src/layout/Findings/Misconfigurations/TabMisconfigurationDetails.jsx +++ b/ui/src/layout/Findings/Misconfigurations/TabMisconfigurationDetails.jsx @@ -1,42 +1,66 @@ -import React from 'react'; -import TitleValueDisplay, { TitleValueDisplayRow } from 'components/TitleValueDisplay'; -import DoublePaneDisplay from 'components/DoublePaneDisplay'; -import { FindingsDetailsCommonFields } from '../utils'; -import { MISCONFIGURATION_SEVERITY_MAP } from './utils'; -import AssetCountDisplay from '../AssetCountDisplay'; +import React from "react"; +import TitleValueDisplay, { + TitleValueDisplayRow, +} from "components/TitleValueDisplay"; +import DoublePaneDisplay from "components/DoublePaneDisplay"; +import { FindingsDetailsCommonFields } from "../utils"; +import { MISCONFIGURATION_SEVERITY_MAP } from "./utils"; +import AssetCountDisplay from "../AssetCountDisplay"; -const TabMisconfigurationDetails = ({data}) => { - const {id: findingId, findingInfo, firstSeen, lastSeen} = data; - const {id, severity, description, scannerName, location, remediation, category, message} = findingInfo; +const TabMisconfigurationDetails = ({ data }) => { + const { id: findingId, findingInfo, firstSeen, lastSeen } = data; + const { + id, + severity, + description, + scannerName, + location, + remediation, + category, + message, + } = findingInfo; - return ( - ( - <> - - {id} - {MISCONFIGURATION_SEVERITY_MAP[severity]} - - - {scannerName} - {location} - - - {category} - {remediation} - - - {message} - - - {description} - - - {AssetCountDisplay(findingId)} - - )} - /> - ) -} + return ( + ( + <> + + {id} + + {MISCONFIGURATION_SEVERITY_MAP[severity]} + + + + + {scannerName} + + {location} + + + {category} + + {remediation} + + + + + {message} + + + + + {description} + + + + {AssetCountDisplay(findingId)} + + )} + /> + ); +}; export default TabMisconfigurationDetails; diff --git a/ui/src/layout/Findings/Misconfigurations/index.jsx b/ui/src/layout/Findings/Misconfigurations/index.jsx index 667b41d629..fe5fa1dea2 100644 --- a/ui/src/layout/Findings/Misconfigurations/index.jsx +++ b/ui/src/layout/Findings/Misconfigurations/index.jsx @@ -1,11 +1,13 @@ -import React from 'react'; -import ListAndDetailsRouter from 'components/ListAndDetailsRouter'; -import MisconfigurationTable from './MisconfigurationTable'; -import MisconfigurationDetails from './MisconfigurationDetails'; +import React from "react"; +import ListAndDetailsRouter from "components/ListAndDetailsRouter"; +import MisconfigurationTable from "./MisconfigurationTable"; +import MisconfigurationDetails from "./MisconfigurationDetails"; const Misconfigurations = () => ( - -) + +); - -export default Misconfigurations; \ No newline at end of file +export default Misconfigurations; diff --git a/ui/src/layout/Findings/Misconfigurations/utils.js b/ui/src/layout/Findings/Misconfigurations/utils.js index 560e12193a..a9b5e23d07 100644 --- a/ui/src/layout/Findings/Misconfigurations/utils.js +++ b/ui/src/layout/Findings/Misconfigurations/utils.js @@ -1,5 +1,5 @@ export const MISCONFIGURATION_SEVERITY_MAP = { - MisconfigurationHighSeverity: "High", - MisconfigurationMediumSeverity: "Medium", - MisconfigurationLowSeverity: "Low" + MisconfigurationHighSeverity: "High", + MisconfigurationMediumSeverity: "Medium", + MisconfigurationLowSeverity: "Low", }; diff --git a/ui/src/layout/Findings/Packages/PackageDetails.jsx b/ui/src/layout/Findings/Packages/PackageDetails.jsx index 80941b089f..f606c09aa7 100644 --- a/ui/src/layout/Findings/Packages/PackageDetails.jsx +++ b/ui/src/layout/Findings/Packages/PackageDetails.jsx @@ -1,49 +1,52 @@ -import React from 'react'; -import { useLocation } from 'react-router-dom'; -import TabbedPage from 'components/TabbedPage'; -import FindingsDetailsPage from '../FindingsDetailsPage'; -import TabPackageDetails from './TabPackageDetails'; -import AssetsForFindingTable from 'layout/Assets/AssetsForFindingTable'; +import React from "react"; +import { useLocation } from "react-router-dom"; +import TabbedPage from "components/TabbedPage"; +import FindingsDetailsPage from "../FindingsDetailsPage"; +import TabPackageDetails from "./TabPackageDetails"; +import AssetsForFindingTable from "layout/Assets/AssetsForFindingTable"; const PACKAGE_DETAILS_PATHS = { - PACKAGE_DETAILS: "", - ASSET_LIST: "assets", -} + PACKAGE_DETAILS: "", + ASSET_LIST: "assets", +}; -const DetailsContent = ({data}) => { - const {pathname} = useLocation(); +const DetailsContent = ({ data }) => { + const { pathname } = useLocation(); - const {id} = data; + const { id } = data; - return ( - - }, - { - id: "assets", - title: "Assets", - path: PACKAGE_DETAILS_PATHS.ASSET_LIST, - component: () => - } - ]} - withInnerPadding={false} - /> - ) -} + return ( + , + }, + { + id: "assets", + title: "Assets", + path: PACKAGE_DETAILS_PATHS.ASSET_LIST, + component: () => , + }, + ]} + withInnerPadding={false} + /> + ); +}; const PackageDetails = () => ( - ({title: findingInfo.name, subTitle: findingInfo.version})} - detailsContent={DetailsContent} - /> -) + ({ + title: findingInfo.name, + subTitle: findingInfo.version, + })} + detailsContent={DetailsContent} + /> +); export default PackageDetails; diff --git a/ui/src/layout/Findings/Packages/PackagesTable.jsx b/ui/src/layout/Findings/Packages/PackagesTable.jsx index 1c08f300e2..aa781581c4 100644 --- a/ui/src/layout/Findings/Packages/PackagesTable.jsx +++ b/ui/src/layout/Findings/Packages/PackagesTable.jsx @@ -1,85 +1,102 @@ -import React, { useMemo } from 'react'; -import ExpandableList from 'components/ExpandableList'; -import { OPERATORS } from 'components/Filter'; -import { getScanColumnsConfigList } from 'layout/Findings/utils'; -import { FILTER_TYPES } from 'context/FiltersProvider'; -import FindingsTablePage from '../FindingsTablePage'; +import React, { useMemo } from "react"; +import ExpandableList from "components/ExpandableList"; +import { OPERATORS } from "components/Filter"; +import { getScanColumnsConfigList } from "layout/Findings/utils"; +import { FILTER_TYPES } from "context/FiltersProvider"; +import FindingsTablePage from "../FindingsTablePage"; import { - getVulnerabilitiesColumnConfigItem, - vulnerabilitiesCountersColumnsFiltersConfig + getVulnerabilitiesColumnConfigItem, + vulnerabilitiesCountersColumnsFiltersConfig, } from "../../../utils/utils.jsx"; const PackagesTable = () => { - const columns = useMemo(() => [ + const columns = useMemo( + () => [ + { + Header: "Package name", + id: "name", + sortIds: ["findingInfo.name"], + accessor: "findingInfo.name", + }, + { + Header: "Version", + id: "version", + sortIds: ["findingInfo.version"], + accessor: "findingInfo.version", + }, + { + Header: "Language", + id: "language", + sortIds: ["findingInfo.language"], + accessor: "findingInfo.language", + }, + { + Header: "Licenses", + id: "licenses", + sortIds: ["findingInfo.licenses"], + Cell: ({ row }) => { + const { licenses } = row.original.findingInfo || {}; + + return ; + }, + }, + getVulnerabilitiesColumnConfigItem("package"), + ...getScanColumnsConfigList(), + ], + [], + ); + + return ( + { - const {licenses} = row.original.findingInfo || {}; - - return ( - - ) - } + value: "findingInfo.licenses", + label: "License", + operators: [ + { ...OPERATORS.contains, valueItems: [], creatable: true }, + ], }, - getVulnerabilitiesColumnConfigItem("package"), - ...getScanColumnsConfigList(), - ], []); - - return ( - - ) -} + ...vulnerabilitiesCountersColumnsFiltersConfig, + ]} + tableTitle="packages" + findingsObjectType="Package" + /> + ); +}; export default PackagesTable; diff --git a/ui/src/layout/Findings/Packages/TabPackageDetails.jsx b/ui/src/layout/Findings/Packages/TabPackageDetails.jsx index 6513689433..3c816dfd6c 100644 --- a/ui/src/layout/Findings/Packages/TabPackageDetails.jsx +++ b/ui/src/layout/Findings/Packages/TabPackageDetails.jsx @@ -1,68 +1,82 @@ -import React from 'react'; -import TitleValueDisplay, { TitleValueDisplayRow, ValuesListDisplay } from 'components/TitleValueDisplay'; -import DoublePaneDisplay from 'components/DoublePaneDisplay'; -import { FindingsDetailsCommonFields } from '../utils'; +import React from "react"; +import TitleValueDisplay, { + TitleValueDisplayRow, + ValuesListDisplay, +} from "components/TitleValueDisplay"; +import DoublePaneDisplay from "components/DoublePaneDisplay"; +import { FindingsDetailsCommonFields } from "../utils"; import Title from "../../../components/Title/index.jsx"; import LinksList from "../../../components/LinksList/index.jsx"; -import {useLocation} from "react-router-dom"; -import {FILTER_TYPES, setFilters, useFilterDispatch} from "../../../context/FiltersProvider.js"; -import {ROUTES} from "../../../utils/systemConsts.js"; +import { useLocation } from "react-router-dom"; +import { + FILTER_TYPES, + setFilters, + useFilterDispatch, +} from "../../../context/FiltersProvider.js"; +import { ROUTES } from "../../../utils/systemConsts.js"; import VulnerabilitiesDisplay from "../../../components/VulnerabilitiesDisplay/index.jsx"; -import AssetCountDisplay from '../AssetCountDisplay'; +import AssetCountDisplay from "../AssetCountDisplay"; -const TabPackageDetails = ({data}) => { - const {pathname} = useLocation(); - const filtersDispatch = useFilterDispatch(); +const TabPackageDetails = ({ data }) => { + const { pathname } = useLocation(); + const filtersDispatch = useFilterDispatch(); - const {id, findingInfo, firstSeen, lastSeen, summary} = data; - const {totalVulnerabilities} = summary || {}; - const {name, version, language, licenses} = findingInfo; + const { id, findingInfo, firstSeen, lastSeen, summary } = data; + const { totalVulnerabilities } = summary || {}; + const { name, version, language, licenses } = findingInfo; - const onVulnerabilitiesClick = () => { - setFilters(filtersDispatch, { - type: FILTER_TYPES.FINDINGS_VULNERABILITIES, - filters: { - filter: `findingInfo/package/name eq '${name}' and findingInfo/package/version eq '${version}'`, - name: `Package ${id}`, - suffix: "finding", - backPath: pathname - }, - isSystem: true - }); - } + const onVulnerabilitiesClick = () => { + setFilters(filtersDispatch, { + type: FILTER_TYPES.FINDINGS_VULNERABILITIES, + filters: { + filter: `findingInfo/package/name eq '${name}' and findingInfo/package/version eq '${version}'`, + name: `Package ${id}`, + suffix: "finding", + backPath: pathname, + }, + isSystem: true, + }); + }; - return ( - ( - <> - - {name} - {version} - - - {language} - - - - {AssetCountDisplay(id)} - - )} - rightPlaneDisplay={() => ( - <> - Package Vulnerabilities - , - callback: onVulnerabilitiesClick - } - ]} - /> - - )} - /> - ) -} + return ( + ( + <> + + {name} + {version} + + + {language} + + + + + + {AssetCountDisplay(id)} + + )} + rightPlaneDisplay={() => ( + <> + Package Vulnerabilities + ( + + ), + callback: onVulnerabilitiesClick, + }, + ]} + /> + + )} + /> + ); +}; export default TabPackageDetails; diff --git a/ui/src/layout/Findings/Packages/index.jsx b/ui/src/layout/Findings/Packages/index.jsx index 78550fe46e..1532a6e21e 100644 --- a/ui/src/layout/Findings/Packages/index.jsx +++ b/ui/src/layout/Findings/Packages/index.jsx @@ -1,11 +1,13 @@ -import React from 'react'; -import ListAndDetailsRouter from 'components/ListAndDetailsRouter'; -import PackagesTable from './PackagesTable'; -import PackageDetails from './PackageDetails'; +import React from "react"; +import ListAndDetailsRouter from "components/ListAndDetailsRouter"; +import PackagesTable from "./PackagesTable"; +import PackageDetails from "./PackageDetails"; const Packages = () => ( - -) + +); - -export default Packages; \ No newline at end of file +export default Packages; diff --git a/ui/src/layout/Findings/Rootkits/RootkitDetails.jsx b/ui/src/layout/Findings/Rootkits/RootkitDetails.jsx index cc05c81d2d..6f58e73958 100644 --- a/ui/src/layout/Findings/Rootkits/RootkitDetails.jsx +++ b/ui/src/layout/Findings/Rootkits/RootkitDetails.jsx @@ -1,49 +1,49 @@ -import React from 'react'; -import { useLocation } from 'react-router-dom'; -import TabbedPage from 'components/TabbedPage'; -import FindingsDetailsPage from '../FindingsDetailsPage'; -import TabRootkitDetails from './TabRootkitDetails'; -import AssetsForFindingTable from 'layout/Assets/AssetsForFindingTable'; +import React from "react"; +import { useLocation } from "react-router-dom"; +import TabbedPage from "components/TabbedPage"; +import FindingsDetailsPage from "../FindingsDetailsPage"; +import TabRootkitDetails from "./TabRootkitDetails"; +import AssetsForFindingTable from "layout/Assets/AssetsForFindingTable"; const ROOTKIT_DETAILS_PATHS = { - ROOTKIT_DETAILS: "", - ASSET_LIST: "assets", -} + ROOTKIT_DETAILS: "", + ASSET_LIST: "assets", +}; -const DetailsContent = ({data}) => { - const {pathname} = useLocation(); +const DetailsContent = ({ data }) => { + const { pathname } = useLocation(); - const {id} = data; + const { id } = data; - return ( - - }, - { - id: "assets", - title: "Assets", - path: ROOTKIT_DETAILS_PATHS.ASSET_LIST, - component: () => - } - ]} - withInnerPadding={false} - /> - ) -} + return ( + , + }, + { + id: "assets", + title: "Assets", + path: ROOTKIT_DETAILS_PATHS.ASSET_LIST, + component: () => , + }, + ]} + withInnerPadding={false} + /> + ); +}; const RootkitDetails = () => ( - ({title: findingInfo.rootkitName})} - detailsContent={DetailsContent} - /> -) + ({ title: findingInfo.rootkitName })} + detailsContent={DetailsContent} + /> +); export default RootkitDetails; diff --git a/ui/src/layout/Findings/Rootkits/RootkitsTable.jsx b/ui/src/layout/Findings/Rootkits/RootkitsTable.jsx index 3954a2b8ac..f08e091cd1 100644 --- a/ui/src/layout/Findings/Rootkits/RootkitsTable.jsx +++ b/ui/src/layout/Findings/Rootkits/RootkitsTable.jsx @@ -1,50 +1,61 @@ -import React, { useMemo } from 'react'; -import { getScanColumnsConfigList } from 'layout/Findings/utils'; -import { FILTER_TYPES } from 'context/FiltersProvider'; -import { OPERATORS } from 'components/Filter'; -import FindingsTablePage from '../FindingsTablePage'; +import React, { useMemo } from "react"; +import { getScanColumnsConfigList } from "layout/Findings/utils"; +import { FILTER_TYPES } from "context/FiltersProvider"; +import { OPERATORS } from "components/Filter"; +import FindingsTablePage from "../FindingsTablePage"; const RootkitsTable = () => { - const columns = useMemo(() => [ + const columns = useMemo( + () => [ + { + Header: "Rootkit name", + id: "rootkitName", + sortIds: ["findingInfo.rootkitName"], + accessor: "findingInfo.rootkitName", + }, + { + Header: "Message", + id: "message", + sortIds: ["findingInfo.message"], + accessor: "findingInfo.message", + }, + ...getScanColumnsConfigList(), + ], + [], + ); + + return ( + - ) -} + ]} + tableTitle="rootkits" + findingsObjectType="Rootkit" + /> + ); +}; export default RootkitsTable; diff --git a/ui/src/layout/Findings/Rootkits/TabRootkitDetails.jsx b/ui/src/layout/Findings/Rootkits/TabRootkitDetails.jsx index f93d5b45c9..b8b4df88d9 100644 --- a/ui/src/layout/Findings/Rootkits/TabRootkitDetails.jsx +++ b/ui/src/layout/Findings/Rootkits/TabRootkitDetails.jsx @@ -1,29 +1,36 @@ -import React from 'react'; -import TitleValueDisplay, { TitleValueDisplayRow } from 'components/TitleValueDisplay'; -import DoublePaneDisplay from 'components/DoublePaneDisplay'; -import { FindingsDetailsCommonFields } from '../utils'; -import AssetCountDisplay from '../AssetCountDisplay'; +import React from "react"; +import TitleValueDisplay, { + TitleValueDisplayRow, +} from "components/TitleValueDisplay"; +import DoublePaneDisplay from "components/DoublePaneDisplay"; +import { FindingsDetailsCommonFields } from "../utils"; +import AssetCountDisplay from "../AssetCountDisplay"; -const TabPackageDetails = ({data}) => { - const {id, findingInfo, firstSeen, lastSeen} = data; - const {rootkitName, message} = findingInfo; +const TabPackageDetails = ({ data }) => { + const { id, findingInfo, firstSeen, lastSeen } = data; + const { rootkitName, message } = findingInfo; - return ( - ( - <> - - {rootkitName} - - - {message} - - - {AssetCountDisplay(id)} - - )} - /> - ) -} + return ( + ( + <> + + + {rootkitName} + + + + {message} + + + {AssetCountDisplay(id)} + + )} + /> + ); +}; export default TabPackageDetails; diff --git a/ui/src/layout/Findings/Rootkits/index.jsx b/ui/src/layout/Findings/Rootkits/index.jsx index e641baf58f..68e03a52fb 100644 --- a/ui/src/layout/Findings/Rootkits/index.jsx +++ b/ui/src/layout/Findings/Rootkits/index.jsx @@ -1,11 +1,13 @@ -import React from 'react'; -import ListAndDetailsRouter from 'components/ListAndDetailsRouter'; -import RootkitsTable from './RootkitsTable'; -import RootkitDetails from './RootkitDetails'; +import React from "react"; +import ListAndDetailsRouter from "components/ListAndDetailsRouter"; +import RootkitsTable from "./RootkitsTable"; +import RootkitDetails from "./RootkitDetails"; const Rootkits = () => ( - -) + +); - -export default Rootkits; \ No newline at end of file +export default Rootkits; diff --git a/ui/src/layout/Findings/Secrets/SecretDetails.jsx b/ui/src/layout/Findings/Secrets/SecretDetails.jsx index 9e2e4c1c53..67c2f17d28 100644 --- a/ui/src/layout/Findings/Secrets/SecretDetails.jsx +++ b/ui/src/layout/Findings/Secrets/SecretDetails.jsx @@ -1,49 +1,49 @@ -import React from 'react'; -import { useLocation } from 'react-router-dom'; -import TabbedPage from 'components/TabbedPage'; -import FindingsDetailsPage from '../FindingsDetailsPage'; -import TabSecretDetails from './TabSecretDetails'; -import AssetsForFindingTable from 'layout/Assets/AssetsForFindingTable'; +import React from "react"; +import { useLocation } from "react-router-dom"; +import TabbedPage from "components/TabbedPage"; +import FindingsDetailsPage from "../FindingsDetailsPage"; +import TabSecretDetails from "./TabSecretDetails"; +import AssetsForFindingTable from "layout/Assets/AssetsForFindingTable"; const SECRET_DETAILS_PATHS = { - SECRET_DETAILS: "", - ASSET_LIST: "assets", -} + SECRET_DETAILS: "", + ASSET_LIST: "assets", +}; -const DetailsContent = ({data}) => { - const {pathname} = useLocation(); +const DetailsContent = ({ data }) => { + const { pathname } = useLocation(); - const {id} = data; + const { id } = data; - return ( - - }, - { - id: "assets", - title: "Assets", - path: SECRET_DETAILS_PATHS.ASSET_LIST, - component: () => - } - ]} - withInnerPadding={false} - /> - ) -} + return ( + , + }, + { + id: "assets", + title: "Assets", + path: SECRET_DETAILS_PATHS.ASSET_LIST, + component: () => , + }, + ]} + withInnerPadding={false} + /> + ); +}; const SecretDetails = () => ( - ({title: findingInfo.fingerprint})} - detailsContent={DetailsContent} - /> -) + ({ title: findingInfo.fingerprint })} + detailsContent={DetailsContent} + /> +); export default SecretDetails; diff --git a/ui/src/layout/Findings/Secrets/SecretsTable.jsx b/ui/src/layout/Findings/Secrets/SecretsTable.jsx index ae66304c8e..77578142b8 100644 --- a/ui/src/layout/Findings/Secrets/SecretsTable.jsx +++ b/ui/src/layout/Findings/Secrets/SecretsTable.jsx @@ -1,65 +1,80 @@ -import React, { useMemo } from 'react'; -import { getScanColumnsConfigList } from 'layout/Findings/utils'; -import { FILTER_TYPES } from 'context/FiltersProvider'; -import { OPERATORS } from 'components/Filter'; -import FindingsTablePage from '../FindingsTablePage'; +import React, { useMemo } from "react"; +import { getScanColumnsConfigList } from "layout/Findings/utils"; +import { FILTER_TYPES } from "context/FiltersProvider"; +import { OPERATORS } from "components/Filter"; +import FindingsTablePage from "../FindingsTablePage"; const SecretsTable = () => { - const columns = useMemo(() => [ + const columns = useMemo( + () => [ + { + Header: "Fingerprint", + id: "fingerprint", + sortIds: ["findingInfo.fingerprint"], + accessor: "findingInfo.fingerprint", + width: 200, + }, + { + Header: "Description", + id: "description", + sortIds: ["findingInfo.description"], + accessor: "findingInfo.description", + }, + { + Header: "File path", + id: "findingInfo", + sortIds: ["findingInfo.filePath"], + accessor: "findingInfo.filePath", + width: 200, + }, + ...getScanColumnsConfigList(), + ], + [], + ); + + return ( + - ) -} + ]} + tableTitle="secrets" + findingsObjectType="Secret" + /> + ); +}; -export default SecretsTable; \ No newline at end of file +export default SecretsTable; diff --git a/ui/src/layout/Findings/Secrets/TabSecretDetails.jsx b/ui/src/layout/Findings/Secrets/TabSecretDetails.jsx index 28789a657b..aa030b5071 100644 --- a/ui/src/layout/Findings/Secrets/TabSecretDetails.jsx +++ b/ui/src/layout/Findings/Secrets/TabSecretDetails.jsx @@ -1,34 +1,46 @@ -import React from 'react'; -import TitleValueDisplay, { TitleValueDisplayRow } from 'components/TitleValueDisplay'; -import DoublePaneDisplay from 'components/DoublePaneDisplay'; -import { FindingsDetailsCommonFields } from '../utils'; -import AssetCountDisplay from '../AssetCountDisplay'; +import React from "react"; +import TitleValueDisplay, { + TitleValueDisplayRow, +} from "components/TitleValueDisplay"; +import DoublePaneDisplay from "components/DoublePaneDisplay"; +import { FindingsDetailsCommonFields } from "../utils"; +import AssetCountDisplay from "../AssetCountDisplay"; -const TabSecretDetails = ({data}) => { - const {id, findingInfo, firstSeen, lastSeen} = data; - const {fingerprint, description, startLine, endLine, filePath} = findingInfo; +const TabSecretDetails = ({ data }) => { + const { id, findingInfo, firstSeen, lastSeen } = data; + const { fingerprint, description, startLine, endLine, filePath } = + findingInfo; - return ( - ( - <> - - {fingerprint} - {description} - - - {startLine} - {endLine} - - - {filePath} - - - {AssetCountDisplay(id)} - - )} - /> - ) -} + return ( + ( + <> + + + {fingerprint} + + + {description} + + + + + {startLine} + + {endLine} + + + {filePath} + + + {AssetCountDisplay(id)} + + )} + /> + ); +}; export default TabSecretDetails; diff --git a/ui/src/layout/Findings/Secrets/index.jsx b/ui/src/layout/Findings/Secrets/index.jsx index 699c9c07a5..5450655ecd 100644 --- a/ui/src/layout/Findings/Secrets/index.jsx +++ b/ui/src/layout/Findings/Secrets/index.jsx @@ -1,11 +1,13 @@ -import React from 'react'; -import ListAndDetailsRouter from 'components/ListAndDetailsRouter'; -import SecretsTable from './SecretsTable'; -import SecretDetails from './SecretDetails'; +import React from "react"; +import ListAndDetailsRouter from "components/ListAndDetailsRouter"; +import SecretsTable from "./SecretsTable"; +import SecretDetails from "./SecretDetails"; const Secrets = () => ( - -) + +); - -export default Secrets; \ No newline at end of file +export default Secrets; diff --git a/ui/src/layout/Findings/Vulnerabilities/ScoreTag/index.jsx b/ui/src/layout/Findings/Vulnerabilities/ScoreTag/index.jsx index b82364d302..afe453a723 100644 --- a/ui/src/layout/Findings/Vulnerabilities/ScoreTag/index.jsx +++ b/ui/src/layout/Findings/Vulnerabilities/ScoreTag/index.jsx @@ -1,24 +1,28 @@ -import React from 'react'; -import Icon, { ICON_NAMES } from 'components/Icon'; -import { toCapitalized } from 'utils/utils'; +import React from "react"; +import Icon, { ICON_NAMES } from "components/Icon"; +import { toCapitalized } from "utils/utils"; -import './score-tag.scss'; +import "./score-tag.scss"; const SCORE_ITEMS = { - NONE: {value: "NONE"}, - HIGH: {value: "HIGH", icon: ICON_NAMES.ARROW_UP}, - LOW: {value: "LOW", icon: ICON_NAMES.ARROW_UP, style: {transform: "rotate(180deg)"}} -} + NONE: { value: "NONE" }, + HIGH: { value: "HIGH", icon: ICON_NAMES.ARROW_UP }, + LOW: { + value: "LOW", + icon: ICON_NAMES.ARROW_UP, + style: { transform: "rotate(180deg)" }, + }, +}; -const ScoreTag = ({score}) => { - const {icon, style} = SCORE_ITEMS[score] || {}; +const ScoreTag = ({ score }) => { + const { icon, style } = SCORE_ITEMS[score] || {}; - return ( -
-
{toCapitalized(score)}
- {!!icon && } -
- ) -} + return ( +
+
{toCapitalized(score)}
+ {!!icon && } +
+ ); +}; -export default ScoreTag; \ No newline at end of file +export default ScoreTag; diff --git a/ui/src/layout/Findings/Vulnerabilities/ScoreTag/score-tag.scss b/ui/src/layout/Findings/Vulnerabilities/ScoreTag/score-tag.scss index ebd899e594..b38d1ce9b7 100644 --- a/ui/src/layout/Findings/Vulnerabilities/ScoreTag/score-tag.scss +++ b/ui/src/layout/Findings/Vulnerabilities/ScoreTag/score-tag.scss @@ -1,10 +1,10 @@ -@import 'utils/scss_variables.module.scss'; +@import "utils/scss_variables.module.scss"; .score-tag { - display: flex; - align-items: center; + display: flex; + align-items: center; - .icon { - margin-left: 5px; - } -} \ No newline at end of file + .icon { + margin-left: 5px; + } +} diff --git a/ui/src/layout/Findings/Vulnerabilities/TabVulnerabilityDetails.jsx b/ui/src/layout/Findings/Vulnerabilities/TabVulnerabilityDetails.jsx index eeb5417260..c62114d29a 100644 --- a/ui/src/layout/Findings/Vulnerabilities/TabVulnerabilityDetails.jsx +++ b/ui/src/layout/Findings/Vulnerabilities/TabVulnerabilityDetails.jsx @@ -1,107 +1,178 @@ -import React from 'react'; -import TitleValueDisplay, { TitleValueDisplayRow, ValuesListDisplay } from 'components/TitleValueDisplay'; -import DoublePaneDisplay from 'components/DoublePaneDisplay'; -import Title from 'components/Title'; -import ProgressBar, { STATUS_MAPPPING } from 'components/ProgressBar'; -import SeverityDisplay from 'components/SeverityDisplay'; -import SeverityWithCvssDisplay from 'components/SeverityWithCvssDisplay'; -import { getHigestVersionCvssData } from 'utils/utils'; -import LinksDisplay from 'layout/Findings/LinkesDisplay'; -import ScoreTag from './ScoreTag'; -import { FindingsDetailsCommonFields } from '../utils'; -import AssetCountDisplay from '../AssetCountDisplay'; +import React from "react"; +import TitleValueDisplay, { + TitleValueDisplayRow, + ValuesListDisplay, +} from "components/TitleValueDisplay"; +import DoublePaneDisplay from "components/DoublePaneDisplay"; +import Title from "components/Title"; +import ProgressBar, { STATUS_MAPPPING } from "components/ProgressBar"; +import SeverityDisplay from "components/SeverityDisplay"; +import SeverityWithCvssDisplay from "components/SeverityWithCvssDisplay"; +import { getHigestVersionCvssData } from "utils/utils"; +import LinksDisplay from "layout/Findings/LinkesDisplay"; +import ScoreTag from "./ScoreTag"; +import { FindingsDetailsCommonFields } from "../utils"; +import AssetCountDisplay from "../AssetCountDisplay"; -import COLORS from 'utils/scss_variables.module.scss'; +import COLORS from "utils/scss_variables.module.scss"; -const Separator = () =>
+const Separator = () => ( +
+); -const ScoreBar = ({score}) => { - if (!score) { - return null; - } +const ScoreBar = ({ score }) => { + if (!score) { + return null; + } - return ( - - ) -} + return ( + + ); +}; -const TabVulnerabilityDetails = ({data}) => { - const {id, findingInfo, firstSeen, lastSeen} = data; - const {vulnerabilityName, package: packageInfo, severity, fix, cvss, description, links} = findingInfo; - const {name: packageName, version} = packageInfo; +const TabVulnerabilityDetails = ({ data }) => { + const { id, findingInfo, firstSeen, lastSeen } = data; + const { + vulnerabilityName, + package: packageInfo, + severity, + fix, + cvss, + description, + links, + } = findingInfo; + const { name: packageName, version } = packageInfo; - const {score, severity: cvssSeverity, vector, metrics, exploitabilityScore, impactScore} = getHigestVersionCvssData(cvss); - const {S={}, AV={}, AC={}, PR={}, UI={}, I={}, A={}, C={}} = metrics || {}; + const { + score, + severity: cvssSeverity, + vector, + metrics, + exploitabilityScore, + impactScore, + } = getHigestVersionCvssData(cvss); + const { + S = {}, + AV = {}, + AC = {}, + PR = {}, + UI = {}, + I = {}, + A = {}, + C = {}, + } = metrics || {}; - return ( - ( - <> - General details - - {vulnerabilityName} - {packageName} - {version} - - - - - - - - - {description} - - - - {AssetCountDisplay(id)} - - )} - rightPlaneDisplay={() => ( - <> - CVSS - - - {!!cvssSeverity && } - - {S.value} - {vector} - - - - - {AV.value} - {!!metrics && } - - - {PR.value} - {UI.value} - {/* empty item for spacing */} - - - - - {!!metrics && } - {!!metrics && } - - - {!!metrics && } - - - )} - /> - ) -} + return ( + ( + <> + General details + + + {vulnerabilityName} + + + {packageName} + + + {version} + + + + + + + + + + + + + {description} + + + + + {AssetCountDisplay(id)} + + )} + rightPlaneDisplay={() => ( + <> + CVSS + + + {!!cvssSeverity && ( + + )} + + {S.value} + {vector} + + + + + + + + + + {AV.value} + + + {!!metrics && } + + + + + {PR.value} + + + {UI.value} + + + {/* empty item for spacing */} + + + + + + + + + + + {!!metrics && } + + + {!!metrics && } + + + + + {!!metrics && } + + + + )} + /> + ); +}; export default TabVulnerabilityDetails; diff --git a/ui/src/layout/Findings/Vulnerabilities/VulnerabilitiesTable.jsx b/ui/src/layout/Findings/Vulnerabilities/VulnerabilitiesTable.jsx index 72e20cbe53..bef02247ef 100644 --- a/ui/src/layout/Findings/Vulnerabilities/VulnerabilitiesTable.jsx +++ b/ui/src/layout/Findings/Vulnerabilities/VulnerabilitiesTable.jsx @@ -1,112 +1,134 @@ -import React, { useMemo } from 'react'; -import ExpandableList from 'components/ExpandableList'; -import SeverityWithCvssDisplay, { SEVERITY_ITEMS } from 'components/SeverityWithCvssDisplay'; -import { OPERATORS } from 'components/Filter'; -import { getHigestVersionCvssData, toCapitalized } from 'utils/utils'; -import { getScanColumnsConfigList } from 'layout/Findings/utils'; -import { FILTER_TYPES } from 'context/FiltersProvider'; -import FindingsTablePage from '../FindingsTablePage'; +import React, { useMemo } from "react"; +import ExpandableList from "components/ExpandableList"; +import SeverityWithCvssDisplay, { + SEVERITY_ITEMS, +} from "components/SeverityWithCvssDisplay"; +import { OPERATORS } from "components/Filter"; +import { getHigestVersionCvssData, toCapitalized } from "utils/utils"; +import { getScanColumnsConfigList } from "layout/Findings/utils"; +import { FILTER_TYPES } from "context/FiltersProvider"; +import FindingsTablePage from "../FindingsTablePage"; -const FILTER_SEVERITY_ITEMS = Object.values(SEVERITY_ITEMS).filter(({valueKey}) => valueKey !== SEVERITY_ITEMS.NONE.valueKey) - .map(({valueKey}) => ({value: valueKey, label: toCapitalized(valueKey)})); +const FILTER_SEVERITY_ITEMS = Object.values(SEVERITY_ITEMS) + .filter(({ valueKey }) => valueKey !== SEVERITY_ITEMS.NONE.valueKey) + .map(({ valueKey }) => ({ value: valueKey, label: toCapitalized(valueKey) })); -const FILTER_FIX_AVAILABLE_ITEMS = [ - {value: "fixed", label: "available"} -] +const FILTER_FIX_AVAILABLE_ITEMS = [{ value: "fixed", label: "available" }]; const VulnerabilitiesTable = () => { - const columns = useMemo(() => [ + const columns = useMemo( + () => [ + { + Header: "Vulnerability name", + id: "name", + sortIds: ["findingInfo.vulnerabilityName"], + accessor: "findingInfo.vulnerabilityName", + }, + { + Header: "Severity", + id: "severity", + sortIds: ["findingInfo.severity"], + Cell: ({ row }) => { + const { id, findingInfo } = row.original; + const { severity, cvss } = findingInfo || {}; + const cvssScoreData = getHigestVersionCvssData(cvss); + + return ( + + ); + }, + }, + { + Header: "Package name", + id: "packageName", + sortIds: ["findingInfo.package.name"], + accessor: "findingInfo.package.name", + }, + { + Header: "Package version", + id: "packageVersion", + sortIds: ["findingInfo.package.version"], + accessor: "findingInfo.package.version", + }, + { + Header: "Fix versions", + id: "fixVersions", + sortIds: ["findingInfo.fix"], + Cell: ({ row }) => { + const { versions } = row.original.findingInfo?.fix || {}; + + return ; + }, + }, + ...getScanColumnsConfigList(), + ], + [], + ); + + return ( + { - const {id, findingInfo} = row.original; - const {severity, cvss} = findingInfo || {}; - const cvssScoreData = getHigestVersionCvssData(cvss); - - return ( - - ) - } + value: "findingInfo.severity", + label: "Vulnerability severity", + operators: [ + { ...OPERATORS.eq, valueItems: FILTER_SEVERITY_ITEMS }, + { ...OPERATORS.ne, valueItems: FILTER_SEVERITY_ITEMS }, + ], }, { - Header: "Package name", - id: "packageName", - sortIds: ["findingInfo.package.name"], - accessor: "findingInfo.package.name" + value: "findingInfo.package.name", + label: "Package name", + operators: [ + { ...OPERATORS.eq, valueItems: [], creatable: true }, + { ...OPERATORS.ne, valueItems: [], creatable: true }, + { ...OPERATORS.startswith }, + { ...OPERATORS.endswith }, + { ...OPERATORS.contains, valueItems: [], creatable: true }, + ], }, { - Header: "Package version", - id: "packageVersion", - sortIds: ["findingInfo.package.version"], - accessor: "findingInfo.package.version" + value: "findingInfo.package.version", + label: "Package version", + operators: [ + { ...OPERATORS.eq, valueItems: [], creatable: true }, + { ...OPERATORS.ne, valueItems: [], creatable: true }, + { ...OPERATORS.startswith }, + { ...OPERATORS.endswith }, + { ...OPERATORS.contains, valueItems: [], creatable: true }, + ], }, { - Header: "Fix versions", - id: "fixVersions", - sortIds: ["findingInfo.fix"], - Cell: ({row}) => { - const {versions} = row.original.findingInfo?.fix || {}; - - return ( - - ) - } + value: "findingInfo.fix.state", + label: "Fix version", + operators: [ + { ...OPERATORS.eq, valueItems: FILTER_FIX_AVAILABLE_ITEMS }, + { ...OPERATORS.ne, valueItems: FILTER_FIX_AVAILABLE_ITEMS }, + ], }, - ...getScanColumnsConfigList() - ], []); - - return ( - - ) -} + ]} + tableTitle="vulnerabilities" + findingsObjectType="Vulnerability" + /> + ); +}; export default VulnerabilitiesTable; diff --git a/ui/src/layout/Findings/Vulnerabilities/VulnerabilityDetails.jsx b/ui/src/layout/Findings/Vulnerabilities/VulnerabilityDetails.jsx index e79281fb9b..2bf6b94ac9 100644 --- a/ui/src/layout/Findings/Vulnerabilities/VulnerabilityDetails.jsx +++ b/ui/src/layout/Findings/Vulnerabilities/VulnerabilityDetails.jsx @@ -1,49 +1,51 @@ -import React from 'react'; -import { useLocation } from 'react-router-dom'; -import TabbedPage from 'components/TabbedPage'; -import FindingsDetailsPage from '../FindingsDetailsPage'; -import TabVulnerabilityDetails from './TabVulnerabilityDetails'; -import AssetsForFindingTable from 'layout/Assets/AssetsForFindingTable'; +import React from "react"; +import { useLocation } from "react-router-dom"; +import TabbedPage from "components/TabbedPage"; +import FindingsDetailsPage from "../FindingsDetailsPage"; +import TabVulnerabilityDetails from "./TabVulnerabilityDetails"; +import AssetsForFindingTable from "layout/Assets/AssetsForFindingTable"; const VULNERABILITY_DETAILS_PATHS = { - VULNERABILITY_DETAILS: "", - ASSET_LIST: "assets", -} + VULNERABILITY_DETAILS: "", + ASSET_LIST: "assets", +}; -const DetailsContent = ({data}) => { - const {pathname} = useLocation(); +const DetailsContent = ({ data }) => { + const { pathname } = useLocation(); - const {id} = data; + const { id } = data; - return ( - - }, - { - id: "assets", - title: "Assets", - path: VULNERABILITY_DETAILS_PATHS.ASSET_LIST, - component: () => - } - ]} - withInnerPadding={false} - /> - ) -} + return ( + , + }, + { + id: "assets", + title: "Assets", + path: VULNERABILITY_DETAILS_PATHS.ASSET_LIST, + component: () => , + }, + ]} + withInnerPadding={false} + /> + ); +}; const VulnerabilityDetails = () => ( - ({title: findingInfo.vulnerabilityName})} - detailsContent={DetailsContent} - /> -) + ({ + title: findingInfo.vulnerabilityName, + })} + detailsContent={DetailsContent} + /> +); export default VulnerabilityDetails; diff --git a/ui/src/layout/Findings/Vulnerabilities/index.jsx b/ui/src/layout/Findings/Vulnerabilities/index.jsx index 1a3f37b830..07ad16a8c0 100644 --- a/ui/src/layout/Findings/Vulnerabilities/index.jsx +++ b/ui/src/layout/Findings/Vulnerabilities/index.jsx @@ -1,11 +1,13 @@ -import React from 'react'; -import ListAndDetailsRouter from 'components/ListAndDetailsRouter'; -import VulnerabilitiesTable from './VulnerabilitiesTable'; -import VulnerabilityDetails from './VulnerabilityDetails'; +import React from "react"; +import ListAndDetailsRouter from "components/ListAndDetailsRouter"; +import VulnerabilitiesTable from "./VulnerabilitiesTable"; +import VulnerabilityDetails from "./VulnerabilityDetails"; const Vulnerabilities = () => ( - -) - + +); export default Vulnerabilities; diff --git a/ui/src/layout/Findings/index.jsx b/ui/src/layout/Findings/index.jsx index 70565bda53..f5475aff33 100644 --- a/ui/src/layout/Findings/index.jsx +++ b/ui/src/layout/Findings/index.jsx @@ -1,85 +1,94 @@ -import React from 'react'; -import { useLocation } from 'react-router-dom'; -import TabbedPage from 'components/TabbedPage'; -import { FINDINGS_MAPPING, VULNERABIITY_FINDINGS_ITEM, ROUTES } from 'utils/systemConsts'; -import Vulnerabilities from './Vulnerabilities'; -import Exploits from './Exploits'; -import Misconfigurations from './Misconfigurations'; -import Secrets from './Secrets'; -import Malware from './Malware'; -import Rootkits from './Rootkits'; -import Packages from './Packages'; +import React from "react"; +import { useLocation } from "react-router-dom"; +import TabbedPage from "components/TabbedPage"; +import { + FINDINGS_MAPPING, + VULNERABIITY_FINDINGS_ITEM, + ROUTES, +} from "utils/systemConsts"; +import Vulnerabilities from "./Vulnerabilities"; +import Exploits from "./Exploits"; +import Misconfigurations from "./Misconfigurations"; +import Secrets from "./Secrets"; +import Malware from "./Malware"; +import Rootkits from "./Rootkits"; +import Packages from "./Packages"; const FINDINGS_TAB_ITEMS = { - VULNERABILITIES: { - id: "vulnerabilities", - path: "vulnerabilities", - title: "Vulnerabilities", - component: Vulnerabilities, - findingsType: VULNERABIITY_FINDINGS_ITEM.value - }, - EXPLOITS: { - id: "exploits", - path: "exploits", - title: "Exploits", - component: Exploits, - findingsType: FINDINGS_MAPPING.EXPLOITS.value - }, - MISCONFIGURATIONS: { - id: "misconfigurations", - path: "misconfigurations", - title: "Misconfigurations", - component: Misconfigurations, - findingsType: FINDINGS_MAPPING.MISCONFIGURATIONS.value - }, - SECRETS: { - id: "secrets", - path: "secrets", - title: "Secrets", - component: Secrets, - findingsType: FINDINGS_MAPPING.SECRETS.value - }, - MALWARE: { - id: "malware", - path: "malware", - title: "Malware", - component: Malware, - findingsType: FINDINGS_MAPPING.MALWARE.value - }, - ROOTKITS: { - id: "rootkits", - path: "rootkits", - title: "Rootkits", - component: Rootkits, - findingsType: FINDINGS_MAPPING.ROOTKITS.value - }, - PACKAGES: { - id: "packages", - path: "packages", - title: "Packages", - component: Packages, - findingsType: FINDINGS_MAPPING.PACKAGES.value - } -} + VULNERABILITIES: { + id: "vulnerabilities", + path: "vulnerabilities", + title: "Vulnerabilities", + component: Vulnerabilities, + findingsType: VULNERABIITY_FINDINGS_ITEM.value, + }, + EXPLOITS: { + id: "exploits", + path: "exploits", + title: "Exploits", + component: Exploits, + findingsType: FINDINGS_MAPPING.EXPLOITS.value, + }, + MISCONFIGURATIONS: { + id: "misconfigurations", + path: "misconfigurations", + title: "Misconfigurations", + component: Misconfigurations, + findingsType: FINDINGS_MAPPING.MISCONFIGURATIONS.value, + }, + SECRETS: { + id: "secrets", + path: "secrets", + title: "Secrets", + component: Secrets, + findingsType: FINDINGS_MAPPING.SECRETS.value, + }, + MALWARE: { + id: "malware", + path: "malware", + title: "Malware", + component: Malware, + findingsType: FINDINGS_MAPPING.MALWARE.value, + }, + ROOTKITS: { + id: "rootkits", + path: "rootkits", + title: "Rootkits", + component: Rootkits, + findingsType: FINDINGS_MAPPING.ROOTKITS.value, + }, + PACKAGES: { + id: "packages", + path: "packages", + title: "Packages", + component: Packages, + findingsType: FINDINGS_MAPPING.PACKAGES.value, + }, +}; -export const FINDINGS_PATHS = Object.keys(FINDINGS_TAB_ITEMS).reduce((acc, curr) => ({...acc, [curr]: FINDINGS_TAB_ITEMS[curr].path}), {}); +export const FINDINGS_PATHS = Object.keys(FINDINGS_TAB_ITEMS).reduce( + (acc, curr) => ({ ...acc, [curr]: FINDINGS_TAB_ITEMS[curr].path }), + {}, +); -export const getFindingsAbsolutePathByFindingType = type => { - const relativePath = Object.values(FINDINGS_TAB_ITEMS).find(({findingsType}) => type === findingsType)?.path; +export const getFindingsAbsolutePathByFindingType = (type) => { + const relativePath = Object.values(FINDINGS_TAB_ITEMS).find( + ({ findingsType }) => type === findingsType, + )?.path; - return `${ROUTES.FINDINGS}/${relativePath}` + return `${ROUTES.FINDINGS}/${relativePath}`; }; const Findings = () => { - const {pathname} = useLocation(); - - return ( - - ) -} + const { pathname } = useLocation(); + + return ( + + ); +}; export default Findings; diff --git a/ui/src/layout/Findings/utils.jsx b/ui/src/layout/Findings/utils.jsx index 7e14e5b142..20ead8d5c2 100644 --- a/ui/src/layout/Findings/utils.jsx +++ b/ui/src/layout/Findings/utils.jsx @@ -1,27 +1,32 @@ -import TitleValueDisplay, { TitleValueDisplayRow } from 'components/TitleValueDisplay'; -import { formatDate } from 'utils/utils'; +import TitleValueDisplay, { + TitleValueDisplayRow, +} from "components/TitleValueDisplay"; +import { formatDate } from "utils/utils"; - -export const getScanColumnsConfigList = () => ([ - { - Header: "First seen", - id: "firstSeen", - sortIds: ["firstSeen"], - accessor: original => formatDate(original.firstSeen), - }, - { - Header: "Last seen", - id: "lastSeen", - sortIds: ["lastSeen"], - accessor: original => formatDate(original.lastSeen) - } -]); +export const getScanColumnsConfigList = () => [ + { + Header: "First seen", + id: "firstSeen", + sortIds: ["firstSeen"], + accessor: (original) => formatDate(original.firstSeen), + }, + { + Header: "Last seen", + id: "lastSeen", + sortIds: ["lastSeen"], + accessor: (original) => formatDate(original.lastSeen), + }, +]; export const FindingsDetailsCommonFields = ({ firstSeen, lastSeen }) => ( - <> - - {formatDate(firstSeen)} - {formatDate(lastSeen)} - - -) + <> + + + {formatDate(firstSeen)} + + + {formatDate(lastSeen)} + + + +); diff --git a/ui/src/layout/Scans/ConfigurationReadOnlyDisplay.jsx b/ui/src/layout/Scans/ConfigurationReadOnlyDisplay.jsx index d3304ebb08..07cfefa6e9 100644 --- a/ui/src/layout/Scans/ConfigurationReadOnlyDisplay.jsx +++ b/ui/src/layout/Scans/ConfigurationReadOnlyDisplay.jsx @@ -1,64 +1,100 @@ -import React from 'react'; -import TitleValueDisplay, { ValuesListDisplay, TitleValueDisplayRow } from 'components/TitleValueDisplay'; -import Title from 'components/Title'; -import { getEnabledScanTypesList, getScanTimeTypeTag } from 'layout/Scans/utils'; -import { cronExpressionToHuman, formatDate } from 'utils/utils'; +import React from "react"; +import TitleValueDisplay, { + ValuesListDisplay, + TitleValueDisplayRow, +} from "components/TitleValueDisplay"; +import Title from "components/Title"; +import { + getEnabledScanTypesList, + getScanTimeTypeTag, +} from "layout/Scans/utils"; +import { cronExpressionToHuman, formatDate } from "utils/utils"; -const FlagPropDisplay = ({checked, label}) =>
{`${label} ${checked ? "enabled" : "disabled"}`}
+const FlagPropDisplay = ({ checked, label }) => ( +
{`${label} ${checked ? "enabled" : "disabled"}`}
+); -const ConfigurationReadOnlyDisplay = ({configData}) => { - const {scanTemplate, scheduled} = configData; - const {scope, maxParallelScanners, assetScanTemplate} = scanTemplate; - const {scannerInstanceCreationConfig, scanFamiliesConfig} = assetScanTemplate; - const {cronLine, operationTime} = scheduled; - const {useSpotInstances} = scannerInstanceCreationConfig || {}; +const ConfigurationReadOnlyDisplay = ({ configData }) => { + const { scanTemplate, scheduled } = configData; + const { scope, maxParallelScanners, assetScanTemplate } = scanTemplate; + const { scannerInstanceCreationConfig, scanFamiliesConfig } = + assetScanTemplate; + const { cronLine, operationTime } = scheduled; + const { useSpotInstances } = scannerInstanceCreationConfig || {}; - return ( + return ( + <> + Schedule + <> - Schedule - - <> -
{getScanTimeTypeTag({cronLine, operationTime})}
-
{!!cronLine ? cronExpressionToHuman(cronLine) : formatDate(operationTime)}
- -
- Scan Configuration - - {scope} - {maxParallelScanners} - - Asset Scan Configuration - - - - - - +
+ {getScanTimeTypeTag({ cronLine, operationTime })} +
+
+ {!!cronLine + ? cronExpressionToHuman(cronLine) + : formatDate(operationTime)} +
- ) -} +
+ Scan Configuration + + {scope} + + {maxParallelScanners} + + + Asset Scan Configuration + + + + + + + + + + ); +}; -export const ScanReadOnlyDisplay = ({scanData}) => { - const {scope, maxParallelScanners, assetScanTemplate} = scanData; - const {scannerInstanceCreationConfig, scanFamiliesConfig} = assetScanTemplate; - const {useSpotInstances} = scannerInstanceCreationConfig || {}; +export const ScanReadOnlyDisplay = ({ scanData }) => { + const { scope, maxParallelScanners, assetScanTemplate } = scanData; + const { scannerInstanceCreationConfig, scanFamiliesConfig } = + assetScanTemplate; + const { useSpotInstances } = scannerInstanceCreationConfig || {}; - return ( - <> - Scan Configuration - - {scope} - {maxParallelScanners} - - Asset Scan Configuration - - - - - - - - ) -} + return ( + <> + Scan Configuration + + {scope} + + {maxParallelScanners} + + + Asset Scan Configuration + + + + + + + + + + ); +}; export default ConfigurationReadOnlyDisplay; diff --git a/ui/src/layout/Scans/Configurations/ConfigurationActionsDisplay/configuration-actions-display.scss b/ui/src/layout/Scans/Configurations/ConfigurationActionsDisplay/configuration-actions-display.scss index 25c754643b..03858efe60 100644 --- a/ui/src/layout/Scans/Configurations/ConfigurationActionsDisplay/configuration-actions-display.scss +++ b/ui/src/layout/Scans/Configurations/ConfigurationActionsDisplay/configuration-actions-display.scss @@ -1,12 +1,12 @@ -@import 'utils/scss_variables.module.scss'; +@import "utils/scss_variables.module.scss"; .configuration-actions-display { - display: flex; + display: flex; - .icon:not(:disabled) { - color: $color-main; - } - .icon-with-tooltip-wrapper:not(:last-child) { - margin-right: 5px; - } -} \ No newline at end of file + .icon:not(:disabled) { + color: $color-main; + } + .icon-with-tooltip-wrapper:not(:last-child) { + margin-right: 5px; + } +} diff --git a/ui/src/layout/Scans/Configurations/ConfigurationActionsDisplay/index.jsx b/ui/src/layout/Scans/Configurations/ConfigurationActionsDisplay/index.jsx index dc41e8d2dd..c7d19396be 100644 --- a/ui/src/layout/Scans/Configurations/ConfigurationActionsDisplay/index.jsx +++ b/ui/src/layout/Scans/Configurations/ConfigurationActionsDisplay/index.jsx @@ -1,117 +1,140 @@ -import React, { useEffect, useState } from 'react'; -import { isNull } from 'lodash'; -import { useDelete, usePrevious, useFetch, FETCH_METHODS } from 'hooks'; -import { ICON_NAMES } from 'components/Icon'; -import IconWithTooltip from 'components/IconWithTooltip'; -import Modal from 'components/Modal'; -import { BoldText } from 'utils/utils'; -import { APIS } from 'utils/systemConsts'; -import { useModalDisplayDispatch, MODAL_DISPLAY_ACTIONS } from 'layout/Scans/ScanConfigWizardModal/ModalDisplayProvider'; +import React, { useEffect, useState } from "react"; +import { isNull } from "lodash"; +import { useDelete, usePrevious, useFetch, FETCH_METHODS } from "hooks"; +import { ICON_NAMES } from "components/Icon"; +import IconWithTooltip from "components/IconWithTooltip"; +import Modal from "components/Modal"; +import { BoldText } from "utils/utils"; +import { APIS } from "utils/systemConsts"; +import { + useModalDisplayDispatch, + MODAL_DISPLAY_ACTIONS, +} from "layout/Scans/ScanConfigWizardModal/ModalDisplayProvider"; -import './configuration-actions-display.scss'; +import "./configuration-actions-display.scss"; -const ConfigurationActionsDisplay = ({data, onDelete, onUpdate}) => { - const modalDisplayDispatch = useModalDisplayDispatch(); - const setScanConfigFormData = (data) => modalDisplayDispatch({type: MODAL_DISPLAY_ACTIONS.SET_MODAL_DISPLAY_DATA, payload: data}); - - const {id, scheduled} = data; - const {operationTime, cronLine} = scheduled || {}; +const ConfigurationActionsDisplay = ({ data, onDelete, onUpdate }) => { + const modalDisplayDispatch = useModalDisplayDispatch(); + const setScanConfigFormData = (data) => + modalDisplayDispatch({ + type: MODAL_DISPLAY_ACTIONS.SET_MODAL_DISPLAY_DATA, + payload: data, + }); - const disableStartScan = (Date.now() - (new Date(operationTime)).valueOf() <= 0) && !cronLine; + const { id, scheduled } = data; + const { operationTime, cronLine } = scheduled || {}; - const [deleteConfigmationData, setDeleteConfigmationData] = useState(null); - const closeDeleteConfigmation = () => setDeleteConfigmationData(null); + const disableStartScan = + Date.now() - new Date(operationTime).valueOf() <= 0 && !cronLine; - const [{deleting}, deleteConfiguration] = useDelete(APIS.SCAN_CONFIGS); - const prevDeleting = usePrevious(deleting); + const [deleteConfigmationData, setDeleteConfigmationData] = useState(null); + const closeDeleteConfigmation = () => setDeleteConfigmationData(null); - const [{loading, error}, fetchScanConfig] = useFetch(APIS.SCAN_CONFIGS, {loadOnMount: false}); - const prevLoading = usePrevious(loading); + const [{ deleting }, deleteConfiguration] = useDelete(APIS.SCAN_CONFIGS); + const prevDeleting = usePrevious(deleting); - useEffect(() => { - if (prevDeleting && !deleting) { - onDelete(); - } - }, [prevDeleting, deleting, onDelete]); + const [{ loading, error }, fetchScanConfig] = useFetch(APIS.SCAN_CONFIGS, { + loadOnMount: false, + }); + const prevLoading = usePrevious(loading); - useEffect(() => { - if (!!onUpdate && prevLoading && !loading && !error) { - onUpdate(); - } - }, [prevLoading, loading, error, onUpdate]); + useEffect(() => { + if (prevDeleting && !deleting) { + onDelete(); + } + }, [prevDeleting, deleting, onDelete]); - return ( - <> -
- { - event.stopPropagation(); - event.preventDefault(); - - fetchScanConfig({ - method: FETCH_METHODS.PATCH, - submitData: {scheduled: {...scheduled, operationTime: (new Date()).toISOString()}, disabled: false}, - formatUrl: url => `${url}/${id}` - }); - }} - disabled={disableStartScan} - /> - { - event.stopPropagation(); - event.preventDefault(); - - setScanConfigFormData({...data, id: null, name: ""}); - }} - /> - { - event.stopPropagation(); - event.preventDefault(); - - setScanConfigFormData(data); - }} - /> - { - event.stopPropagation(); - event.preventDefault(); + useEffect(() => { + if (!!onUpdate && prevLoading && !loading && !error) { + onUpdate(); + } + }, [prevLoading, loading, error, onUpdate]); - setDeleteConfigmationData(data); - }} - /> -
- {!isNull(deleteConfigmationData) && - { - deleteConfiguration(deleteConfigmationData.id); - closeDeleteConfigmation(); - }} - > - {`Once `}{deleteConfigmationData.name}{` will be deleted, the action cannot be reverted`}
- {`Are you sure you want to delete ${deleteConfigmationData.name}?`} -
- } - - ); -} + return ( + <> +
+ { + event.stopPropagation(); + event.preventDefault(); -export default ConfigurationActionsDisplay; \ No newline at end of file + fetchScanConfig({ + method: FETCH_METHODS.PATCH, + submitData: { + scheduled: { + ...scheduled, + operationTime: new Date().toISOString(), + }, + disabled: false, + }, + formatUrl: (url) => `${url}/${id}`, + }); + }} + disabled={disableStartScan} + /> + { + event.stopPropagation(); + event.preventDefault(); + + setScanConfigFormData({ ...data, id: null, name: "" }); + }} + /> + { + event.stopPropagation(); + event.preventDefault(); + + setScanConfigFormData(data); + }} + /> + { + event.stopPropagation(); + event.preventDefault(); + + setDeleteConfigmationData(data); + }} + /> +
+ {!isNull(deleteConfigmationData) && ( + { + deleteConfiguration(deleteConfigmationData.id); + closeDeleteConfigmation(); + }} + > + {`Once `} + {deleteConfigmationData.name} + {` will be deleted, the action cannot be reverted`} +
+ {`Are you sure you want to delete ${deleteConfigmationData.name}?`} +
+ )} + + ); +}; + +export default ConfigurationActionsDisplay; diff --git a/ui/src/layout/Scans/Configurations/ConfigurationDetails/TabConfiguration.jsx b/ui/src/layout/Scans/Configurations/ConfigurationDetails/TabConfiguration.jsx index 6d7fc8645a..fa679e3636 100644 --- a/ui/src/layout/Scans/Configurations/ConfigurationDetails/TabConfiguration.jsx +++ b/ui/src/layout/Scans/Configurations/ConfigurationDetails/TabConfiguration.jsx @@ -1,74 +1,82 @@ -import React from 'react'; -import { useNavigate, useLocation } from 'react-router-dom'; -import { useFetch } from 'hooks'; -import { TitleValueDisplayColumn } from 'components/TitleValueDisplay'; -import DoublePaneDisplay from 'components/DoublePaneDisplay'; -import Button from 'components/Button'; -import Title from 'components/Title'; -import Loader from 'components/Loader'; -import ConfigurationReadOnlyDisplay from 'layout/Scans/ConfigurationReadOnlyDisplay'; -import { ROUTES, APIS } from 'utils/systemConsts'; -import { formatNumber } from 'utils/utils'; -import { useFilterDispatch, setFilters, FILTER_TYPES } from 'context/FiltersProvider'; +import React from "react"; +import { useNavigate, useLocation } from "react-router-dom"; +import { useFetch } from "hooks"; +import { TitleValueDisplayColumn } from "components/TitleValueDisplay"; +import DoublePaneDisplay from "components/DoublePaneDisplay"; +import Button from "components/Button"; +import Title from "components/Title"; +import Loader from "components/Loader"; +import ConfigurationReadOnlyDisplay from "layout/Scans/ConfigurationReadOnlyDisplay"; +import { ROUTES, APIS } from "utils/systemConsts"; +import { formatNumber } from "utils/utils"; +import { + useFilterDispatch, + setFilters, + FILTER_TYPES, +} from "context/FiltersProvider"; -const ConfigurationScansDisplay = ({configId, configName}) => { - const {pathname} = useLocation(); - const navigate = useNavigate(); - const filtersDispatch = useFilterDispatch(); +const ConfigurationScansDisplay = ({ configId, configName }) => { + const { pathname } = useLocation(); + const navigate = useNavigate(); + const filtersDispatch = useFilterDispatch(); - const scansFilter = `scanConfig/id eq '${configId}'`; + const scansFilter = `scanConfig/id eq '${configId}'`; - const onScansClick = () => { - setFilters(filtersDispatch, { - type: FILTER_TYPES.SCANS, - filters: { - filter: scansFilter, - name: configName, - suffix: "configuration", - backPath: pathname - }, - isSystem: true - }); + const onScansClick = () => { + setFilters(filtersDispatch, { + type: FILTER_TYPES.SCANS, + filters: { + filter: scansFilter, + name: configName, + suffix: "configuration", + backPath: pathname, + }, + isSystem: true, + }); - navigate(ROUTES.SCANS); - } + navigate(ROUTES.SCANS); + }; - const [{loading, data, error}] = useFetch(APIS.SCANS, {queryParams: {"$filter": scansFilter, "$count": true}}); - - if (error) { - return null; - } + const [{ loading, data, error }] = useFetch(APIS.SCANS, { + queryParams: { $filter: scansFilter, $count: true }, + }); - if (loading) { - return ; - } - - return ( + if (error) { + return null; + } + + if (loading) { + return ; + } + + return ( + <> + Configuration's scans + + + ); +}; + +const TabConfiguration = ({ data }) => { + const { id, name } = data || {}; + + return ( + ( <> - Configuration's scans - + Configuration + + + - ) -} - -const TabConfiguration = ({data}) => { - const {id, name} = data || {}; - - return ( - ( - <> - Configuration - - - - - )} - rightPlaneDisplay={() => ( - - )} - /> - ) -} + )} + rightPlaneDisplay={() => ( + + )} + /> + ); +}; export default TabConfiguration; diff --git a/ui/src/layout/Scans/Configurations/ConfigurationDetails/configuration-details.scss b/ui/src/layout/Scans/Configurations/ConfigurationDetails/configuration-details.scss index 4e5285e1a5..031df3a03b 100644 --- a/ui/src/layout/Scans/Configurations/ConfigurationDetails/configuration-details.scss +++ b/ui/src/layout/Scans/Configurations/ConfigurationDetails/configuration-details.scss @@ -1,14 +1,14 @@ -@import 'utils/scss_variables.module.scss'; +@import "utils/scss_variables.module.scss"; .configuration-details-page-wrapper { - .configuration-details-content { - .configuration-details-content-header { - height: 40px; - border-bottom: 1px solid $color-grey-light; - display: flex; - justify-content: flex-end; - align-items: center; - padding-right: $main-content-padding; - } + .configuration-details-content { + .configuration-details-content-header { + height: 40px; + border-bottom: 1px solid $color-grey-light; + display: flex; + justify-content: flex-end; + align-items: center; + padding-right: $main-content-padding; } -} \ No newline at end of file + } +} diff --git a/ui/src/layout/Scans/Configurations/ConfigurationDetails/index.jsx b/ui/src/layout/Scans/Configurations/ConfigurationDetails/index.jsx index 1cb5159418..17226e65d0 100644 --- a/ui/src/layout/Scans/Configurations/ConfigurationDetails/index.jsx +++ b/ui/src/layout/Scans/Configurations/ConfigurationDetails/index.jsx @@ -1,45 +1,45 @@ -import React from 'react'; -import { useLocation, useNavigate, useParams } from 'react-router-dom'; -import DetailsPageWrapper from 'components/DetailsPageWrapper'; -import { APIS } from 'utils/systemConsts'; -import ConfigurationActionsDisplay from '../ConfigurationActionsDisplay'; -import TabConfiguration from './TabConfiguration'; +import React from "react"; +import { useLocation, useNavigate, useParams } from "react-router-dom"; +import DetailsPageWrapper from "components/DetailsPageWrapper"; +import { APIS } from "utils/systemConsts"; +import ConfigurationActionsDisplay from "../ConfigurationActionsDisplay"; +import TabConfiguration from "./TabConfiguration"; -import './configuration-details.scss'; +import "./configuration-details.scss"; export const SCAN_CONFIGS_SCAN_TAB_PATH = "scans"; -const DetailsContent = ({data, fetchData}) => { - const navigate = useNavigate(); - const {pathname} = useLocation(); - const params = useParams(); +const DetailsContent = ({ data, fetchData }) => { + const navigate = useNavigate(); + const { pathname } = useLocation(); + const params = useParams(); - const id = params["id"]; - const innerTab = params["*"]; + const id = params["id"]; + const innerTab = params["*"]; - return ( -
-
- navigate(pathname.replace(`/${id}/${innerTab}`, ""))} - onUpdate={fetchData} - /> -
- -
- ) -} + return ( +
+
+ navigate(pathname.replace(`/${id}/${innerTab}`, ""))} + onUpdate={fetchData} + /> +
+ +
+ ); +}; const ConfigurationDetails = () => ( - ({title: data?.name})} - detailsContent={DetailsContent} - /> -) + ({ title: data?.name })} + detailsContent={DetailsContent} + /> +); export default ConfigurationDetails; diff --git a/ui/src/layout/Scans/Configurations/ConfigurationsTable/configurations-table.scss b/ui/src/layout/Scans/Configurations/ConfigurationsTable/configurations-table.scss index f5ff589f04..1e1bc9bf69 100644 --- a/ui/src/layout/Scans/Configurations/ConfigurationsTable/configurations-table.scss +++ b/ui/src/layout/Scans/Configurations/ConfigurationsTable/configurations-table.scss @@ -1,13 +1,13 @@ -@import 'utils/scss_variables.module.scss'; +@import "utils/scss_variables.module.scss"; .modal-inner-wrapper.scan-config-delete-confirmation { - padding: 30px; + padding: 30px; - .modal-title { - color: $color-main; - } - .clarity-button.modal-submit-button { - background-color: $color-error; - color: white; - } -} \ No newline at end of file + .modal-title { + color: $color-main; + } + .clarity-button.modal-submit-button { + background-color: $color-error; + color: white; + } +} diff --git a/ui/src/layout/Scans/Configurations/ConfigurationsTable/index.jsx b/ui/src/layout/Scans/Configurations/ConfigurationsTable/index.jsx index 46ae5980fc..4e627d52c1 100644 --- a/ui/src/layout/Scans/Configurations/ConfigurationsTable/index.jsx +++ b/ui/src/layout/Scans/Configurations/ConfigurationsTable/index.jsx @@ -1,166 +1,206 @@ -import React, { useCallback, useMemo, useState } from 'react'; -import { isNull } from 'lodash'; -import ButtonWithIcon from 'components/ButtonWithIcon'; -import { ICON_NAMES } from 'components/Icon'; -import EmptyDisplay from 'components/EmptyDisplay'; -import TablePage from 'components/TablePage'; -import { OPERATORS } from 'components/Filter'; -import { BoldText, toCapitalized, formatDate } from 'utils/utils'; -import { APIS } from 'utils/systemConsts'; -import { getScanTimeTypeTag } from 'layout/Scans/utils'; -import { useModalDisplayDispatch, MODAL_DISPLAY_ACTIONS } from 'layout/Scans/ScanConfigWizardModal/ModalDisplayProvider'; -import { FILTER_TYPES } from 'context/FiltersProvider'; -import ConfigurationActionsDisplay from '../ConfigurationActionsDisplay'; +import React, { useCallback, useMemo, useState } from "react"; +import { isNull } from "lodash"; +import ButtonWithIcon from "components/ButtonWithIcon"; +import { ICON_NAMES } from "components/Icon"; +import EmptyDisplay from "components/EmptyDisplay"; +import TablePage from "components/TablePage"; +import { OPERATORS } from "components/Filter"; +import { BoldText, toCapitalized, formatDate } from "utils/utils"; +import { APIS } from "utils/systemConsts"; +import { getScanTimeTypeTag } from "layout/Scans/utils"; +import { + useModalDisplayDispatch, + MODAL_DISPLAY_ACTIONS, +} from "layout/Scans/ScanConfigWizardModal/ModalDisplayProvider"; +import { FILTER_TYPES } from "context/FiltersProvider"; +import ConfigurationActionsDisplay from "../ConfigurationActionsDisplay"; -import './configurations-table.scss'; +import "./configurations-table.scss"; const TABLE_TITLE = "scan configurations"; const SCAN_TYPES_FILTER_ITEMS = [ - "vulnerabilities", - "exploits", - "malware", - "misconfigurations", - "rootkits", - "secrets", - "infoFinder", - "sbom" -].map(type => ({value: `scanTemplate.assetScanTemplate.scanFamiliesConfig.${type}.enabled`, label: toCapitalized(type)})); + "vulnerabilities", + "exploits", + "malware", + "misconfigurations", + "rootkits", + "secrets", + "infoFinder", + "sbom", +].map((type) => ({ + value: `scanTemplate.assetScanTemplate.scanFamiliesConfig.${type}.enabled`, + label: toCapitalized(type), +})); -const formatScanTypesToOdata = (valuesList, operator) => ( - valuesList.map(value => `(${value} eq ${operator === OPERATORS.contains.value ? "true" : "false"})`).join(` or `) -) +const formatScanTypesToOdata = (valuesList, operator) => + valuesList + .map( + (value) => + `(${value} eq ${operator === OPERATORS.contains.value ? "true" : "false"})`, + ) + .join(` or `); const ConfigurationsTable = () => { - const modalDisplayDispatch = useModalDisplayDispatch(); - const setScanConfigFormData = (data) => modalDisplayDispatch({type: MODAL_DISPLAY_ACTIONS.SET_MODAL_DISPLAY_DATA, payload: data}); + const modalDisplayDispatch = useModalDisplayDispatch(); + const setScanConfigFormData = (data) => + modalDisplayDispatch({ + type: MODAL_DISPLAY_ACTIONS.SET_MODAL_DISPLAY_DATA, + payload: data, + }); - const columns = useMemo(() => [ - { - Header: "Name", - id: "name", - sortIds: ["name"], - accessor: "name" - }, - { - Header: "Scope", - id: "scanTemplate.scope", - sortIds: ["scanTemplate.scope"], - accessor: "scanTemplate.scope" + const columns = useMemo( + () => [ + { + Header: "Name", + id: "name", + sortIds: ["name"], + accessor: "name", + }, + { + Header: "Scope", + id: "scanTemplate.scope", + sortIds: ["scanTemplate.scope"], + accessor: "scanTemplate.scope", + }, + { + Header: "Scan time", + id: "timeConfig", + sortIds: ["scheduled.operationTime"], + Cell: ({ row }) => { + const { operationTime, cronLine } = row.original.scheduled; + const scanType = getScanTimeTypeTag({ operationTime, cronLine }); + + return ( +
+ {!!scanType && {scanType}} +
{formatDate(operationTime)}
+
+ ); }, - { - Header: "Scan time", - id: "timeConfig", - sortIds: ["scheduled.operationTime"], - Cell: ({row}) => { - const {operationTime, cronLine} = row.original.scheduled; - const scanType = getScanTimeTypeTag({operationTime, cronLine}); - - return ( -
- {!!scanType && {scanType}} -
{formatDate(operationTime)}
-
- ) - } + }, + { + Header: "Scan types", + id: "scanTypes", + sortIds: [ + "scanTemplate.assetScanTemplate.scanFamiliesConfig.exploits.enabled", + "scanTemplate.assetScanTemplate.scanFamiliesConfig.malware.enabled", + "scanTemplate.assetScanTemplate.scanFamiliesConfig.misconfigurations.enabled", + "scanTemplate.assetScanTemplate.scanFamiliesConfig.rootkits.enabled", + "scanTemplate.assetScanTemplate.scanFamiliesConfig.sbom.enabled", + "scanTemplate.assetScanTemplate.scanFamiliesConfig.secrets.enabled", + "scanTemplate.assetScanTemplate.scanFamiliesConfig.vulnerabilities.enabled", + "scanTemplate.assetScanTemplate.scanFamiliesConfig.infoFinder.enabled", + ], + Cell: ({ row }) => { + const { scanFamiliesConfig } = + row.original.scanTemplate.assetScanTemplate; + + return ( +
+ {Object.keys(scanFamiliesConfig) + .map((type) => { + const { enabled } = scanFamiliesConfig[type]; + + return enabled ? toCapitalized(type) : null; + }) + .filter((type) => !isNull(type)) + .join(" - ")} +
+ ); }, - { - Header: "Scan types", - id: "scanTypes", - sortIds: [ - "scanTemplate.assetScanTemplate.scanFamiliesConfig.exploits.enabled", - "scanTemplate.assetScanTemplate.scanFamiliesConfig.malware.enabled", - "scanTemplate.assetScanTemplate.scanFamiliesConfig.misconfigurations.enabled", - "scanTemplate.assetScanTemplate.scanFamiliesConfig.rootkits.enabled", - "scanTemplate.assetScanTemplate.scanFamiliesConfig.sbom.enabled", - "scanTemplate.assetScanTemplate.scanFamiliesConfig.secrets.enabled", - "scanTemplate.assetScanTemplate.scanFamiliesConfig.vulnerabilities.enabled", - "scanTemplate.assetScanTemplate.scanFamiliesConfig.infoFinder.enabled" - ], - Cell: ({row}) => { - const {scanFamiliesConfig} = row.original.scanTemplate.assetScanTemplate; + }, + ], + [], + ); - return ( -
- { - Object.keys(scanFamiliesConfig).map(type => { - const {enabled} = scanFamiliesConfig[type]; + const [refreshTimestamp, setRefreshTimestamp] = useState(Date()); + const doRefreshTimestamp = useCallback(() => setRefreshTimestamp(Date()), []); - return enabled ? toCapitalized(type) : null; - }).filter(type => !isNull(type)).join(" - ") - } -
- ) + return ( +
+ ( + setScanConfigFormData({})} + > + New scan configuration + + )} + refreshTimestamp={refreshTimestamp} + actionsColumnWidth={130} + actionsComponent={({ original }) => ( + + )} + customEmptyResultsDisplay={() => ( + +
No scan configurations detected.
+
+ Create your first scan configuration to see your VM's issues. +
+ } - } - ], []); - - const [refreshTimestamp, setRefreshTimestamp] = useState(Date()); - const doRefreshTimestamp = useCallback(() => setRefreshTimestamp(Date()), []); - - return ( -
- ( - setScanConfigFormData({})}> - New scan configuration - - )} - refreshTimestamp={refreshTimestamp} - actionsColumnWidth={130} - actionsComponent={({original}) => ( - - )} - customEmptyResultsDisplay={() => ( - -
No scan configurations detected.
-
Create your first scan configuration to see your VM's issues.
- - )} - title="New scan configuration" - onClick={() => setScanConfigFormData({})} - /> - )} - absoluteSystemBanner - /> -
- ) -} + title="New scan configuration" + onClick={() => setScanConfigFormData({})} + /> + )} + absoluteSystemBanner + /> +
+ ); +}; export default ConfigurationsTable; diff --git a/ui/src/layout/Scans/Configurations/index.jsx b/ui/src/layout/Scans/Configurations/index.jsx index a1fa306f4f..c20f858146 100644 --- a/ui/src/layout/Scans/Configurations/index.jsx +++ b/ui/src/layout/Scans/Configurations/index.jsx @@ -1,11 +1,13 @@ -import React from 'react'; -import ListAndDetailsRouter from 'components/ListAndDetailsRouter'; -import ConfigurationsTable from './ConfigurationsTable'; -import ConfigurationDetails from './ConfigurationDetails'; +import React from "react"; +import ListAndDetailsRouter from "components/ListAndDetailsRouter"; +import ConfigurationsTable from "./ConfigurationsTable"; +import ConfigurationDetails from "./ConfigurationDetails"; const Configurations = () => ( - -) + +); - -export default Configurations; \ No newline at end of file +export default Configurations; diff --git a/ui/src/layout/Scans/ScanConfigWizardModal/ModalDisplayProvider.js b/ui/src/layout/Scans/ScanConfigWizardModal/ModalDisplayProvider.js index 3239b93980..7f7d0d02c2 100644 --- a/ui/src/layout/Scans/ScanConfigWizardModal/ModalDisplayProvider.js +++ b/ui/src/layout/Scans/ScanConfigWizardModal/ModalDisplayProvider.js @@ -1,35 +1,32 @@ -import { create } from 'context/utils'; +import { create } from "context/utils"; const initialState = { - modalDisplayData: null + modalDisplayData: null, }; export const MODAL_DISPLAY_ACTIONS = { - SET_MODAL_DISPLAY_DATA: "SET_MODAL_DISPLAY_DATA", - CLOSE_DISPLAY_MODAL: "CLOSE_DISPLAY_MODAL" -} + SET_MODAL_DISPLAY_DATA: "SET_MODAL_DISPLAY_DATA", + CLOSE_DISPLAY_MODAL: "CLOSE_DISPLAY_MODAL", +}; const reducer = (state, action) => { - switch (action.type) { - case MODAL_DISPLAY_ACTIONS.SET_MODAL_DISPLAY_DATA: { - return { - modalDisplayData: action.payload - }; - } - case MODAL_DISPLAY_ACTIONS.CLOSE_DISPLAY_MODAL: { - return { - modalDisplayData: null - }; - } - default: - return state; + switch (action.type) { + case MODAL_DISPLAY_ACTIONS.SET_MODAL_DISPLAY_DATA: { + return { + modalDisplayData: action.payload, + }; + } + case MODAL_DISPLAY_ACTIONS.CLOSE_DISPLAY_MODAL: { + return { + modalDisplayData: null, + }; } -} + default: + return state; + } +}; -const [ModalDisplayProvider, useModalDisplayState, useModalDisplayDispatch] = create(reducer, initialState); +const [ModalDisplayProvider, useModalDisplayState, useModalDisplayDispatch] = + create(reducer, initialState); -export { - ModalDisplayProvider, - useModalDisplayState, - useModalDisplayDispatch -}; +export { ModalDisplayProvider, useModalDisplayState, useModalDisplayDispatch }; diff --git a/ui/src/layout/Scans/ScanConfigWizardModal/StepAdvancedSettings.jsx b/ui/src/layout/Scans/ScanConfigWizardModal/StepAdvancedSettings.jsx index 7bf76cef4d..1d4f56e45b 100644 --- a/ui/src/layout/Scans/ScanConfigWizardModal/StepAdvancedSettings.jsx +++ b/ui/src/layout/Scans/ScanConfigWizardModal/StepAdvancedSettings.jsx @@ -1,30 +1,37 @@ -import React from 'react'; -import { TextField, CheckboxField, validators } from 'components/Form'; +import React from "react"; +import { TextField, CheckboxField, validators } from "components/Form"; const StepAdvancedSettings = () => ( -
- - The maximum number of scanners (per instance) that that can run in parallel for each scan -
- )} - validate={validators.validateRequired} - /> - -
When selected, Spot instances will be used for the VM scanners to lower billing prices.
-
However, this can result in unexpected Asset Scan failures if a spot instance is terminated during a scan.
-
- )} - /> -
-) +
+ + The maximum number of scanners (per instance) that that can run in + parallel for each scan +
+ } + validate={validators.validateRequired} + /> + +
+ When selected, Spot instances will be used for the VM scanners to + lower billing prices. +
+
+ However, this can result in unexpected Asset Scan failures if a spot + instance is terminated during a scan. +
+
+ } + /> +
+); export default StepAdvancedSettings; diff --git a/ui/src/layout/Scans/ScanConfigWizardModal/StepGeneralProperties.jsx b/ui/src/layout/Scans/ScanConfigWizardModal/StepGeneralProperties.jsx index 63ba56a497..249235d8ea 100644 --- a/ui/src/layout/Scans/ScanConfigWizardModal/StepGeneralProperties.jsx +++ b/ui/src/layout/Scans/ScanConfigWizardModal/StepGeneralProperties.jsx @@ -1,22 +1,22 @@ -import React from 'react'; -import { TextField, validators } from 'components/Form'; +import React from "react"; +import { TextField, validators } from "components/Form"; const StepGeneralProperties = () => { - return ( -
- - -
- ) -} + return ( +
+ + +
+ ); +}; export default StepGeneralProperties; diff --git a/ui/src/layout/Scans/ScanConfigWizardModal/StepScanTypes.jsx b/ui/src/layout/Scans/ScanConfigWizardModal/StepScanTypes.jsx index d40f82bc39..021d87e9a6 100644 --- a/ui/src/layout/Scans/ScanConfigWizardModal/StepScanTypes.jsx +++ b/ui/src/layout/Scans/ScanConfigWizardModal/StepScanTypes.jsx @@ -1,20 +1,44 @@ -import React from 'react'; -import { CheckboxField, FieldLabel } from 'components/Form'; +import React from "react"; +import { CheckboxField, FieldLabel } from "components/Form"; const StepScanTypes = () => { - return ( -
- What would you like to scan for? - - - - - - - - -
- ) -} + return ( +
+ What would you like to scan for? + + + + + + + + +
+ ); +}; export default StepScanTypes; diff --git a/ui/src/layout/Scans/ScanConfigWizardModal/StepTimeConfiguration.jsx b/ui/src/layout/Scans/ScanConfigWizardModal/StepTimeConfiguration.jsx index 020908499f..11cc690677 100644 --- a/ui/src/layout/Scans/ScanConfigWizardModal/StepTimeConfiguration.jsx +++ b/ui/src/layout/Scans/ScanConfigWizardModal/StepTimeConfiguration.jsx @@ -1,72 +1,76 @@ -import React, { useEffect } from 'react'; -import { SelectField, DateField, TimeField, CronField, useFormikContext, validators } from 'components/Form'; -import { usePrevious } from 'hooks'; +import React, { useEffect } from "react"; +import { + SelectField, + DateField, + TimeField, + CronField, + useFormikContext, + validators, +} from "components/Form"; +import { usePrevious } from "hooks"; -import 'react-js-cron/dist/styles.css'; +import "react-js-cron/dist/styles.css"; export const SCHEDULE_TYPES_ITEMS = { - NOW: {value: "NOW", label: "Now"}, - LATER: {value: "LATER", label: "Specified time"}, - REPETITIVE: {value: "REPETITIVE", label: "Repetitive"} -} + NOW: { value: "NOW", label: "Now" }, + LATER: { value: "LATER", label: "Specified time" }, + REPETITIVE: { value: "REPETITIVE", label: "Repetitive" }, +}; export const CRON_QUICK_OPTIONS = [ - {value: "0 */12 * * *", label: "Every 12 hours"}, - {value: "0 0 * * *", label: "Once a day"}, - {value: "0 0 * * 5", label: "Once a week"} + { value: "0 */12 * * *", label: "Every 12 hours" }, + { value: "0 0 * * *", label: "Once a day" }, + { value: "0 0 * * 5", label: "Once a week" }, ]; const LaterFormFields = () => ( -
- - -
-) +
+ + +
+); const RepetitiveFormFields = () => ( -
- -
-) +
+ +
+); const StepTimeConfiguration = () => { - const {values, validateForm} = useFormikContext(); - - const {scheduledSelect} = values?.scheduled; - const prevScheduledSelect = usePrevious(scheduledSelect); + const { values, validateForm } = useFormikContext(); - useEffect(() => { - if (prevScheduledSelect !== scheduledSelect) { - validateForm(); - } - }, [prevScheduledSelect, scheduledSelect, validateForm]); + const { scheduledSelect } = values?.scheduled; + const prevScheduledSelect = usePrevious(scheduledSelect); - return ( -
- - {scheduledSelect === SCHEDULE_TYPES_ITEMS.LATER.value && - - } - {scheduledSelect === SCHEDULE_TYPES_ITEMS.REPETITIVE.value && - - } -
- ) -} + useEffect(() => { + if (prevScheduledSelect !== scheduledSelect) { + validateForm(); + } + }, [prevScheduledSelect, scheduledSelect, validateForm]); + + return ( +
+ + {scheduledSelect === SCHEDULE_TYPES_ITEMS.LATER.value && ( + + )} + {scheduledSelect === SCHEDULE_TYPES_ITEMS.REPETITIVE.value && ( + + )} +
+ ); +}; export default StepTimeConfiguration; diff --git a/ui/src/layout/Scans/ScanConfigWizardModal/index.jsx b/ui/src/layout/Scans/ScanConfigWizardModal/index.jsx index 74477d8ed1..6b53f5dbe9 100644 --- a/ui/src/layout/Scans/ScanConfigWizardModal/index.jsx +++ b/ui/src/layout/Scans/ScanConfigWizardModal/index.jsx @@ -1,139 +1,149 @@ -import React from 'react'; -import { FETCH_METHODS } from 'hooks'; -import WizardModal from 'components/WizardModal'; -import { APIS } from 'utils/systemConsts'; -import StepGeneralProperties from './StepGeneralProperties'; -import StepScanTypes from './StepScanTypes'; -import StepTimeConfiguration, { SCHEDULE_TYPES_ITEMS, CRON_QUICK_OPTIONS } from './StepTimeConfiguration'; -import StepAdvancedSettings from './StepAdvancedSettings'; - -import './scan-config-wizard-modal.scss'; - -const padDateTime = time => String(time).padStart(2, "0"); - -const ScanConfigWizardModal = ({initialData, onClose, onSubmitSuccess}) => { - const {id, name, scanTemplate, scheduled} = initialData || {}; - const {scope, maxParallelScanners, assetScanTemplate} = scanTemplate || {}; - const {operationTime, cronLine} = scheduled || {}; - - const {scanFamiliesConfig, scannerInstanceCreationConfig} = assetScanTemplate || {} - const {useSpotInstances} = scannerInstanceCreationConfig || {}; - - const isEditForm = !!id; - - const initialValues = { - id: id || null, - name: name || "", +import React from "react"; +import { FETCH_METHODS } from "hooks"; +import WizardModal from "components/WizardModal"; +import { APIS } from "utils/systemConsts"; +import StepGeneralProperties from "./StepGeneralProperties"; +import StepScanTypes from "./StepScanTypes"; +import StepTimeConfiguration, { + SCHEDULE_TYPES_ITEMS, + CRON_QUICK_OPTIONS, +} from "./StepTimeConfiguration"; +import StepAdvancedSettings from "./StepAdvancedSettings"; + +import "./scan-config-wizard-modal.scss"; + +const padDateTime = (time) => String(time).padStart(2, "0"); + +const ScanConfigWizardModal = ({ initialData, onClose, onSubmitSuccess }) => { + const { id, name, scanTemplate, scheduled } = initialData || {}; + const { scope, maxParallelScanners, assetScanTemplate } = scanTemplate || {}; + const { operationTime, cronLine } = scheduled || {}; + + const { scanFamiliesConfig, scannerInstanceCreationConfig } = + assetScanTemplate || {}; + const { useSpotInstances } = scannerInstanceCreationConfig || {}; + + const isEditForm = !!id; + + const initialValues = { + id: id || null, + name: name || "", + scanFamiliesConfig: { + sbom: { enabled: true }, + vulnerabilities: { enabled: true }, + malware: { enabled: false }, + rootkits: { enabled: false }, + secrets: { enabled: false }, + misconfigurations: { enabled: false }, + infoFinder: { enabled: false }, + exploits: { enabled: false }, + }, + scanTemplate: { + scope: scope || "", + maxParallelScanners: maxParallelScanners || 2, + assetScanTemplate: { scanFamiliesConfig: { - sbom: {enabled: true}, - vulnerabilities: {enabled: true}, - malware: {enabled: false}, - rootkits: {enabled: false}, - secrets: {enabled: false}, - misconfigurations: {enabled: false}, - infoFinder: {enabled: false}, - exploits: {enabled: false} + sbom: { enabled: true }, + vulnerabilities: { enabled: true }, + malware: { enabled: false }, + rootkits: { enabled: false }, + secrets: { enabled: false }, + misconfigurations: { enabled: false }, + infoFinder: { enabled: false }, + exploits: { enabled: false }, }, - scanTemplate: { - scope: scope || "", - maxParallelScanners: maxParallelScanners || 2, - assetScanTemplate: { - scanFamiliesConfig: { - sbom: {enabled: true}, - vulnerabilities: {enabled: true}, - malware: {enabled: false}, - rootkits: {enabled: false}, - secrets: {enabled: false}, - misconfigurations: {enabled: false}, - infoFinder: {enabled: false}, - exploits: {enabled: false} - }, - scannerInstanceCreationConfig: { - useSpotInstances: useSpotInstances || false - } - } + scannerInstanceCreationConfig: { + useSpotInstances: useSpotInstances || false, }, - scheduled: { - scheduledSelect: !!cronLine ? SCHEDULE_TYPES_ITEMS.REPETITIVE.value : SCHEDULE_TYPES_ITEMS.NOW.value, - laterDate: "", - laterTime: "", - cronLine: cronLine || CRON_QUICK_OPTIONS[0].value - }, - } - - if (!!operationTime && !cronLine) { - const dateTime = new Date(operationTime); - initialValues.scheduled.scheduledSelect = SCHEDULE_TYPES_ITEMS.LATER.value; - initialValues.scheduled.laterTime = `${padDateTime(dateTime.getHours())}:${padDateTime(dateTime.getMinutes())}`; - initialValues.scheduled.laterDate = `${dateTime.getFullYear()}-${padDateTime(dateTime.getMonth() + 1)}-${padDateTime(dateTime.getDate())}`; - } - - Object.keys(scanFamiliesConfig || {}).forEach(type => { - const {enabled} = scanFamiliesConfig[type]; - initialValues.scanTemplate.assetScanTemplate.scanFamiliesConfig[type].enabled = enabled; - }) - - const steps = [ - { - id: "general", - title: "General properties", - component: StepGeneralProperties - }, - { - id: "scanTypes", - title: "Scan types", - component: StepScanTypes - }, - { - id: "time", - title: "Time configuration", - component: StepTimeConfiguration - }, - { - id: "advance", - title: "Advanced settings", - component: StepAdvancedSettings + }, + }, + scheduled: { + scheduledSelect: !!cronLine + ? SCHEDULE_TYPES_ITEMS.REPETITIVE.value + : SCHEDULE_TYPES_ITEMS.NOW.value, + laterDate: "", + laterTime: "", + cronLine: cronLine || CRON_QUICK_OPTIONS[0].value, + }, + }; + + if (!!operationTime && !cronLine) { + const dateTime = new Date(operationTime); + initialValues.scheduled.scheduledSelect = SCHEDULE_TYPES_ITEMS.LATER.value; + initialValues.scheduled.laterTime = `${padDateTime(dateTime.getHours())}:${padDateTime(dateTime.getMinutes())}`; + initialValues.scheduled.laterDate = `${dateTime.getFullYear()}-${padDateTime(dateTime.getMonth() + 1)}-${padDateTime(dateTime.getDate())}`; + } + + Object.keys(scanFamiliesConfig || {}).forEach((type) => { + const { enabled } = scanFamiliesConfig[type]; + initialValues.scanTemplate.assetScanTemplate.scanFamiliesConfig[ + type + ].enabled = enabled; + }); + + const steps = [ + { + id: "general", + title: "General properties", + component: StepGeneralProperties, + }, + { + id: "scanTypes", + title: "Scan types", + component: StepScanTypes, + }, + { + id: "time", + title: "Time configuration", + component: StepTimeConfiguration, + }, + { + id: "advance", + title: "Advanced settings", + component: StepAdvancedSettings, + }, + ]; + + return ( + { + const { id, scheduled, ...submitData } = formValues; + + const { scheduledSelect, laterDate, laterTime, cronLine } = scheduled; + const isNow = scheduledSelect === SCHEDULE_TYPES_ITEMS.NOW.value; + + let formattedDate = new Date(); + + if (!isNow) { + const [hours, minutes] = laterTime.split(":"); + formattedDate = new Date(laterDate); + formattedDate.setHours(hours, minutes); } - ]; - - return ( - { - const {id, scheduled, ...submitData} = formValues; - - const {scheduledSelect, laterDate, laterTime, cronLine} = scheduled; - const isNow = scheduledSelect === SCHEDULE_TYPES_ITEMS.NOW.value; - - let formattedDate = new Date(); - - if (!isNow) { - const [hours, minutes] = laterTime.split(":"); - formattedDate = new Date(laterDate); - formattedDate.setHours(hours, minutes); - } - - submitData.scheduled = {}; - - if (scheduledSelect === SCHEDULE_TYPES_ITEMS.REPETITIVE.value) { - submitData.scheduled.cronLine = cronLine; - } else { - submitData.scheduled.operationTime = formattedDate.toISOString(); - } - - return !isEditForm ? {submitData} : { - method: FETCH_METHODS.PUT, - formatUrl: url => `${url}/${id}`, - submitData - } - }} - onSubmitSuccess={onSubmitSuccess} - /> - ) -} + + submitData.scheduled = {}; + + if (scheduledSelect === SCHEDULE_TYPES_ITEMS.REPETITIVE.value) { + submitData.scheduled.cronLine = cronLine; + } else { + submitData.scheduled.operationTime = formattedDate.toISOString(); + } + + return !isEditForm + ? { submitData } + : { + method: FETCH_METHODS.PUT, + formatUrl: (url) => `${url}/${id}`, + submitData, + }; + }} + onSubmitSuccess={onSubmitSuccess} + /> + ); +}; export default ScanConfigWizardModal; diff --git a/ui/src/layout/Scans/ScanConfigWizardModal/scan-config-wizard-modal.scss b/ui/src/layout/Scans/ScanConfigWizardModal/scan-config-wizard-modal.scss index a207652c38..2dd3791839 100644 --- a/ui/src/layout/Scans/ScanConfigWizardModal/scan-config-wizard-modal.scss +++ b/ui/src/layout/Scans/ScanConfigWizardModal/scan-config-wizard-modal.scss @@ -1,31 +1,31 @@ -@import 'utils/scss_variables.module.scss'; +@import "utils/scss_variables.module.scss"; .wizard-modal { - .scan-config-scan-types-step { - .checkbox-field-wrapper { - margin-bottom: 10px; - } + .scan-config-scan-types-step { + .checkbox-field-wrapper { + margin-bottom: 10px; } - .scan-config-general-step { - .scan-config-instances .form-field-wrapper { - margin-bottom: 17px; - } + } + .scan-config-general-step { + .scan-config-instances .form-field-wrapper { + margin-bottom: 17px; } - .scan-config-time-config-step { - .schedule-later-form-fields { - display: flex; - - .date-field-wrapper { - min-width: 100px; - } - } - .repetitive-later-form-fields { - margin-top: 30px; - } + } + .scan-config-time-config-step { + .schedule-later-form-fields { + display: flex; + + .date-field-wrapper { + min-width: 100px; + } + } + .repetitive-later-form-fields { + margin-top: 30px; } - .scan-config-advanced-settings-step { - .checkbox-field-wrapper { - margin-top: 30px; - } + } + .scan-config-advanced-settings-step { + .checkbox-field-wrapper { + margin-top: 30px; } -} \ No newline at end of file + } +} diff --git a/ui/src/layout/Scans/Scans/ScanActionsDisplay/index.jsx b/ui/src/layout/Scans/Scans/ScanActionsDisplay/index.jsx index 9a1457d82f..b7a6546e29 100644 --- a/ui/src/layout/Scans/Scans/ScanActionsDisplay/index.jsx +++ b/ui/src/layout/Scans/Scans/ScanActionsDisplay/index.jsx @@ -1,54 +1,66 @@ -import React, { useEffect } from 'react'; -import { useFetch, FETCH_METHODS, usePrevious } from 'hooks'; -import { ICON_NAMES } from 'components/Icon'; -import IconWithTooltip from 'components/IconWithTooltip'; -import { SCAN_STATES } from 'components/ScanProgressBar'; -import { APIS } from 'utils/systemConsts'; +import React, { useEffect } from "react"; +import { useFetch, FETCH_METHODS, usePrevious } from "hooks"; +import { ICON_NAMES } from "components/Icon"; +import IconWithTooltip from "components/IconWithTooltip"; +import { SCAN_STATES } from "components/ScanProgressBar"; +import { APIS } from "utils/systemConsts"; -import './scan-actions-display.scss'; +import "./scan-actions-display.scss"; -const ScanActionsDisplay = ({data, onUpdate}) => { - const {id, status: { state }} = data; +const ScanActionsDisplay = ({ data, onUpdate }) => { + const { + id, + status: { state }, + } = data; - const [{loading, error}, fetchScan] = useFetch(APIS.SCANS, {loadOnMount: false}); - const prevLoading = usePrevious(loading); + const [{ loading, error }, fetchScan] = useFetch(APIS.SCANS, { + loadOnMount: false, + }); + const prevLoading = usePrevious(loading); - useEffect(() => { - if (prevLoading && !loading && !error && !!onUpdate) { - onUpdate(); - } - }, [loading, prevLoading, error, onUpdate]) - - if ([SCAN_STATES.Done.state, SCAN_STATES.Failed.state, SCAN_STATES.Aborted.state].includes(state) || loading) { - return null; + useEffect(() => { + if (prevLoading && !loading && !error && !!onUpdate) { + onUpdate(); } + }, [loading, prevLoading, error, onUpdate]); + + if ( + [ + SCAN_STATES.Done.state, + SCAN_STATES.Failed.state, + SCAN_STATES.Aborted.state, + ].includes(state) || + loading + ) { + return null; + } + + return ( +
+ { + event.stopPropagation(); + event.preventDefault(); - return ( -
- { - event.stopPropagation(); - event.preventDefault(); - - fetchScan({ - method: FETCH_METHODS.PATCH, - submitData: { - status: { - state: SCAN_STATES.Aborted.state, - reason: "Cancellation", - message: "Scan has been aborted", - lastTransitionTime: new Date().toISOString(), - }, - }, - formatUrl: url => `${url}/${id}` - }); - }} - /> -
- ); -} + fetchScan({ + method: FETCH_METHODS.PATCH, + submitData: { + status: { + state: SCAN_STATES.Aborted.state, + reason: "Cancellation", + message: "Scan has been aborted", + lastTransitionTime: new Date().toISOString(), + }, + }, + formatUrl: (url) => `${url}/${id}`, + }); + }} + /> +
+ ); +}; export default ScanActionsDisplay; diff --git a/ui/src/layout/Scans/Scans/ScanActionsDisplay/scan-actions-display.scss b/ui/src/layout/Scans/Scans/ScanActionsDisplay/scan-actions-display.scss index 780788ead0..30601ac99d 100644 --- a/ui/src/layout/Scans/Scans/ScanActionsDisplay/scan-actions-display.scss +++ b/ui/src/layout/Scans/Scans/ScanActionsDisplay/scan-actions-display.scss @@ -1,12 +1,12 @@ -@import 'utils/scss_variables.module.scss'; +@import "utils/scss_variables.module.scss"; .scan-actions-display { - display: flex; + display: flex; - .icon:not(:disabled) { - color: $color-main; - } - .icon-with-tooltip-wrapper:not(:last-child) { - margin-right: 5px; - } -} \ No newline at end of file + .icon:not(:disabled) { + color: $color-main; + } + .icon-with-tooltip-wrapper:not(:last-child) { + margin-right: 5px; + } +} diff --git a/ui/src/layout/Scans/Scans/ScanDetails.jsx b/ui/src/layout/Scans/Scans/ScanDetails.jsx index 5b61c21a5e..8984ac020a 100644 --- a/ui/src/layout/Scans/Scans/ScanDetails.jsx +++ b/ui/src/layout/Scans/Scans/ScanDetails.jsx @@ -1,62 +1,70 @@ -import React from 'react'; -import { useLocation } from 'react-router-dom'; -import DetailsPageWrapper from 'components/DetailsPageWrapper'; -import TabbedPage from 'components/TabbedPage'; -import { APIS } from 'utils/systemConsts'; -import { formatDate, getScanName } from 'utils/utils'; -import { ScanDetails as ScanDetailsTab, Findings } from 'layout/detail-displays'; -import ScanActionsDisplay from './ScanActionsDisplay'; +import React from "react"; +import { useLocation } from "react-router-dom"; +import DetailsPageWrapper from "components/DetailsPageWrapper"; +import TabbedPage from "components/TabbedPage"; +import { APIS } from "utils/systemConsts"; +import { formatDate, getScanName } from "utils/utils"; +import { + ScanDetails as ScanDetailsTab, + Findings, +} from "layout/detail-displays"; +import ScanActionsDisplay from "./ScanActionsDisplay"; export const SCAN_DETAILS_PATHS = { - SCAN_DETALS: "", - FINDINGS: "findings" -} + SCAN_DETALS: "", + FINDINGS: "findings", +}; -const DetailsContent = ({data, fetchData}) => { - const {pathname} = useLocation(); - - const {id, name, startTime} = data; +const DetailsContent = ({ data, fetchData }) => { + const { pathname } = useLocation(); - return ( - - }, - { - id: "findings", - title: "Findings", - path: SCAN_DETAILS_PATHS.FINDINGS, - component: () => ( - - ) - } - ]} - headerCustomDisplay={() => ( - - )} - withInnerPadding={false} - /> - ) -} + const { id, name, startTime } = data; -const ScanDetails = () => ( - ({title: name, subTitle: formatDate(startTime)})} - detailsContent={props => } + return ( + ( + + ), + }, + { + id: "findings", + title: "Findings", + path: SCAN_DETAILS_PATHS.FINDINGS, + component: () => ( + + ), + }, + ]} + headerCustomDisplay={() => ( + + )} + withInnerPadding={false} /> -) + ); +}; + +const ScanDetails = () => ( + ({ + title: name, + subTitle: formatDate(startTime), + })} + detailsContent={(props) => } + /> +); export default ScanDetails; diff --git a/ui/src/layout/Scans/Scans/ScansTable.jsx b/ui/src/layout/Scans/Scans/ScansTable.jsx index 1bf66548f7..75c414aa4a 100644 --- a/ui/src/layout/Scans/Scans/ScansTable.jsx +++ b/ui/src/layout/Scans/Scans/ScansTable.jsx @@ -1,165 +1,206 @@ -import React, { useMemo, useState, useCallback } from 'react'; -import { useNavigate } from 'react-router-dom'; -import TablePage from 'components/TablePage'; -import { utils } from 'components/Table'; -import ScanProgressBar, { SCAN_STATES } from 'components/ScanProgressBar'; -import EmptyDisplay from 'components/EmptyDisplay'; -import { OPERATORS } from 'components/Filter'; -import { useModalDisplayDispatch, MODAL_DISPLAY_ACTIONS } from 'layout/Scans/ScanConfigWizardModal/ModalDisplayProvider'; -import { APIS, ROUTES } from 'utils/systemConsts'; -import { formatDate, getFindingsColumnsConfigList, getVulnerabilitiesColumnConfigItem, formatNumber, findingsColumnsFiltersConfig, - vulnerabilitiesCountersColumnsFiltersConfig } from 'utils/utils'; -import { FILTER_TYPES } from 'context/FiltersProvider'; -import ScanActionsDisplay from './ScanActionsDisplay'; -import { SCANS_PATHS } from '../utils'; +import React, { useMemo, useState, useCallback } from "react"; +import { useNavigate } from "react-router-dom"; +import TablePage from "components/TablePage"; +import { utils } from "components/Table"; +import ScanProgressBar, { SCAN_STATES } from "components/ScanProgressBar"; +import EmptyDisplay from "components/EmptyDisplay"; +import { OPERATORS } from "components/Filter"; +import { + useModalDisplayDispatch, + MODAL_DISPLAY_ACTIONS, +} from "layout/Scans/ScanConfigWizardModal/ModalDisplayProvider"; +import { APIS, ROUTES } from "utils/systemConsts"; +import { + formatDate, + getFindingsColumnsConfigList, + getVulnerabilitiesColumnConfigItem, + formatNumber, + findingsColumnsFiltersConfig, + vulnerabilitiesCountersColumnsFiltersConfig, +} from "utils/utils"; +import { FILTER_TYPES } from "context/FiltersProvider"; +import ScanActionsDisplay from "./ScanActionsDisplay"; +import { SCANS_PATHS } from "../utils"; const TABLE_TITLE = "scans"; const TIME_CELL_WIDTH = 110; const START_TIME_SORT_IDS = ["startTime"]; -const FILTER_SCAN_STATUS_ITEMS = Object.values(SCAN_STATES).map(({state, title}) => ({value: state, label: title})); - -const TimeDisplay = ({time}) => ( - !!time ? formatDate(time) : +const FILTER_SCAN_STATUS_ITEMS = Object.values(SCAN_STATES).map( + ({ state, title }) => ({ value: state, label: title }), ); +const TimeDisplay = ({ time }) => + !!time ? formatDate(time) : ; + const ScansTable = () => { - const modalDisplayDispatch = useModalDisplayDispatch(); + const modalDisplayDispatch = useModalDisplayDispatch(); + + const navigate = useNavigate(); - const navigate = useNavigate(); + const [refreshTimestamp, setRefreshTimestamp] = useState(Date()); + const doRefreshTimestamp = useCallback(() => setRefreshTimestamp(Date()), []); - const [refreshTimestamp, setRefreshTimestamp] = useState(Date()); - const doRefreshTimestamp = useCallback(() => setRefreshTimestamp(Date()), []); + const columns = useMemo( + () => [ + { + Header: "Name", + id: "name", + sortIds: ["name"], + accessor: "name", + }, + { + Header: "Started", + id: "startTime", + sortIds: START_TIME_SORT_IDS, + Cell: ({ row }) => , + width: TIME_CELL_WIDTH, + }, + { + Header: "Ended", + id: "endTime", + sortIds: ["endTime"], + Cell: ({ row }) => , + width: TIME_CELL_WIDTH, + }, + { + Header: "Scope", + id: "scope", + sortIds: ["scope"], + accessor: "scope", + }, + { + Header: "Status", + id: "status", + sortIds: ["state"], + Cell: ({ row }) => { + const { state, reason, message } = row.original.status || {}; + const { jobsCompleted, jobsLeftToRun } = row.original.summary || {}; - const columns = useMemo(() => [ + return ( + + ); + }, + width: 150, + }, + getVulnerabilitiesColumnConfigItem({ tableTile: TABLE_TITLE }), + ...getFindingsColumnsConfigList({ tableTitle: TABLE_TITLE }), + { + Header: "Scanned assets", + id: "assets", + sortIds: ["summary.jobsCompleted"], + accessor: (original) => { + const { jobsCompleted, jobsLeftToRun } = original.summary || {}; + + return `${formatNumber(jobsCompleted)}/${formatNumber(jobsCompleted + jobsLeftToRun)}`; + }, + }, + ], + [], + ); + + return ( + , - width: TIME_CELL_WIDTH + value: "scope", + label: "Scope", + operators: [ + { ...OPERATORS.eq, valueitems: [], creatable: true }, + { ...OPERATORS.ne, valueitems: [], creatable: true }, + { ...OPERATORS.startswith }, + { ...OPERATORS.endswith }, + { ...OPERATORS.contains, valueitems: [], creatable: true }, + ], }, { - Header: "Ended", - id: "endTime", - sortIds: ["endTime"], - Cell: ({row}) => , - width: TIME_CELL_WIDTH + value: "startTime", + label: "Started", + isDate: true, + operators: [{ ...OPERATORS.ge }, { ...OPERATORS.le }], }, { - Header: "Scope", - id: "scope", - sortIds: ["scope"], - accessor: "scope" + value: "endTime", + label: "Ended", + isDate: true, + operators: [{ ...OPERATORS.ge }, { ...OPERATORS.le }], }, { - Header: "Status", - id: "status", - sortIds: ["state"], - Cell: ({row}) => { - const {state, reason, message} = row.original.status || {}; - const {jobsCompleted, jobsLeftToRun} = row.original.summary || {}; - - return ( - - ) - }, - width: 150 + value: "state", + label: "Status", + operators: [ + { ...OPERATORS.eq, valueItems: FILTER_SCAN_STATUS_ITEMS }, + { ...OPERATORS.ne, valueItems: FILTER_SCAN_STATUS_ITEMS }, + ], }, - getVulnerabilitiesColumnConfigItem({tableTile: TABLE_TITLE}), - ...getFindingsColumnsConfigList({tableTitle: TABLE_TITLE}), + ...vulnerabilitiesCountersColumnsFiltersConfig, + ...findingsColumnsFiltersConfig, { - Header: "Scanned assets", - id: "assets", - sortIds: ["summary.jobsCompleted"], - accessor: original => { - const {jobsCompleted, jobsLeftToRun} = original.summary || {}; - - return `${formatNumber(jobsCompleted)}/${formatNumber(jobsCompleted + jobsLeftToRun)}`; - } - } - ], []); - - return ( - ( - - )} - customEmptyResultsDisplay={() => ( - -
No scans detected.
-
Start your first scan to see your VM's issues.
- - )} - title="New scan configuration" - onClick={() => modalDisplayDispatch({type: MODAL_DISPLAY_ACTIONS.SET_MODAL_DISPLAY_DATA, payload: {}})} - subTitle="Start scan from config" - onSubClick={() => navigate(`${ROUTES.SCANS}/${SCANS_PATHS.CONFIGURATIONS}`)} - /> - )} - refreshTimestamp={refreshTimestamp} - absoluteSystemBanner + value: "summary.jobsCompleted", + label: "Scanned assets", + isNumber: true, + operators: [ + { ...OPERATORS.eq, valueItems: [], creatable: true }, + { ...OPERATORS.ne, valueItems: [], creatable: true }, + { ...OPERATORS.ge }, + { ...OPERATORS.le }, + ], + }, + ]} + defaultSortBy={{ sortIds: START_TIME_SORT_IDS, desc: true }} + actionsComponent={({ original }) => ( + + )} + customEmptyResultsDisplay={() => ( + +
No scans detected.
+
Start your first scan to see your VM's issues.
+ + } + title="New scan configuration" + onClick={() => + modalDisplayDispatch({ + type: MODAL_DISPLAY_ACTIONS.SET_MODAL_DISPLAY_DATA, + payload: {}, + }) + } + subTitle="Start scan from config" + onSubClick={() => + navigate(`${ROUTES.SCANS}/${SCANS_PATHS.CONFIGURATIONS}`) + } /> - ) -} + )} + refreshTimestamp={refreshTimestamp} + absoluteSystemBanner + /> + ); +}; export default ScansTable; diff --git a/ui/src/layout/Scans/Scans/index.jsx b/ui/src/layout/Scans/Scans/index.jsx index 7aca435254..d87f87c66d 100644 --- a/ui/src/layout/Scans/Scans/index.jsx +++ b/ui/src/layout/Scans/Scans/index.jsx @@ -1,10 +1,13 @@ -import React from 'react'; -import ListAndDetailsRouter from 'components/ListAndDetailsRouter'; -import ScansTable from './ScansTable'; -import ScanDetails from './ScanDetails'; +import React from "react"; +import ListAndDetailsRouter from "components/ListAndDetailsRouter"; +import ScansTable from "./ScansTable"; +import ScanDetails from "./ScanDetails"; const Scans = () => ( - -) + +); -export default Scans; \ No newline at end of file +export default Scans; diff --git a/ui/src/layout/Scans/index.jsx b/ui/src/layout/Scans/index.jsx index e729abc1e0..c83f58170a 100644 --- a/ui/src/layout/Scans/index.jsx +++ b/ui/src/layout/Scans/index.jsx @@ -1,106 +1,119 @@ -import React, { useCallback, useEffect } from 'react'; -import { useLocation, useSearchParams } from 'react-router-dom'; -import { isNull } from 'lodash'; -import { useMountMultiFetch, usePrevious } from 'hooks'; -import TabbedPage from 'components/TabbedPage'; -import Loader from 'components/Loader'; -import EmptyDisplay from 'components/EmptyDisplay'; -import { APIS } from 'utils/systemConsts'; -import ScanConfigWizardModal from './ScanConfigWizardModal'; -import { ModalDisplayProvider, useModalDisplayState, useModalDisplayDispatch, - MODAL_DISPLAY_ACTIONS } from './ScanConfigWizardModal/ModalDisplayProvider'; -import Scans from './Scans'; -import Configurations from './Configurations'; -import { SCANS_PATHS } from './utils'; +import React, { useCallback, useEffect } from "react"; +import { useLocation, useSearchParams } from "react-router-dom"; +import { isNull } from "lodash"; +import { useMountMultiFetch, usePrevious } from "hooks"; +import TabbedPage from "components/TabbedPage"; +import Loader from "components/Loader"; +import EmptyDisplay from "components/EmptyDisplay"; +import { APIS } from "utils/systemConsts"; +import ScanConfigWizardModal from "./ScanConfigWizardModal"; +import { + ModalDisplayProvider, + useModalDisplayState, + useModalDisplayDispatch, + MODAL_DISPLAY_ACTIONS, +} from "./ScanConfigWizardModal/ModalDisplayProvider"; +import Scans from "./Scans"; +import Configurations from "./Configurations"; +import { SCANS_PATHS } from "./utils"; -export { - SCANS_PATHS, -} +export { SCANS_PATHS }; export const OPEN_CONFIG_FORM_PARAM = "openConfigForm"; const ScansTabbedPage = () => { - const {pathname} = useLocation(); - const [searchParams] = useSearchParams(); - const openConfigForm = searchParams.get(OPEN_CONFIG_FORM_PARAM); - const prevOpenConfigForm = usePrevious(openConfigForm); - - const {modalDisplayData} = useModalDisplayState(); - const modalDisplayDispatch = useModalDisplayDispatch(); - const closeDisplayModal = () => modalDisplayDispatch({type: MODAL_DISPLAY_ACTIONS.CLOSE_DISPLAY_MODAL}); - const openDisplayModal = useCallback(() => modalDisplayDispatch({type: MODAL_DISPLAY_ACTIONS.SET_MODAL_DISPLAY_DATA, payload: {}}), [modalDisplayDispatch]); + const { pathname } = useLocation(); + const [searchParams] = useSearchParams(); + const openConfigForm = searchParams.get(OPEN_CONFIG_FORM_PARAM); + const prevOpenConfigForm = usePrevious(openConfigForm); - useEffect(() => { - if (!prevOpenConfigForm && openConfigForm) { - openDisplayModal(); - } - }, [prevOpenConfigForm, openConfigForm, openDisplayModal]); + const { modalDisplayData } = useModalDisplayState(); + const modalDisplayDispatch = useModalDisplayDispatch(); + const closeDisplayModal = () => + modalDisplayDispatch({ type: MODAL_DISPLAY_ACTIONS.CLOSE_DISPLAY_MODAL }); + const openDisplayModal = useCallback( + () => + modalDisplayDispatch({ + type: MODAL_DISPLAY_ACTIONS.SET_MODAL_DISPLAY_DATA, + payload: {}, + }), + [modalDisplayDispatch], + ); - const [{data, error, loading}, fetchData] = useMountMultiFetch([ - {key: "scans", url: APIS.SCANS}, - {key: "scanConfigs", url: APIS.SCAN_CONFIGS} - ]); - - if (loading) { - return ; + useEffect(() => { + if (!prevOpenConfigForm && openConfigForm) { + openDisplayModal(); } + }, [prevOpenConfigForm, openConfigForm, openDisplayModal]); - if (error) { - return null; - } - - const {scans, scanConfigs} = data; + const [{ data, error, loading }, fetchData] = useMountMultiFetch([ + { key: "scans", url: APIS.SCANS }, + { key: "scanConfigs", url: APIS.SCAN_CONFIGS }, + ]); + + if (loading) { + return ; + } + + if (error) { + return null; + } + + const { scans, scanConfigs } = data; - return ( - <> - {(scans?.length === 0 && scanConfigs?.total === 0) ? - -
No scans detected.
-
Create your first scan configuration to see your VM's issues.
- - )} - title="New scan configuration" - onClick={openDisplayModal} - /> : - - } - {!isNull(modalDisplayData) && - { - closeDisplayModal(); - fetchData(); - }} - /> - } - - ) -} + return ( + <> + {scans?.length === 0 && scanConfigs?.total === 0 ? ( + +
No scans detected.
+
+ Create your first scan configuration to see your VM's issues. +
+ + } + title="New scan configuration" + onClick={openDisplayModal} + /> + ) : ( + + )} + {!isNull(modalDisplayData) && ( + { + closeDisplayModal(); + fetchData(); + }} + /> + )} + + ); +}; const ScansWrapper = () => ( - - - -) + + + +); -export default ScansWrapper; \ No newline at end of file +export default ScansWrapper; diff --git a/ui/src/layout/Scans/utils.jsx b/ui/src/layout/Scans/utils.jsx index 63e5cc5f64..31890002c4 100644 --- a/ui/src/layout/Scans/utils.jsx +++ b/ui/src/layout/Scans/utils.jsx @@ -1,58 +1,68 @@ -import { isEmpty, isNull } from 'lodash'; +import { isEmpty, isNull } from "lodash"; export const SCANS_PATHS = { - SCANS: "scans", - CONFIGURATIONS: "configurations" -} + SCANS: "scans", + CONFIGURATIONS: "configurations", +}; -export const formatStringInstancesToTags = items => items.map(item => { +export const formatStringInstancesToTags = (items) => + items.map((item) => { const [key, value] = item.split("="); - return {key, value}; -}); - -export const formatRegionsToStrings = regions => { - const SEPARATOR = "/"; - - return regions?.reduce((acc, curr) => { - const {name: region, vpcs} = curr; - - const formattedVpcs = vpcs?.reduce((acc, curr) => { - const {id: vpc, securityGroups} = curr; - - if (!vpc) { - return acc; - } - - return [ - ...acc, - ...(isEmpty(securityGroups) ? [vpc] : securityGroups.map(({id: group}) => `${vpc}${SEPARATOR}${group}`)) - ]; - }, []); - - return [ - ...acc, - ...(isEmpty(formattedVpcs) ? [region] : formattedVpcs.map(formattedVpc => `${region}${SEPARATOR}${formattedVpc}`)) - ] + return { key, value }; + }); + +export const formatRegionsToStrings = (regions) => { + const SEPARATOR = "/"; + + return regions?.reduce((acc, curr) => { + const { name: region, vpcs } = curr; + + const formattedVpcs = vpcs?.reduce((acc, curr) => { + const { id: vpc, securityGroups } = curr; + + if (!vpc) { + return acc; + } + + return [ + ...acc, + ...(isEmpty(securityGroups) + ? [vpc] + : securityGroups.map( + ({ id: group }) => `${vpc}${SEPARATOR}${group}`, + )), + ]; }, []); -} -export const getEnabledScanTypesList = scanFamiliesConfig => ( - Object.keys(scanFamiliesConfig).map(scanType => { - const {enabled} = scanFamiliesConfig[scanType]; + return [ + ...acc, + ...(isEmpty(formattedVpcs) + ? [region] + : formattedVpcs.map( + (formattedVpc) => `${region}${SEPARATOR}${formattedVpc}`, + )), + ]; + }, []); +}; - return enabled ? scanType : null; +export const getEnabledScanTypesList = (scanFamiliesConfig) => + Object.keys(scanFamiliesConfig) + .map((scanType) => { + const { enabled } = scanFamiliesConfig[scanType]; + + return enabled ? scanType : null; }) -).filter(scanType => !isNull(scanType)); - -export const getScanTimeTypeTag = ({operationTime, cronLine}) => { - if (!!cronLine) { - return "Repetitive"; - } - - if (Date.now() - (new Date(operationTime)).valueOf() <= 0) { - return "Scheduled"; - } - - return "Once"; -} \ No newline at end of file + .filter((scanType) => !isNull(scanType)); + +export const getScanTimeTypeTag = ({ operationTime, cronLine }) => { + if (!!cronLine) { + return "Repetitive"; + } + + if (Date.now() - new Date(operationTime).valueOf() <= 0) { + return "Scheduled"; + } + + return "Once"; +}; diff --git a/ui/src/layout/detail-displays/AssetDetails/AssetScansDisplay.jsx b/ui/src/layout/detail-displays/AssetDetails/AssetScansDisplay.jsx index d43e705f90..308e4fa85a 100644 --- a/ui/src/layout/detail-displays/AssetDetails/AssetScansDisplay.jsx +++ b/ui/src/layout/detail-displays/AssetDetails/AssetScansDisplay.jsx @@ -1,46 +1,56 @@ -import React from 'react'; -import { useNavigate, useLocation } from 'react-router-dom'; -import { useFetch } from 'hooks'; -import Title from 'components/Title'; -import Button from 'components/Button'; -import Loader from 'components/Loader'; -import { ROUTES, APIS } from 'utils/systemConsts'; -import { formatNumber } from 'utils/utils'; -import { useFilterDispatch, setFilters, FILTER_TYPES } from 'context/FiltersProvider'; - -export const AssetScansDisplay = ({assetName, assetId}) => { - const {pathname} = useLocation(); - const navigate = useNavigate(); - const filtersDispatch = useFilterDispatch(); - - const filter = `asset/id eq '${assetId}'`; - - const onAssetScansClick = () => { - setFilters(filtersDispatch, { - type: FILTER_TYPES.ASSET_SCANS, - filters: {filter, name: assetName, suffix: "asset", backPath: pathname}, - isSystem: true - }); - - navigate(ROUTES.ASSET_SCANS); - } - - const [{loading, data, error}] = useFetch(APIS.ASSET_SCANS, { - queryParams: {"$filter": filter, "$count": true, "$select": "id,asset,summary,scan"} +import React from "react"; +import { useNavigate, useLocation } from "react-router-dom"; +import { useFetch } from "hooks"; +import Title from "components/Title"; +import Button from "components/Button"; +import Loader from "components/Loader"; +import { ROUTES, APIS } from "utils/systemConsts"; +import { formatNumber } from "utils/utils"; +import { + useFilterDispatch, + setFilters, + FILTER_TYPES, +} from "context/FiltersProvider"; + +export const AssetScansDisplay = ({ assetName, assetId }) => { + const { pathname } = useLocation(); + const navigate = useNavigate(); + const filtersDispatch = useFilterDispatch(); + + const filter = `asset/id eq '${assetId}'`; + + const onAssetScansClick = () => { + setFilters(filtersDispatch, { + type: FILTER_TYPES.ASSET_SCANS, + filters: { filter, name: assetName, suffix: "asset", backPath: pathname }, + isSystem: true, }); - - if (error) { - return null; - } - - if (loading) { - return - } - - return ( - <> - Asset scans - - - ) -} + + navigate(ROUTES.ASSET_SCANS); + }; + + const [{ loading, data, error }] = useFetch(APIS.ASSET_SCANS, { + queryParams: { + $filter: filter, + $count: true, + $select: "id,asset,summary,scan", + }, + }); + + if (error) { + return null; + } + + if (loading) { + return ; + } + + return ( + <> + Asset scans + + + ); +}; diff --git a/ui/src/layout/detail-displays/AssetDetails/CommonAssetMetadata.jsx b/ui/src/layout/detail-displays/AssetDetails/CommonAssetMetadata.jsx index 7c8dc69272..9b5a42cefc 100644 --- a/ui/src/layout/detail-displays/AssetDetails/CommonAssetMetadata.jsx +++ b/ui/src/layout/detail-displays/AssetDetails/CommonAssetMetadata.jsx @@ -1,15 +1,26 @@ -import React from 'react'; -import TitleValueDisplay, { TitleValueDisplayRow } from 'components/TitleValueDisplay'; -import { formatDate } from 'utils/utils'; +import React from "react"; +import TitleValueDisplay, { + TitleValueDisplayRow, +} from "components/TitleValueDisplay"; +import { formatDate } from "utils/utils"; -export const CommonAssetMetadata = ({assetData: {firstSeen, lastSeen, terminatedOn, assetInfo}}) => - <> - - {assetInfo.objectType} - {formatDate(firstSeen)} - - - {formatDate(lastSeen)} - {formatDate(terminatedOn)} - - +export const CommonAssetMetadata = ({ + assetData: { firstSeen, lastSeen, terminatedOn, assetInfo }, +}) => ( + <> + + {assetInfo.objectType} + + {formatDate(firstSeen)} + + + + + {formatDate(lastSeen)} + + + {formatDate(terminatedOn)} + + + +); diff --git a/ui/src/layout/detail-displays/AssetDetails/ContainerImageInfoDetails.jsx b/ui/src/layout/detail-displays/AssetDetails/ContainerImageInfoDetails.jsx index fdcb8b4c35..de22df769c 100644 --- a/ui/src/layout/detail-displays/AssetDetails/ContainerImageInfoDetails.jsx +++ b/ui/src/layout/detail-displays/AssetDetails/ContainerImageInfoDetails.jsx @@ -1,36 +1,50 @@ -import React from 'react'; -import TitleValueDisplay, { TitleValueDisplayRow } from 'components/TitleValueDisplay'; -import { TagsList } from 'components/Tag'; -import { formatTagsToStringsList } from 'utils/utils'; -import prettyBytes from 'pretty-bytes'; +import React from "react"; +import TitleValueDisplay, { + TitleValueDisplayRow, +} from "components/TitleValueDisplay"; +import { TagsList } from "components/Tag"; +import { formatTagsToStringsList } from "utils/utils"; +import prettyBytes from "pretty-bytes"; +export const ContainerImageInfoDetails = ({ assetData }) => { + const { imageID, repoDigests, repoTags, labels, architecture, os, size } = + assetData.assetInfo || {}; -export const ContainerImageInfoDetails = ({assetData}) => { - const {imageID, repoDigests, repoTags, labels, architecture, os, size} = assetData.assetInfo || {}; + return ( + <> + + {imageID} + {prettyBytes(size)} + - return ( - <> - - {imageID} - {prettyBytes(size)} - + + + {(repoDigests ?? []).map((digest) => ( +
{digest}
+ ))} +
+
- - {(repoDigests ?? []).map(digest =>
{digest}
)}
-
+ + + {(repoTags ?? []).map((tag) => ( +
{tag}
+ ))} +
+
- - {(repoTags ?? []).map(tag =>
{tag}
)}
-
- - - {architecture} - {os} - - - - - - - ) -} + + + {architecture} + + {os} + + + + + + + + + ); +}; diff --git a/ui/src/layout/detail-displays/AssetDetails/ContainerInfoDetails.jsx b/ui/src/layout/detail-displays/AssetDetails/ContainerInfoDetails.jsx index 14d21d9416..f1961b67de 100644 --- a/ui/src/layout/detail-displays/AssetDetails/ContainerInfoDetails.jsx +++ b/ui/src/layout/detail-displays/AssetDetails/ContainerInfoDetails.jsx @@ -1,28 +1,36 @@ -import React from 'react'; -import TitleValueDisplay, { TitleValueDisplayRow } from 'components/TitleValueDisplay'; -import { TagsList } from 'components/Tag'; -import { ContainerImageInfoDetails } from './ContainerImageInfoDetails'; -import { FieldSet } from 'components/FieldSet'; -import { formatTagsToStringsList } from 'utils/utils'; +import React from "react"; +import TitleValueDisplay, { + TitleValueDisplayRow, +} from "components/TitleValueDisplay"; +import { TagsList } from "components/Tag"; +import { ContainerImageInfoDetails } from "./ContainerImageInfoDetails"; +import { FieldSet } from "components/FieldSet"; +import { formatTagsToStringsList } from "utils/utils"; +export const ContainerInfoDetails = ({ assetData }) => { + const { containerName, containerID, image, labels } = + assetData.assetInfo || {}; -export const ContainerInfoDetails = ({assetData}) => { - const {containerName, containerID, image, labels} = assetData.assetInfo || {}; + return ( + <> + + + {containerName} + + + {containerID} + + - return ( - <> - - {containerName} - {containerID} - + + + + + - - - - -
- -
- - ) -} +
+ +
+ + ); +}; diff --git a/ui/src/layout/detail-displays/AssetDetails/DirInfoDetails.jsx b/ui/src/layout/detail-displays/AssetDetails/DirInfoDetails.jsx index 00ed66880a..0fc1f000b3 100644 --- a/ui/src/layout/detail-displays/AssetDetails/DirInfoDetails.jsx +++ b/ui/src/layout/detail-displays/AssetDetails/DirInfoDetails.jsx @@ -1,14 +1,15 @@ -import React from 'react'; -import TitleValueDisplay, { TitleValueDisplayRow } from 'components/TitleValueDisplay'; +import React from "react"; +import TitleValueDisplay, { + TitleValueDisplayRow, +} from "components/TitleValueDisplay"; +export const DirInfoDetails = ({ assetData }) => { + const { dirName, location } = assetData.assetInfo || {}; -export const DirInfoDetails = ({assetData}) => { - const {dirName, location} = assetData.assetInfo || {}; - - return ( - - {dirName} - {location} - - ) -} + return ( + + {dirName} + {location} + + ); +}; diff --git a/ui/src/layout/detail-displays/AssetDetails/PodInfoDetails.jsx b/ui/src/layout/detail-displays/AssetDetails/PodInfoDetails.jsx index afc0cdac34..f7f418e210 100644 --- a/ui/src/layout/detail-displays/AssetDetails/PodInfoDetails.jsx +++ b/ui/src/layout/detail-displays/AssetDetails/PodInfoDetails.jsx @@ -1,14 +1,15 @@ -import React from 'react'; -import TitleValueDisplay, { TitleValueDisplayRow } from 'components/TitleValueDisplay'; +import React from "react"; +import TitleValueDisplay, { + TitleValueDisplayRow, +} from "components/TitleValueDisplay"; +export const PodInfoDetails = ({ assetData }) => { + const { podName, location } = assetData.assetInfo || {}; -export const PodInfoDetails = ({assetData}) => { - const {podName, location} = assetData.assetInfo || {}; - - return ( - - {podName} - {location} - - ) -} + return ( + + {podName} + {location} + + ); +}; diff --git a/ui/src/layout/detail-displays/AssetDetails/VMInfoDetails.jsx b/ui/src/layout/detail-displays/AssetDetails/VMInfoDetails.jsx index e45262d7a1..6a296c1a7f 100644 --- a/ui/src/layout/detail-displays/AssetDetails/VMInfoDetails.jsx +++ b/ui/src/layout/detail-displays/AssetDetails/VMInfoDetails.jsx @@ -1,37 +1,58 @@ -import React from 'react'; -import TitleValueDisplay, { TitleValueDisplayRow } from 'components/TitleValueDisplay'; -import { TagsList } from 'components/Tag'; -import { formatDate, formatTagsToStringsList } from 'utils/utils'; +import React from "react"; +import TitleValueDisplay, { + TitleValueDisplayRow, +} from "components/TitleValueDisplay"; +import { TagsList } from "components/Tag"; +import { formatDate, formatTagsToStringsList } from "utils/utils"; -export const VMInfoDetails = ({assetData}) => { - const {instanceID, location, tags, image, instanceType, platform, launchTime, rootVolume} = assetData.assetInfo || {}; - const {sizeGB, encrypted} = rootVolume || {}; +export const VMInfoDetails = ({ assetData }) => { + const { + instanceID, + location, + tags, + image, + instanceType, + platform, + launchTime, + rootVolume, + } = assetData.assetInfo || {}; + const { sizeGB, encrypted } = rootVolume || {}; - return ( - <> - - {instanceID} - {location} - + return ( + <> + + {instanceID} + {location} + - - - + + + + + - - {image} - {instanceType} - + + {image} + + {instanceType} + + - - {platform} - {formatDate(launchTime)} - + + {platform} + + {formatDate(launchTime)} + + - - {sizeGB} GB - {encrypted} - - - ) -} + + + {sizeGB} GB + + + {encrypted} + + + + ); +}; diff --git a/ui/src/layout/detail-displays/AssetDetails/index.jsx b/ui/src/layout/detail-displays/AssetDetails/index.jsx index 4f9e1e39d8..86316482e9 100644 --- a/ui/src/layout/detail-displays/AssetDetails/index.jsx +++ b/ui/src/layout/detail-displays/AssetDetails/index.jsx @@ -1,55 +1,77 @@ -import React from 'react'; -import { useNavigate } from 'react-router-dom'; -import DoublePaneDisplay from 'components/DoublePaneDisplay'; -import Title from 'components/Title'; -import { ROUTES } from 'utils/systemConsts'; -import { getAssetName } from 'utils/utils'; -import { VMInfoDetails } from './VMInfoDetails'; -import { ContainerImageInfoDetails } from './ContainerImageInfoDetails'; -import { ContainerInfoDetails } from './ContainerInfoDetails'; -import { AssetScansDisplay } from './AssetScansDisplay'; -import { PodInfoDetails } from './PodInfoDetails'; -import { DirInfoDetails } from './DirInfoDetails'; -import { CommonAssetMetadata } from './CommonAssetMetadata'; +import React from "react"; +import { useNavigate } from "react-router-dom"; +import DoublePaneDisplay from "components/DoublePaneDisplay"; +import Title from "components/Title"; +import { ROUTES } from "utils/systemConsts"; +import { getAssetName } from "utils/utils"; +import { VMInfoDetails } from "./VMInfoDetails"; +import { ContainerImageInfoDetails } from "./ContainerImageInfoDetails"; +import { ContainerInfoDetails } from "./ContainerInfoDetails"; +import { AssetScansDisplay } from "./AssetScansDisplay"; +import { PodInfoDetails } from "./PodInfoDetails"; +import { DirInfoDetails } from "./DirInfoDetails"; +import { CommonAssetMetadata } from "./CommonAssetMetadata"; const AssetDetailsByType = ({ assetData }) => { - if (!assetData?.assetInfo) { - return null; - } + if (!assetData?.assetInfo) { + return null; + } - switch (assetData.assetInfo.objectType) { - case 'VMInfo': - return ; - case 'ContainerImageInfo': - return ; - case 'ContainerInfo': - return ; - case 'PodInfo': - return ; - case 'DirInfo': - return ; - default: - return null; - } -} + switch (assetData.assetInfo.objectType) { + case "VMInfo": + return ; + case "ContainerImageInfo": + return ; + case "ContainerInfo": + return ; + case "PodInfo": + return ; + case "DirInfo": + return ; + default: + return null; + } +}; -const AssetDetails = ({assetData, withAssetLink=false, withAssetScansLink=false}) => { - const navigate = useNavigate(); +const AssetDetails = ({ + assetData, + withAssetLink = false, + withAssetScansLink = false, +}) => { + const navigate = useNavigate(); - const {id, assetInfo} = assetData; + const { id, assetInfo } = assetData; - return ( - ( - <> - navigate(`${ROUTES.ASSETS}/${id}`) : undefined}>Asset - - - - )} - rightPlaneDisplay={!withAssetScansLink ? null : () => } - /> - ) -} + return ( + ( + <> + navigate(`${ROUTES.ASSETS}/${id}`) + : undefined + } + > + Asset + + + + + )} + rightPlaneDisplay={ + !withAssetScansLink + ? null + : () => ( + + ) + } + /> + ); +}; export default AssetDetails; diff --git a/ui/src/layout/detail-displays/Findings/FindingsCounterDisplay/findings-counter-display.scss b/ui/src/layout/detail-displays/Findings/FindingsCounterDisplay/findings-counter-display.scss index 3c6e1f37b3..50e0fd6c3f 100644 --- a/ui/src/layout/detail-displays/Findings/FindingsCounterDisplay/findings-counter-display.scss +++ b/ui/src/layout/detail-displays/Findings/FindingsCounterDisplay/findings-counter-display.scss @@ -1,22 +1,22 @@ -@import 'utils/scss_variables.module.scss'; +@import "utils/scss_variables.module.scss"; .findings-item-display { - display: flex; - align-items: center; + display: flex; + align-items: center; - .icon { - color: $color-main; - } - .findings-item-counter { - font-weight: 400; - font-size: 22px; - line-height: 18px; - margin-left: 5px; - } - .findings-item-title { - font-weight: 400; - font-size: 14px; - line-height: 18px; - margin-left: 10px; - } -} \ No newline at end of file + .icon { + color: $color-main; + } + .findings-item-counter { + font-weight: 400; + font-size: 22px; + line-height: 18px; + margin-left: 5px; + } + .findings-item-title { + font-weight: 400; + font-size: 14px; + line-height: 18px; + margin-left: 10px; + } +} diff --git a/ui/src/layout/detail-displays/Findings/FindingsCounterDisplay/index.jsx b/ui/src/layout/detail-displays/Findings/FindingsCounterDisplay/index.jsx index 20c0fab5c7..0940a5c0ed 100644 --- a/ui/src/layout/detail-displays/Findings/FindingsCounterDisplay/index.jsx +++ b/ui/src/layout/detail-displays/Findings/FindingsCounterDisplay/index.jsx @@ -1,15 +1,15 @@ -import React from 'react'; -import Icon from 'components/Icon'; -import { formatNumber } from 'utils/utils'; +import React from "react"; +import Icon from "components/Icon"; +import { formatNumber } from "utils/utils"; -import './findings-counter-display.scss'; +import "./findings-counter-display.scss"; -const FindingsCounterDisplay = ({icon, color, count, title}) => ( -
- -
{formatNumber(count)}
-
{title}
-
-) +const FindingsCounterDisplay = ({ icon, color, count, title }) => ( +
+ +
{formatNumber(count)}
+
{title}
+
+); -export default FindingsCounterDisplay; \ No newline at end of file +export default FindingsCounterDisplay; diff --git a/ui/src/layout/detail-displays/Findings/FindingsSystemFilterLinks/findings-system-filter-links.scss b/ui/src/layout/detail-displays/Findings/FindingsSystemFilterLinks/findings-system-filter-links.scss index 22b1ab3765..17853e9ae1 100644 --- a/ui/src/layout/detail-displays/Findings/FindingsSystemFilterLinks/findings-system-filter-links.scss +++ b/ui/src/layout/detail-displays/Findings/FindingsSystemFilterLinks/findings-system-filter-links.scss @@ -1,22 +1,22 @@ -@import 'utils/scss_variables.module.scss'; +@import "utils/scss_variables.module.scss"; .findings-system-filters-links { + display: flex; + align-items: center; + + .findings-system-filters-title { + margin-right: 10px; + white-space: nowrap; + } + .finging-filter-link { display: flex; align-items: center; + margin-right: 10px; + white-space: nowrap; - .findings-system-filters-title { - margin-right: 10px; - white-space: nowrap; - } - .finging-filter-link { - display: flex; - align-items: center; - margin-right: 10px; - white-space: nowrap; - - .icon { - color: $color-main; - margin-right: 5px; - } + .icon { + color: $color-main; + margin-right: 5px; } -} \ No newline at end of file + } +} diff --git a/ui/src/layout/detail-displays/Findings/FindingsSystemFilterLinks/index.jsx b/ui/src/layout/detail-displays/Findings/FindingsSystemFilterLinks/index.jsx index bae0545352..f9f215231a 100644 --- a/ui/src/layout/detail-displays/Findings/FindingsSystemFilterLinks/index.jsx +++ b/ui/src/layout/detail-displays/Findings/FindingsSystemFilterLinks/index.jsx @@ -1,38 +1,53 @@ -import React from 'react'; -import { useNavigate } from 'react-router-dom'; -import Button from 'components/Button'; -import Icon from 'components/Icon'; -import { FINDINGS_MAPPING, VULNERABIITY_FINDINGS_ITEM } from 'utils/systemConsts'; -import { formatNumber } from 'utils/utils'; -import { getFindingsAbsolutePathByFindingType } from 'layout/Findings'; +import React from "react"; +import { useNavigate } from "react-router-dom"; +import Button from "components/Button"; +import Icon from "components/Icon"; +import { + FINDINGS_MAPPING, + VULNERABIITY_FINDINGS_ITEM, +} from "utils/systemConsts"; +import { formatNumber } from "utils/utils"; +import { getFindingsAbsolutePathByFindingType } from "layout/Findings"; -import './findings-system-filter-links.scss'; +import "./findings-system-filter-links.scss"; -const FindingFilterLink = ({icon, title, appRoute}) => { - const navigate = useNavigate(); +const FindingFilterLink = ({ icon, title, appRoute }) => { + const navigate = useNavigate(); - return ( -
- - -
- ) -} + return ( +
+ + +
+ ); +}; -const FindingsSystemFilterLinks = ({totalVulnerabilitiesCount, findingsSummary}) => ( -
-
See other findings:
- { - [VULNERABIITY_FINDINGS_ITEM, ...Object.values(FINDINGS_MAPPING)].map(({value, totalKey, title, icon}) => { - const LinkTitle = VULNERABIITY_FINDINGS_ITEM.value === value ? `${formatNumber(totalVulnerabilitiesCount)} ${title}` : - `${!!findingsSummary ? (formatNumber(findingsSummary[totalKey] || 0)) : 0} ${title}`; +const FindingsSystemFilterLinks = ({ + totalVulnerabilitiesCount, + findingsSummary, +}) => ( +
+
See other findings:
+ {[VULNERABIITY_FINDINGS_ITEM, ...Object.values(FINDINGS_MAPPING)].map( + ({ value, totalKey, title, icon }) => { + const LinkTitle = + VULNERABIITY_FINDINGS_ITEM.value === value + ? `${formatNumber(totalVulnerabilitiesCount)} ${title}` + : `${!!findingsSummary ? formatNumber(findingsSummary[totalKey] || 0) : 0} ${title}`; - return ( - - ) - }) - } -
-) + return ( + + ); + }, + )} +
+); -export default FindingsSystemFilterLinks; \ No newline at end of file +export default FindingsSystemFilterLinks; diff --git a/ui/src/layout/detail-displays/Findings/index.jsx b/ui/src/layout/detail-displays/Findings/index.jsx index ba645a0bd4..b55712cb11 100644 --- a/ui/src/layout/detail-displays/Findings/index.jsx +++ b/ui/src/layout/detail-displays/Findings/index.jsx @@ -1,69 +1,91 @@ -import React from 'react'; -import { useLocation } from 'react-router-dom'; -import DoublePaneDisplay from 'components/DoublePaneDisplay'; -import Title from 'components/Title'; -import LinksList from 'components/LinksList'; -import VulnerabilitiesDisplay, { getTotlalVulnerabilitiesFromCounters } from 'components/VulnerabilitiesDisplay'; -import { FINDINGS_MAPPING } from 'utils/systemConsts'; -import { useFilterDispatch, setFilters, FILTER_TYPES } from 'context/FiltersProvider'; -import FindingsCounterDisplay from './FindingsCounterDisplay'; -import FindingsSystemFilterLinks from './FindingsSystemFilterLinks'; +import React from "react"; +import { useLocation } from "react-router-dom"; +import DoublePaneDisplay from "components/DoublePaneDisplay"; +import Title from "components/Title"; +import LinksList from "components/LinksList"; +import VulnerabilitiesDisplay, { + getTotlalVulnerabilitiesFromCounters, +} from "components/VulnerabilitiesDisplay"; +import { FINDINGS_MAPPING } from "utils/systemConsts"; +import { + useFilterDispatch, + setFilters, + FILTER_TYPES, +} from "context/FiltersProvider"; +import FindingsCounterDisplay from "./FindingsCounterDisplay"; +import FindingsSystemFilterLinks from "./FindingsSystemFilterLinks"; -const Findings = ({findingsSummary, findingsFilter, findingsFilterTitle, findingsFilterSuffix=""}) => { - findingsSummary = findingsSummary || {}; - const {totalVulnerabilities} = findingsSummary; +const Findings = ({ + findingsSummary, + findingsFilter, + findingsFilterTitle, + findingsFilterSuffix = "", +}) => { + findingsSummary = findingsSummary || {}; + const { totalVulnerabilities } = findingsSummary; - const {pathname} = useLocation(); - const filtersDispatch = useFilterDispatch(); + const { pathname } = useLocation(); + const filtersDispatch = useFilterDispatch(); - const onFindingsClick = () => { - setFilters(filtersDispatch, { - type: FILTER_TYPES.FINDINGS_GENERAL, - filters: { - filter: findingsFilter, - name: findingsFilterTitle, - suffix: findingsFilterSuffix, - backPath: pathname, - customDisplay: () => ( - - ) - }, - isSystem: true - }); - } - - return ( - ( - <> - Findings - , - callback: onFindingsClick - }, - ...Object.keys(FINDINGS_MAPPING).map(findingType => { - const {totalKey, title, icon, color, value} = FINDINGS_MAPPING[findingType]; - - return { - path: pathname, - component: () => ( - - ), - callback: onFindingsClick - } - }) - ]} - /> - + const onFindingsClick = () => { + setFilters(filtersDispatch, { + type: FILTER_TYPES.FINDINGS_GENERAL, + filters: { + filter: findingsFilter, + name: findingsFilterTitle, + suffix: findingsFilterSuffix, + backPath: pathname, + customDisplay: () => ( + - ) -} + /> + ), + }, + isSystem: true, + }); + }; + + return ( + ( + <> + Findings + ( + + ), + callback: onFindingsClick, + }, + ...Object.keys(FINDINGS_MAPPING).map((findingType) => { + const { totalKey, title, icon, color, value } = + FINDINGS_MAPPING[findingType]; + + return { + path: pathname, + component: () => ( + + ), + callback: onFindingsClick, + }; + }), + ]} + /> + + )} + /> + ); +}; export default Findings; diff --git a/ui/src/layout/detail-displays/ScanDetails/ConfigurationAlertLink/configuration-alert-link.scss b/ui/src/layout/detail-displays/ScanDetails/ConfigurationAlertLink/configuration-alert-link.scss index 1097df07fa..55784890d9 100644 --- a/ui/src/layout/detail-displays/ScanDetails/ConfigurationAlertLink/configuration-alert-link.scss +++ b/ui/src/layout/detail-displays/ScanDetails/ConfigurationAlertLink/configuration-alert-link.scss @@ -1,15 +1,15 @@ -@import 'utils/scss_variables.module.scss'; +@import "utils/scss_variables.module.scss"; .configuration-alert-link { - display: flex; - align-items: center; - margin-bottom: 20px; + display: flex; + align-items: center; + margin-bottom: 20px; - .clarity-title { - margin-right: 15px; - } - .icon { - color: $color-warning; - margin: auto 0; - } -} \ No newline at end of file + .clarity-title { + margin-right: 15px; + } + .icon { + color: $color-warning; + margin: auto 0; + } +} diff --git a/ui/src/layout/detail-displays/ScanDetails/ConfigurationAlertLink/index.jsx b/ui/src/layout/detail-displays/ScanDetails/ConfigurationAlertLink/index.jsx index 7ae67765e3..d86ac1d97f 100644 --- a/ui/src/layout/detail-displays/ScanDetails/ConfigurationAlertLink/index.jsx +++ b/ui/src/layout/detail-displays/ScanDetails/ConfigurationAlertLink/index.jsx @@ -1,39 +1,50 @@ -import React from 'react'; -import { useNavigate } from 'react-router-dom'; -import { isEqual } from 'lodash'; -import Title from 'components/Title'; -import { ICON_NAMES } from 'components/Icon'; -import IconWithTooltip from 'components/IconWithTooltip'; -import { SCANS_PATHS } from 'layout/Scans'; -import { ROUTES } from 'utils/systemConsts'; +import React from "react"; +import { useNavigate } from "react-router-dom"; +import { isEqual } from "lodash"; +import Title from "components/Title"; +import { ICON_NAMES } from "components/Icon"; +import IconWithTooltip from "components/IconWithTooltip"; +import { SCANS_PATHS } from "layout/Scans"; +import { ROUTES } from "utils/systemConsts"; -import './configuration-alert-link.scss'; +import "./configuration-alert-link.scss"; const CONFIGURATION_ALERT_TEXT = ( - - Configuration has been modified since
- the scan has performed and it might not
- match the scan's configuration
-
-) + + Configuration has been modified since +
+ the scan has performed and it might not +
+ match the scan's configuration +
+
+); -const ConfigurationAlertLink = ({scanConfigData, updatedConfigData}) => { - const navigate = useNavigate(); +const ConfigurationAlertLink = ({ scanConfigData, updatedConfigData }) => { + const navigate = useNavigate(); - const {id, ...dataToCompare} = updatedConfigData || {}; + const { id, ...dataToCompare } = updatedConfigData || {}; - return ( -
- navigate(`${ROUTES.SCANS}/${SCANS_PATHS.CONFIGURATIONS}/${id}`)}>Configuration - {!isEqual(dataToCompare, scanConfigData) && - - } -
- ) -} + return ( +
+ + navigate(`${ROUTES.SCANS}/${SCANS_PATHS.CONFIGURATIONS}/${id}`) + } + > + Configuration + + {!isEqual(dataToCompare, scanConfigData) && ( + + )} +
+ ); +}; -export default ConfigurationAlertLink; \ No newline at end of file +export default ConfigurationAlertLink; diff --git a/ui/src/layout/detail-displays/ScanDetails/index.jsx b/ui/src/layout/detail-displays/ScanDetails/index.jsx index 8ee937881d..56753b32a1 100644 --- a/ui/src/layout/detail-displays/ScanDetails/index.jsx +++ b/ui/src/layout/detail-displays/ScanDetails/index.jsx @@ -1,77 +1,115 @@ -import React from 'react'; -import { useNavigate, useLocation } from 'react-router-dom'; -import TitleValueDisplay, { TitleValueDisplayColumn, TitleValueDisplayRow } from 'components/TitleValueDisplay'; -import DoublePaneDisplay from 'components/DoublePaneDisplay'; -import Title from 'components/Title'; -import ScanProgressBar from 'components/ScanProgressBar'; -import Button from 'components/Button'; -import { SCANS_PATHS } from 'layout/Scans'; -import { ScanReadOnlyDisplay } from 'layout/Scans/ConfigurationReadOnlyDisplay'; -import { formatDate, calculateDuration, formatNumber } from 'utils/utils'; -import { ROUTES } from 'utils/systemConsts'; -import { useFilterDispatch, setFilters, FILTER_TYPES } from 'context/FiltersProvider'; -import ConfigurationAlertLink from './ConfigurationAlertLink'; +import React from "react"; +import { useNavigate, useLocation } from "react-router-dom"; +import TitleValueDisplay, { + TitleValueDisplayColumn, + TitleValueDisplayRow, +} from "components/TitleValueDisplay"; +import DoublePaneDisplay from "components/DoublePaneDisplay"; +import Title from "components/Title"; +import ScanProgressBar from "components/ScanProgressBar"; +import Button from "components/Button"; +import { SCANS_PATHS } from "layout/Scans"; +import { ScanReadOnlyDisplay } from "layout/Scans/ConfigurationReadOnlyDisplay"; +import { formatDate, calculateDuration, formatNumber } from "utils/utils"; +import { ROUTES } from "utils/systemConsts"; +import { + useFilterDispatch, + setFilters, + FILTER_TYPES, +} from "context/FiltersProvider"; +import ConfigurationAlertLink from "./ConfigurationAlertLink"; -const ScanDetails = ({scanData, withScanLink=false, withAssetScansLink=false}) => { - const {pathname} = useLocation(); - const navigate = useNavigate(); - const filtersDispatch = useFilterDispatch(); +const ScanDetails = ({ + scanData, + withScanLink = false, + withAssetScansLink = false, +}) => { + const { pathname } = useLocation(); + const navigate = useNavigate(); + const filtersDispatch = useFilterDispatch(); - const {id, name, scanConfig, scope, assetScanTemplate, startTime, endTime, summary, status} = scanData || {}; - const {jobsCompleted, jobsLeftToRun} = summary || {}; - const {state, reason, message} = status || {} + const { + id, + name, + scanConfig, + scope, + assetScanTemplate, + startTime, + endTime, + summary, + status, + } = scanData || {}; + const { jobsCompleted, jobsLeftToRun } = summary || {}; + const { state, reason, message } = status || {}; - const formattedStartTime = formatDate(startTime); - - const onAssetScansClick = () => { - setFilters(filtersDispatch, { - type: FILTER_TYPES.ASSET_SCANS, - filters: { - filter: `scan/id eq '${id}'`, - name: name || id, - suffix: "scan", - backPath: pathname - }, - isSystem: true - }); + const formattedStartTime = formatDate(startTime); - navigate(ROUTES.ASSET_SCANS); - } - - return ( - ( - - - - )} - rightPlaneDisplay={() => ( - <> - navigate(`${ROUTES.SCANS}/${SCANS_PATHS.SCANS}/${id}`) : undefined}>Scan -
- -
- - {formattedStartTime} - {formatDate(endTime)} - {calculateDuration(startTime, endTime)} - - {withAssetScansLink && -
- Asset scans - -
- } - - )} - /> - ) -} + const onAssetScansClick = () => { + setFilters(filtersDispatch, { + type: FILTER_TYPES.ASSET_SCANS, + filters: { + filter: `scan/id eq '${id}'`, + name: name || id, + suffix: "scan", + backPath: pathname, + }, + isSystem: true, + }); + + navigate(ROUTES.ASSET_SCANS); + }; + + return ( + ( + + + + )} + rightPlaneDisplay={() => ( + <> + navigate(`${ROUTES.SCANS}/${SCANS_PATHS.SCANS}/${id}`) + : undefined + } + > + Scan + +
+ +
+ + + {formattedStartTime} + + + {formatDate(endTime)} + + + {calculateDuration(startTime, endTime)} + + + {withAssetScansLink && ( +
+ Asset scans + +
+ )} + + )} + /> + ); +}; export default ScanDetails; diff --git a/ui/src/layout/detail-displays/index.js b/ui/src/layout/detail-displays/index.js index 3a171d25db..e71c7d08e6 100644 --- a/ui/src/layout/detail-displays/index.js +++ b/ui/src/layout/detail-displays/index.js @@ -1,9 +1,5 @@ -import Findings from './Findings'; -import ScanDetails from './ScanDetails'; -import AssetDetails from './AssetDetails'; +import Findings from "./Findings"; +import ScanDetails from "./ScanDetails"; +import AssetDetails from "./AssetDetails"; -export { - Findings, - ScanDetails, - AssetDetails -} \ No newline at end of file +export { Findings, ScanDetails, AssetDetails }; diff --git a/ui/src/utils/fonts/fonts.scss b/ui/src/utils/fonts/fonts.scss index 76711312b3..fea4e9bd76 100644 --- a/ui/src/utils/fonts/fonts.scss +++ b/ui/src/utils/fonts/fonts.scss @@ -1,36 +1,48 @@ @font-face { - font-family: 'CiscoSansTT'; - src: url(./CiscoSansTTExtraLight.woff) format('woff'), url(./CiscoSansTTExtraLight.woff2) format('woff2'); - font-weight: 200; - font-style: normal; + font-family: "CiscoSansTT"; + src: + url(./CiscoSansTTExtraLight.woff) format("woff"), + url(./CiscoSansTTExtraLight.woff2) format("woff2"); + font-weight: 200; + font-style: normal; } @font-face { - font-family: 'CiscoSansTT'; - src: url(./CiscoSansTTExtraLightOblique.woff) format('woff'), url(./CiscoSansTTExtraLightOblique.woff2) format('woff2'); - font-weight: 2009; - font-style: oblique; + font-family: "CiscoSansTT"; + src: + url(./CiscoSansTTExtraLightOblique.woff) format("woff"), + url(./CiscoSansTTExtraLightOblique.woff2) format("woff2"); + font-weight: 2009; + font-style: oblique; } @font-face { - font-family: 'CiscoSansTT'; - src: url(./CiscoSansTTRegular.woff) format('woff'), url(./CiscoSansTTRegular.woff2) format('woff2'); - font-weight: 400; //(normal) - font-style: normal; + font-family: "CiscoSansTT"; + src: + url(./CiscoSansTTRegular.woff) format("woff"), + url(./CiscoSansTTRegular.woff2) format("woff2"); + font-weight: 400; //(normal) + font-style: normal; } @font-face { - font-family: 'CiscoSansTT'; - src: url(./CiscoSansTTRegularOblique.woff) format('woff'), url(./CiscoSansTTRegularOblique.woff2) format('woff2'); - font-weight: 400; //(normal) - font-style: oblique; + font-family: "CiscoSansTT"; + src: + url(./CiscoSansTTRegularOblique.woff) format("woff"), + url(./CiscoSansTTRegularOblique.woff2) format("woff2"); + font-weight: 400; //(normal) + font-style: oblique; } @font-face { - font-family: 'CiscoSansTT'; - src: url(./CiscoSansTTBold.woff) format('woff'), url(./CiscoSansTTBold.woff2) format('woff2'); - font-weight: 700; //(bold) - font-style: normal; + font-family: "CiscoSansTT"; + src: + url(./CiscoSansTTBold.woff) format("woff"), + url(./CiscoSansTTBold.woff2) format("woff2"); + font-weight: 700; //(bold) + font-style: normal; } @font-face { - font-family: 'CiscoSansTT'; - src: url(./CiscoSansTTBoldOblique.woff) format('woff'), url(./CiscoSansTTBoldOblique.woff2) format('woff2'); - font-weight: 700; //(bold) - font-style: oblique; -} \ No newline at end of file + font-family: "CiscoSansTT"; + src: + url(./CiscoSansTTBoldOblique.woff) format("woff"), + url(./CiscoSansTTBoldOblique.woff2) format("woff2"); + font-weight: 700; //(bold) + font-style: oblique; +} diff --git a/ui/src/utils/scss_variables.module.scss b/ui/src/utils/scss_variables.module.scss index 3c9a363356..ab9fd4608b 100644 --- a/ui/src/utils/scss_variables.module.scss +++ b/ui/src/utils/scss_variables.module.scss @@ -8,87 +8,102 @@ $field-height: 36px; $field-height-small: 20px; // Brand -$color-main: #27446E; -$color-main-dark: #13274A; -$color-main-light: #00BCEB; +$color-main: #27446e; +$color-main-dark: #13274a; +$color-main-light: #00bceb; $color-main-lighter: #1ad6ff; // Greys -$color-grey: #AEB5BC; -$color-grey-off-white: #F9FCFE; -$color-grey-light: #DFE2E5; -$color-grey-lighter: #E8EDF2; +$color-grey: #aeb5bc; +$color-grey-off-white: #f9fcfe; +$color-grey-light: #dfe2e5; +$color-grey-lighter: #e8edf2; $color-grey-black: #222529; $color-grey-dark: #596068; -$color-grey-grid: #E4EAF1; +$color-grey-grid: #e4eaf1; // Blues -$color-blue: #CFEBFB; -$color-blue-light: #EEF7FC; -$color-blue-lilac: #E6EBFC; +$color-blue: #cfebfb; +$color-blue-light: #eef7fc; +$color-blue-lilac: #e6ebfc; // Shadows $color-shadow-main: #20264d; $color-shadow-dark: #222b36; // Background: -$color-background: #F5F5F5; +$color-background: #f5f5f5; // Status -$color-error: #D0392A; -$color-error-dark: #771F16; -$color-error-light: #F1D8D5; -$color-success: #38CDA0; -$color-warning: #F0AD42; -$color-warning-dark: #D48931; -$color-warning-low: #EAD246; -$color-status-violet: #721BD5; -$color-status-blue: #30749E; -$color-status-yellow: #FAEFDB; +$color-error: #d0392a; +$color-error-dark: #771f16; +$color-error-light: #f1d8d5; +$color-success: #38cda0; +$color-warning: #f0ad42; +$color-warning-dark: #d48931; +$color-warning-low: #ead246; +$color-status-violet: #721bd5; +$color-status-blue: #30749e; +$color-status-yellow: #faefdb; //Findings: -$color-findings-1: #2C5A9C; -$color-findings-2: #3984B6; -$color-findings-3: #52BAE6; -$color-findings-4: #7FCFCE; -$color-findings-5: #8DD7C9; +$color-findings-1: #2c5a9c; +$color-findings-2: #3984b6; +$color-findings-3: #52bae6; +$color-findings-4: #7fcfce; +$color-findings-5: #8dd7c9; //dark background variations: -$color-main-dark-variation-dark: #5D677E; -$color-main-variation-dark: #6C7C97; -$color-findings-1-variation-dark: #718AB6; +$color-main-dark-variation-dark: #5d677e; +$color-main-variation-dark: #6c7c97; +$color-findings-1-variation-dark: #718ab6; //Gradients: -$color-gradient-green: linear-gradient(0deg, rgba(255, 255, 255, 0.7), rgba(255, 255, 255, 0.7)), linear-gradient(87.46deg, #38CDA0 0%, #7EC6E4 102.96%); -$color-gradient-blue: linear-gradient(0deg, rgba(255, 255, 255, 0.7), rgba(255, 255, 255, 0.7)), linear-gradient(87.46deg, #3861CD 0%, #7ED8E4 102.96%); -$color-gradient-yellow: linear-gradient(0deg, rgba(255, 255, 255, 0.7), rgba(255, 255, 255, 0.7)), linear-gradient(87.46deg, #CD9A38 0%, #E4E07E 102.96%); +$color-gradient-green: linear-gradient( + 0deg, + rgba(255, 255, 255, 0.7), + rgba(255, 255, 255, 0.7) + ), + linear-gradient(87.46deg, #38cda0 0%, #7ec6e4 102.96%); +$color-gradient-blue: linear-gradient( + 0deg, + rgba(255, 255, 255, 0.7), + rgba(255, 255, 255, 0.7) + ), + linear-gradient(87.46deg, #3861cd 0%, #7ed8e4 102.96%); +$color-gradient-yellow: linear-gradient( + 0deg, + rgba(255, 255, 255, 0.7), + rgba(255, 255, 255, 0.7) + ), + linear-gradient(87.46deg, #cd9a38 0%, #e4e07e 102.96%); :export { - color-main: $color-main; - color-main-dark: $color-main-dark; - color-main-light: $color-main-light; - color-grey: $color-grey; - color-grey-light: $color-grey-light; - color-grey-lighter: $color-grey-lighter; - color-grey-dark: $color-grey-dark; - color-blue: $color-blue; - color-blue-light: $color-blue-light; - color-success: $color-success; - color-error-dark: $color-error-dark; - color-error: $color-error; - color-warning: $color-warning; - color-warning-low: $color-warning-low; - color-status-blue: $color-status-blue; - color-grey-black: $color-grey-black; - color-findings-1: $color-findings-1; - color-findings-2: $color-findings-2; - color-findings-3: $color-findings-3; - color-findings-4: $color-findings-4; - color-findings-5: $color-findings-5; - color-gradient-green: $color-gradient-green; - color-gradient-blue: $color-gradient-blue; - color-gradient-yellow: $color-gradient-yellow; - color-main-dark-variation-dark: $color-main-dark-variation-dark; - color-main-variation-dark: $color-main-variation-dark; - color-findings-1-variation-dark: $color-findings-1-variation-dark; -} \ No newline at end of file + color-main: $color-main; + color-main-dark: $color-main-dark; + color-main-light: $color-main-light; + color-grey: $color-grey; + color-grey-light: $color-grey-light; + color-grey-lighter: $color-grey-lighter; + color-grey-dark: $color-grey-dark; + color-blue: $color-blue; + color-blue-light: $color-blue-light; + color-success: $color-success; + color-error-dark: $color-error-dark; + color-error: $color-error; + color-warning: $color-warning; + color-warning-low: $color-warning-low; + color-status-blue: $color-status-blue; + color-grey-black: $color-grey-black; + color-findings-1: $color-findings-1; + color-findings-2: $color-findings-2; + color-findings-3: $color-findings-3; + color-findings-4: $color-findings-4; + color-findings-5: $color-findings-5; + color-gradient-green: $color-gradient-green; + color-gradient-blue: $color-gradient-blue; + color-gradient-yellow: $color-gradient-yellow; + color-main-dark-variation-dark: $color-main-dark-variation-dark; + color-main-variation-dark: $color-main-variation-dark; + color-findings-1-variation-dark: $color-findings-1-variation-dark; +} diff --git a/ui/src/utils/systemConsts.js b/ui/src/utils/systemConsts.js index 91a58e513d..2c7dc1382d 100644 --- a/ui/src/utils/systemConsts.js +++ b/ui/src/utils/systemConsts.js @@ -1,93 +1,93 @@ -import { ICON_NAMES } from 'components/Icon'; +import { ICON_NAMES } from "components/Icon"; -import COLORS from 'utils/scss_variables.module.scss'; +import COLORS from "utils/scss_variables.module.scss"; export const ROUTES = { - DEFAULT: "/", - SCANS: "/scans", - ASSETS: "/assets", - ASSET_SCANS: "/assets-scans", - FINDINGS: "/findings" -} + DEFAULT: "/", + SCANS: "/scans", + ASSETS: "/assets", + ASSET_SCANS: "/assets-scans", + FINDINGS: "/findings", +}; export const APIS = { - SCANS: "scans", - SCAN_CONFIGS: "scanConfigs", - ASSETS: "assets", - ASSET_SCANS: "assetScans", - FINDINGS: "findings", - ASSET_FINDINGS: "assetFindings", - DASHBOARD_RISKIEST_REGIONS: "dashboard/riskiestRegions", - DASHBOARD_RISKIEST_ASSETS: "dashboard/riskiestAssets", - DASHBOARD_FINDINGS_TRENDS: "dashboard/findingsTrends", - DASHBOARD_FINDINGS_IMPACT: "dashboard/findingsImpact" -} + SCANS: "scans", + SCAN_CONFIGS: "scanConfigs", + ASSETS: "assets", + ASSET_SCANS: "assetScans", + FINDINGS: "findings", + ASSET_FINDINGS: "assetFindings", + DASHBOARD_RISKIEST_REGIONS: "dashboard/riskiestRegions", + DASHBOARD_RISKIEST_ASSETS: "dashboard/riskiestAssets", + DASHBOARD_FINDINGS_TRENDS: "dashboard/findingsTrends", + DASHBOARD_FINDINGS_IMPACT: "dashboard/findingsImpact", +}; export const FINDINGS_MAPPING = { - EXPLOITS: { - value: "EXPLOITS", - dataKey: "exploits", - totalKey: "totalExploits", - typeKey: "EXPLOIT", - title: "Exploits", - icon: ICON_NAMES.BOMB, - color: COLORS["color-main"], - darkColor: COLORS["color-main-variation-dark"] - }, - MISCONFIGURATIONS: { - value: "MISCONFIGURATIONS", - dataKey: "misconfigurations", - totalKey: "totalMisconfigurations", - typeKey: "MISCONFIGURATION", - title: "Misconfigurations", - icon: ICON_NAMES.COG, - color: COLORS["color-findings-1"], - darkColor: COLORS["color-findings-1-variation-dark"] - }, - SECRETS: { - value: "SECRETS", - dataKey: "secrets", - totalKey: "totalSecrets", - typeKey: "SECRET", - title: "Secrets", - icon: ICON_NAMES.KEY, - color: COLORS["color-findings-2"] - }, - MALWARE: { - value: "MALWARE", - dataKey: "malware", - totalKey: "totalMalware", - typeKey: "MALWARE", - title: "Malware", - icon: ICON_NAMES.BUG, - color: COLORS["color-findings-3"] - }, - ROOTKITS: { - value: "ROOTKITS", - dataKey: "rootkits", - totalKey: "totalRootkits", - typeKey: "ROOTKIT", - title: "Rootkits", - icon: ICON_NAMES.GHOST, - color: COLORS["color-findings-4"] - }, - PACKAGES: { - value: "PACKAGES", - dataKey:"packages", - totalKey: "totalPackages", - typeKey: "PACKAGE", - title: "Packages", - icon: ICON_NAMES.PACKAGE, - color: COLORS["color-findings-5"] - } -} + EXPLOITS: { + value: "EXPLOITS", + dataKey: "exploits", + totalKey: "totalExploits", + typeKey: "EXPLOIT", + title: "Exploits", + icon: ICON_NAMES.BOMB, + color: COLORS["color-main"], + darkColor: COLORS["color-main-variation-dark"], + }, + MISCONFIGURATIONS: { + value: "MISCONFIGURATIONS", + dataKey: "misconfigurations", + totalKey: "totalMisconfigurations", + typeKey: "MISCONFIGURATION", + title: "Misconfigurations", + icon: ICON_NAMES.COG, + color: COLORS["color-findings-1"], + darkColor: COLORS["color-findings-1-variation-dark"], + }, + SECRETS: { + value: "SECRETS", + dataKey: "secrets", + totalKey: "totalSecrets", + typeKey: "SECRET", + title: "Secrets", + icon: ICON_NAMES.KEY, + color: COLORS["color-findings-2"], + }, + MALWARE: { + value: "MALWARE", + dataKey: "malware", + totalKey: "totalMalware", + typeKey: "MALWARE", + title: "Malware", + icon: ICON_NAMES.BUG, + color: COLORS["color-findings-3"], + }, + ROOTKITS: { + value: "ROOTKITS", + dataKey: "rootkits", + totalKey: "totalRootkits", + typeKey: "ROOTKIT", + title: "Rootkits", + icon: ICON_NAMES.GHOST, + color: COLORS["color-findings-4"], + }, + PACKAGES: { + value: "PACKAGES", + dataKey: "packages", + totalKey: "totalPackages", + typeKey: "PACKAGE", + title: "Packages", + icon: ICON_NAMES.PACKAGE, + color: COLORS["color-findings-5"], + }, +}; export const VULNERABIITY_FINDINGS_ITEM = { - value: "VULNERABIITIES", - dataKey: "vulnerabilities", - typeKey: "VULNERABILITY", - title: "Vulnerabilities", - icon: ICON_NAMES.SHIELD, - color: COLORS["color-main-dark"], - darkColor: COLORS["color-main-dark-variation-dark"], -} + value: "VULNERABIITIES", + dataKey: "vulnerabilities", + typeKey: "VULNERABILITY", + title: "Vulnerabilities", + icon: ICON_NAMES.SHIELD, + color: COLORS["color-main-dark"], + darkColor: COLORS["color-main-dark-variation-dark"], +}; diff --git a/ui/src/utils/utils.jsx b/ui/src/utils/utils.jsx index b701d19235..3dd4b7a7ef 100644 --- a/ui/src/utils/utils.jsx +++ b/ui/src/utils/utils.jsx @@ -1,208 +1,282 @@ -import moment from 'moment'; -import cronstrue from 'cronstrue'; -import CVSS from '@turingpointde/cvss.js'; -import { isEmpty, orderBy } from 'lodash'; -import { FINDINGS_MAPPING, VULNERABIITY_FINDINGS_ITEM } from 'utils/systemConsts'; -import IconWithTooltip from 'components/IconWithTooltip'; -import VulnerabilitiesDisplay, { VULNERABILITY_SEVERITY_ITEMS } from 'components/VulnerabilitiesDisplay'; -import { OPERATORS } from 'components/Filter'; - -export const formatDateBy = (date, format) => !!date ? moment(date).format(format): ""; +import moment from "moment"; +import cronstrue from "cronstrue"; +import CVSS from "@turingpointde/cvss.js"; +import { isEmpty, orderBy } from "lodash"; +import { + FINDINGS_MAPPING, + VULNERABIITY_FINDINGS_ITEM, +} from "utils/systemConsts"; +import IconWithTooltip from "components/IconWithTooltip"; +import VulnerabilitiesDisplay, { + VULNERABILITY_SEVERITY_ITEMS, +} from "components/VulnerabilitiesDisplay"; +import { OPERATORS } from "components/Filter"; + +export const formatDateBy = (date, format) => + !!date ? moment(date).format(format) : ""; export const formatDate = (date) => formatDateBy(date, "MMM Do, YYYY HH:mm:ss"); export const calculateDuration = (startTime, endTime) => { - const startMoment = moment(startTime); - const endMoment = moment(endTime || new Date()); + const startMoment = moment(startTime); + const endMoment = moment(endTime || new Date()); - if (!startTime) { - return null; - } + if (!startTime) { + return null; + } - const range = ["days", "hours", "minutes", "seconds"].map(item => ({diff: endMoment.diff(startMoment, item), label: item})) - .find(({diff}) => diff > 1); + const range = ["days", "hours", "minutes", "seconds"] + .map((item) => ({ diff: endMoment.diff(startMoment, item), label: item })) + .find(({ diff }) => diff > 1); - return !!range ? `${range.diff} ${range.label}` : 'less than 1 second'; -} + return !!range ? `${range.diff} ${range.label}` : "less than 1 second"; +}; -export const toCapitalized = string => string.charAt(0).toUpperCase() + string.slice(1).toLowerCase(); +export const toCapitalized = (string) => + string.charAt(0).toUpperCase() + string.slice(1).toLowerCase(); -export const BoldText = ({children, style={}}) => {children}; +export const BoldText = ({ children, style = {} }) => ( + {children} +); -export const cronExpressionToHuman = value => cronstrue.toString(value, {use24HourTimeFormat: true}); +export const cronExpressionToHuman = (value) => + cronstrue.toString(value, { use24HourTimeFormat: true }); -export const formatNumber = value => ( - new Intl.NumberFormat("en-US").format(parseInt(value || 0, 10)) -) +export const formatNumber = (value) => + new Intl.NumberFormat("en-US").format(parseInt(value || 0, 10)); -export const getScanName = ({name, startTime}) => `${name} ${formatDate(startTime)}`; +export const getScanName = ({ name, startTime }) => + `${name} ${formatDate(startTime)}`; export const getHigestVersionCvssData = (cvssData) => { - if (isEmpty(cvssData)) { - return {}; - } - - const sortedCvss = orderBy(cvssData || [], ["version"], ["desc"]); - - const {vector, metrics, version} = sortedCvss[0]; - - const serverData = { - vector, - score: metrics.baseScore, - exploitabilityScore: metrics.exploitabilityScore, - impactScore: metrics.impactScore - } - - if (version === "2.0") { - return serverData - } - - const cvssVector = CVSS(vector); - - return { - ...serverData, - temporalScore: cvssVector.getTemporalScore(), - environmentalScore: cvssVector.getEnvironmentalScore(), - severity: cvssVector.getRating(), - metrics: cvssVector.getDetailedVectorObject().metrics, - } -} + if (isEmpty(cvssData)) { + return {}; + } + + const sortedCvss = orderBy(cvssData || [], ["version"], ["desc"]); + + const { vector, metrics, version } = sortedCvss[0]; + + const serverData = { + vector, + score: metrics.baseScore, + exploitabilityScore: metrics.exploitabilityScore, + impactScore: metrics.impactScore, + }; + + if (version === "2.0") { + return serverData; + } + + const cvssVector = CVSS(vector); + + return { + ...serverData, + temporalScore: cvssVector.getTemporalScore(), + environmentalScore: cvssVector.getEnvironmentalScore(), + severity: cvssVector.getRating(), + metrics: cvssVector.getDetailedVectorObject().metrics, + }; +}; -export const getFindingsColumnsConfigList = (props) => Object.values(FINDINGS_MAPPING).map(({totalKey, title, icon}) => { - const {tableTitle, withAssetPrefix=false} = props; - const prefix = (withAssetPrefix) ? "asset." : ""; +export const getFindingsColumnsConfigList = (props) => + Object.values(FINDINGS_MAPPING).map(({ totalKey, title, icon }) => { + const { tableTitle, withAssetPrefix = false } = props; + const prefix = withAssetPrefix ? "asset." : ""; return { - Header: , - id: totalKey, - sortIds: [`${prefix}summary.${totalKey}`], - accessor: original => { - const {summary} = (withAssetPrefix) ? original.asset : original; - - return isEmpty(summary) ? 0 : (formatNumber(summary[totalKey] || 0)); - }, - width: 50 - } -}); + Header: ( + + ), + id: totalKey, + sortIds: [`${prefix}summary.${totalKey}`], + accessor: (original) => { + const { summary } = withAssetPrefix ? original.asset : original; + + return isEmpty(summary) ? 0 : formatNumber(summary[totalKey] || 0); + }, + width: 50, + }; + }); export const getVulnerabilitiesColumnConfigItem = (props) => { - const { tableTitle, withAssetPrefix=false } = props; - const prefix = (withAssetPrefix) ? "asset." : ""; - const {title: vulnerabilitiesTitle, icon: vulnerabilitiesIcon} = VULNERABIITY_FINDINGS_ITEM; - - return { - Header: ( - - ), - id: "vulnerabilities", - sortIds: [ - `${prefix}summary.totalVulnerabilities.totalCriticalVulnerabilities`, - `${prefix}summary.totalVulnerabilities.totalHighVulnerabilities`, - `${prefix}summary.totalVulnerabilities.totalMediumVulnerabilities`, - `${prefix}summary.totalVulnerabilities.totalLowVulnerabilities`, - `${prefix}summary.totalVulnerabilities.totalNegligibleVulnerabilities` - ], - Cell: ({row}) => { - const {id, summary} = (withAssetPrefix) ? row.original.asset : row.original; - - return ( - - ) - }, - width: 50 - } + const { tableTitle, withAssetPrefix = false } = props; + const prefix = withAssetPrefix ? "asset." : ""; + const { title: vulnerabilitiesTitle, icon: vulnerabilitiesIcon } = + VULNERABIITY_FINDINGS_ITEM; + + return { + Header: ( + + ), + id: "vulnerabilities", + sortIds: [ + `${prefix}summary.totalVulnerabilities.totalCriticalVulnerabilities`, + `${prefix}summary.totalVulnerabilities.totalHighVulnerabilities`, + `${prefix}summary.totalVulnerabilities.totalMediumVulnerabilities`, + `${prefix}summary.totalVulnerabilities.totalLowVulnerabilities`, + `${prefix}summary.totalVulnerabilities.totalNegligibleVulnerabilities`, + ], + Cell: ({ row }) => { + const { id, summary } = withAssetPrefix + ? row.original.asset + : row.original; + + return ( + + ); + }, + width: 50, + }; }; -export const findingsColumnsFiltersConfig = Object.values(FINDINGS_MAPPING).map(({totalKey, title}) => { +export const findingsColumnsFiltersConfig = Object.values(FINDINGS_MAPPING).map( + ({ totalKey, title }) => { const fitlerKey = `summary.${totalKey}`; - return {value: fitlerKey, label: title, isNumber: true, operators: [ - {...OPERATORS.eq, valueItems: [], creatable: true}, - {...OPERATORS.ne, valueItems: [], creatable: true}, - {...OPERATORS.ge}, - {...OPERATORS.le}, - ]} -}); - -export const vulnerabilitiesCountersColumnsFiltersConfig = Object.values(VULNERABILITY_SEVERITY_ITEMS).map(({totalKey, title}) => { - const fitlerKey = `summary.totalVulnerabilities.${totalKey}`; - - return {value: fitlerKey, label: `${title} vulnerabilities`, isNumber: true, operators: [ - {...OPERATORS.eq, valueItems: [], creatable: true}, - {...OPERATORS.ne, valueItems: [], creatable: true}, - {...OPERATORS.ge}, - {...OPERATORS.le}, - ]} + return { + value: fitlerKey, + label: title, + isNumber: true, + operators: [ + { ...OPERATORS.eq, valueItems: [], creatable: true }, + { ...OPERATORS.ne, valueItems: [], creatable: true }, + { ...OPERATORS.ge }, + { ...OPERATORS.le }, + ], + }; + }, +); + +export const vulnerabilitiesCountersColumnsFiltersConfig = Object.values( + VULNERABILITY_SEVERITY_ITEMS, +).map(({ totalKey, title }) => { + const fitlerKey = `summary.totalVulnerabilities.${totalKey}`; + + return { + value: fitlerKey, + label: `${title} vulnerabilities`, + isNumber: true, + operators: [ + { ...OPERATORS.eq, valueItems: [], creatable: true }, + { ...OPERATORS.ne, valueItems: [], creatable: true }, + { ...OPERATORS.ge }, + { ...OPERATORS.le }, + ], + }; }); export const scanColumnsFiltersConfig = [ - {value: "scan.name", label: "Scan name", operators: [ - {...OPERATORS.eq, valueItems: [], creatable: true}, - {...OPERATORS.ne, valueItems: [], creatable: true}, - {...OPERATORS.startswith}, - {...OPERATORS.endswith}, - {...OPERATORS.contains, valueItems: [], creatable: true} - ]}, - {value: "scan.endTime", label: "Scan end time", isDate: true, operators: [ - {...OPERATORS.ge}, - {...OPERATORS.le}, - ]} -] + { + value: "scan.name", + label: "Scan name", + operators: [ + { ...OPERATORS.eq, valueItems: [], creatable: true }, + { ...OPERATORS.ne, valueItems: [], creatable: true }, + { ...OPERATORS.startswith }, + { ...OPERATORS.endswith }, + { ...OPERATORS.contains, valueItems: [], creatable: true }, + ], + }, + { + value: "scan.endTime", + label: "Scan end time", + isDate: true, + operators: [{ ...OPERATORS.ge }, { ...OPERATORS.le }], + }, +]; export const getAssetColumnsFiltersConfig = (props) => { - const {prefix="assetInfo", withType=true, withLabels=true} = props || {}; - - const ASSET_TYPE_ITEMS = [ - {value: "VMInfo", label: "VMInfo"}, - {value: "ContainerInfo", label: "ContainerInfo"}, - {value: "ContainerImageInfo", label: "ContainerImageInfo"}, - {value: "PodInfo", label: "PodInfo"}, - {value: "DirInfo", label: "DirInfo"} - ] - - return [ - {value: `${prefix}.instanceID`, label: "Asset name", operators: [ - {...OPERATORS.eq, valueItems: [], creatable: true}, - {...OPERATORS.ne, valueItems: [], creatable: true}, - {...OPERATORS.startswith}, - {...OPERATORS.endswith}, - {...OPERATORS.contains, valueItems: [], creatable: true} - ]}, - ...(!withLabels ? [] : [{value: `${prefix}.tags`, label: "Labels", operators: [ - {...OPERATORS.contains, valueItems: [], creatable: true} - ]}]), - ...(!withType ? [] : [{value: `${prefix}.objectType`, label: "Asset type", operators: [ - {...OPERATORS.eq, valueItems: ASSET_TYPE_ITEMS}, - {...OPERATORS.ne, valueItems: ASSET_TYPE_ITEMS} - ]}]), - {value: `${prefix}.location`, label: "Asset location", operators: [ - {...OPERATORS.eq, valueItems: [], creatable: true}, - {...OPERATORS.ne, valueItems: [], creatable: true}, - {...OPERATORS.startswith}, - {...OPERATORS.endswith}, - {...OPERATORS.contains, valueItems: [], creatable: true} - ]}, - ] -} + const { + prefix = "assetInfo", + withType = true, + withLabels = true, + } = props || {}; + + const ASSET_TYPE_ITEMS = [ + { value: "VMInfo", label: "VMInfo" }, + { value: "ContainerInfo", label: "ContainerInfo" }, + { value: "ContainerImageInfo", label: "ContainerImageInfo" }, + { value: "PodInfo", label: "PodInfo" }, + { value: "DirInfo", label: "DirInfo" }, + ]; + + return [ + { + value: `${prefix}.instanceID`, + label: "Asset name", + operators: [ + { ...OPERATORS.eq, valueItems: [], creatable: true }, + { ...OPERATORS.ne, valueItems: [], creatable: true }, + { ...OPERATORS.startswith }, + { ...OPERATORS.endswith }, + { ...OPERATORS.contains, valueItems: [], creatable: true }, + ], + }, + ...(!withLabels + ? [] + : [ + { + value: `${prefix}.tags`, + label: "Labels", + operators: [ + { ...OPERATORS.contains, valueItems: [], creatable: true }, + ], + }, + ]), + ...(!withType + ? [] + : [ + { + value: `${prefix}.objectType`, + label: "Asset type", + operators: [ + { ...OPERATORS.eq, valueItems: ASSET_TYPE_ITEMS }, + { ...OPERATORS.ne, valueItems: ASSET_TYPE_ITEMS }, + ], + }, + ]), + { + value: `${prefix}.location`, + label: "Asset location", + operators: [ + { ...OPERATORS.eq, valueItems: [], creatable: true }, + { ...OPERATORS.ne, valueItems: [], creatable: true }, + { ...OPERATORS.startswith }, + { ...OPERATORS.endswith }, + { ...OPERATORS.contains, valueItems: [], creatable: true }, + ], + }, + ]; +}; -export const formatTagsToStringsList = tags => tags?.map(({key, value}) => `${key}=${value}`); +export const formatTagsToStringsList = (tags) => + tags?.map(({ key, value }) => `${key}=${value}`); export function getAssetName(assetInfo) { - switch (assetInfo.objectType) { - case "VMInfo": - return assetInfo.instanceID; - case "PodInfo": - return assetInfo.podName; - case "DirInfo": - return assetInfo.dirName; - case "ContainerImageInfo": - return assetInfo.imageID; - case "ContainerInfo": - return assetInfo.containerName; - default: - return assetInfo.id; - } + switch (assetInfo.objectType) { + case "VMInfo": + return assetInfo.instanceID; + case "PodInfo": + return assetInfo.podName; + case "DirInfo": + return assetInfo.dirName; + case "ContainerImageInfo": + return assetInfo.imageID; + case "ContainerInfo": + return assetInfo.containerName; + default: + return assetInfo.id; + } } diff --git a/ui/tsconfig.json b/ui/tsconfig.json index cbdaf77781..f3c914ac7c 100644 --- a/ui/tsconfig.json +++ b/ui/tsconfig.json @@ -1,29 +1,23 @@ { - "compilerOptions": { - "target": "ESNext", - "lib": [ - "dom", - "dom.iterable", - "esnext" - ], - "baseUrl": "src", - "allowJs": true, - "skipLibCheck": true, - "esModuleInterop": true, - "allowSyntheticDefaultImports": true, - "strict": true, - "forceConsistentCasingInFileNames": true, - "noFallthroughCasesInSwitch": true, - "noImplicitAny": false, - "module": "esnext", - "moduleResolution": "node", - "resolveJsonModule": true, - "isolatedModules": true, - "noEmit": true, - "jsx": "react-jsx", - "types": ["vite/client"] - }, - "include": [ - "src" - ] + "compilerOptions": { + "target": "ESNext", + "lib": ["dom", "dom.iterable", "esnext"], + "baseUrl": "src", + "allowJs": true, + "skipLibCheck": true, + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "noFallthroughCasesInSwitch": true, + "noImplicitAny": false, + "module": "esnext", + "moduleResolution": "node", + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + "types": ["vite/client"] + }, + "include": ["src"] } diff --git a/ui/vite.config.ts b/ui/vite.config.ts index de72505a44..460c4f45ba 100644 --- a/ui/vite.config.ts +++ b/ui/vite.config.ts @@ -1,42 +1,46 @@ -import { defineConfig } from 'vite'; -import react from '@vitejs/plugin-react'; -import viteTsconfigPaths from 'vite-tsconfig-paths'; -import { esbuildPluginBrowserslist } from 'esbuild-plugin-browserslist'; -import { browserslist } from './package.json'; +import { defineConfig } from "vite"; +import react from "@vitejs/plugin-react"; +import viteTsconfigPaths from "vite-tsconfig-paths"; +import { esbuildPluginBrowserslist } from "esbuild-plugin-browserslist"; +import { browserslist } from "./package.json"; -const PROXY_TARGET = 'http://localhost:8080'; +const PROXY_TARGET = "http://localhost:8080"; export default defineConfig(({ mode }) => { - return { - base: '/', - plugins: [ - react(), - viteTsconfigPaths(), - esbuildPluginBrowserslist(mode === 'production' ? browserslist.production : browserslist.development), - ], - build: { - outDir: 'build', + return { + base: "/", + plugins: [ + react(), + viteTsconfigPaths(), + esbuildPluginBrowserslist( + mode === "production" + ? browserslist.production + : browserslist.development, + ), + ], + build: { + outDir: "build", + }, + resolve: { + alias: { + utils: "/src/utils", + }, + }, + server: { + open: true, + port: 3000, + proxy: { + "/api": { + target: PROXY_TARGET, }, - resolve: { - alias: { - utils: '/src/utils', - } + "/ui/api": { + target: PROXY_TARGET, }, - server: { - open: true, - port: 3000, - proxy: { - '/api': { - target: PROXY_TARGET, - }, - '/ui/api': { - target: PROXY_TARGET, - }, - }, - }, - test: { - globals: true, - environment: 'happy-dom' - } - } + }, + }, + test: { + globals: true, + environment: "happy-dom", + }, + }; });