+
+
IMAGEPULLBACKOFF:
{renderErrorHeaderMessage(appDetails, 'error-bar')}
diff --git a/src/Shared/Components/Error/types.tsx b/src/Shared/Components/Error/types.tsx
index ce1fd62a9..f80a98ebb 100644
--- a/src/Shared/Components/Error/types.tsx
+++ b/src/Shared/Components/Error/types.tsx
@@ -18,6 +18,10 @@ import { AppDetails } from '../../types'
export interface ErrorBarType {
appDetails: AppDetails
+ /**
+ * @default true
+ */
+ useParentMargin?: boolean
}
export enum AppDetailsErrorType {
diff --git a/src/Shared/Components/Error/utils.tsx b/src/Shared/Components/Error/utils.tsx
index aadaa48c3..55cb563f3 100644
--- a/src/Shared/Components/Error/utils.tsx
+++ b/src/Shared/Components/Error/utils.tsx
@@ -14,7 +14,24 @@
* limitations under the License.
*/
-import { AppDetails } from '../../types'
+import { AppDetails, AppType } from '../../types'
+import { AppDetailsErrorType } from './types'
+
+export const getIsImagePullBackOff = (appDetails: AppDetails): boolean => {
+ if (appDetails?.appType === AppType.DEVTRON_APP && appDetails.resourceTree?.nodes?.length) {
+ return appDetails.resourceTree.nodes.some((node) =>
+ (node.info || []).some((info) => {
+ const infoValueLowerCase = info.value?.toLowerCase()
+ return (
+ infoValueLowerCase === AppDetailsErrorType.ERRIMAGEPULL ||
+ infoValueLowerCase === AppDetailsErrorType.IMAGEPULLBACKOFF
+ )
+ }),
+ )
+ }
+
+ return false
+}
export const renderErrorHeaderMessage = (appDetails: AppDetails, key: string, onClickActionButton?): JSX.Element => (
diff --git a/src/Shared/Components/FramerComponents/index.ts b/src/Shared/Components/FramerComponents/index.ts
index 3093fdcff..d316ca871 100644
--- a/src/Shared/Components/FramerComponents/index.ts
+++ b/src/Shared/Components/FramerComponents/index.ts
@@ -1,4 +1,4 @@
-import { AnimatePresence } from 'framer-motion'
+import { AnimatePresence, motion } from 'framer-motion'
export * from './MotionDiv'
-export { AnimatePresence }
+export { AnimatePresence, motion }
diff --git a/src/Shared/Components/Header/HeaderWithCreateButton/HeaderWithCreateButon.tsx b/src/Shared/Components/Header/HeaderWithCreateButton/HeaderWithCreateButon.tsx
index 811226972..0b503f3d2 100644
--- a/src/Shared/Components/Header/HeaderWithCreateButton/HeaderWithCreateButon.tsx
+++ b/src/Shared/Components/Header/HeaderWithCreateButton/HeaderWithCreateButon.tsx
@@ -14,122 +14,57 @@
* limitations under the License.
*/
-import { useState } from 'react'
-import { useHistory, useLocation, useParams } from 'react-router-dom'
+import { useLocation, useParams } from 'react-router-dom'
-import { ReactComponent as AddIcon } from '@Icons/ic-add.svg'
-import { ReactComponent as DropDown } from '@Icons/ic-caret-down-small.svg'
-import { ReactComponent as ChartIcon } from '@Icons/ic-charts.svg'
-import { ReactComponent as JobIcon } from '@Icons/ic-k8s-job.svg'
+import { SERVER_MODE, URLS } from '@Common/Constants'
+import { noop } from '@Common/Helper'
+import { ActionMenu } from '@Shared/Components/ActionMenu'
+import { ButtonComponentType } from '@Shared/Components/Button'
import Button from '@Shared/Components/Button/Button.component'
+import { Icon } from '@Shared/Components/Icon'
+import { AppListConstants, ComponentSizeType } from '@Shared/constants'
+import { useMainContext } from '@Shared/Providers'
-import { Modal, SERVER_MODE, URLS } from '../../../../Common'
-import { AppListConstants, ComponentSizeType } from '../../../constants'
-import { useMainContext } from '../../../Providers'
import PageHeader from '../PageHeader'
-import { getIsShowingLicenseData } from '../utils'
-
-import './HeaderWithCreateButton.scss'
-
-export interface HeaderWithCreateButtonProps {
- headerName: string
-}
+import { HeaderWithCreateButtonProps } from './types'
+import { getCreateActionMenuOptions } from './utils'
export const HeaderWithCreateButton = ({ headerName }: HeaderWithCreateButtonProps) => {
+ // HOOKS
+ const { serverMode } = useMainContext()
const params = useParams<{ appType: string }>()
- const history = useHistory()
const location = useLocation()
- const { serverMode, licenseData } = useMainContext()
- const [showCreateSelectionModal, setShowCreateSelectionModal] = useState(false)
-
- const showingLicenseBar = getIsShowingLicenseData(licenseData)
-
- const handleCreateButton = () => {
- setShowCreateSelectionModal((prevState) => !prevState)
- }
- const redirectToHelmAppDiscover = () => {
- history.push(URLS.CHARTS_DISCOVER)
- }
-
- const openCreateDevtronAppModel = () => {
- const _urlPrefix = `${URLS.APP}/${URLS.APP_LIST}/${params.appType ?? AppListConstants.AppType.DEVTRON_APPS}`
- history.push(`${_urlPrefix}/${AppListConstants.CREATE_DEVTRON_APP_URL}${location.search}`)
- }
-
- const openCreateJobModel = () => {
- history.push(`${URLS.JOB}/${URLS.APP_LIST}/${URLS.CREATE_JOB}`)
- }
+ // CONSTANTS
+ const createCustomAppURL = `${URLS.APP}/${URLS.APP_LIST}/${params.appType ?? AppListConstants.AppType.DEVTRON_APPS}/${AppListConstants.CREATE_DEVTRON_APP_URL}${location.search}`
const renderActionButtons = () =>
serverMode === SERVER_MODE.FULL ? (
-
}
- size={ComponentSizeType.small}
+ ,
+ size: ComponentSizeType.small,
+ }}
/>
) : (
)
- const renderCreateSelectionModal = () => (
-
-
-
-
-
Custom app
-
- Connect a git repository to deploy a custom application
-
-
-
-
-
-
-
From Chart store
-
- Deploy apps using third party helm charts (eg. prometheus, redis etc.)
-
-
-
-
-
-
-
Job
-
- Jobs allow manual and automated execution of developer actions.
-
-
-
-
- )
return (
- {showCreateSelectionModal && renderCreateSelectionModal()}
)
}
diff --git a/src/Shared/Components/Header/HeaderWithCreateButton/HeaderWithCreateButton.scss b/src/Shared/Components/Header/HeaderWithCreateButton/HeaderWithCreateButton.scss
deleted file mode 100644
index de5227b13..000000000
--- a/src/Shared/Components/Header/HeaderWithCreateButton/HeaderWithCreateButton.scss
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * 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.
- */
-
-.create-modal-wrapper {
- position: fixed;
- right: 33px;
- top: 59px;
- overflow: hidden;
-
- // Adding 28px for license bar/ announcement banner
- &.with-bar {
- top: 87px;
- }
-
- .create-modal-child {
- padding: 10px 15px;
- display: flex;
-
- &:first-child {
- border-bottom: 1px solid var(--N100);
- }
-
- &:hover {
- background-color: var(--N100);
- }
- }
-
- &--try-devtron {
- right: 84px !important;
- }
-}
diff --git a/src/Shared/Components/Header/HeaderWithCreateButton/types.ts b/src/Shared/Components/Header/HeaderWithCreateButton/types.ts
new file mode 100644
index 000000000..60a67ae7e
--- /dev/null
+++ b/src/Shared/Components/Header/HeaderWithCreateButton/types.ts
@@ -0,0 +1,13 @@
+import { ActionMenuProps } from '@Shared/Components/ActionMenu'
+
+export interface HeaderWithCreateButtonProps {
+ headerName: string
+}
+
+export enum CreateActionMenuItems {
+ CUSTOM_APP = 'create-custom-app',
+ CHART_STORE = 'create-from-chart-store',
+ JOB = 'create-job',
+}
+
+export type CreateActionMenuProps = ActionMenuProps
diff --git a/src/Shared/Components/Header/HeaderWithCreateButton/utils.ts b/src/Shared/Components/Header/HeaderWithCreateButton/utils.ts
new file mode 100644
index 000000000..829e6b159
--- /dev/null
+++ b/src/Shared/Components/Header/HeaderWithCreateButton/utils.ts
@@ -0,0 +1,34 @@
+import { URLS } from '@Common/Constants'
+
+import { CreateActionMenuItems, CreateActionMenuProps } from './types'
+
+export const getCreateActionMenuOptions = (createCustomAppURL: string): CreateActionMenuProps['options'] => [
+ {
+ items: [
+ {
+ id: CreateActionMenuItems.CUSTOM_APP,
+ label: 'Custom app',
+ description: 'Connect a git repository to deploy a custom application',
+ startIcon: { name: 'ic-add' },
+ componentType: 'link',
+ to: createCustomAppURL,
+ },
+ {
+ id: CreateActionMenuItems.CHART_STORE,
+ label: 'From Chart store',
+ description: 'Deploy apps using third party helm charts (eg. prometheus, redis etc.)',
+ startIcon: { name: 'ic-helm' },
+ componentType: 'link',
+ to: URLS.CHARTS_DISCOVER,
+ },
+ {
+ id: CreateActionMenuItems.JOB,
+ label: 'Job',
+ description: 'Jobs allow manual and automated execution of developer actions.',
+ startIcon: { name: 'ic-k8s-job' },
+ componentType: 'link',
+ to: `${URLS.JOB}/${URLS.APP_LIST}/${URLS.CREATE_JOB}`,
+ },
+ ],
+ },
+]
diff --git a/src/Shared/Components/Header/HelpButton.tsx b/src/Shared/Components/Header/HelpButton.tsx
new file mode 100644
index 000000000..6851c9e37
--- /dev/null
+++ b/src/Shared/Components/Header/HelpButton.tsx
@@ -0,0 +1,145 @@
+import { useRef, useState } from 'react'
+import ReactGA from 'react-ga4'
+import { SliderButton } from '@typeform/embed-react'
+
+import { URLS } from '@Common/Constants'
+import { ComponentSizeType } from '@Shared/constants'
+import { useMainContext } from '@Shared/Providers'
+
+import { ActionMenu, ActionMenuItemType } from '../ActionMenu'
+import { Button, ButtonComponentType, ButtonVariantType } from '../Button'
+import { Icon } from '../Icon'
+import { HelpButtonActionMenuProps, HelpButtonProps, HelpMenuItems, InstallationType } from './types'
+import { getHelpActionMenuOptions } from './utils'
+
+const CheckForUpdates = ({
+ serverInfo,
+ fetchingServerInfo,
+}: Pick) => (
+
+ {fetchingServerInfo ? (
+
Checking version
+ ) : (
+
Version {serverInfo?.currentVersion || ''}
+ )}
+
+
+)
+
+export const HelpButton = ({ serverInfo, fetchingServerInfo, onClick }: HelpButtonProps) => {
+ // STATES
+ const [isActionMenuOpen, setIsActionMenuOpen] = useState(false)
+
+ // HOOKS
+ const { currentServerInfo, handleOpenLicenseInfoDialog, licenseData, setGettingStartedClicked } = useMainContext()
+
+ // REFS
+ const typeFormSliderButtonRef = useRef(null)
+
+ // CONSTANTS
+ const FEEDBACK_FORM_ID = `UheGN3KJ#source=${window.location.hostname}`
+ const isEnterprise = currentServerInfo?.serverInfo?.installationType === InstallationType.ENTERPRISE
+
+ // HANDLERS
+ const handleAnalytics = (option: ActionMenuItemType) => {
+ ReactGA.event({
+ category: 'Help Nav',
+ action: `${option.label} Clicked`,
+ })
+ }
+
+ const handleOpenAboutDevtron = () => {
+ ReactGA.event({
+ category: 'help-nav__about-devtron',
+ action: 'ABOUT_DEVTRON_CLICKED',
+ })
+ handleOpenLicenseInfoDialog()
+ }
+
+ const handleFeedbackClick = () => {
+ typeFormSliderButtonRef.current?.open()
+ }
+
+ const handleGettingStartedClick = () => {
+ setGettingStartedClicked(true)
+ }
+
+ const handleActionMenuClick: HelpButtonActionMenuProps['onClick'] = (item) => {
+ switch (item.id) {
+ case HelpMenuItems.GETTING_STARTED:
+ handleGettingStartedClick()
+ break
+ case HelpMenuItems.ABOUT_DEVTRON:
+ handleOpenAboutDevtron()
+ break
+ case HelpMenuItems.GIVE_FEEDBACK:
+ handleFeedbackClick()
+ break
+ case HelpMenuItems.JOIN_DISCORD_COMMUNITY:
+ case HelpMenuItems.VIEW_DOCUMENTATION:
+ case HelpMenuItems.OPEN_NEW_TICKET:
+ case HelpMenuItems.VIEW_ALL_TICKETS:
+ case HelpMenuItems.CHAT_WITH_SUPPORT:
+ case HelpMenuItems.RAISE_ISSUE_REQUEST:
+ handleAnalytics(item)
+ break
+ default:
+ }
+ }
+
+ return (
+ <>
+
+ id="page-header-help-action-menu"
+ alignment="end"
+ width={220}
+ options={getHelpActionMenuOptions({
+ isTrial: licenseData?.isTrial ?? false,
+ isEnterprise,
+ })}
+ onClick={handleActionMenuClick}
+ onOpen={setIsActionMenuOpen}
+ {...(serverInfo?.installationType === InstallationType.OSS_HELM
+ ? {
+ menuListFooterConfig: {
+ type: 'customNode',
+ value: (
+
+ ),
+ },
+ }
+ : {})}
+ >
+
+
+ Help
+
+
+
+ {isEnterprise && (
+
+ This button is hidden on UI (opening type-form via ref)
+
+ )}
+ >
+ )
+}
diff --git a/src/Shared/Components/Header/HelpNav.tsx b/src/Shared/Components/Header/HelpNav.tsx
deleted file mode 100644
index 0916f53f9..000000000
--- a/src/Shared/Components/Header/HelpNav.tsx
+++ /dev/null
@@ -1,160 +0,0 @@
-/*
- * 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 { Fragment } from 'react'
-import ReactGA from 'react-ga4'
-import { NavLink } from 'react-router-dom'
-import { SliderButton } from '@typeform/embed-react'
-
-import { ReactComponent as Feedback } from '../../../Assets/Icon/ic-feedback.svg'
-import { ReactComponent as GettingStartedIcon } from '../../../Assets/Icon/ic-onboarding.svg'
-import { stopPropagation, URLS } from '../../../Common'
-import { useMainContext } from '../../Providers'
-import { Icon } from '../Icon'
-import { HelpNavType, HelpOptionType, InstallationType } from './types'
-import { getHelpOptions } from './utils'
-
-const HelpNav = ({
- className,
- setShowHelpCard,
- serverInfo,
- fetchingServerInfo,
- setGettingStartedClicked,
- showHelpCard,
-}: HelpNavType) => {
- const { currentServerInfo, handleOpenLicenseInfoDialog, licenseData } = useMainContext()
- const isEnterprise = currentServerInfo?.serverInfo?.installationType === InstallationType.ENTERPRISE
- const isTrial = licenseData?.isTrial ?? false
- const FEEDBACK_FORM_ID = `UheGN3KJ#source=${window.location.hostname}`
-
- const CommonHelpOptions: HelpOptionType[] = getHelpOptions(isEnterprise, isTrial)
-
- const onClickGettingStarted = (): void => {
- setGettingStartedClicked(true)
- }
-
- const onClickHelpOptions = (option: HelpOptionType): void => {
- ReactGA.event({
- category: 'Help Nav',
- action: `${option.name} Clicked`,
- })
- }
-
- const toggleHelpCard = (): void => {
- setShowHelpCard(!showHelpCard)
- }
-
- const renderHelpFeedback = (): JSX.Element => (
-
-
-
- Give feedback
-
-
- )
-
- const handleHelpOptions = (e) => {
- const option = CommonHelpOptions[e.currentTarget.dataset.index]
- onClickHelpOptions(option)
- }
-
- const handleOpenAboutDevtron = () => {
- ReactGA.event({
- category: 'help-nav__about-devtron',
- action: 'ABOUT_DEVTRON_CLICKED',
- })
- handleOpenLicenseInfoDialog()
- }
-
- const renderHelpOptions = (): JSX.Element => (
- <>
- {CommonHelpOptions.map((option, index) => (
-
-
-
- {option.name}
-
- {index === 1 && (
- <>
-
-
- About Devtron
-
- {isEnterprise && (
-
- Enterprise Support
-
- )}
- >
- )}
-
- ))}
- >
- )
-
- return (
-
-
- {!window._env_.K8S_CLIENT && (
-
-
-
- Getting started
-
-
- )}
- {renderHelpOptions()}
- {isEnterprise && renderHelpFeedback()}
- {serverInfo?.installationType === InstallationType.OSS_HELM && (
-
- {fetchingServerInfo ? (
- Checking current version
- ) : (
- version {serverInfo?.currentVersion || ''}
- )}
-
- Check for Updates
-
- )}
-
-
- )
-}
-
-export default HelpNav
diff --git a/src/Shared/Components/Header/PageHeader.tsx b/src/Shared/Components/Header/PageHeader.tsx
index b31e9034f..e30dfdedb 100644
--- a/src/Shared/Components/Header/PageHeader.tsx
+++ b/src/Shared/Components/Header/PageHeader.tsx
@@ -20,17 +20,15 @@ import Tippy from '@tippyjs/react'
import { ReactComponent as ICCaretDownSmall } from '@Icons/ic-caret-down-small.svg'
import { ReactComponent as Close } from '@Icons/ic-close.svg'
-import { ReactComponent as Question } from '@Icons/ic-help-outline.svg'
import { ReactComponent as ICMediumPaintBucket } from '@IconsV2/ic-medium-paintbucket.svg'
import { getAlphabetIcon, TippyCustomized, TippyTheme } from '../../../Common'
import { MAX_LOGIN_COUNT, POSTHOG_EVENT_ONBOARDING } from '../../../Common/Constants'
import { useMainContext, useTheme, useUserEmail } from '../../Providers'
-import AnnouncementBanner from '../AnnouncementBanner/AnnouncementBanner'
import GettingStartedCard from '../GettingStartedCard/GettingStarted'
import { InfoIconTippy } from '../InfoIconTippy'
import LogoutCard from '../LogoutCard'
-import HelpNav from './HelpNav'
+import { HelpButton } from './HelpButton'
import { IframePromoButton } from './IframePromoButton'
import { getServerInfo } from './service'
import { InstallationType, PageHeaderType, ServerInfo } from './types'
@@ -49,22 +47,14 @@ const PageHeader = ({
showCloseButton = false,
onClose,
markAsBeta,
- showAnnouncementHeader,
tippyProps,
}: PageHeaderType) => {
- const {
- loginCount,
- setLoginCount,
- showGettingStartedCard,
- setShowGettingStartedCard,
- setGettingStartedClicked,
- licenseData,
- } = useMainContext()
+ const { loginCount, setLoginCount, showGettingStartedCard, setShowGettingStartedCard, licenseData } =
+ useMainContext()
const { showSwitchThemeLocationTippy, handleShowSwitchThemeLocationTippyChange } = useTheme()
const { isTippyCustomized, tippyRedirectLink, TippyIcon, tippyMessage, onClickTippyButton, additionalContent } =
tippyProps || {}
- const [showHelpCard, setShowHelpCard] = useState(false)
const [showLogOutCard, setShowLogOutCard] = useState(false)
const { email } = useUserEmail()
const [currentServerInfo, setCurrentServerInfo] = useState<{ serverInfo: ServerInfo; fetchingServerInfo: boolean }>(
@@ -110,24 +100,22 @@ const PageHeader = ({
const onClickLogoutButton = () => {
handleCloseSwitchThemeLocationTippyChange()
setShowLogOutCard(!showLogOutCard)
- if (showHelpCard) {
- setShowHelpCard(false)
- }
setActionWithExpiry('clickedOkay', 1)
hideGettingStartedCard()
}
- const onClickHelp = async () => {
+ const onHelpButtonClick = async () => {
if (
!window._env_.K8S_CLIENT &&
currentServerInfo.serverInfo?.installationType !== InstallationType.ENTERPRISE
) {
await getCurrentServerInfo()
}
- setShowHelpCard(!showHelpCard)
+
if (showLogOutCard) {
setShowLogOutCard(false)
}
+
setActionWithExpiry('clickedOkay', 1)
hideGettingStartedCard()
await handlePostHogEventUpdate(POSTHOG_EVENT_ONBOARDING.HELP)
@@ -146,18 +134,11 @@ const PageHeader = ({
const renderLogoutHelpSection = () => (
<>
-
-
-
-
-
- Help
-
-
-
+
{!window._env_.K8S_CLIENT && (
{showTabs && renderHeaderTabs()}
- {showHelpCard && (
-
- )}
{!window._env_.K8S_CLIENT &&
showGettingStartedCard &&
loginCount >= 0 &&
@@ -313,7 +284,6 @@ const PageHeader = ({
{renderLogoutHelpSection()}
)}
- {showAnnouncementHeader &&
}
)
}
diff --git a/src/Shared/Components/Header/constants.ts b/src/Shared/Components/Header/constants.ts
index e4b2ac646..0d0263c34 100644
--- a/src/Shared/Components/Header/constants.ts
+++ b/src/Shared/Components/Header/constants.ts
@@ -14,45 +14,89 @@
* limitations under the License.
*/
+import { DISCORD_LINK, DOCUMENTATION_HOME_PAGE, URLS } from '@Common/Constants'
import { CONTACT_SUPPORT_LINK, OPEN_NEW_TICKET, RAISE_ISSUE, VIEW_ALL_TICKETS } from '@Shared/constants'
-import { ReactComponent as Chat } from '../../../Assets/Icon/ic-chat-circle-dots.svg'
-import { ReactComponent as EditFile } from '../../../Assets/Icon/ic-edit-file.svg'
-import { ReactComponent as Files } from '../../../Assets/Icon/ic-files.svg'
-import { DISCORD_LINK } from '../../../Common'
-import { HelpOptionType } from './types'
+import { HelpButtonActionMenuProps, HelpMenuItems } from './types'
-export const EnterpriseHelpOptions: HelpOptionType[] = [
+export const COMMON_HELP_ACTION_MENU_ITEMS: HelpButtonActionMenuProps['options'][number]['items'] = [
+ ...((!window._env_?.K8S_CLIENT
+ ? [
+ {
+ id: HelpMenuItems.GETTING_STARTED,
+ label: 'Getting started',
+ startIcon: { name: 'ic-path' },
+ componentType: 'link',
+ to: `/${URLS.GETTING_STARTED}`,
+ },
+ ]
+ : []) satisfies HelpButtonActionMenuProps['options'][number]['items']),
{
- name: 'Open new ticket',
- link: OPEN_NEW_TICKET,
- icon: EditFile,
+ id: HelpMenuItems.VIEW_DOCUMENTATION,
+ label: 'View documentation',
+ startIcon: { name: 'ic-book-open' },
+ componentType: 'anchor',
+ href: DOCUMENTATION_HOME_PAGE,
},
{
- name: 'View all tickets',
- link: VIEW_ALL_TICKETS,
- icon: Files,
+ id: HelpMenuItems.JOIN_DISCORD_COMMUNITY,
+ label: 'Join discord community',
+ startIcon: { name: 'ic-discord-fill' },
+ componentType: 'anchor',
+ href: DISCORD_LINK,
+ },
+ {
+ id: HelpMenuItems.ABOUT_DEVTRON,
+ label: 'About Devtron',
+ startIcon: { name: 'ic-devtron' },
},
]
-export const OSSHelpOptions: HelpOptionType[] = [
+export const OSS_HELP_ACTION_MENU_ITEMS: HelpButtonActionMenuProps['options'][number]['items'] = [
{
- name: 'Chat with support',
- link: DISCORD_LINK,
- icon: Chat,
+ id: HelpMenuItems.CHAT_WITH_SUPPORT,
+ label: 'Chat with support',
+ componentType: 'anchor',
+ href: DISCORD_LINK,
+ startIcon: { name: 'ic-chat-circle-online' },
},
+ {
+ id: HelpMenuItems.RAISE_ISSUE_REQUEST,
+ label: 'Raise an issue/request',
+ startIcon: { name: 'ic-file-edit' },
+ componentType: 'anchor',
+ href: RAISE_ISSUE,
+ },
+]
+export const ENTERPRISE_TRIAL_HELP_ACTION_MENU_ITEMS: HelpButtonActionMenuProps['options'][number]['items'] = [
{
- name: 'Raise an issue/request',
- link: RAISE_ISSUE,
- icon: EditFile,
+ id: HelpMenuItems.REQUEST_SUPPORT,
+ label: 'Request Support',
+ startIcon: { name: 'ic-file-edit' },
+ componentType: 'anchor',
+ href: CONTACT_SUPPORT_LINK,
},
]
-export const TrialHelpOptions: HelpOptionType[] = [
+export const ENTERPRISE_HELP_ACTION_MENU_ITEMS: HelpButtonActionMenuProps['options'][number]['items'] = [
+ {
+ id: HelpMenuItems.OPEN_NEW_TICKET,
+ label: 'Open new ticket',
+ startIcon: { name: 'ic-edit' },
+ componentType: 'anchor',
+ href: OPEN_NEW_TICKET,
+ },
+ {
+ id: HelpMenuItems.VIEW_ALL_TICKETS,
+ label: 'View all tickets',
+ startIcon: { name: 'ic-files' },
+ componentType: 'anchor',
+ href: VIEW_ALL_TICKETS,
+ },
{
- name: 'Request Support',
- link: CONTACT_SUPPORT_LINK,
- icon: EditFile,
+ id: HelpMenuItems.GIVE_FEEDBACK,
+ label: 'Give feedback',
+ startIcon: { name: 'ic-megaphone-right' },
},
]
diff --git a/src/Shared/Components/Header/types.ts b/src/Shared/Components/Header/types.ts
index dc352b95a..0dc15f387 100644
--- a/src/Shared/Components/Header/types.ts
+++ b/src/Shared/Components/Header/types.ts
@@ -17,6 +17,7 @@
import { ModuleStatus } from '@Shared/types'
import { ResponseType, TippyCustomizedProps } from '../../../Common'
+import { ActionMenuProps } from '../ActionMenu'
export enum InstallationType {
OSS_KUBECTL = 'oss_kubectl',
@@ -35,7 +36,6 @@ export interface PageHeaderType {
showCloseButton?: boolean
onClose?: () => void
markAsBeta?: boolean
- showAnnouncementHeader?: boolean
tippyProps?: Pick
& {
isTippyCustomized?: boolean
tippyRedirectLink?: string
@@ -56,17 +56,23 @@ export interface ServerInfoResponse extends ResponseType {
result?: ServerInfo
}
-export interface HelpNavType {
- className: string
- setShowHelpCard: React.Dispatch>
+export interface HelpButtonProps {
serverInfo: ServerInfo
fetchingServerInfo: boolean
- setGettingStartedClicked: (isClicked: boolean) => void
- showHelpCard: boolean
+ onClick: () => void
}
-export interface HelpOptionType {
- name: string
- link: string
- icon: React.FunctionComponent>
+export enum HelpMenuItems {
+ GETTING_STARTED = 'getting-started',
+ VIEW_DOCUMENTATION = 'view-documentation',
+ JOIN_DISCORD_COMMUNITY = 'join-discord-community',
+ ABOUT_DEVTRON = 'about-devtron',
+ REQUEST_SUPPORT = 'request-support',
+ OPEN_NEW_TICKET = 'open-new-ticket',
+ VIEW_ALL_TICKETS = 'view-all-tickets',
+ GIVE_FEEDBACK = 'give-feedback',
+ CHAT_WITH_SUPPORT = 'chat-with-support',
+ RAISE_ISSUE_REQUEST = 'raise-issue-request',
}
+
+export type HelpButtonActionMenuProps = ActionMenuProps
diff --git a/src/Shared/Components/Header/utils.ts b/src/Shared/Components/Header/utils.ts
index feebdc56a..769041b14 100644
--- a/src/Shared/Components/Header/utils.ts
+++ b/src/Shared/Components/Header/utils.ts
@@ -14,13 +14,17 @@
* limitations under the License.
*/
-import { ReactComponent as Discord } from '@Icons/ic-discord-fill.svg'
-import { ReactComponent as File } from '@Icons/ic-file-text.svg'
+import { LOGIN_COUNT } from '@Common/Constants'
-import { DISCORD_LINK, DOCUMENTATION_HOME_PAGE, LOGIN_COUNT } from '../../../Common'
import { DevtronLicenseInfo, LicenseStatus } from '../License'
-import { EnterpriseHelpOptions, OSSHelpOptions, TrialHelpOptions } from './constants'
+import {
+ COMMON_HELP_ACTION_MENU_ITEMS,
+ ENTERPRISE_HELP_ACTION_MENU_ITEMS,
+ ENTERPRISE_TRIAL_HELP_ACTION_MENU_ITEMS,
+ OSS_HELP_ACTION_MENU_ITEMS,
+} from './constants'
import { updatePostHogEvent } from './service'
+import { HelpButtonActionMenuProps } from './types'
const millisecondsInDay = 86400000
export const getDateInMilliseconds = (days) => 1 + new Date().valueOf() + (days ?? 0) * millisecondsInDay
@@ -42,27 +46,26 @@ export const setActionWithExpiry = (key: string, days: number): void => {
export const getIsShowingLicenseData = (licenseData: DevtronLicenseInfo) =>
licenseData && (licenseData.licenseStatus !== LicenseStatus.ACTIVE || licenseData.isTrial)
-const getInstallationSpecificHelpOptions = (isEnterprise: boolean, isTrial: boolean) => {
- if (isEnterprise) {
- return isTrial ? TrialHelpOptions : EnterpriseHelpOptions
- }
- return OSSHelpOptions
-}
-
-export const getHelpOptions = (isEnterprise: boolean, isTrial: boolean) => {
- const HelpOptions = getInstallationSpecificHelpOptions(isEnterprise, isTrial)
- return [
- {
- name: 'View documentation',
- link: DOCUMENTATION_HOME_PAGE,
- icon: File,
- },
-
- {
- name: 'Join discord community',
- link: DISCORD_LINK,
- icon: Discord,
- },
- ...HelpOptions,
- ]
-}
+export const getHelpActionMenuOptions = ({
+ isEnterprise,
+ isTrial,
+}: {
+ isEnterprise: boolean
+ isTrial: boolean
+}): HelpButtonActionMenuProps['options'] => [
+ {
+ items: COMMON_HELP_ACTION_MENU_ITEMS,
+ },
+ ...(isEnterprise
+ ? [
+ {
+ groupLabel: 'Enterprise Support',
+ items: isTrial ? ENTERPRISE_TRIAL_HELP_ACTION_MENU_ITEMS : ENTERPRISE_HELP_ACTION_MENU_ITEMS,
+ },
+ ]
+ : [
+ {
+ items: OSS_HELP_ACTION_MENU_ITEMS,
+ },
+ ]),
+]
diff --git a/src/Shared/Components/Icon/Icon.tsx b/src/Shared/Components/Icon/Icon.tsx
index 5db478d3f..d26e99da4 100644
--- a/src/Shared/Components/Icon/Icon.tsx
+++ b/src/Shared/Components/Icon/Icon.tsx
@@ -3,13 +3,16 @@
import { ReactComponent as IC73strings } from '@IconsV2/ic-73strings.svg'
import { ReactComponent as ICAborted } from '@IconsV2/ic-aborted.svg'
import { ReactComponent as ICAdd } from '@IconsV2/ic-add.svg'
+import { ReactComponent as ICAmazonEks } from '@IconsV2/ic-amazon-eks.svg'
import { ReactComponent as ICApica } from '@IconsV2/ic-apica.svg'
import { ReactComponent as ICAppGroup } from '@IconsV2/ic-app-group.svg'
import { ReactComponent as ICArrowClockwise } from '@IconsV2/ic-arrow-clockwise.svg'
import { ReactComponent as ICArrowRight } from '@IconsV2/ic-arrow-right.svg'
+import { ReactComponent as ICArrowSquareOut } from '@IconsV2/ic-arrow-square-out.svg'
import { ReactComponent as ICArrowsLeftRight } from '@IconsV2/ic-arrows-left-right.svg'
import { ReactComponent as ICAther } from '@IconsV2/ic-ather.svg'
import { ReactComponent as ICAzure } from '@IconsV2/ic-azure.svg'
+import { ReactComponent as ICAzureAks } from '@IconsV2/ic-azure-aks.svg'
import { ReactComponent as ICBgCluster } from '@IconsV2/ic-bg-cluster.svg'
import { ReactComponent as ICBharatpe } from '@IconsV2/ic-bharatpe.svg'
import { ReactComponent as ICBitbucket } from '@IconsV2/ic-bitbucket.svg'
@@ -19,9 +22,12 @@ import { ReactComponent as ICBuildColor } from '@IconsV2/ic-build-color.svg'
import { ReactComponent as ICCalendar } from '@IconsV2/ic-calendar.svg'
import { ReactComponent as ICCancelled } from '@IconsV2/ic-cancelled.svg'
import { ReactComponent as ICCardStack } from '@IconsV2/ic-card-stack.svg'
+import { ReactComponent as ICCaretDownSmall } from '@IconsV2/ic-caret-down-small.svg'
import { ReactComponent as ICCaretLeft } from '@IconsV2/ic-caret-left.svg'
+import { ReactComponent as ICCaretRight } from '@IconsV2/ic-caret-right.svg'
import { ReactComponent as ICCd } from '@IconsV2/ic-cd.svg'
import { ReactComponent as ICChatCircleDots } from '@IconsV2/ic-chat-circle-dots.svg'
+import { ReactComponent as ICChatCircleOnline } from '@IconsV2/ic-chat-circle-online.svg'
import { ReactComponent as ICCheck } from '@IconsV2/ic-check.svg'
import { ReactComponent as ICChecks } from '@IconsV2/ic-checks.svg'
import { ReactComponent as ICCiLinked } from '@IconsV2/ic-ci-linked.svg'
@@ -30,6 +36,8 @@ import { ReactComponent as ICCircleLoader } from '@IconsV2/ic-circle-loader.svg'
import { ReactComponent as ICClock } from '@IconsV2/ic-clock.svg'
import { ReactComponent as ICCloseLarge } from '@IconsV2/ic-close-large.svg'
import { ReactComponent as ICCloseSmall } from '@IconsV2/ic-close-small.svg'
+import { ReactComponent as ICCloudVms } from '@IconsV2/ic-cloud-vms.svg'
+import { ReactComponent as ICCluster } from '@IconsV2/ic-cluster.svg'
import { ReactComponent as ICCode } from '@IconsV2/ic-code.svg'
import { ReactComponent as ICContainer } from '@IconsV2/ic-container.svg'
import { ReactComponent as ICCookr } from '@IconsV2/ic-cookr.svg'
@@ -38,24 +46,38 @@ import { ReactComponent as ICCpu } from '@IconsV2/ic-cpu.svg'
import { ReactComponent as ICCrown } from '@IconsV2/ic-crown.svg'
import { ReactComponent as ICCube } from '@IconsV2/ic-cube.svg'
import { ReactComponent as ICDelete } from '@IconsV2/ic-delete.svg'
+import { ReactComponent as ICDeleteDots } from '@IconsV2/ic-delete-dots.svg'
import { ReactComponent as ICDeleteLightning } from '@IconsV2/ic-delete-lightning.svg'
import { ReactComponent as ICDelhivery } from '@IconsV2/ic-delhivery.svg'
import { ReactComponent as ICDevtron } from '@IconsV2/ic-devtron.svg'
import { ReactComponent as ICDevtronHeaderLogo } from '@IconsV2/ic-devtron-header-logo.svg'
+import { ReactComponent as ICDisconnect } from '@IconsV2/ic-disconnect.svg'
+import { ReactComponent as ICDiscordFill } from '@IconsV2/ic-discord-fill.svg'
import { ReactComponent as ICDockerhub } from '@IconsV2/ic-dockerhub.svg'
import { ReactComponent as ICEcr } from '@IconsV2/ic-ecr.svg'
+import { ReactComponent as ICEdit } from '@IconsV2/ic-edit.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 ICError } from '@IconsV2/ic-error.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'
+import { ReactComponent as ICFile } from '@IconsV2/ic-file.svg'
+import { ReactComponent as ICFileEdit } from '@IconsV2/ic-file-edit.svg'
import { ReactComponent as ICFileKey } from '@IconsV2/ic-file-key.svg'
+import { ReactComponent as ICFiles } from '@IconsV2/ic-files.svg'
import { ReactComponent as ICFolderUser } from '@IconsV2/ic-folder-user.svg'
import { ReactComponent as ICGear } from '@IconsV2/ic-gear.svg'
+import { ReactComponent as ICGift } from '@IconsV2/ic-gift.svg'
+import { ReactComponent as ICGiftGradient } from '@IconsV2/ic-gift-gradient.svg'
import { ReactComponent as ICGit } from '@IconsV2/ic-git.svg'
import { ReactComponent as ICGithub } from '@IconsV2/ic-github.svg'
import { ReactComponent as ICGitlab } from '@IconsV2/ic-gitlab.svg'
import { ReactComponent as ICGoogle } from '@IconsV2/ic-google.svg'
import { ReactComponent as ICGoogleArtifactRegistry } from '@IconsV2/ic-google-artifact-registry.svg'
import { ReactComponent as ICGoogleContainerRegistry } from '@IconsV2/ic-google-container-registry.svg'
+import { ReactComponent as ICGoogleGke } from '@IconsV2/ic-google-gke.svg'
import { ReactComponent as ICGridView } from '@IconsV2/ic-grid-view.svg'
import { ReactComponent as ICHandPointing } from '@IconsV2/ic-hand-pointing.svg'
import { ReactComponent as ICHeartGreen } from '@IconsV2/ic-heart-green.svg'
@@ -68,20 +90,27 @@ import { ReactComponent as ICHibernate } from '@IconsV2/ic-hibernate.svg'
import { ReactComponent as ICInProgress } from '@IconsV2/ic-in-progress.svg'
import { ReactComponent as ICInfoFilled } from '@IconsV2/ic-info-filled.svg'
import { ReactComponent as ICInfoOutline } from '@IconsV2/ic-info-outline.svg'
+import { ReactComponent as ICInstall } from '@IconsV2/ic-install.svg'
import { ReactComponent as ICJobColor } from '@IconsV2/ic-job-color.svg'
+import { ReactComponent as ICK3s } from '@IconsV2/ic-k3s.svg'
import { ReactComponent as ICK8sJob } from '@IconsV2/ic-k8s-job.svg'
import { ReactComponent as ICKey } from '@IconsV2/ic-key.svg'
+import { ReactComponent as ICKind } from '@IconsV2/ic-kind.svg'
+import { ReactComponent as ICLaptop } from '@IconsV2/ic-laptop.svg'
import { ReactComponent as ICLdap } from '@IconsV2/ic-ldap.svg'
import { ReactComponent as ICLightning } from '@IconsV2/ic-lightning.svg'
import { ReactComponent as ICLightningFill } from '@IconsV2/ic-lightning-fill.svg'
import { ReactComponent as ICLivspace } from '@IconsV2/ic-livspace.svg'
-import { ReactComponent as ICLoginDevtronLogo } from '@IconsV2/ic-login-devtron-logo.svg'
import { ReactComponent as ICLogout } from '@IconsV2/ic-logout.svg'
import { ReactComponent as ICMediumDelete } from '@IconsV2/ic-medium-delete.svg'
import { ReactComponent as ICMediumPaintbucket } from '@IconsV2/ic-medium-paintbucket.svg'
+import { ReactComponent as ICMegaphoneLeft } from '@IconsV2/ic-megaphone-left.svg'
+import { ReactComponent as ICMegaphoneRight } from '@IconsV2/ic-megaphone-right.svg'
import { ReactComponent as ICMemory } from '@IconsV2/ic-memory.svg'
import { ReactComponent as ICMicrosoft } from '@IconsV2/ic-microsoft.svg'
+import { ReactComponent as ICMinikube } from '@IconsV2/ic-minikube.svg'
import { ReactComponent as ICMissing } from '@IconsV2/ic-missing.svg'
+import { ReactComponent as ICMobile } from '@IconsV2/ic-mobile.svg'
import { ReactComponent as ICMonitoring } from '@IconsV2/ic-monitoring.svg'
import { ReactComponent as ICNew } from '@IconsV2/ic-new.svg'
import { ReactComponent as ICNodeScript } from '@IconsV2/ic-node-script.svg'
@@ -91,6 +120,7 @@ import { ReactComponent as ICOpenInNew } from '@IconsV2/ic-open-in-new.svg'
import { ReactComponent as ICOpenshift } from '@IconsV2/ic-openshift.svg'
import { ReactComponent as ICOutOfSync } from '@IconsV2/ic-out-of-sync.svg'
import { ReactComponent as ICPaperPlaneColor } from '@IconsV2/ic-paper-plane-color.svg'
+import { ReactComponent as ICPath } from '@IconsV2/ic-path.svg'
import { ReactComponent as ICPencil } from '@IconsV2/ic-pencil.svg'
import { ReactComponent as ICQuay } from '@IconsV2/ic-quay.svg'
import { ReactComponent as ICQuote } from '@IconsV2/ic-quote.svg'
@@ -101,22 +131,28 @@ import { ReactComponent as ICSlidersVertical } from '@IconsV2/ic-sliders-vertica
import { ReactComponent as ICSortAscending } from '@IconsV2/ic-sort-ascending.svg'
import { ReactComponent as ICSortDescending } from '@IconsV2/ic-sort-descending.svg'
import { ReactComponent as ICSortable } from '@IconsV2/ic-sortable.svg'
+import { ReactComponent as ICSparkleColor } from '@IconsV2/ic-sparkle-color.svg'
import { ReactComponent as ICSpinny } from '@IconsV2/ic-spinny.svg'
import { ReactComponent as ICStack } from '@IconsV2/ic-stack.svg'
import { ReactComponent as ICStamp } from '@IconsV2/ic-stamp.svg'
import { ReactComponent as ICSuccess } from '@IconsV2/ic-success.svg'
import { ReactComponent as ICSuspended } from '@IconsV2/ic-suspended.svg'
import { ReactComponent as ICTata1mg } from '@IconsV2/ic-tata1mg.svg'
+import { ReactComponent as ICTerminal } from '@IconsV2/ic-terminal.svg'
import { ReactComponent as ICTerminalFill } from '@IconsV2/ic-terminal-fill.svg'
+import { ReactComponent as ICThermometer } from '@IconsV2/ic-thermometer.svg'
import { ReactComponent as ICThumbDown } from '@IconsV2/ic-thumb-down.svg'
import { ReactComponent as ICThumbUp } from '@IconsV2/ic-thumb-up.svg'
import { ReactComponent as ICTimeoutDash } from '@IconsV2/ic-timeout-dash.svg'
import { ReactComponent as ICTimer } from '@IconsV2/ic-timer.svg'
import { ReactComponent as ICTravclan } from '@IconsV2/ic-travclan.svg'
+import { ReactComponent as ICUbuntu } from '@IconsV2/ic-ubuntu.svg'
import { ReactComponent as ICUnknown } from '@IconsV2/ic-unknown.svg'
+import { ReactComponent as ICUserCircle } from '@IconsV2/ic-user-circle.svg'
import { ReactComponent as ICUserKey } from '@IconsV2/ic-user-key.svg'
import { ReactComponent as ICUsers } from '@IconsV2/ic-users.svg'
import { ReactComponent as ICWarning } from '@IconsV2/ic-warning.svg'
+import { ReactComponent as ICWifiSlash } from '@IconsV2/ic-wifi-slash.svg'
// eslint-disable-next-line no-restricted-imports
import { IconBase } from './IconBase'
@@ -126,12 +162,15 @@ export const iconMap = {
'ic-73strings': IC73strings,
'ic-aborted': ICAborted,
'ic-add': ICAdd,
+ 'ic-amazon-eks': ICAmazonEks,
'ic-apica': ICApica,
'ic-app-group': ICAppGroup,
'ic-arrow-clockwise': ICArrowClockwise,
'ic-arrow-right': ICArrowRight,
+ 'ic-arrow-square-out': ICArrowSquareOut,
'ic-arrows-left-right': ICArrowsLeftRight,
'ic-ather': ICAther,
+ 'ic-azure-aks': ICAzureAks,
'ic-azure': ICAzure,
'ic-bg-cluster': ICBgCluster,
'ic-bharatpe': ICBharatpe,
@@ -142,9 +181,12 @@ export const iconMap = {
'ic-calendar': ICCalendar,
'ic-cancelled': ICCancelled,
'ic-card-stack': ICCardStack,
+ 'ic-caret-down-small': ICCaretDownSmall,
'ic-caret-left': ICCaretLeft,
+ 'ic-caret-right': ICCaretRight,
'ic-cd': ICCd,
'ic-chat-circle-dots': ICChatCircleDots,
+ 'ic-chat-circle-online': ICChatCircleOnline,
'ic-check': ICCheck,
'ic-checks': ICChecks,
'ic-ci-linked': ICCiLinked,
@@ -153,6 +195,8 @@ export const iconMap = {
'ic-clock': ICClock,
'ic-close-large': ICCloseLarge,
'ic-close-small': ICCloseSmall,
+ 'ic-cloud-vms': ICCloudVms,
+ 'ic-cluster': ICCluster,
'ic-code': ICCode,
'ic-container': ICContainer,
'ic-cookr': ICCookr,
@@ -160,24 +204,38 @@ export const iconMap = {
'ic-cpu': ICCpu,
'ic-crown': ICCrown,
'ic-cube': ICCube,
+ 'ic-delete-dots': ICDeleteDots,
'ic-delete-lightning': ICDeleteLightning,
'ic-delete': ICDelete,
'ic-delhivery': ICDelhivery,
'ic-devtron-header-logo': ICDevtronHeaderLogo,
'ic-devtron': ICDevtron,
+ 'ic-disconnect': ICDisconnect,
+ 'ic-discord-fill': ICDiscordFill,
'ic-dockerhub': ICDockerhub,
'ic-ecr': ICEcr,
+ 'ic-edit': ICEdit,
+ 'ic-enterprise-feat': ICEnterpriseFeat,
+ 'ic-enterprise-tag': ICEnterpriseTag,
'ic-env': ICEnv,
'ic-error': ICError,
+ 'ic-expand-right-sm': ICExpandRightSm,
+ 'ic-expand-sm': ICExpandSm,
'ic-failure': ICFailure,
+ 'ic-file-edit': ICFileEdit,
'ic-file-key': ICFileKey,
+ 'ic-file': ICFile,
+ 'ic-files': ICFiles,
'ic-folder-user': ICFolderUser,
'ic-gear': ICGear,
+ 'ic-gift-gradient': ICGiftGradient,
+ 'ic-gift': ICGift,
'ic-git': ICGit,
'ic-github': ICGithub,
'ic-gitlab': ICGitlab,
'ic-google-artifact-registry': ICGoogleArtifactRegistry,
'ic-google-container-registry': ICGoogleContainerRegistry,
+ 'ic-google-gke': ICGoogleGke,
'ic-google': ICGoogle,
'ic-grid-view': ICGridView,
'ic-hand-pointing': ICHandPointing,
@@ -191,20 +249,27 @@ export const iconMap = {
'ic-in-progress': ICInProgress,
'ic-info-filled': ICInfoFilled,
'ic-info-outline': ICInfoOutline,
+ 'ic-install': ICInstall,
'ic-job-color': ICJobColor,
+ 'ic-k3s': ICK3s,
'ic-k8s-job': ICK8sJob,
'ic-key': ICKey,
+ 'ic-kind': ICKind,
+ 'ic-laptop': ICLaptop,
'ic-ldap': ICLdap,
'ic-lightning-fill': ICLightningFill,
'ic-lightning': ICLightning,
'ic-livspace': ICLivspace,
- 'ic-login-devtron-logo': ICLoginDevtronLogo,
'ic-logout': ICLogout,
'ic-medium-delete': ICMediumDelete,
'ic-medium-paintbucket': ICMediumPaintbucket,
+ 'ic-megaphone-left': ICMegaphoneLeft,
+ 'ic-megaphone-right': ICMegaphoneRight,
'ic-memory': ICMemory,
'ic-microsoft': ICMicrosoft,
+ 'ic-minikube': ICMinikube,
'ic-missing': ICMissing,
+ 'ic-mobile': ICMobile,
'ic-monitoring': ICMonitoring,
'ic-new': ICNew,
'ic-node-script': ICNodeScript,
@@ -214,6 +279,7 @@ export const iconMap = {
'ic-openshift': ICOpenshift,
'ic-out-of-sync': ICOutOfSync,
'ic-paper-plane-color': ICPaperPlaneColor,
+ 'ic-path': ICPath,
'ic-pencil': ICPencil,
'ic-quay': ICQuay,
'ic-quote': ICQuote,
@@ -224,6 +290,7 @@ export const iconMap = {
'ic-sort-ascending': ICSortAscending,
'ic-sort-descending': ICSortDescending,
'ic-sortable': ICSortable,
+ 'ic-sparkle-color': ICSparkleColor,
'ic-spinny': ICSpinny,
'ic-stack': ICStack,
'ic-stamp': ICStamp,
@@ -231,15 +298,20 @@ export const iconMap = {
'ic-suspended': ICSuspended,
'ic-tata1mg': ICTata1mg,
'ic-terminal-fill': ICTerminalFill,
+ 'ic-terminal': ICTerminal,
+ 'ic-thermometer': ICThermometer,
'ic-thumb-down': ICThumbDown,
'ic-thumb-up': ICThumbUp,
'ic-timeout-dash': ICTimeoutDash,
'ic-timer': ICTimer,
'ic-travclan': ICTravclan,
+ 'ic-ubuntu': ICUbuntu,
'ic-unknown': ICUnknown,
+ 'ic-user-circle': ICUserCircle,
'ic-user-key': ICUserKey,
'ic-users': ICUsers,
'ic-warning': ICWarning,
+ 'ic-wifi-slash': ICWifiSlash,
}
export type IconName = keyof typeof iconMap
diff --git a/src/Shared/Components/Icon/IconBase.tsx b/src/Shared/Components/Icon/IconBase.tsx
index cb837f9fc..a42bdce0b 100644
--- a/src/Shared/Components/Icon/IconBase.tsx
+++ b/src/Shared/Components/Icon/IconBase.tsx
@@ -16,6 +16,7 @@
import { ConditionalWrap } from '@Common/Helper'
import { Tooltip } from '@Common/Tooltip'
+import { isNullOrUndefined } from '@Shared/Helpers'
import { ICON_STROKE_WIDTH_MAP } from './constants'
import { IconBaseProps } from './types'
@@ -28,7 +29,16 @@ const conditionalWrap = (tooltipProps: IconBaseProps['tooltipProps']) => (childr
)
-export const IconBase = ({ name, iconMap, size = 16, tooltipProps, color }: IconBaseProps) => {
+export const IconBase = ({
+ name,
+ iconMap,
+ size = 16,
+ tooltipProps,
+ color,
+ dataTestId,
+ rotateBy,
+ fillSpace = false,
+}: IconBaseProps) => {
const IconComponent = iconMap[name]
if (!IconComponent) {
@@ -38,12 +48,14 @@ export const IconBase = ({ name, iconMap, size = 16, tooltipProps, color }: Icon
return (
diff --git a/src/Shared/Components/Icon/types.ts b/src/Shared/Components/Icon/types.ts
index d5ddd47b1..bfcf0be32 100644
--- a/src/Shared/Components/Icon/types.ts
+++ b/src/Shared/Components/Icon/types.ts
@@ -22,21 +22,43 @@ import { IconBaseColorType, IconBaseSizeType } from '@Shared/index'
type IconMap = Record>>
export interface IconBaseProps {
- /** The name of the icon to render. */
+ /**
+ * The name of the icon to render.
+ */
name: keyof IconMap
- /** The map containing all available icons. */
+ /**
+ * A map containing all available icons.
+ */
iconMap: IconMap
/**
- * The size of the icon in pixels.
+ * The size of the icon in pixels. If not provided, the default size is `16px`.
+ *
* @default 16
*/
- size?: IconBaseSizeType
- /** Props to configure the tooltip when hovering over the icon. */
+ size?: IconBaseSizeType | null
+ /**
+ * Configuration for the tooltip displayed when hovering over the icon.
+ */
tooltipProps?: TooltipProps
/**
- * The color of the icon (color tokens). \
- * If `null`, the default color present in icon is used.
- * @example `'B500'`, `'N200'`, `'G50'`, `'R700'`
+ * The color of the icon, specified using predefined color tokens.
+ * If set to `null`, the icon's default color will be used.
+ *
+ * @example 'B500', 'N200', 'G50', 'R700'
*/
color: IconBaseColorType
+ /**
+ * A unique identifier for testing purposes, typically used in test automation.
+ */
+ dataTestId?: string
+ /**
+ * Rotates the icon by the specified number of degrees.
+ *
+ * @example 90, 180, 270
+ */
+ rotateBy?: number
+ /**
+ * If true, the icon will expand to fill the available space of its container.
+ */
+ fillSpace?: boolean
}
diff --git a/src/Shared/Components/InfoBlock/InfoBlock.component.tsx b/src/Shared/Components/InfoBlock/InfoBlock.component.tsx
index e87831c04..0f5309242 100644
--- a/src/Shared/Components/InfoBlock/InfoBlock.component.tsx
+++ b/src/Shared/Components/InfoBlock/InfoBlock.component.tsx
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-import { ComponentSizeType } from '@Shared/constants'
+import { ComponentSizeType, VARIANT_TO_BG_MAP, VARIANT_TO_BORDER_MAP } from '@Shared/constants'
import { deriveBorderRadiusAndBorderClassFromConfig } from '@Shared/Helpers'
import { Button } from '../Button'
@@ -22,7 +22,6 @@ import {
CONTAINER_SIZE_TO_BUTTON_SIZE,
CONTAINER_SIZE_TO_CLASS_MAP,
SIZE_TO_ICON_CLASS_MAP,
- VARIANT_TO_BG_MAP,
VARIANT_TO_ICON_MAP,
} from './constants'
import { InfoBlockProps } from './types'
@@ -38,7 +37,7 @@ const InfoBlock = ({
borderRadiusConfig,
borderConfig,
}: InfoBlockProps) => {
- const baseContainerClass = `${CONTAINER_SIZE_TO_CLASS_MAP[size]} ${VARIANT_TO_BG_MAP[variant]} ${deriveBorderRadiusAndBorderClassFromConfig({ borderConfig, borderRadiusConfig })} w-100 py-8 br-4 bw-1`
+ const baseContainerClass = `${CONTAINER_SIZE_TO_CLASS_MAP[size]} ${VARIANT_TO_BG_MAP[variant]} ${VARIANT_TO_BORDER_MAP[variant]} ${deriveBorderRadiusAndBorderClassFromConfig({ borderConfig, borderRadiusConfig })} w-100 py-8 br-4 bw-1`
const iconClass = `dc__no-shrink flex dc__fill-available-space ${SIZE_TO_ICON_CLASS_MAP[size]}`
const icon = customIcon ?? VARIANT_TO_ICON_MAP[variant]
diff --git a/src/Shared/Components/InfoBlock/constants.tsx b/src/Shared/Components/InfoBlock/constants.tsx
index 7d6867861..037ad7b23 100644
--- a/src/Shared/Components/InfoBlock/constants.tsx
+++ b/src/Shared/Components/InfoBlock/constants.tsx
@@ -14,28 +14,19 @@
* limitations under the License.
*/
-import { ComponentSizeType } from '@Shared/constants'
+import { ComponentSizeType, InfoBlockVariant } from '@Shared/constants'
import { ButtonProps } from '../Button'
import { Icon } from '../Icon'
import { InfoBlockProps } from './types'
-export const VARIANT_TO_BG_MAP: Record = {
- error: 'bcr-1 er-2',
- help: 'bcv-1 ev-2',
- information: 'bcb-1 eb-2',
- success: 'bcg-1 eg-2',
- warning: 'bcy-1 ey-2',
- neutral: 'bcn-1 en-2',
-}
-
-export const VARIANT_TO_ICON_MAP: Record = {
- error: ,
- help: ,
- information: ,
- success: ,
- warning: ,
- neutral: ,
+export const VARIANT_TO_ICON_MAP: Record = {
+ [InfoBlockVariant.ERROR]: ,
+ [InfoBlockVariant.HELP]: ,
+ [InfoBlockVariant.INFORMATION]: ,
+ [InfoBlockVariant.SUCCESS]: ,
+ [InfoBlockVariant.WARNING]: ,
+ [InfoBlockVariant.NEUTRAL]: ,
}
export const CONTAINER_SIZE_TO_CLASS_MAP: Record = {
diff --git a/src/Shared/Components/InfoBlock/types.ts b/src/Shared/Components/InfoBlock/types.ts
index 776488e81..53009853f 100644
--- a/src/Shared/Components/InfoBlock/types.ts
+++ b/src/Shared/Components/InfoBlock/types.ts
@@ -16,7 +16,7 @@
import { ReactElement, ReactNode } from 'react'
-import { ComponentSizeType } from '@Shared/constants'
+import { ComponentSizeType, InfoBlockVariantType } from '@Shared/constants'
import { BorderConfigType, ComponentLayoutType } from '@Shared/types'
import { ButtonComponentType, ButtonProps } from '../Button'
@@ -29,7 +29,7 @@ export type InfoBlockProps = {
/**
* @default 'information'
*/
- variant?: 'error' | 'help' | 'information' | 'success' | 'warning' | 'neutral'
+ variant?: InfoBlockVariantType
/**
* @default ComponentSizeType.large
*/
diff --git a/src/Shared/Components/InfoIconTippy/InfoIconTippy.tsx b/src/Shared/Components/InfoIconTippy/InfoIconTippy.tsx
index 437b05def..eb79c7339 100644
--- a/src/Shared/Components/InfoIconTippy/InfoIconTippy.tsx
+++ b/src/Shared/Components/InfoIconTippy/InfoIconTippy.tsx
@@ -31,6 +31,7 @@ const InfoIconTippy = ({
dataTestid = 'info-tippy-button',
children,
headingInfo,
+ buttonPadding = 'p-0',
}: InfoIconTippyProps) => (
diff --git a/src/Shared/Components/License/index.tsx b/src/Shared/Components/License/index.tsx
index 671b23814..9c4fddeb1 100644
--- a/src/Shared/Components/License/index.tsx
+++ b/src/Shared/Components/License/index.tsx
@@ -2,4 +2,4 @@ export { default as ActivateLicenseDialog } from './ActivateLicenseDialog'
export { default as DevtronLicenseCard } from './DevtronLicenseCard'
export { ICDevtronWithBorder, default as InstallationFingerprintInfo } from './License.components'
export * from './types'
-export { parseDevtronLicenseData } from './utils'
+export { parseDevtronLicenseData, parseDevtronLicenseDTOIntoLicenseCardData } from './utils'
diff --git a/src/Shared/Components/License/utils.tsx b/src/Shared/Components/License/utils.tsx
index 0f011841f..65f484e53 100644
--- a/src/Shared/Components/License/utils.tsx
+++ b/src/Shared/Components/License/utils.tsx
@@ -34,7 +34,7 @@ const getDevtronLicenseStatus = ({
return LicenseStatus.ACTIVE
}
-const parseDevtronLicenseDTOIntoLicenseCardData = (
+export const parseDevtronLicenseDTOIntoLicenseCardData = (
licenseDTO: DevtronLicenseDTO,
currentUserEmail?: isCentralDashboard extends true ? string : never,
): Omit => {
diff --git a/src/Shared/Components/LoginBanner/Testimonials.components.tsx b/src/Shared/Components/LoginBanner/Testimonials.components.tsx
new file mode 100644
index 000000000..d78b78c21
--- /dev/null
+++ b/src/Shared/Components/LoginBanner/Testimonials.components.tsx
@@ -0,0 +1,21 @@
+import { Icon } from '../Icon'
+import { TestimonialCardConfig } from './types'
+
+const TestimonialContent = ({ quote, name, designation, iconName }: TestimonialCardConfig) => (
+ <>
+ {quote}"
+
+
+ {name}
+ {designation}
+
+ {iconName && (
+
+
+
+ )}
+
+ >
+)
+
+export default TestimonialContent
diff --git a/src/Shared/Components/LoginBanner/index.tsx b/src/Shared/Components/LoginBanner/index.tsx
index bead5bda4..726c6f74d 100644
--- a/src/Shared/Components/LoginBanner/index.tsx
+++ b/src/Shared/Components/LoginBanner/index.tsx
@@ -1 +1,3 @@
+export { TESTIMONIAL_CARD_DATA } from './constants'
export { default as LoginBanner } from './LoginBanner'
+export { default as TestimonialContent } from './Testimonials.components'
diff --git a/src/Shared/Components/Popover/Popover.component.tsx b/src/Shared/Components/Popover/Popover.component.tsx
new file mode 100644
index 000000000..68640c840
--- /dev/null
+++ b/src/Shared/Components/Popover/Popover.component.tsx
@@ -0,0 +1,38 @@
+import { AnimatePresence, motion } from 'framer-motion'
+
+import { Button } from '../Button'
+import { PopoverProps } from './types'
+
+import './popover.scss'
+
+/**
+ * Popover Component \
+ * This component serves as a base for creating popovers. It is not intended to be used directly.
+ * @note Use this component in conjunction with the `usePopover` hook to create a custom popover component. \
+ * For example, see the `ActionMenu` component for reference.
+ */
+export const Popover = ({
+ open,
+ popoverProps,
+ overlayProps,
+ triggerProps,
+ buttonProps,
+ triggerElement,
+ children,
+}: PopoverProps) => (
+
+
{triggerElement || }
+
+
+ {open && (
+ <>
+ {/* Overlay to block interactions with the background */}
+
+
+ {children}
+
+ >
+ )}
+
+
+)
diff --git a/src/Shared/Components/Popover/index.ts b/src/Shared/Components/Popover/index.ts
new file mode 100644
index 000000000..76a40223d
--- /dev/null
+++ b/src/Shared/Components/Popover/index.ts
@@ -0,0 +1,3 @@
+export * from './Popover.component'
+export * from './types'
+export * from './usePopover.hook'
diff --git a/src/Shared/Components/Popover/popover.scss b/src/Shared/Components/Popover/popover.scss
new file mode 100644
index 000000000..f241ad180
--- /dev/null
+++ b/src/Shared/Components/Popover/popover.scss
@@ -0,0 +1,12 @@
+.popover-overlay {
+ position: fixed;
+ top:0;
+ right:0;
+ bottom:0;
+ left:0;
+ z-index: var(--modal-index);
+}
+
+.popover-content {
+ z-index: var(--modal-index);
+}
diff --git a/src/Shared/Components/Popover/types.ts b/src/Shared/Components/Popover/types.ts
new file mode 100644
index 000000000..5b1fdaac8
--- /dev/null
+++ b/src/Shared/Components/Popover/types.ts
@@ -0,0 +1,107 @@
+import { DetailedHTMLProps, KeyboardEvent, MutableRefObject, ReactElement } from 'react'
+import { HTMLMotionProps } from 'framer-motion'
+
+import { ButtonProps } from '../Button'
+
+export interface UsePopoverProps {
+ /**
+ * A unique identifier for the popover.
+ */
+ id: string
+ /**
+ * The width of the popover.
+ * Can be a number representing the width in pixels or the string `'auto'` for automatic sizing (up to 250px).
+ * @default 'auto'
+ */
+ width?: number | 'auto'
+ /**
+ * The position of the popover relative to its trigger element.
+ * Possible values are:
+ * - `'bottom'`: Positions the popover below the trigger.
+ * - `'top'`: Positions the popover above the trigger.
+ * - `'left'`: Positions the popover to the left of the trigger.
+ * - `'right'`: Positions the popover to the right of the trigger.
+ * @default 'bottom'
+ */
+ position?: 'bottom' | 'top' | 'left' | 'right'
+ /**
+ * The alignment of the popover relative to its trigger element.
+ * Possible values are:
+ * - `'start'`: Aligns the popover to the start of the trigger.
+ * - `'middle'`: Aligns the popover to the center of the trigger.
+ * - `'end'`: Aligns the popover to the end of the trigger.
+ * @default 'start'
+ */
+ alignment?: 'start' | 'middle' | 'end'
+ /**
+ * Callback function triggered when the popover is opened or closed.
+ * @param open - A boolean indicating whether the popover is open.
+ */
+ onOpen?: (open: boolean) => void
+ /**
+ * Callback function triggered when a key is pressed while the trigger element is focused.
+ * @param e - The keyboard event.
+ * @param openState - A boolean indicating whether the popover is currently open.
+ * @param closePopover - A function to close the popover.
+ */
+ onTriggerKeyDown?: (e: KeyboardEvent, openState: boolean, closePopover: () => void) => void
+ /**
+ * Callback function triggered when a key is pressed while the popover is focused.
+ * @param e - The keyboard event.
+ * @param openState - A boolean indicating whether the popover is currently open.
+ * @param closePopover - A function to close the popover.
+ */
+ onPopoverKeyDown?: (e: KeyboardEvent, openState: boolean, closePopover: () => void) => void
+}
+
+/**
+ * Represents the return type of the `usePopover` hook, providing properties and methods
+ * to manage and interact with a popover component.
+ */
+export interface UsePopoverReturnType {
+ /**
+ * Indicates whether the popover is currently open.
+ */
+ open: boolean
+ /**
+ * Props to be spread onto the trigger element that opens the popover.
+ * These props include standard HTML attributes for a `div` element.
+ */
+ triggerProps: DetailedHTMLProps, HTMLDivElement>
+ /**
+ * Props to be spread onto the overlay element of the popover.
+ * These props include standard HTML attributes for a `div` element.
+ */
+ overlayProps: DetailedHTMLProps, HTMLDivElement>
+ /**
+ * Props to be spread onto the popover element itself.
+ * Includes motion-related props for animations and a `ref` to the popover's `div` element.
+ */
+ popoverProps: HTMLMotionProps<'div'> & { ref: MutableRefObject }
+ /**
+ * A mutable reference to the scrollable element inside the popover. \
+ * This reference should be assigned to the element that is scrollable.
+ */
+ scrollableRef: MutableRefObject
+ /**
+ * A function to close the popover.
+ */
+ closePopover: () => void
+}
+
+export type PopoverProps = Pick & {
+ /**
+ * Popover contents.
+ * This can be any React element or JSX to render inside the popover.
+ */
+ children: ReactElement
+ /**
+ * Properties for the button to which the Popover is attached.
+ */
+ buttonProps: ButtonProps | null
+ /**
+ * The React element to which the Popover is attached.
+ * @note only use when `triggerElement` is not `Button` component otherwise use `buttonProps`.
+ */
+ triggerElement: ReactElement | null
+}
diff --git a/src/Shared/Components/Popover/usePopover.hook.ts b/src/Shared/Components/Popover/usePopover.hook.ts
new file mode 100644
index 000000000..d95ff93b2
--- /dev/null
+++ b/src/Shared/Components/Popover/usePopover.hook.ts
@@ -0,0 +1,126 @@
+import { useLayoutEffect, useRef, useState } from 'react'
+
+import { UsePopoverProps, UsePopoverReturnType } from './types'
+import {
+ getPopoverActualPositionAlignment,
+ getPopoverAlignmentStyle,
+ getPopoverFramerProps,
+ getPopoverPositionStyle,
+} from './utils'
+
+export const usePopover = ({
+ id,
+ position = 'bottom',
+ alignment = 'start',
+ width = 'auto',
+ onOpen,
+ onPopoverKeyDown,
+ onTriggerKeyDown,
+}: UsePopoverProps): UsePopoverReturnType => {
+ // STATES
+ const [open, setOpen] = useState(false)
+ const [actualPosition, setActualPosition] = useState(position)
+ const [actualAlignment, setActualAlignment] = useState(alignment)
+
+ // CONSTANTS
+ const isAutoWidth = width === 'auto'
+
+ // REFS
+ const triggerRef = useRef(null)
+ const popover = useRef(null)
+ const scrollableRef = useRef(null)
+
+ // HANDLERS
+ const updateOpenState = (openState: typeof open) => {
+ setOpen(openState)
+ onOpen?.(openState)
+ }
+
+ const togglePopover = () => updateOpenState(!open)
+
+ const closePopover = () => updateOpenState(false)
+
+ const handleTriggerKeyDown = (e: React.KeyboardEvent) => {
+ if (!open && (e.key === 'Enter' || e.key === ' ')) {
+ e.preventDefault()
+ updateOpenState(true)
+ }
+
+ onTriggerKeyDown?.(e, open, closePopover)
+ }
+
+ const handlePopoverKeyDown = (e: React.KeyboardEvent) => onPopoverKeyDown(e, open, closePopover)
+
+ useLayoutEffect(() => {
+ if (!open || !triggerRef.current || !popover.current || !scrollableRef.current) {
+ return
+ }
+
+ const triggerRect = triggerRef.current.getBoundingClientRect()
+ const popoverRect = popover.current.getBoundingClientRect()
+
+ const { fallbackPosition, fallbackAlignment } = getPopoverActualPositionAlignment({
+ position,
+ alignment,
+ triggerRect,
+ popoverRect,
+ })
+
+ setActualPosition(fallbackPosition)
+ setActualAlignment(fallbackAlignment)
+
+ // prevent scroll propagation unless scrollable
+ const handleWheel = (e: WheelEvent) => {
+ e.stopPropagation()
+
+ const atTop = scrollableRef.current.scrollTop === 0 && e.deltaY < 0
+ const atBottom =
+ scrollableRef.current.scrollHeight - scrollableRef.current.clientHeight ===
+ scrollableRef.current.scrollTop && e.deltaY > 0
+
+ if (atTop || atBottom) {
+ e.preventDefault()
+ }
+ }
+
+ scrollableRef.current.addEventListener('wheel', handleWheel, { passive: false })
+ // eslint-disable-next-line consistent-return
+ return () => {
+ scrollableRef.current.removeEventListener('wheel', handleWheel)
+ }
+ }, [open, position, alignment])
+
+ return {
+ open,
+ triggerProps: {
+ role: 'button',
+ ref: triggerRef,
+ onClick: togglePopover,
+ onKeyDown: handleTriggerKeyDown,
+ 'aria-haspopup': 'listbox',
+ 'aria-expanded': open,
+ tabIndex: 0,
+ },
+ overlayProps: {
+ role: 'dialog',
+ onClick: closePopover,
+ className: 'popover-overlay',
+ },
+ popoverProps: {
+ id,
+ ref: popover,
+ role: 'listbox',
+ className: `popover-content dc__position-abs bg__menu--primary shadow__menu border__primary br-6 dc__overflow-hidden ${isAutoWidth ? 'dc_width-max-content dc__mxw-250' : ''}`,
+ onKeyDown: handlePopoverKeyDown,
+ style: {
+ width: !isAutoWidth ? `${width}px` : undefined,
+ ...getPopoverPositionStyle({ position: actualPosition }),
+ ...getPopoverAlignmentStyle({ position: actualPosition, alignment: actualAlignment }),
+ },
+ ...getPopoverFramerProps({ position: actualPosition, alignment: actualAlignment }),
+ transition: { duration: 0.2 },
+ },
+ scrollableRef,
+ closePopover,
+ }
+}
diff --git a/src/Shared/Components/Popover/utils.ts b/src/Shared/Components/Popover/utils.ts
new file mode 100644
index 000000000..c1d6f3928
--- /dev/null
+++ b/src/Shared/Components/Popover/utils.ts
@@ -0,0 +1,119 @@
+import { HTMLMotionProps } from 'framer-motion'
+
+import { UsePopoverProps } from './types'
+
+export const getPopoverAlignmentStyle = ({ position, alignment }: Pick) => {
+ if (position === 'top' || position === 'bottom') {
+ switch (alignment) {
+ case 'end':
+ return { right: 0 }
+ case 'middle':
+ return { left: '50%' }
+ case 'start':
+ default:
+ return { left: 0 }
+ }
+ }
+
+ switch (alignment) {
+ case 'end':
+ return { bottom: 0 }
+ case 'middle':
+ return { top: '50%' }
+ case 'start':
+ default:
+ return { top: 0 }
+ }
+}
+
+export const getPopoverPositionStyle = ({ position }: Pick) => {
+ switch (position) {
+ case 'top':
+ return { bottom: '100%', marginBottom: 6 }
+ case 'left':
+ return { right: '100%', marginRight: 6 }
+ case 'right':
+ return { left: '100%', marginLeft: 6 }
+ case 'bottom':
+ default:
+ return { top: '100%', marginTop: 6 }
+ }
+}
+
+const getPopoverAnimationProps = (axisKey: 'x' | 'y', axisInitialValue: number, isMiddleAlignment: boolean) =>
+ ({
+ initial: { opacity: 0, [axisKey]: axisInitialValue },
+ animate: { opacity: 1, [axisKey]: 0 },
+ exit: { opacity: 0, [axisKey]: axisInitialValue },
+ transformTemplate: (isMiddleAlignment
+ ? (params) =>
+ axisKey === 'y' ? `translate(-50%, ${params[axisKey]})` : `translate(${params[axisKey]}, -50%,)`
+ : undefined) as HTMLMotionProps<'div'>['transformTemplate'],
+ }) satisfies HTMLMotionProps<'div'>
+
+export const getPopoverFramerProps = ({ position, alignment }: Pick) => {
+ const isMiddleAlignment = alignment === 'middle'
+
+ if (position === 'top' || position === 'bottom') {
+ const initialY = position === 'bottom' ? -12 : 12
+ return getPopoverAnimationProps('y', initialY, isMiddleAlignment)
+ }
+
+ const initialX = position === 'right' ? -12 : 12
+ return getPopoverAnimationProps('x', initialX, isMiddleAlignment)
+}
+
+export const getPopoverActualPositionAlignment = ({
+ position,
+ alignment,
+ triggerRect,
+ popoverRect,
+}: Pick & { triggerRect: DOMRect; popoverRect: DOMRect }) => {
+ const space = {
+ top: triggerRect.top,
+ bottom: window.innerHeight - triggerRect.bottom,
+ left: triggerRect.left,
+ right: window.innerWidth - triggerRect.right,
+ }
+
+ const fits = {
+ top: popoverRect.height <= space.top,
+ bottom: popoverRect.height <= space.bottom,
+ left: popoverRect.width <= space.left,
+ right: popoverRect.width <= space.right,
+ }
+
+ const fallbackPosition =
+ (fits[position] && position) ||
+ (fits.bottom && 'bottom') ||
+ (fits.top && 'top') ||
+ (fits.right && 'right') ||
+ (fits.left && 'left') ||
+ position
+
+ const fitsAlign =
+ fallbackPosition === 'top' || fallbackPosition === 'bottom'
+ ? {
+ start: triggerRect.left + popoverRect.width <= window.innerWidth,
+ middle:
+ triggerRect.left + triggerRect.width / 2 - popoverRect.width / 2 >= 0 &&
+ triggerRect.left + triggerRect.width / 2 + popoverRect.width / 2 <= window.innerWidth,
+ end: triggerRect.right - popoverRect.width >= 0,
+ }
+ : {
+ start: triggerRect.top + popoverRect.height <= window.innerHeight,
+ middle:
+ triggerRect.top + triggerRect.height / 2 - popoverRect.height / 2 >= 0 &&
+ triggerRect.top + triggerRect.height / 2 + popoverRect.height / 2 <= window.innerHeight,
+ end: triggerRect.bottom - popoverRect.height >= 0,
+ }
+
+ const fallbackAlignment =
+ (fitsAlign[alignment] && alignment) ||
+ (fitsAlign.start && 'start') ||
+ (fitsAlign.middle && 'middle') ||
+ (fitsAlign.end && 'end') ||
+ alignment
+
+ return { fallbackPosition, fallbackAlignment }
+}
diff --git a/src/Shared/Components/Security/SecurityModal/types.ts b/src/Shared/Components/Security/SecurityModal/types.ts
index b1588666a..79e1dac6e 100644
--- a/src/Shared/Components/Security/SecurityModal/types.ts
+++ b/src/Shared/Components/Security/SecurityModal/types.ts
@@ -16,7 +16,7 @@
import React from 'react'
-import { SegmentedBarChartProps } from '@Common/SegmentedBarChart'
+import { Entity } from '@Common/SegmentedBarChart/types'
import { ServerErrors } from '@Common/ServerError'
import { GenericEmptyStateType } from '@Common/Types'
import { LastExecutionResultType, Nodes, NodeType } from '@Shared/types'
@@ -103,7 +103,7 @@ export interface StatusType {
}
export interface InfoCardPropsType extends Pick {
- entities: SegmentedBarChartProps['entities']
+ entities: NonNullable
lastScanTimeString?: string
}
diff --git a/src/Shared/Components/SelectPicker/common.tsx b/src/Shared/Components/SelectPicker/common.tsx
index 6e960cb35..772593823 100644
--- a/src/Shared/Components/SelectPicker/common.tsx
+++ b/src/Shared/Components/SelectPicker/common.tsx
@@ -225,7 +225,14 @@ export const SelectPickerOption = ({
{startIcon &&
{startIcon}
}
-
+
@@ -252,7 +259,7 @@ export const SelectPickerOption = ({
)
}
-const SelectPickerMenuListFooter = ({
+export const SelectPickerMenuListFooter = ({
menuListFooterConfig,
}: Required>) => {
if (!menuListFooterConfig) {
@@ -287,6 +294,12 @@ const SelectPickerMenuListFooter = ({
)
}
+ if (type === 'customNode') {
+ const { value } = menuListFooterConfig
+
+ return value
+ }
+
return null
}
@@ -431,3 +444,15 @@ export const SelectPickerGroupHeading = ({
export const renderLoadingMessage = () => (
Loading
)
+
+export const ValueContainerWithLoadingShimmer = (props: ValueContainerProps) => {
+ const {
+ selectProps: { isLoading },
+ } = props
+
+ if (!isLoading) {
+ return
+ }
+
+ return
+}
diff --git a/src/Shared/Components/SelectPicker/index.ts b/src/Shared/Components/SelectPicker/index.ts
index 999db404c..35c4d8336 100644
--- a/src/Shared/Components/SelectPicker/index.ts
+++ b/src/Shared/Components/SelectPicker/index.ts
@@ -14,6 +14,7 @@
* limitations under the License.
*/
+export { ValueContainerWithLoadingShimmer } from './common'
export { default as FilterSelectPicker } from './FilterSelectPicker'
export { default as SelectPicker } from './SelectPicker.component'
export * from './SelectPickerTextArea.component'
diff --git a/src/Shared/Components/SelectPicker/type.ts b/src/Shared/Components/SelectPicker/type.ts
index 3ef481f64..2ef42c294 100644
--- a/src/Shared/Components/SelectPicker/type.ts
+++ b/src/Shared/Components/SelectPicker/type.ts
@@ -73,6 +73,11 @@ type MenuListFooterConfigType =
variant: ButtonVariantType.primary | ButtonVariantType.borderLess
} & Omit, 'size' | 'fullWidth' | 'icon' | 'endIcon' | 'variant' | 'style'>
}
+ | {
+ type: 'customNode'
+ value: ReactElement
+ buttonProps?: never
+ }
declare module 'react-select/base' {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
diff --git a/src/Shared/Components/StatusComponent/StatusComponent.tsx b/src/Shared/Components/StatusComponent/StatusComponent.tsx
index 75c58ef03..cb83de839 100644
--- a/src/Shared/Components/StatusComponent/StatusComponent.tsx
+++ b/src/Shared/Components/StatusComponent/StatusComponent.tsx
@@ -64,7 +64,7 @@ export const StatusComponent = ({
{textContent}
diff --git a/src/Shared/Components/StatusComponent/types.ts b/src/Shared/Components/StatusComponent/types.ts
index 5c8388b85..d03739c1b 100644
--- a/src/Shared/Components/StatusComponent/types.ts
+++ b/src/Shared/Components/StatusComponent/types.ts
@@ -34,6 +34,7 @@ export enum StatusType {
INPROGRESS = 'INPROGRESS',
HIBERNATING = 'HIBERNATING',
TIMED_OUT = 'TIMED_OUT',
+ DELETING = 'DELETING',
}
export interface StatusComponentProps {
diff --git a/src/Shared/Components/StatusComponent/utils.ts b/src/Shared/Components/StatusComponent/utils.ts
index b6303d7c8..5e5bf1e93 100644
--- a/src/Shared/Components/StatusComponent/utils.ts
+++ b/src/Shared/Components/StatusComponent/utils.ts
@@ -14,8 +14,7 @@
* limitations under the License.
*/
-import { TIMELINE_STATUS } from '@Shared/constants'
-import { WorkflowStatusEnum } from '@Shared/types'
+import { TIMELINE_STATUS, WorkflowStatusEnum } from '@Shared/types'
import { IconName, IconsProps } from '../Icon'
import { StatusType } from './types'
@@ -71,6 +70,12 @@ export const getIconName = (status: string, showAnimatedIcon: boolean): IconName
case 'timedout':
case 'timed_out':
return 'ic-timeout-dash'
+ case 'unable_to_fetch':
+ return 'ic-disconnect'
+ case 'deleting':
+ return 'ic-delete-dots'
+ case 'deleted':
+ return 'ic-delete'
default:
return null
}
@@ -91,8 +96,11 @@ export const getIconColor = (status: string): IconsProps['color'] => {
case 'request_accepted':
case 'starting':
return 'O500'
+ case 'unable_to_fetch':
case 'timedout':
case 'timed_out':
+ case 'deleting':
+ case 'deleted':
return 'R500'
default:
return null
diff --git a/src/Shared/Components/TabGroup/TabGroup.component.tsx b/src/Shared/Components/TabGroup/TabGroup.component.tsx
index 63b3a54b1..d0ddae61e 100644
--- a/src/Shared/Components/TabGroup/TabGroup.component.tsx
+++ b/src/Shared/Components/TabGroup/TabGroup.component.tsx
@@ -56,6 +56,7 @@ const Tab = ({
shouldWrapTooltip,
tooltipProps,
uniqueGroupId,
+ iconElement,
}: TabProps & Pick & AdditionalTabProps) => {
const { pathname, search } = useLocation()
const ref = useRef(null)
@@ -88,7 +89,7 @@ const Tab = ({
const content = (
<>
- {getTabIcon({ className: iconClassName, icon, showError, showWarning, size, active })}
+ {getTabIcon({ className: iconClassName, icon, showError, showWarning, size, active, iconElement })}
{label}
diff --git a/src/Shared/Components/TabGroup/TabGroup.helpers.tsx b/src/Shared/Components/TabGroup/TabGroup.helpers.tsx
index 861e7550b..6be862718 100644
--- a/src/Shared/Components/TabGroup/TabGroup.helpers.tsx
+++ b/src/Shared/Components/TabGroup/TabGroup.helpers.tsx
@@ -28,7 +28,8 @@ export const getTabIcon = ({
className,
size,
active,
-}: Pick &
+ iconElement,
+}: Pick &
Pick & { className: string }) => {
if (showError) {
return
@@ -43,6 +44,9 @@ export const getTabIcon = ({
const RenderIcon = icon
return
}
+ if (iconElement) {
+ return iconElement
+ }
return null
}
diff --git a/src/Shared/Components/TabGroup/TabGroup.types.ts b/src/Shared/Components/TabGroup/TabGroup.types.ts
index 022b83254..619b9a064 100644
--- a/src/Shared/Components/TabGroup/TabGroup.types.ts
+++ b/src/Shared/Components/TabGroup/TabGroup.types.ts
@@ -93,6 +93,42 @@ type TabTooltipProps =
tooltipProps?: never
}
+/**
+ * Represents the properties for defining an icon in a tab group.
+ * This type allows for three configurations:
+ *
+ * 1. **Icon as a functional component or string**:
+ * - Use the `icon` property to specify either a functional component that renders an SVG or a string representing the name of the icon.
+ * - The `iconElement` property must not be provided in this case.
+ *
+ * 2. **Icon as a JSX element**:
+ * - Use the `iconElement` property to specify a JSX element representing the icon.
+ * - The `icon` property must not be provided in this case.
+ *
+ * 3. **No icon**:
+ * - Neither `icon` nor `iconElement` is provided, resulting in no icon being displayed.
+ *
+ */
+type TabGroupIconProp =
+ | {
+ /**
+ * A functional component rendering an SVG or a string representing the icon name. Mutually exclusive with `iconElement`.
+ */
+ icon: React.FunctionComponent> | IconName
+ iconElement?: never
+ }
+ | {
+ icon?: never
+ /**
+ * A JSX element representing the icon. Mutually exclusive with `icon`.
+ */
+ iconElement: JSX.Element
+ }
+ | {
+ icon?: never
+ iconElement?: never
+ }
+
export type TabProps = {
/**
* Unique identifier for the tab.
@@ -107,12 +143,6 @@ export type TabProps = {
* @note - If passed as a `string[]`, it will be rendered with a bullet in-between strings.
*/
description?: string | string[]
- /**
- * Icon to be displayed in the tab.
- * This can either be a functional component that renders a SVG
- * or a string representing the name of the icon to be rendered by the Icon component.
- */
- icon?: React.FunctionComponent> | IconName
/**
* Badge number to be displayed on the tab, typically for notifications.
*/
@@ -136,7 +166,8 @@ export type TabProps = {
*/
disabled?: boolean
} & ConditionalTabType &
- TabTooltipProps
+ TabTooltipProps &
+ TabGroupIconProp
export interface TabGroupProps {
/**
diff --git a/src/Shared/Components/TargetPlatforms/TargetPlatformListTooltip.tsx b/src/Shared/Components/TargetPlatforms/TargetPlatformListTooltip.tsx
index 31fad83e2..31a9e19cd 100644
--- a/src/Shared/Components/TargetPlatforms/TargetPlatformListTooltip.tsx
+++ b/src/Shared/Components/TargetPlatforms/TargetPlatformListTooltip.tsx
@@ -33,12 +33,13 @@ const TooltipContent = ({ targetPlatforms }: Pick
)
-const TargetPlatformListTooltip = ({ targetPlatforms, children }: TargetPlatformListTooltipProps) => (
+const TargetPlatformListTooltip = ({ targetPlatforms, children, appendTo }: TargetPlatformListTooltipProps) => (
}
placement="right"
alwaysShowTippyOnHover
interactive
+ appendTo={appendTo}
>
{children}
diff --git a/src/Shared/Components/TargetPlatforms/types.ts b/src/Shared/Components/TargetPlatforms/types.ts
index 99258901f..5df88d691 100644
--- a/src/Shared/Components/TargetPlatforms/types.ts
+++ b/src/Shared/Components/TargetPlatforms/types.ts
@@ -18,6 +18,6 @@ import { TooltipProps } from '@Common/Tooltip/types'
import { TargetPlatformsDTO } from '@Shared/types'
export interface TargetPlatformBadgeListProps extends Required> {}
-export interface TargetPlatformListTooltipProps extends Pick {
- children: TooltipProps['children']
-}
+export interface TargetPlatformListTooltipProps
+ extends Pick,
+ Pick {}
diff --git a/src/Shared/Components/index.ts b/src/Shared/Components/index.ts
index abcb23727..12e39877c 100644
--- a/src/Shared/Components/index.ts
+++ b/src/Shared/Components/index.ts
@@ -19,8 +19,8 @@ export * from './ActionMenu'
export * from './ActivityIndicator'
export * from './AnimatedDeployButton'
export * from './AnimatedTimer'
-export * from './AnnouncementBanner'
export * from './APIResponseHandler'
+export * from './AppStatusModal'
export * from './ArtifactInfoModal'
export * from './Backdrop'
export * from './BulkOperations'
@@ -40,12 +40,12 @@ export * from './CountrySelect'
export * from './CustomInput'
export * from './DatePicker'
export * from './DeploymentConfigDiff'
+export * from './DeploymentStatusBreakdown'
export * from './DetectBottom'
export * from './DiffViewer'
export * from './DynamicDataTable'
export * from './EditableTextArea'
export * from './EditImageFormField'
-export * from './EnterpriseTag'
export * from './EnvironmentSelector'
export * from './Error'
export * from './ExcludedImageNode'
diff --git a/src/Shared/Helpers.tsx b/src/Shared/Helpers.tsx
index b73a459ad..2120400ef 100644
--- a/src/Shared/Helpers.tsx
+++ b/src/Shared/Helpers.tsx
@@ -36,7 +36,6 @@ import { MaterialHistoryType } from '@Shared/Services/app.types'
import {
ApprovalConfigDataType,
DATE_TIME_FORMATS,
- handleUTCTime,
ManualApprovalType,
mapByKey,
MaterialInfo,
@@ -46,18 +45,14 @@ import {
SortingOrder,
SourceTypeMap,
TOKEN_COOKIE_NAME,
+ URLS,
UserApprovalConfigType,
UserApprovalInfo,
ZERO_TIME_STRING,
} from '../Common'
import { getAggregator } from '../Pages'
-import {
- AggregatedNodes,
- DeploymentStatusDetailsBreakdownDataType,
- DeploymentStatusDetailsType,
- PodMetadatum,
-} from './Components'
-import { DEPLOYMENT_STATUS, TIMELINE_STATUS, UNSAVED_CHANGES_PROMPT_MESSAGE } from './constants'
+import { AggregatedNodes, PodMetadatum } from './Components'
+import { UNSAVED_CHANGES_PROMPT_MESSAGE } from './constants'
import {
AggregationKeys,
BorderConfigType,
@@ -306,399 +301,6 @@ export const renderValidInputButtonTippy = (children: ReactElement) => (
)
-// NOTE: Need to improve logic since in some cases the unknown status would leak to previous entites, can do that by not setting deploymentStatus as Failed by ourselves and let backend be source of truth of that
-export const processDeploymentStatusDetailsData = (
- data?: DeploymentStatusDetailsType,
-): DeploymentStatusDetailsBreakdownDataType => {
- const deploymentData = {
- deploymentStatus: 'inprogress',
- deploymentStatusText: 'In progress',
- deploymentTriggerTime: data?.deploymentStartedOn || '',
- deploymentEndTime: data?.deploymentFinishedOn || '',
- deploymentError: '',
- triggeredBy: data?.triggeredBy || '',
- nonDeploymentError: '',
- deploymentStatusBreakdown: {
- DEPLOYMENT_INITIATED: {
- icon: 'success',
- displayText: `Deployment initiated ${data?.triggeredBy ? `by ${data?.triggeredBy}` : ''}`,
- displaySubText: '',
- time: '',
- },
- GIT_COMMIT: {
- icon: '',
- displayText: 'Push manifest to Git',
- displaySubText: '',
- timelineStatus: '',
- time: '',
- isCollapsed: true,
- },
- ARGOCD_SYNC: {
- icon: '',
- displayText: 'Synced with Argo CD',
- displaySubText: '',
- timelineStatus: '',
- time: '',
- isCollapsed: true,
- },
- KUBECTL_APPLY: {
- icon: '',
- displayText: 'Apply manifest to Kubernetes',
- timelineStatus: '',
- displaySubText: '',
- time: '',
- resourceDetails: [],
- isCollapsed: true,
- kubeList: [],
- },
- APP_HEALTH: {
- icon: '',
- displayText: 'Propogate manifest to Kubernetes resources',
- timelineStatus: '',
- displaySubText: '',
- time: '',
- isCollapsed: true,
- },
- },
- }
-
- const lastFetchedTime = handleUTCTime(data?.statusLastFetchedAt, true)
- const deploymentPhases = ['PreSync', 'Sync', 'PostSync', 'Skip', 'SyncFail']
- const tableData: { currentPhase: string; currentTableData: { icon: string; phase?: string; message: string }[] } = {
- currentPhase: '',
- currentTableData: [{ icon: 'success', message: 'Started by Argo CD' }],
- }
-
- // data when timelines is available
- if (data?.timelines?.length) {
- // TO Support legacy data have to make sure that if ARGOCD_SYNC is not available then we fill it with dummy values
- const isArgoCDAvailable = data.timelines.some((timeline) =>
- timeline.status.includes(TIMELINE_STATUS.ARGOCD_SYNC),
- )
-
- for (let index = data.timelines.length - 1; index >= 0; index--) {
- const element = data.timelines[index]
- if (element.status === TIMELINE_STATUS.HEALTHY || element.status === TIMELINE_STATUS.DEGRADED) {
- deploymentData.deploymentStatus = DEPLOYMENT_STATUS.SUCCEEDED
- deploymentData.deploymentStatusText = 'Succeeded'
- deploymentData.deploymentStatusBreakdown.APP_HEALTH.displaySubText =
- element.status === TIMELINE_STATUS.HEALTHY ? '' : 'Degraded'
- deploymentData.deploymentStatusBreakdown.APP_HEALTH.time = element.statusTime
- deploymentData.deploymentStatusBreakdown.APP_HEALTH.icon = 'success'
- deploymentData.deploymentStatusBreakdown.KUBECTL_APPLY.icon = 'success'
- deploymentData.deploymentStatusBreakdown.KUBECTL_APPLY.isCollapsed = true
- deploymentData.deploymentStatusBreakdown.APP_HEALTH.isCollapsed = true
- deploymentData.deploymentStatusBreakdown.ARGOCD_SYNC.icon = 'success'
- deploymentData.deploymentStatusBreakdown.GIT_COMMIT.icon = 'success'
- } else if (element.status === TIMELINE_STATUS.DEPLOYMENT_FAILED) {
- deploymentData.deploymentStatus = DEPLOYMENT_STATUS.FAILED
- deploymentData.deploymentStatusText = 'Failed'
- deploymentData.deploymentStatusBreakdown.APP_HEALTH.displaySubText = ' Failed'
- deploymentData.deploymentError = element.statusDetail
- } else if (element.status === TIMELINE_STATUS.DEPLOYMENT_SUPERSEDED) {
- deploymentData.deploymentStatus = DEPLOYMENT_STATUS.SUPERSEDED
- } else if (
- index === data.timelines.length - 1 &&
- (element.status === TIMELINE_STATUS.FETCH_TIMED_OUT ||
- element.status === TIMELINE_STATUS.UNABLE_TO_FETCH_STATUS)
- ) {
- if (element.status === TIMELINE_STATUS.FETCH_TIMED_OUT) {
- deploymentData.deploymentStatus = DEPLOYMENT_STATUS.TIMED_OUT
- deploymentData.deploymentStatusText = 'Timed out'
- } else if (element.status === TIMELINE_STATUS.UNABLE_TO_FETCH_STATUS) {
- deploymentData.deploymentStatus = DEPLOYMENT_STATUS.UNABLE_TO_FETCH
- deploymentData.deploymentStatusText = 'Unable to fetch status'
- }
- deploymentData.deploymentError = `Below resources did not become healthy within 10 mins. Resource status shown below was last fetched ${lastFetchedTime}. ${data.statusFetchCount} retries failed.`
- } else if (element.status.includes(TIMELINE_STATUS.KUBECTL_APPLY)) {
- if (!isArgoCDAvailable) {
- deploymentData.deploymentStatusBreakdown.ARGOCD_SYNC.icon = 'success'
- deploymentData.deploymentStatusBreakdown.ARGOCD_SYNC.displaySubText = ''
- deploymentData.deploymentStatusBreakdown.ARGOCD_SYNC.time = element.statusTime
- }
-
- if (element?.resourceDetails) {
- deploymentPhases.forEach((phase) => {
- // eslint-disable-next-line no-restricted-syntax
- for (const item of element.resourceDetails) {
- if (phase === item.resourcePhase) {
- tableData.currentPhase = phase
- // Seems else block was forgotten to add here, TODO: Sync for this later
- // eslint-disable-next-line no-empty
- if (item.resourceStatus === 'failed') {
- }
- tableData.currentTableData.push({
- icon: 'success',
- phase,
- message: `${phase}: Create and update resources based on manifest`,
- })
- return
- }
- }
- })
- }
- if (
- element.status === TIMELINE_STATUS.KUBECTL_APPLY_STARTED &&
- deploymentData.deploymentStatusBreakdown.KUBECTL_APPLY.time === '' &&
- deploymentData.deploymentStatus !== DEPLOYMENT_STATUS.SUCCEEDED
- ) {
- deploymentData.deploymentStatusBreakdown.KUBECTL_APPLY.resourceDetails =
- element.resourceDetails?.filter((item) => item.resourcePhase === tableData.currentPhase)
- if (deploymentData.deploymentStatus === DEPLOYMENT_STATUS.FAILED) {
- deploymentData.deploymentStatusBreakdown.KUBECTL_APPLY.icon = 'unknown'
- deploymentData.deploymentStatusBreakdown.KUBECTL_APPLY.displaySubText = ': Unknown'
- deploymentData.deploymentStatusBreakdown.APP_HEALTH.icon = 'unknown'
- deploymentData.deploymentStatusBreakdown.APP_HEALTH.displaySubText = ': Unknown'
- } else if (deploymentData.deploymentStatus === DEPLOYMENT_STATUS.SUCCEEDED) {
- deploymentData.deploymentStatusBreakdown.KUBECTL_APPLY.icon = 'success'
- deploymentData.deploymentStatusBreakdown.KUBECTL_APPLY.displaySubText = ''
- } else if (
- deploymentData.deploymentStatus === DEPLOYMENT_STATUS.TIMED_OUT ||
- deploymentData.deploymentStatus === DEPLOYMENT_STATUS.UNABLE_TO_FETCH
- ) {
- if (deploymentData.deploymentStatus === DEPLOYMENT_STATUS.TIMED_OUT) {
- deploymentData.deploymentStatusBreakdown.KUBECTL_APPLY.icon = 'unknown'
- deploymentData.deploymentStatusBreakdown.KUBECTL_APPLY.isCollapsed = false
- } else {
- deploymentData.deploymentStatusBreakdown.KUBECTL_APPLY.icon = 'disconnect'
- }
- deploymentData.deploymentStatusBreakdown.KUBECTL_APPLY.displaySubText = 'Unknown'
- deploymentData.deploymentStatusBreakdown.KUBECTL_APPLY.timelineStatus =
- deploymentData.deploymentError
- deploymentData.deploymentStatusBreakdown.KUBECTL_APPLY.kubeList =
- tableData.currentTableData.map((item) => ({
- icon: item.phase === tableData.currentPhase ? 'failed' : 'success',
- message: item.message,
- }))
- } else {
- deploymentData.deploymentStatusBreakdown.KUBECTL_APPLY.icon = 'inprogress'
- deploymentData.deploymentStatusBreakdown.KUBECTL_APPLY.displaySubText = 'In progress'
- deploymentData.deploymentStatusBreakdown.KUBECTL_APPLY.time = element.statusTime
- deploymentData.deploymentStatusBreakdown.KUBECTL_APPLY.timelineStatus = element.statusDetail
- deploymentData.deploymentStatusBreakdown.KUBECTL_APPLY.isCollapsed = false
- deploymentData.deploymentStatusBreakdown.KUBECTL_APPLY.kubeList =
- tableData.currentTableData.map((item) => ({
- icon: item.phase === tableData.currentPhase ? 'loading' : 'success',
- message: item.message,
- }))
- }
- } else if (element.status === TIMELINE_STATUS.KUBECTL_APPLY_SYNCED) {
- deploymentData.deploymentStatusBreakdown.KUBECTL_APPLY.resourceDetails = []
- deploymentData.deploymentStatusBreakdown.KUBECTL_APPLY.displaySubText = ''
- deploymentData.deploymentStatusBreakdown.KUBECTL_APPLY.time = element.statusTime
- deploymentData.deploymentStatusBreakdown.KUBECTL_APPLY.icon = 'success'
- deploymentData.deploymentStatusBreakdown.ARGOCD_SYNC.icon = 'success'
- deploymentData.deploymentStatusBreakdown.GIT_COMMIT.icon = 'success'
- deploymentData.deploymentStatusBreakdown.KUBECTL_APPLY.kubeList = tableData.currentTableData
-
- if (deploymentData.deploymentStatus === DEPLOYMENT_STATUS.INPROGRESS) {
- deploymentData.deploymentStatusBreakdown.APP_HEALTH.icon = 'inprogress'
- deploymentData.deploymentStatusBreakdown.APP_HEALTH.isCollapsed = false
- } else if (deploymentData.deploymentStatus === DEPLOYMENT_STATUS.FAILED) {
- deploymentData.deploymentStatusBreakdown.APP_HEALTH.icon = 'failed'
- deploymentData.deploymentStatusBreakdown.APP_HEALTH.displaySubText = 'Failed'
- } else if (deploymentData.deploymentStatus === DEPLOYMENT_STATUS.TIMED_OUT) {
- deploymentData.deploymentStatusBreakdown.APP_HEALTH.icon = 'timed_out'
- deploymentData.deploymentStatusBreakdown.APP_HEALTH.displaySubText = 'Unknown'
- deploymentData.deploymentStatusBreakdown.APP_HEALTH.timelineStatus =
- deploymentData.deploymentError
- deploymentData.deploymentStatusBreakdown.APP_HEALTH.isCollapsed = false
- } else if (deploymentData.deploymentStatus === DEPLOYMENT_STATUS.UNABLE_TO_FETCH) {
- deploymentData.deploymentStatusBreakdown.APP_HEALTH.icon = 'disconnect'
- deploymentData.deploymentStatusBreakdown.APP_HEALTH.displaySubText = 'Unknown'
- deploymentData.deploymentStatusBreakdown.APP_HEALTH.timelineStatus =
- deploymentData.deploymentError
- deploymentData.deploymentStatusBreakdown.APP_HEALTH.isCollapsed = false
- }
- }
- } else if (element.status.includes(TIMELINE_STATUS.ARGOCD_SYNC)) {
- deploymentData.deploymentStatusBreakdown.ARGOCD_SYNC.time = element.statusTime
-
- if (element.status === TIMELINE_STATUS.ARGOCD_SYNC_FAILED) {
- deploymentData.deploymentStatusBreakdown.ARGOCD_SYNC.displaySubText = 'Failed'
- deploymentData.deploymentStatusBreakdown.ARGOCD_SYNC.icon = 'failed'
-
- deploymentData.deploymentStatusBreakdown.KUBECTL_APPLY.displaySubText = ''
- deploymentData.deploymentStatusBreakdown.KUBECTL_APPLY.icon = 'unreachable'
-
- deploymentData.deploymentStatusBreakdown.APP_HEALTH.displaySubText = ''
- deploymentData.deploymentStatusBreakdown.APP_HEALTH.icon = 'unreachable'
-
- deploymentData.deploymentStatusBreakdown.ARGOCD_SYNC.isCollapsed = false
- deploymentData.deploymentStatus = DEPLOYMENT_STATUS.FAILED
- deploymentData.deploymentStatusText = 'Failed'
- deploymentData.deploymentStatusBreakdown.ARGOCD_SYNC.timelineStatus = element.statusDetail
- } else {
- deploymentData.deploymentStatusBreakdown.ARGOCD_SYNC.icon = 'success'
- if (deploymentData.deploymentStatus === DEPLOYMENT_STATUS.FAILED) {
- deploymentData.deploymentStatusBreakdown.KUBECTL_APPLY.displaySubText = ''
- deploymentData.deploymentStatusBreakdown.KUBECTL_APPLY.icon = 'unreachable'
-
- deploymentData.deploymentStatusBreakdown.APP_HEALTH.displaySubText = ''
- deploymentData.deploymentStatusBreakdown.APP_HEALTH.icon = 'unreachable'
-
- if (deploymentData.nonDeploymentError === '') {
- deploymentData.nonDeploymentError = TIMELINE_STATUS.KUBECTL_APPLY
- }
- } else if (
- deploymentData.deploymentStatusBreakdown.KUBECTL_APPLY.time === '' &&
- deploymentData.deploymentStatus === DEPLOYMENT_STATUS.INPROGRESS
- ) {
- deploymentData.deploymentStatusBreakdown.KUBECTL_APPLY.icon = ''
- deploymentData.deploymentStatusBreakdown.KUBECTL_APPLY.displaySubText = 'Waiting'
- deploymentData.deploymentStatusBreakdown.KUBECTL_APPLY.kubeList = [
- { icon: '', message: 'Waiting to be started by Argo CD' },
- { icon: '', message: 'Create and update resources based on manifest' },
- ]
- deploymentData.deploymentStatusBreakdown.KUBECTL_APPLY.isCollapsed = false
- }
- }
- } else if (element.status.includes(TIMELINE_STATUS.GIT_COMMIT)) {
- deploymentData.deploymentStatusBreakdown.GIT_COMMIT.time = element.statusTime
- if (element.status === TIMELINE_STATUS.GIT_COMMIT_FAILED) {
- deploymentData.deploymentStatusBreakdown.GIT_COMMIT.displaySubText = 'Failed'
- deploymentData.deploymentStatusBreakdown.GIT_COMMIT.icon = 'failed'
-
- deploymentData.deploymentStatusBreakdown.ARGOCD_SYNC.displaySubText = ''
- deploymentData.deploymentStatusBreakdown.ARGOCD_SYNC.icon = 'unreachable'
-
- deploymentData.deploymentStatusBreakdown.KUBECTL_APPLY.displaySubText = ''
- deploymentData.deploymentStatusBreakdown.KUBECTL_APPLY.icon = 'unreachable'
-
- deploymentData.deploymentStatusBreakdown.APP_HEALTH.displaySubText = ''
- deploymentData.deploymentStatusBreakdown.APP_HEALTH.icon = 'unreachable'
-
- deploymentData.deploymentStatusBreakdown.GIT_COMMIT.isCollapsed = false
- deploymentData.deploymentStatus = DEPLOYMENT_STATUS.FAILED
- deploymentData.deploymentStatusText = 'Failed'
- deploymentData.deploymentStatusBreakdown.GIT_COMMIT.timelineStatus = element.statusDetail
- } else {
- deploymentData.deploymentStatusBreakdown.GIT_COMMIT.icon = 'success'
- if (deploymentData.deploymentStatus === DEPLOYMENT_STATUS.FAILED) {
- if (deploymentData.deploymentStatusBreakdown.ARGOCD_SYNC.icon === '') {
- deploymentData.deploymentStatusBreakdown.ARGOCD_SYNC.displaySubText = ''
- deploymentData.deploymentStatusBreakdown.ARGOCD_SYNC.icon = 'unreachable'
- }
-
- deploymentData.deploymentStatusBreakdown.KUBECTL_APPLY.displaySubText = ''
- deploymentData.deploymentStatusBreakdown.KUBECTL_APPLY.icon = 'unreachable'
-
- deploymentData.deploymentStatusBreakdown.APP_HEALTH.displaySubText = ''
- deploymentData.deploymentStatusBreakdown.APP_HEALTH.icon = 'unreachable'
-
- if (deploymentData.nonDeploymentError === '') {
- deploymentData.nonDeploymentError = TIMELINE_STATUS.ARGOCD_SYNC
- }
- } else if (
- deploymentData.deploymentStatusBreakdown.ARGOCD_SYNC.time === '' &&
- deploymentData.deploymentStatus === DEPLOYMENT_STATUS.INPROGRESS
- ) {
- deploymentData.deploymentStatusBreakdown.KUBECTL_APPLY.icon = ''
- deploymentData.deploymentStatusBreakdown.KUBECTL_APPLY.displaySubText = 'Waiting'
- deploymentData.deploymentStatusBreakdown.KUBECTL_APPLY.kubeList = [
- { icon: '', message: 'Waiting to be started by Argo CD' },
- { icon: '', message: 'Create and update resources based on manifest' },
- ]
- deploymentData.deploymentStatusBreakdown.KUBECTL_APPLY.isCollapsed = false
- deploymentData.deploymentStatusBreakdown.ARGOCD_SYNC.icon = 'inprogress'
- deploymentData.deploymentStatusBreakdown.ARGOCD_SYNC.displaySubText = 'In progress'
- }
- }
- } else if (element.status === TIMELINE_STATUS.DEPLOYMENT_INITIATED) {
- deploymentData.deploymentStatusBreakdown.DEPLOYMENT_INITIATED.time = element.statusTime
- if (
- deploymentData.deploymentStatusBreakdown.GIT_COMMIT.time === '' &&
- deploymentData.deploymentStatus === DEPLOYMENT_STATUS.INPROGRESS
- ) {
- deploymentData.deploymentStatusBreakdown.GIT_COMMIT.icon = 'inprogress'
-
- deploymentData.deploymentStatusBreakdown.ARGOCD_SYNC.icon = ''
- deploymentData.deploymentStatusBreakdown.ARGOCD_SYNC.displaySubText = 'Waiting'
-
- deploymentData.deploymentStatusBreakdown.KUBECTL_APPLY.icon = ''
- deploymentData.deploymentStatusBreakdown.KUBECTL_APPLY.displaySubText = 'Waiting'
- deploymentData.deploymentStatusBreakdown.KUBECTL_APPLY.kubeList = [
- { icon: '', message: 'Waiting to be started by Argo CD' },
- { icon: '', message: 'Create and update resources based on manifest' },
- ]
- deploymentData.deploymentStatusBreakdown.KUBECTL_APPLY.isCollapsed = false
- }
- if (deploymentData.deploymentStatus === DEPLOYMENT_STATUS.FAILED) {
- if (deploymentData.deploymentStatusBreakdown.GIT_COMMIT.time === '') {
- deploymentData.deploymentStatusBreakdown.GIT_COMMIT.displaySubText = ''
- deploymentData.deploymentStatusBreakdown.GIT_COMMIT.icon = 'unreachable'
-
- deploymentData.deploymentStatusBreakdown.ARGOCD_SYNC.displaySubText = ''
- deploymentData.deploymentStatusBreakdown.ARGOCD_SYNC.icon = 'unreachable'
-
- deploymentData.deploymentStatusBreakdown.KUBECTL_APPLY.displaySubText = ''
- deploymentData.deploymentStatusBreakdown.KUBECTL_APPLY.icon = 'unreachable'
-
- deploymentData.deploymentStatusBreakdown.APP_HEALTH.displaySubText = ''
- deploymentData.deploymentStatusBreakdown.APP_HEALTH.icon = 'unreachable'
-
- deploymentData.nonDeploymentError = TIMELINE_STATUS.GIT_COMMIT
- } else if (deploymentData.deploymentStatusBreakdown.GIT_COMMIT.icon !== 'failed') {
- if (deploymentData.deploymentStatusBreakdown.ARGOCD_SYNC.time === '') {
- deploymentData.deploymentStatusBreakdown.ARGOCD_SYNC.displaySubText = 'Unknown'
- deploymentData.deploymentStatusBreakdown.ARGOCD_SYNC.icon = 'unknown'
-
- deploymentData.deploymentStatusBreakdown.KUBECTL_APPLY.displaySubText = 'Unknown'
- deploymentData.deploymentStatusBreakdown.KUBECTL_APPLY.icon = 'unknown'
-
- deploymentData.deploymentStatusBreakdown.APP_HEALTH.displaySubText = ': Unknown'
- deploymentData.deploymentStatusBreakdown.APP_HEALTH.icon = 'unknown'
- } else if (deploymentData.deploymentStatusBreakdown.ARGOCD_SYNC.icon !== 'failed') {
- deploymentData.deploymentStatusBreakdown.KUBECTL_APPLY.displaySubText = 'Unknown'
- deploymentData.deploymentStatusBreakdown.KUBECTL_APPLY.icon = 'unknown'
-
- deploymentData.deploymentStatusBreakdown.APP_HEALTH.displaySubText = ': Unknown'
- deploymentData.deploymentStatusBreakdown.APP_HEALTH.icon = 'unknown'
- }
- } else {
- deploymentData.deploymentStatusBreakdown.GIT_COMMIT.displaySubText = ''
- deploymentData.deploymentStatusBreakdown.GIT_COMMIT.icon = 'unreachable'
-
- deploymentData.deploymentStatusBreakdown.ARGOCD_SYNC.displaySubText = ''
- deploymentData.deploymentStatusBreakdown.ARGOCD_SYNC.icon = 'unreachable'
-
- deploymentData.deploymentStatusBreakdown.KUBECTL_APPLY.displaySubText = ''
- deploymentData.deploymentStatusBreakdown.KUBECTL_APPLY.icon = 'unreachable'
-
- deploymentData.deploymentStatusBreakdown.APP_HEALTH.displaySubText = ''
- deploymentData.deploymentStatusBreakdown.APP_HEALTH.icon = 'unreachable'
-
- deploymentData.nonDeploymentError = TIMELINE_STATUS.GIT_COMMIT
- }
- }
- }
- }
- } else if (!data?.timelines) {
- // data when timelines is not available in case of the previously deployed app(deployment-status/timline api) )
- if (data?.wfrStatus === 'Healthy' || data?.wfrStatus === 'Succeeded') {
- deploymentData.deploymentStatus = DEPLOYMENT_STATUS.SUCCEEDED
- deploymentData.deploymentStatusText = 'Succeeded'
- deploymentData.deploymentStatusBreakdown.APP_HEALTH.icon = 'success'
- deploymentData.deploymentStatusBreakdown.KUBECTL_APPLY.icon = 'success'
- deploymentData.deploymentStatusBreakdown.KUBECTL_APPLY.isCollapsed = true
- deploymentData.deploymentStatusBreakdown.APP_HEALTH.isCollapsed = true
- deploymentData.deploymentStatusBreakdown.ARGOCD_SYNC.icon = 'success'
- deploymentData.deploymentStatusBreakdown.GIT_COMMIT.icon = 'success'
- } else if (data?.wfrStatus === 'Failed' || data?.wfrStatus === 'Degraded') {
- deploymentData.deploymentStatus = DEPLOYMENT_STATUS.FAILED
- deploymentData.deploymentStatusText = 'Failed'
- deploymentData.deploymentStatusBreakdown.APP_HEALTH.displaySubText = 'Failed'
- } else if (data?.wfrStatus === 'Progressing') {
- deploymentData.deploymentStatus = DEPLOYMENT_STATUS.INPROGRESS
- deploymentData.deploymentStatusText = 'In progress'
- } else if (data?.wfrStatus === 'TimedOut') {
- deploymentData.deploymentStatus = DEPLOYMENT_STATUS.TIMED_OUT
- deploymentData.deploymentStatusText = 'Timed out'
- }
- }
- return deploymentData
-}
-
export function aggregateNodes(nodes: any[], podMetadata: PodMetadatum[]): AggregatedNodes {
const podMetadataMap = mapByKey(podMetadata, 'name')
// group nodes
@@ -1090,3 +692,11 @@ export const getClassNameForStickyHeaderWithShadow = (isStuck: boolean, topClass
export const clearCookieOnLogout = () => {
document.cookie = `${TOKEN_COOKIE_NAME}=; expires=Thu, 01-Jan-1970 00:00:01 GMT;path=/`
}
+
+export const getAppDetailsURL = (appId: number | string, envId?: number | string): string => {
+ const baseURL = `${URLS.APP}/${appId}/${URLS.APP_DETAILS}`
+ if (envId) {
+ return `${baseURL}/${envId}`
+ }
+ return baseURL
+}
diff --git a/src/Shared/Providers/index.ts b/src/Shared/Providers/index.ts
index 3e07be345..1ba004223 100644
--- a/src/Shared/Providers/index.ts
+++ b/src/Shared/Providers/index.ts
@@ -17,5 +17,5 @@
export * from './ImageSelectionUtility'
export * from './MainContextProvider'
export * from './ThemeProvider'
-export type { MainContext } from './types'
+export type { MainContext, ReloadVersionConfigTypes } from './types'
export * from './UserEmailProvider'
diff --git a/src/Shared/Providers/types.ts b/src/Shared/Providers/types.ts
index d3d456ad2..82f1630cb 100644
--- a/src/Shared/Providers/types.ts
+++ b/src/Shared/Providers/types.ts
@@ -18,8 +18,17 @@ import { Dispatch, MutableRefObject, ReactNode, SetStateAction } from 'react'
import { SERVER_MODE } from '../../Common'
import { ServerInfo } from '../Components/Header/types'
-import { DevtronLicenseInfo, IntelligenceConfig, LicenseInfoDialogType } from '..'
+import { DevtronLicenseInfo, IntelligenceConfig, LicenseInfoDialogType, ToastManager } from '..'
+export interface ReloadVersionConfigTypes {
+ bgUpdated: boolean
+ handleAppUpdate: () => void
+ doesNeedRefresh: boolean
+ updateServiceWorker: () => Promise
+ handleControllerChange: () => void
+ updateToastRef: MutableRefObject> | null
+ isRefreshing: boolean
+}
export interface MainContext {
serverMode: SERVER_MODE
setServerMode: (serverMode: SERVER_MODE) => void
@@ -66,6 +75,7 @@ export interface MainContext {
licenseData: DevtronLicenseInfo
setLicenseData: Dispatch>
canFetchHelmAppStatus: boolean
+ reloadVersionConfig: ReloadVersionConfigTypes
intelligenceConfig: IntelligenceConfig
setIntelligenceConfig: Dispatch>
}
diff --git a/src/Shared/Store/IndexStore.tsx b/src/Shared/Store/IndexStore.tsx
index baa1e38b0..376344a78 100644
--- a/src/Shared/Store/IndexStore.tsx
+++ b/src/Shared/Store/IndexStore.tsx
@@ -245,6 +245,7 @@ export const IndexStore = {
const podMetadata = data.resourceTree?.podMetadata || []
+ // Not brave enough to remove this method but seems like its not doing anything
getiNodesByRootNodeWithChildNodes(
_nodes,
_nodes.filter((_n) => (_n.parentRefs ?? []).length == 0).map((_n) => _n as iNode),
diff --git a/src/Shared/constants.tsx b/src/Shared/constants.tsx
index d53848f04..501c21c70 100644
--- a/src/Shared/constants.tsx
+++ b/src/Shared/constants.tsx
@@ -14,9 +14,10 @@
* limitations under the License.
*/
-import { OptionType } from '@Common/Types'
+import { DeploymentNodeType, OptionType } from '@Common/Types'
-import { CDMaterialSidebarType, ConfigKeysWithLockType, ConfigurationType } from './types'
+import { InfoBlockProps } from './Components'
+import { CDMaterialSidebarType, ConfigKeysWithLockType, ConfigurationType, IconBaseColorType } from './types'
export const ARTIFACT_STATUS = {
PROGRESSING: 'Progressing',
@@ -80,28 +81,6 @@ export enum EnvironmentTypeEnum {
nonProduction = 'Non-Production',
}
-export enum TIMELINE_STATUS {
- DEPLOYMENT_INITIATED = 'DEPLOYMENT_INITIATED',
- GIT_COMMIT = 'GIT_COMMIT',
- GIT_COMMIT_FAILED = 'GIT_COMMIT_FAILED',
- ARGOCD_SYNC = 'ARGOCD_SYNC',
- ARGOCD_SYNC_FAILED = 'ARGOCD_SYNC_FAILED',
- KUBECTL_APPLY = 'KUBECTL_APPLY',
- KUBECTL_APPLY_STARTED = 'KUBECTL_APPLY_STARTED',
- KUBECTL_APPLY_SYNCED = 'KUBECTL_APPLY_SYNCED',
- HEALTHY = 'HEALTHY',
- APP_HEALTH = 'APP_HEALTH',
- DEPLOYMENT_FAILED = 'FAILED',
- FETCH_TIMED_OUT = 'TIMED_OUT',
- UNABLE_TO_FETCH_STATUS = 'UNABLE_TO_FETCH_STATUS',
- DEGRADED = 'DEGRADED',
- DEPLOYMENT_SUPERSEDED = 'DEPLOYMENT_SUPERSEDED',
- ABORTED = 'ABORTED',
- INPROGRESS = 'INPROGRESS',
- HELM_PACKAGE_GENERATED = 'HELM_PACKAGE_GENERATED',
- HELM_MANIFEST_PUSHED_TO_HELM_REPO = 'HELM_MANIFEST_PUSHED_TO_HELM_REPO',
- HELM_MANIFEST_PUSHED_TO_HELM_REPO_FAILED = 'HELM_MANIFEST_PUSHED_TO_HELM_REPO_FAILED',
-}
/**
* Constants for NO MATCHING result
*/
@@ -555,3 +534,49 @@ export const Routes = {
export const ENTERPRISE_SUPPORT_LINK = 'enterprise@devtron.ai'
export const INVALID_LICENSE_KEY = 'inValid'
+
+export enum InfoBlockVariant {
+ ERROR = 'error',
+ HELP = 'help',
+ INFORMATION = 'information',
+ SUCCESS = 'success',
+ WARNING = 'warning',
+ NEUTRAL = 'neutral',
+}
+
+export type InfoBlockVariantType = 'error' | 'help' | 'information' | 'success' | 'warning' | 'neutral'
+
+export const VARIANT_TO_BG_MAP: Record = {
+ [InfoBlockVariant.ERROR]: 'bcr-1',
+ [InfoBlockVariant.HELP]: 'bcv-1',
+ [InfoBlockVariant.INFORMATION]: 'bcb-1',
+ [InfoBlockVariant.SUCCESS]: 'bcg-1',
+ [InfoBlockVariant.WARNING]: 'bcy-1',
+ [InfoBlockVariant.NEUTRAL]: 'bcn-1',
+}
+
+export const VARIANT_TO_ICON_COLOR_MAP: Record = {
+ [InfoBlockVariant.ERROR]: 'R500',
+ [InfoBlockVariant.HELP]: 'V500',
+ [InfoBlockVariant.INFORMATION]: 'B500',
+ [InfoBlockVariant.SUCCESS]: 'G500',
+ [InfoBlockVariant.WARNING]: 'Y700',
+ [InfoBlockVariant.NEUTRAL]: 'N500',
+}
+
+export const VARIANT_TO_BORDER_MAP: Record = {
+ [InfoBlockVariant.ERROR]: 'er-2',
+ [InfoBlockVariant.HELP]: 'ev-2',
+ [InfoBlockVariant.INFORMATION]: 'eb-2',
+ [InfoBlockVariant.SUCCESS]: 'eg-2',
+ [InfoBlockVariant.WARNING]: 'ey-2',
+ [InfoBlockVariant.NEUTRAL]: 'en-2',
+}
+export const DEPLOYMENT_STAGE_TO_NODE_MAP: Readonly> = {
+ [DeploymentStageType.DEPLOY]: DeploymentNodeType.CD,
+ [DeploymentStageType.POST]: DeploymentNodeType.POSTCD,
+ [DeploymentStageType.PRE]: DeploymentNodeType.PRECD,
+}
+
+export const APP_DETAILS_FALLBACK_POLLING_INTERVAL = 30000
+export const PROGRESSING_DEPLOYMENT_STATUS_POLLING_INTERVAL = 10000
diff --git a/src/Shared/types.ts b/src/Shared/types.ts
index 869bd4357..0aee65e8e 100644
--- a/src/Shared/types.ts
+++ b/src/Shared/types.ts
@@ -14,10 +14,12 @@
* limitations under the License.
*/
+import { ReactNode } from 'react'
import { ParsedCountry } from 'react-international-phone'
import { Dayjs } from 'dayjs'
import { APIOptions, ApprovalConfigDataType } from '@Common/Types'
+import { ReleaseMode } from '@Pages/index'
import {
CommonNodeAttr,
@@ -33,8 +35,8 @@ import {
VariableType,
VulnerabilityType,
} from '../Common'
-import { SelectPickerOptionType } from './Components'
-import { BASE_CONFIGURATION_ENV_ID, EnvironmentTypeEnum, PatchOperationType } from './constants'
+import { SelectPickerOptionType, WorkflowRunnerStatusDTO } from './Components'
+import { BASE_CONFIGURATION_ENV_ID, DEPLOYMENT_STATUS, EnvironmentTypeEnum, PatchOperationType } from './constants'
export enum EnvType {
CHART = 'helm_charts',
@@ -153,6 +155,7 @@ export interface Node {
canBeHibernated: boolean
isHibernated: boolean
hasDrift?: boolean
+ status?: string
}
// eslint-disable-next-line no-use-before-define
@@ -171,6 +174,7 @@ export interface ResourceTree {
podMetadata: Array
status: string
resourcesSyncResult?: Record
+ wfrId?: number
}
export enum AppType {
@@ -244,6 +248,13 @@ export interface AppDetails {
chartAvatar?: string
fluxTemplateType?: string
FluxAppStatusDetail?: FluxAppStatusDetail
+ isPipelineTriggered?: boolean
+ releaseMode?: ReleaseMode
+}
+
+export interface ConfigDriftModalProps extends Required> {
+ envId: number
+ handleCloseModal?: () => void
}
export enum RegistryType {
@@ -476,6 +487,7 @@ export enum ResourceKindType {
releaseChannel = 'release-channel',
tenant = 'tenant',
installation = 'installation',
+ infrastructureInstallation = 'installations',
environment = 'environment',
cdPipeline = 'cd-pipeline',
ciPipeline = 'ci-pipeline',
@@ -1110,6 +1122,11 @@ export enum ResponseHeaders {
LICENSE_STATUS = 'X-License-Status',
}
+export enum InstallationClusterType {
+ EKS_AUTO_CLUSTER = 'eksAutoCluster',
+ EKS_MANUAL_CLUSTER = 'eksManualCluster',
+}
+
export type IconBaseSizeType =
| 6
| 8
@@ -1153,6 +1170,122 @@ export enum RegistryCredentialsType {
ANONYMOUS = 'anonymous',
}
+export interface SyncStageResourceDetail {
+ id: number
+ cdWorkflowRunnerId: number
+ resourceGroup: string
+ resourceKind: string
+ resourceName: string
+ resourcePhase: string
+ resourceStatus: string
+ statusMessage: string
+}
+
+export enum TIMELINE_STATUS {
+ DEPLOYMENT_INITIATED = 'DEPLOYMENT_INITIATED',
+ GIT_COMMIT = 'GIT_COMMIT',
+ GIT_COMMIT_FAILED = 'GIT_COMMIT_FAILED',
+ ARGOCD_SYNC = 'ARGOCD_SYNC',
+ ARGOCD_SYNC_FAILED = 'ARGOCD_SYNC_FAILED',
+ KUBECTL_APPLY = 'KUBECTL_APPLY',
+ KUBECTL_APPLY_STARTED = 'KUBECTL_APPLY_STARTED',
+ KUBECTL_APPLY_SYNCED = 'KUBECTL_APPLY_SYNCED',
+ HEALTHY = 'HEALTHY',
+ APP_HEALTH = 'APP_HEALTH',
+ DEPLOYMENT_FAILED = 'FAILED',
+ FETCH_TIMED_OUT = 'TIMED_OUT',
+ UNABLE_TO_FETCH_STATUS = 'UNABLE_TO_FETCH_STATUS',
+ DEGRADED = 'DEGRADED',
+ DEPLOYMENT_SUPERSEDED = 'DEPLOYMENT_SUPERSEDED',
+ ABORTED = 'ABORTED',
+ INPROGRESS = 'INPROGRESS',
+ HELM_PACKAGE_GENERATED = 'HELM_PACKAGE_GENERATED',
+ HELM_PACKAGE_GENERATION_FAILED = 'HELM_PACKAGE_GENERATION_FAILED',
+ HELM_MANIFEST_PUSHED_TO_HELM_REPO = 'HELM_MANIFEST_PUSHED_TO_HELM_REPO',
+ HELM_MANIFEST_PUSHED_TO_HELM_REPO_FAILED = 'HELM_MANIFEST_PUSHED_TO_HELM_REPO_FAILED',
+}
+
+export interface DeploymentStatusDetailsTimelineType
+ extends Pick {
+ status: TIMELINE_STATUS
+ statusDetail: string
+ statusTime: string
+ resourceDetails?: SyncStageResourceDetail[]
+}
+
+export interface DeploymentStatusDetailsType {
+ deploymentFinishedOn: string
+ deploymentStartedOn: string
+ triggeredBy: string
+ statusFetchCount: number
+ statusLastFetchedAt: string
+ timelines: DeploymentStatusDetailsTimelineType[]
+ wfrStatus?: WorkflowRunnerStatusDTO
+ isDeploymentWithoutApproval: boolean
+}
+
+export type DeploymentStatusTimelineType =
+ | TIMELINE_STATUS.DEPLOYMENT_INITIATED
+ | TIMELINE_STATUS.GIT_COMMIT
+ | TIMELINE_STATUS.ARGOCD_SYNC
+ | TIMELINE_STATUS.KUBECTL_APPLY
+ | TIMELINE_STATUS.APP_HEALTH
+ | TIMELINE_STATUS.HELM_PACKAGE_GENERATED
+ | TIMELINE_STATUS.HELM_MANIFEST_PUSHED_TO_HELM_REPO
+
+export type DeploymentStatusBreakdownItemIconType =
+ | 'success'
+ | 'failed'
+ | 'unknown'
+ | 'inprogress'
+ | 'unreachable'
+ // Loading is for subSteps
+ | 'loading'
+ | 'disconnect'
+ | 'timed_out'
+ | ''
+
+export enum DeploymentPhaseType {
+ PRE_SYNC = 'PreSync',
+ SYNC = 'Sync',
+ POST_SYNC = 'PostSync',
+ SKIP = 'Skip',
+ SYNC_FAIL = 'SyncFail',
+}
+
+export interface DeploymentStatusBreakdownItemType {
+ icon: DeploymentStatusBreakdownItemIconType
+ displayText: ReactNode
+ displaySubText: string
+ time: string
+ /**
+ * Shown in accordion details if type is TIMELINE_STATUS.KUBECTL_APPLY to display resource details
+ */
+ resourceDetails?: SyncStageResourceDetail[]
+ isCollapsed?: boolean
+ /**
+ * Sub-Steps in accordion details in case type is TIMELINE_STATUS.KUBECTL_APPLY
+ */
+ subSteps?: { icon: DeploymentStatusBreakdownItemIconType; message: string; phase?: DeploymentPhaseType }[]
+ /**
+ * To be shown in accordion details below heading tile
+ */
+ timelineStatus?: ReactNode
+ showHelmManifest?: boolean
+}
+
+export interface DeploymentStatusDetailsBreakdownDataType {
+ deploymentStatus: (typeof DEPLOYMENT_STATUS)[keyof typeof DEPLOYMENT_STATUS]
+ deploymentTriggerTime: string
+ deploymentEndTime: string
+ triggeredBy: string
+ deploymentStatusBreakdown: Partial>
+ errorBarConfig?: {
+ deploymentErrorMessage: string
+ nextTimelineToProcess: DeploymentStatusTimelineType
+ } | null
+}
+
export interface IntelligenceConfig {
clusterId: number
metadata: Record
diff --git a/src/index.ts b/src/index.ts
index 4d5f80fe0..98f920ca5 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -57,6 +57,9 @@ export interface customEnv {
ENABLE_SCOPED_VARIABLES?: boolean
DEFAULT_CI_TRIGGER_TYPE_MANUAL: boolean
ANNOUNCEMENT_BANNER_MSG?: string
+ ANNOUNCEMENT_BANNER_TYPE?: string
+ ANNOUNCEMENT_BANNER_BUTTON_TEXT?: string
+ ANNOUNCEMENT_BANNER_BUTTON_LINK?: string
HIDE_DEFAULT_CLUSTER?: boolean
GLOBAL_API_TIMEOUT?: number
TRIGGER_API_TIMEOUT?: number
@@ -149,6 +152,10 @@ export interface customEnv {
* @default false
*/
FEATURE_APPLICATION_TEMPLATES_ENABLE?: boolean
+ /**
+ * @default false
+ */
+ FEATURE_REDFISH_NODE_ENABLE?: boolean
GATEKEEPER_URL?: string
FEATURE_AI_INTEGRATION_ENABLE?: boolean
LOGIN_PAGE_IMAGE?: string
diff --git a/vite.config.ts b/vite.config.ts
index e62fa6155..bbf0c0665 100644
--- a/vite.config.ts
+++ b/vite.config.ts
@@ -88,7 +88,7 @@ export default defineConfig({
return '@vendor'
}
- if (id.includes('codemirror') || id.includes('src/Common/CodeMirror')) {
+ if (id.match('codemirror') || id.includes('src/Shared/Components/CodeEditor')) {
return '@code-editor'
}