diff --git a/package-lock.json b/package-lock.json index c8fba8d7e..ffb530526 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@devtron-labs/devtron-fe-common-lib", - "version": "1.20.7-pre-1", + "version": "1.21.0-pre-0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@devtron-labs/devtron-fe-common-lib", - "version": "1.20.7-pre-1", + "version": "1.21.0-pre-0", "hasInstallScript": true, "license": "ISC", "dependencies": { diff --git a/package.json b/package.json index 51b989ee7..8f213fce3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@devtron-labs/devtron-fe-common-lib", - "version": "1.20.7-pre-1", + "version": "1.21.0-pre-0", "description": "Supporting common component library", "type": "module", "main": "dist/index.js", diff --git a/src/Assets/IconV2/ic-binoculars.svg b/src/Assets/IconV2/ic-binoculars.svg new file mode 100644 index 000000000..826f834d8 --- /dev/null +++ b/src/Assets/IconV2/ic-binoculars.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/Assets/IconV2/ic-enter-fullscreen.svg b/src/Assets/IconV2/ic-enter-fullscreen.svg new file mode 100644 index 000000000..a59434ec8 --- /dev/null +++ b/src/Assets/IconV2/ic-enter-fullscreen.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/Assets/IconV2/ic-exit-fullscreen.svg b/src/Assets/IconV2/ic-exit-fullscreen.svg new file mode 100644 index 000000000..93f2add7f --- /dev/null +++ b/src/Assets/IconV2/ic-exit-fullscreen.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/Common/BreadCrumb/BreadCrumb.tsx b/src/Common/BreadCrumb/BreadCrumb.tsx index 38d9bc5c5..f94cdd7ca 100644 --- a/src/Common/BreadCrumb/BreadCrumb.tsx +++ b/src/Common/BreadCrumb/BreadCrumb.tsx @@ -16,7 +16,7 @@ import React, { useMemo, useEffect } from 'react' import { Link, useRouteMatch, useParams } from 'react-router-dom' -import { useBreadcrumbContext } from './BreadcrumbStore' +import { getBreadCrumbSeparator, useBreadcrumbContext } from './BreadcrumbStore' import { ConditionalWrap } from '../Helper' import { Breadcrumb, Breadcrumbs, UseBreadcrumbOptionalProps, UseBreadcrumbState } from './Types' @@ -90,7 +90,7 @@ export function useBreadcrumb(props?: UseBreadcrumbOptionalProps, deps?: any[]): export const BreadCrumb: React.FC = ({ breadcrumbs, sep = '/', - className = 'dc__devtron-breadcrumb__item', + className = 'dc__devtron-breadcrumb__item fs-16 fw-4 lh-1-5 dc__ellipsis-right dc__mxw-155', }) => { const { url } = useRouteMatch() const filteredCrumbs = breadcrumbs.filter((crumb) => !!crumb.name) @@ -114,9 +114,7 @@ export const BreadCrumb: React.FC = ({ {breadcrumb.name} - {idx + 1 !== filteredCrumbs.length && breadcrumb.name && ( - {sep} - )} + {idx + 1 !== filteredCrumbs.length && breadcrumb.name && getBreadCrumbSeparator()} ))} diff --git a/src/Common/BreadCrumb/BreadcrumbStore.tsx b/src/Common/BreadCrumb/BreadcrumbStore.tsx index cf91aad28..006488a62 100644 --- a/src/Common/BreadCrumb/BreadcrumbStore.tsx +++ b/src/Common/BreadCrumb/BreadcrumbStore.tsx @@ -22,9 +22,14 @@ const initialState = { } export const BreadcrumbText = ({ heading, isActive, shouldTruncate = false }: BreadcrumbTextProps) => ( -

{heading}

+ {heading} ) +export const getBreadCrumbSeparator = (sep: string = '/') => ( + {sep} +) + + const Store = ({ children }) => { const [state, setState] = useState(initialState) return {children} diff --git a/src/Common/Constants.ts b/src/Common/Constants.ts index 22219c2e0..ce392ca05 100644 --- a/src/Common/Constants.ts +++ b/src/Common/Constants.ts @@ -49,6 +49,7 @@ export const PATTERNS = { } const GLOBAL_CONFIG_TEMPLATES_DEVTRON_APP = '/global-config/templates/devtron-apps' +const OBSERVABILITY_ROOT = '/observability' export const URLS = { LOGIN: '/login', @@ -88,6 +89,10 @@ export const URLS = { GLOBAL_CONFIG_TEMPLATES_DEVTRON_APP_DETAIL: `${GLOBAL_CONFIG_TEMPLATES_DEVTRON_APP}/detail/:appId`, LICENSE_AUTH: '/license-auth', GLOBAL_CONFIG_EDIT_CLUSTER: '/global-config/cluster-env/edit/:clusterId', + // OBSERVABILITY + OBSERVABILITY: OBSERVABILITY_ROOT, + OBSERVABILITY_OVERVIEW: `${OBSERVABILITY_ROOT}/overview`, + OBSERVABILITY_CUSTOMER_LIST: `${OBSERVABILITY_ROOT}/tenants`, } as const export const ROUTES = { diff --git a/src/Shared/Components/DatePicker/DayPickerRangeController.tsx b/src/Shared/Components/DatePicker/DayPickerRangeController.tsx new file mode 100644 index 000000000..865789b43 --- /dev/null +++ b/src/Shared/Components/DatePicker/DayPickerRangeController.tsx @@ -0,0 +1,173 @@ +/* + * Copyright (c) 2024. Devtron Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { useState } from 'react' +import { DayPickerRangeController, isInclusivelyBeforeDay } from 'react-dates' +// eslint-disable-next-line import/extensions +import CustomizableCalendarDay from 'react-dates/esm/components/CustomizableCalendarDay.js' +import moment, { Moment } from 'moment' + +import { ComponentSizeType } from '@Shared/constants' + +import 'react-dates/initialize' + +import { Button } from '../Button' +import { Icon } from '../Icon' +import { customDayStyles, DayPickerCalendarInfoHorizontal, DayPickerRangeControllerPresets, styles } from './constants' +import { DatePickerRangeControllerProps } from './types' + +import 'react-dates/lib/css/_datepicker.css' + +export const DatePickerRangeController = ({ + handlePredefinedRange, + handleApply, + calendar, + calendarInputs, + handleDateInput, + handleDatesChange, + calendarValue, + focusedInput, + handleFocusChange, +}: DatePickerRangeControllerProps) => { + const [showCalendar, setShowCalender] = useState(false) + const onClickApplyTimeChange = () => { + setShowCalender(false) + handleApply() + } + + const onClickPredefinedTimeRange = (startDate: Moment, endDate: Moment, endStr: string) => () => { + handlePredefinedRange(startDate, endDate, endStr) + setShowCalender(false) + } + + const renderDatePresets = () => ( +
+
+

Pick time range

+
+
+ From + { + handleDateInput('startDate', event.target.value) + }} + /> +
+
+ To + { + handleDateInput('endDate', event.target.value) + }} + /> +
+
+
+
+ {DayPickerRangeControllerPresets.map(({ text, startDate, endDate, endStr }) => { + const isSelected = + startDate.isSame(calendar.startDate, 'minute') && + startDate.isSame(calendar.startDate, 'hour') && + startDate.isSame(calendar.startDate, 'day') && + endDate.isSame(calendar.endDate, 'day') + let buttonStyles = { + ...styles.PresetDateRangePicker_button, + } + if (isSelected) { + buttonStyles = { + ...buttonStyles, + ...styles.PresetDateRangePicker_button__selected, + } + } + return ( + + ) + })} +
+
+ ) + + const toggleCalender = () => { + setShowCalender(!showCalendar) + } + + const hideCalender = () => setShowCalender(false) + + return ( + <> +
+

{calendarValue}

+ +
+ {showCalendar && ( + !isInclusivelyBeforeDay(day, moment())} // enable past dates + renderCalendarDay={(props) => } + onOutsideClick={hideCalender} + initialVisibleMonth={() => moment().subtract(2, 'd')} // + /> + )} + + ) +} diff --git a/src/Shared/Components/DatePicker/constants.ts b/src/Shared/Components/DatePicker/constants.ts index 976fbb6ca..08e27ce3f 100644 --- a/src/Shared/Components/DatePicker/constants.ts +++ b/src/Shared/Components/DatePicker/constants.ts @@ -14,7 +14,9 @@ * limitations under the License. */ -const selectedStyles = { +import moment from 'moment' + +export const selectedStyles = { background: 'var(--B100)', color: 'var(--B500)', @@ -24,7 +26,7 @@ const selectedStyles = { }, } -const selectedSpanStyles = { +export const selectedSpanStyles = { background: 'var(--B100)', color: 'var(--B500)', hover: { @@ -33,7 +35,7 @@ const selectedSpanStyles = { }, } -const hoveredSpanStyles = { +export const hoveredSpanStyles = { background: 'var(--B100)', color: 'var(--B500)', } @@ -145,3 +147,74 @@ export const DATE_PICKER_IDS = { MONTH: 'month_picker', TIME: 'time_picker', } + +export const styles = { + PresetDateRangePicker_panel: { + padding: '0px', + width: '200px', + height: '100%', + }, + PresetDateRangePicker_button: { + width: '188px', + background: 'var(--transparent)', + border: 'none', + color: 'var(--N900)', + padding: '8px', + font: 'inherit', + fontWeight: 500, + lineHeight: 'normal', + overflow: 'visible', + cursor: 'pointer', + ':active': { + outline: 0, + }, + }, + DayPicker__horizontal: { + borderRadius: '4px', + }, + PresetDateRangePicker_button__selected: { + color: 'var(--B500)', + fontWeight: 600, + background: 'var(--B100)', + outline: 'none', + }, +} + +export const DayPickerCalendarInfoHorizontal = { + width: '532px', + boxShadow: 'none', +} + +export const DayPickerRangeControllerPresets = [ + { text: 'Last 5 minutes', endDate: moment(), startDate: moment().subtract(5, 'minutes'), endStr: 'now-5m' }, + { text: 'Last 30 minutes', endDate: moment(), startDate: moment().subtract(30, 'minutes'), endStr: 'now-30m' }, + { text: 'Last 1 hour', endDate: moment(), startDate: moment().subtract(1, 'hours'), endStr: 'now-1h' }, + { text: 'Last 24 hours', endDate: moment(), startDate: moment().subtract(24, 'hours'), endStr: 'now-24h' }, + { text: 'Last 7 days', endDate: moment(), startDate: moment().subtract(7, 'days'), endStr: 'now-7d' }, + { text: 'Last 1 month', endDate: moment(), startDate: moment().subtract(1, 'months'), endStr: 'now-1M' }, + { text: 'Last 6 months', endDate: moment(), startDate: moment().subtract(6, 'months'), endStr: 'now-6M' }, +] + +/** + * Returns a string representing the range of dates + * given by the start and end dates. If the end date + * is 'now' and the start date includes 'now', + * it will return the corresponding range from the + * DayPickerRangeControllerPresets array. + * @param startDateStr - the start date string + * @param endDateStr - the end date string + * @returns - a string representing the range of dates + */ + +export function getCalendarValue(startDateStr: string, endDateStr: string): string { + let str: string = `${startDateStr} - ${endDateStr}` + if (endDateStr === 'now' && startDateStr.includes('now')) { + const range = DayPickerRangeControllerPresets.find((d) => d.endStr === startDateStr) + if (range) { + str = range.text + } else { + str = `${startDateStr} - ${endDateStr}` + } + } + return str +} diff --git a/src/Shared/Components/DatePicker/index.ts b/src/Shared/Components/DatePicker/index.ts index aced868c3..f0cc559fa 100644 --- a/src/Shared/Components/DatePicker/index.ts +++ b/src/Shared/Components/DatePicker/index.ts @@ -16,6 +16,7 @@ export * from './constants' export { default as DateTimePicker } from './DateTimePicker' +export { DatePickerRangeController } from './DayPickerRangeController' export * from './MonthlySelect' export { default as SingleDatePickerComponent } from './SingleDatePickerComponent' export * from './TimeSelect' diff --git a/src/Shared/Components/DatePicker/types.ts b/src/Shared/Components/DatePicker/types.ts index 82cafc3cf..d8844e7d4 100644 --- a/src/Shared/Components/DatePicker/types.ts +++ b/src/Shared/Components/DatePicker/types.ts @@ -143,3 +143,16 @@ export interface DateTimePickerProps */ onChange: (date: Date) => void } + +export interface DatePickerRangeControllerProps { + calendar + calendarInputs + focusedInput + handleFocusChange + handleDatesChange + handleCalendarInputs? + calendarValue: string + handlePredefinedRange: (start: Moment, end: Moment, endStr: string) => void + handleDateInput: (key: 'startDate' | 'endDate', value: string) => void + handleApply: (...args) => void +} diff --git a/src/Shared/Components/Icon/Icon.tsx b/src/Shared/Components/Icon/Icon.tsx index 9182ed7c6..9126256d7 100644 --- a/src/Shared/Components/Icon/Icon.tsx +++ b/src/Shared/Components/Icon/Icon.tsx @@ -20,6 +20,7 @@ import { ReactComponent as ICAzureAks } from '@IconsV2/ic-azure-aks.svg' import { ReactComponent as ICBgCluster } from '@IconsV2/ic-bg-cluster.svg' import { ReactComponent as ICBgEnvironment } from '@IconsV2/ic-bg-environment.svg' import { ReactComponent as ICBharatpe } from '@IconsV2/ic-bharatpe.svg' +import { ReactComponent as ICBinoculars } from '@IconsV2/ic-binoculars.svg' import { ReactComponent as ICBitbucket } from '@IconsV2/ic-bitbucket.svg' import { ReactComponent as ICBookOpen } from '@IconsV2/ic-book-open.svg' import { ReactComponent as ICBrain } from '@IconsV2/ic-brain.svg' @@ -77,12 +78,14 @@ import { ReactComponent as ICDownload } from '@IconsV2/ic-download.svg' import { ReactComponent as ICEcr } from '@IconsV2/ic-ecr.svg' import { ReactComponent as ICEdit } from '@IconsV2/ic-edit.svg' import { ReactComponent as ICEmail } from '@IconsV2/ic-email.svg' +import { ReactComponent as ICEnterFullscreen } from '@IconsV2/ic-enter-fullscreen.svg' import { ReactComponent as ICEnterpriseFeat } from '@IconsV2/ic-enterprise-feat.svg' import { ReactComponent as ICEnterpriseTag } from '@IconsV2/ic-enterprise-tag.svg' import { ReactComponent as ICEnv } from '@IconsV2/ic-env.svg' import { ReactComponent as ICEnvironment } from '@IconsV2/ic-environment.svg' import { ReactComponent as ICEnvironmentIsolated } from '@IconsV2/ic-environment-isolated.svg' import { ReactComponent as ICError } from '@IconsV2/ic-error.svg' +import { ReactComponent as ICExitFullscreen } from '@IconsV2/ic-exit-fullscreen.svg' import { ReactComponent as ICExpandRightSm } from '@IconsV2/ic-expand-right-sm.svg' import { ReactComponent as ICExpandSm } from '@IconsV2/ic-expand-sm.svg' import { ReactComponent as ICFailure } from '@IconsV2/ic-failure.svg' @@ -262,6 +265,7 @@ export const iconMap = { 'ic-bg-cluster': ICBgCluster, 'ic-bg-environment': ICBgEnvironment, 'ic-bharatpe': ICBharatpe, + 'ic-binoculars': ICBinoculars, 'ic-bitbucket': ICBitbucket, 'ic-book-open': ICBookOpen, 'ic-brain': ICBrain, @@ -319,12 +323,14 @@ export const iconMap = { 'ic-ecr': ICEcr, 'ic-edit': ICEdit, 'ic-email': ICEmail, + 'ic-enter-fullscreen': ICEnterFullscreen, 'ic-enterprise-feat': ICEnterpriseFeat, 'ic-enterprise-tag': ICEnterpriseTag, 'ic-env': ICEnv, 'ic-environment-isolated': ICEnvironmentIsolated, 'ic-environment': ICEnvironment, 'ic-error': ICError, + 'ic-exit-fullscreen': ICExitFullscreen, 'ic-expand-right-sm': ICExpandRightSm, 'ic-expand-sm': ICExpandSm, 'ic-failure': ICFailure,