diff --git a/src/assets/icons/ic-onboarding.svg b/src/assets/icons/ic-onboarding.svg
new file mode 100644
index 0000000000..91bad8534f
--- /dev/null
+++ b/src/assets/icons/ic-onboarding.svg
@@ -0,0 +1,3 @@
+
\ No newline at end of file
diff --git a/src/assets/img/guide-create-app.png b/src/assets/img/guide-create-app.png
new file mode 100644
index 0000000000..e0f6a13fdf
Binary files /dev/null and b/src/assets/img/guide-create-app.png differ
diff --git a/src/assets/img/guide-onboard.png b/src/assets/img/guide-onboard.png
new file mode 100644
index 0000000000..2dabd036e5
Binary files /dev/null and b/src/assets/img/guide-onboard.png differ
diff --git a/src/assets/img/guided-chart-repository.png b/src/assets/img/guided-chart-repository.png
new file mode 100644
index 0000000000..2ece6ae284
Binary files /dev/null and b/src/assets/img/guided-chart-repository.png differ
diff --git a/src/assets/img/guided-helm-cluster.png b/src/assets/img/guided-helm-cluster.png
new file mode 100644
index 0000000000..3fe6df7226
Binary files /dev/null and b/src/assets/img/guided-helm-cluster.png differ
diff --git a/src/assets/img/guided-helm-collage.png b/src/assets/img/guided-helm-collage.png
new file mode 100644
index 0000000000..59c173d333
Binary files /dev/null and b/src/assets/img/guided-helm-collage.png differ
diff --git a/src/assets/img/guided-helm-search.png b/src/assets/img/guided-helm-search.png
new file mode 100644
index 0000000000..0188a61a9c
Binary files /dev/null and b/src/assets/img/guided-helm-search.png differ
diff --git a/src/assets/img/helm-collage.png b/src/assets/img/helm-collage.png
new file mode 100644
index 0000000000..f79f448e60
Binary files /dev/null and b/src/assets/img/helm-collage.png differ
diff --git a/src/assets/img/ic-preview.png b/src/assets/img/ic-preview.png
new file mode 100644
index 0000000000..b92a99f52a
Binary files /dev/null and b/src/assets/img/ic-preview.png differ
diff --git a/src/assets/img/lifebuoy.png b/src/assets/img/lifebuoy.png
new file mode 100644
index 0000000000..1a71e76f62
Binary files /dev/null and b/src/assets/img/lifebuoy.png differ
diff --git a/src/components/app/create/createApp.css b/src/components/app/create/createApp.css
index de00fa9531..8ed0cce9b9 100644
--- a/src/components/app/create/createApp.css
+++ b/src/components/app/create/createApp.css
@@ -1,7 +1,9 @@
.create-app-modal {
width: 450px;
max-height: 90vh;
- margin-top: 40px;
+ margin-top: 0px;
+ border-top-left-radius: 0px;
+ border-top-right-radius: 0px;
}
.footer {
diff --git a/src/components/app/details/main.tsx b/src/components/app/details/main.tsx
index 022b93f7c7..a743b854cb 100644
--- a/src/components/app/details/main.tsx
+++ b/src/components/app/details/main.tsx
@@ -19,6 +19,7 @@ import { ReactComponent as Settings } from '../../../assets/icons/ic-settings.sv
import { ReactComponent as Info } from '../../../assets/icons/ic-info-outlined.svg'
import { EnvType } from '../../v2/appDetails/appDetails.type'
import PageHeader from '../../common/header/PageHeader'
+import { AppDetailsProps } from './triggerView/types'
const TriggerView = lazy(() => import('./triggerView/TriggerView'))
const DeploymentMetrics = lazy(() => import('./metrics/DeploymentMetrics'))
@@ -29,7 +30,7 @@ const IndexComponent = lazy(() => import('../../v2/index'))
const CDDetails = lazy(() => import('./cdDetails/CDDetails'))
const TestRunList = lazy(() => import('./testViewer/TestRunList'))
-export default function AppDetailsPage({ isV2 }) {
+export default function AppDetailsPage({ isV2 }: AppDetailsProps) {
const { path } = useRouteMatch()
const { appId } = useParams<{ appId }>()
diff --git a/src/components/app/details/triggerView/types.ts b/src/components/app/details/triggerView/types.ts
index 703c563b98..2add5cf1cd 100644
--- a/src/components/app/details/triggerView/types.ts
+++ b/src/components/app/details/triggerView/types.ts
@@ -469,3 +469,6 @@ export interface BranchRegexModalProps {
regexValue
onCloseBranchRegexModal
}
+export interface AppDetailsProps {
+ isV2: boolean
+}
diff --git a/src/components/app/list-new/AppList.tsx b/src/components/app/list-new/AppList.tsx
index c3052f3df7..a5f25fe4d6 100644
--- a/src/components/app/list-new/AppList.tsx
+++ b/src/components/app/list-new/AppList.tsx
@@ -14,8 +14,6 @@ import {
import { ReactComponent as Search } from '../../../assets/icons/ic-search.svg'
import { ReactComponent as ChartIcon } from '../../../assets/icons/ic-charts.svg'
import { ReactComponent as AddIcon } from '../../../assets/icons/ic-add.svg'
-import InstallDevtronFullImage from '../../../assets/img/install-devtron-full@2x.png'
-import EmptyState from '../../EmptyState/EmptyState'
import { getInitData, buildClusterVsNamespace, getNamespaces } from './AppListService'
import { ServerErrors } from '../../../modals/commonTypes'
import { AppListViewType } from '../config'
@@ -24,7 +22,7 @@ import { ReactComponent as Clear } from '../../../assets/icons/ic-error.svg'
import DevtronAppListContainer from '../list/DevtronAppListContainer'
import HelmAppList from './HelmAppList'
import * as queryString from 'query-string'
-import { OrderBy, SortBy } from '../list/types'
+import { AppListPropType, OrderBy, SortBy } from '../list/types'
import { AddNewApp } from '../create/CreateApp'
import { mainContext } from '../../common/navigation/NavigationRoutes'
import '../list/list.css'
@@ -37,9 +35,8 @@ import { FILE_NAMES } from '../../common/ExportToCsv/constants'
import { getAppList } from '../service'
import moment from 'moment'
import { getUserRole } from '../../userGroups/userGroup.service'
-import Tippy from '@tippyjs/react'
-export default function AppList() {
+export default function AppList({isSuperAdmin, appListCount} : AppListPropType) {
const location = useLocation()
const history = useHistory()
const params = useParams<{ appType: string }>()
@@ -50,7 +47,6 @@ export default function AppList() {
const [lastDataSync, setLastDataSync] = useState(false)
const [fetchingNamespaces, setFetchingNamespaces] = useState(false)
const [fetchingNamespacesErrored, setFetchingNamespacesErrored] = useState(false)
-
const [parsedPayloadOnUrlChange, setParsedPayloadOnUrlChange] = useState({})
const [currentTab, setCurrentTab] = useState(undefined)
const [showCreateNewAppSelectionModal, setShowCreateNewAppSelectionModal] = useState(false)
@@ -79,7 +75,7 @@ export default function AppList() {
// on page load
useEffect(() => {
let _currentTab =
- params.appType == AppListConstants.AppType.DEVTRON_APPS
+ params.appType === AppListConstants.AppType.DEVTRON_APPS
? AppListConstants.AppTabs.DEVTRON_APPS
: AppListConstants.AppTabs.HELM_APPS
setCurrentTab(_currentTab)
@@ -104,7 +100,7 @@ export default function AppList() {
setEnvironmentListRes(initData.environmentListRes)
setMasterFilters(initData.filters)
setDataStateType(AppListViewType.LIST)
- if (serverMode == SERVER_MODE.EA_ONLY) {
+ if (serverMode === SERVER_MODE.EA_ONLY) {
applyClusterSelectionFilterOnPageLoadIfSingle(initData.filters.clusters, _currentTab)
}
})
@@ -1014,65 +1010,70 @@ export default function AppList() {
return (
- {dataStateType == AppListViewType.LOADING && (
+ {dataStateType === AppListViewType.LOADING && (
)}
- {dataStateType == AppListViewType.ERROR && (
+ {dataStateType === AppListViewType.ERROR && (
)}
- {dataStateType == AppListViewType.LIST && (
+ {dataStateType === AppListViewType.LIST && (
<>
{renderPageHeader()}
{renderMasterFilters()}
{renderAppliedFilters()}
{renderAppTabs()}
- {serverMode == SERVER_MODE.FULL && renderAppCreateRouter()}
- {params.appType == AppListConstants.AppType.DEVTRON_APPS && serverMode == SERVER_MODE.FULL && (
-
- )}
- {params.appType == AppListConstants.AppType.DEVTRON_APPS && serverMode == SERVER_MODE.EA_ONLY && (
-
-
-
- )}
- {params.appType == AppListConstants.AppType.HELM_APPS && (
+ {serverMode === SERVER_MODE.FULL && renderAppCreateRouter()}
<>
-
- {fetchingExternalApps && (
-
+ {params.appType === AppListConstants.AppType.DEVTRON_APPS &&
+ serverMode === SERVER_MODE.FULL && (
+
+ )}
+ {params.appType === AppListConstants.AppType.DEVTRON_APPS &&
+ serverMode === SERVER_MODE.EA_ONLY && (
+
+
+
+ )}
+ {params.appType === AppListConstants.AppType.HELM_APPS && (
+ <>
+
+ {fetchingExternalApps && (
+
+ )}
+ >
)}
>
- )}
>
)}
diff --git a/src/components/app/list-new/HelmAppList.tsx b/src/components/app/list-new/HelmAppList.tsx
index 907e56dfcc..dd3f00328e 100644
--- a/src/components/app/list-new/HelmAppList.tsx
+++ b/src/components/app/list-new/HelmAppList.tsx
@@ -10,7 +10,6 @@ import {
Pagination,
LazyImage,
handleUTCTime,
- useEventSource,
} from '../../common';
import { Host, SERVER_MODE, URLS, DOCUMENTATION } from '../../../config';
import { AppListViewType } from '../config';
diff --git a/src/components/app/list/AppListView.tsx b/src/components/app/list/AppListView.tsx
index d6c018ab2f..9d41ab917a 100644
--- a/src/components/app/list/AppListView.tsx
+++ b/src/components/app/list/AppListView.tsx
@@ -10,8 +10,7 @@ import { ReactComponent as Edit } from '../../../assets/icons/ic-settings.svg';
import {ReactComponent as DevtronAppIcon} from '../../../assets/icons/ic-devtron-app.svg';
import {ReactComponent as HelpOutlineIcon} from '../../../assets/icons/ic-help-outline.svg';
import Tippy from '@tippyjs/react';
-
-
+import DevtronAppGuidePage from '../../onboardingGuide/DevtronAppGuidePage';
interface AppListViewProps extends AppListState, RouteComponentProps<{}> {
expandRow: (app: App | null) => void;
closeExpandedRow: () => void;
@@ -21,6 +20,9 @@ interface AppListViewProps extends AppListState, RouteComponentProps<{}> {
clearAll: () => void;
changePage: (pageNo: number) => void;
changePageSize: (size: number) => void;
+ appListCount: number
+ isSuperAdmin: boolean
+ openDevtronAppCreateModel: (event) => void
}
export class AppListView extends Component{
@@ -129,18 +131,13 @@ export class AppListView extends Component{
}
+
else if (this.props.view === AppListViewType.EMPTY) {
return
-
+
}
+
else if (this.props.view === AppListViewType.NO_RESULT) {
return
{
clearAll={this.props.clearAllFilters}
changePage={this.changePage}
changePageSize={this.changePageSize}
+ isSuperAdmin={this.props.isSuperAdmin}
+ appListCount={this.props.appListCount}
+ openDevtronAppCreateModel={this.props.openDevtronAppCreateModel}
/>
}
}
diff --git a/src/components/app/list/list.css b/src/components/app/list/list.css
index 1fc510e74f..03df3e2e8e 100644
--- a/src/components/app/list/list.css
+++ b/src/components/app/list/list.css
@@ -482,3 +482,7 @@
background-color: var(--R100);
border: solid 1px var(--R200);
}
+
+.first-app-container {
+ margin-top: 60px;
+}
\ No newline at end of file
diff --git a/src/components/app/list/types.tsx b/src/components/app/list/types.tsx
index 496462d15c..4793ba8290 100644
--- a/src/components/app/list/types.tsx
+++ b/src/components/app/list/types.tsx
@@ -59,6 +59,9 @@ export interface AppListProps extends RouteComponentProps<{ route: string }> {
clearAllFilters: () => void;
sortApplicationList : (key: string) => void;
updateLastDataSync : () => void;
+ appListCount: number
+ isSuperAdmin: boolean
+ openDevtronAppCreateModel: (event) => void
setAppCount: React.Dispatch>
}
@@ -114,3 +117,7 @@ export const SortBy = {
ENVIRONMENT: "environmentSort",
}
+export interface AppListPropType {
+ isSuperAdmin: boolean
+ appListCount: number
+}
\ No newline at end of file
diff --git a/src/components/common/HelpNav.tsx b/src/components/common/HelpNav.tsx
index de456f4652..8c9991c44f 100644
--- a/src/components/common/HelpNav.tsx
+++ b/src/components/common/HelpNav.tsx
@@ -6,7 +6,8 @@ import { ReactComponent as Discord } from '../../assets/icons/ic-discord-fill.sv
import { ReactComponent as Edit } from '../../assets/icons/ic-pencil.svg'
import { ReactComponent as Chat } from '../../assets/icons/ic-chat-circle-dots.svg'
import { InstallationType, ServerInfo } from '../v2/devtronStackManager/DevtronStackManager.type'
-import { NavLink } from 'react-router-dom'
+import { NavLink, useHistory } from 'react-router-dom'
+import { ReactComponent as GettingStartedIcon } from '../../assets/icons/ic-onboarding.svg'
export interface HelpNavType {
className: string
@@ -14,9 +15,12 @@ export interface HelpNavType {
setShowHelpCard: React.Dispatch>
serverInfo: ServerInfo
fetchingServerInfo: boolean
+ setGettingStartedClicked: (isClicked: boolean) => void
}
-function HelpNav({ className, showHelpCard, setShowHelpCard, serverInfo, fetchingServerInfo }: HelpNavType) {
+function HelpNav({ className, showHelpCard, setShowHelpCard, serverInfo, fetchingServerInfo, setGettingStartedClicked }: HelpNavType) {
+ const history = useHistory()
+
const HelpOptions = [
{
name: 'View documentation',
@@ -28,6 +32,7 @@ function HelpNav({ className, showHelpCard, setShowHelpCard, serverInfo, fetchin
name: 'Chat with support',
link: 'https://discord.devtron.ai/',
icon: Chat,
+ showSeparator: true,
},
{
name: 'Join discord community',
@@ -42,30 +47,42 @@ function HelpNav({ className, showHelpCard, setShowHelpCard, serverInfo, fetchin
},
]
+ const onClickGettingStarted = () => {
+ setGettingStartedClicked(true)
+ }
+
return (
setShowHelpCard(!showHelpCard)}>
+
+
+ Getting started
+
+
{HelpOptions.map((option) => {
return (
-
+ {
+ ReactGA.event({
+ category: 'Main Navigation',
+ action: `${option.name} Clicked`,
+ })
+ }}
+ >
+
+ {option.name}
+
{option.showSeparator && }
)
diff --git a/src/components/common/gettingStartedCard/GettingStarted.tsx b/src/components/common/gettingStartedCard/GettingStarted.tsx
new file mode 100644
index 0000000000..33097600f7
--- /dev/null
+++ b/src/components/common/gettingStartedCard/GettingStarted.tsx
@@ -0,0 +1,54 @@
+import React from 'react'
+import GettingToast from '../../../assets/img/lifebuoy.png'
+import { updateLoginCount } from '../../../services/service'
+import { handlePostHogEventUpdate, LOGIN_COUNT, MAX_LOGIN_COUNT, POSTHOG_EVENT_ONBOARDING } from '../../onboardingGuide/onboarding.utils'
+import { GettingStartedType } from '../../onboardingGuide/OnboardingGuide.type'
+import { setActionWithExpiry } from '../helpers/Helpers'
+import './gettingStarted.scss'
+
+function GettingStartedCard({ className, hideGettingStartedCard }: GettingStartedType) {
+ const onClickedOkay = (e) => {
+ setActionWithExpiry('clickedOkay', 1)
+ hideGettingStartedCard()
+ handlePostHogEventUpdate(e)
+ }
+
+ const onClickedDontShowAgain = (e) => {
+ const updatedPayload = {
+ key: LOGIN_COUNT,
+ value: `${MAX_LOGIN_COUNT}`,
+ }
+ updateLoginCount(updatedPayload)
+ hideGettingStartedCard(updatedPayload.value)
+ handlePostHogEventUpdate(e)
+ }
+
+ return (
+
+
+
+

+
Getting started
+
You can always access the Getting Started guide from here.
+
+
+
+
+
+
+ )
+}
+
+export default GettingStartedCard
diff --git a/src/components/common/gettingStartedCard/gettingStarted.scss b/src/components/common/gettingStartedCard/gettingStarted.scss
new file mode 100644
index 0000000000..6ab064dcab
--- /dev/null
+++ b/src/components/common/gettingStartedCard/gettingStarted.scss
@@ -0,0 +1,23 @@
+.getting-started-card {
+ background-color: #2d3452;
+ position: fixed;
+ z-index: 6;
+ right: 20px;
+ top: 54px;
+
+}
+
+.arrow-up {
+ width: 0;
+ height: 0;
+ border-left: 12px solid transparent;
+ border-right: 12px solid transparent;
+ border-bottom: 12px solid #2d3452;
+ right: 64px;
+ position: fixed;
+ top: 42px
+}
+
+.getting_tippy__position {
+ position: fixed;
+}
\ No newline at end of file
diff --git a/src/components/common/header/PageHeader.tsx b/src/components/common/header/PageHeader.tsx
index e58489adf8..770b0aa2e9 100644
--- a/src/components/common/header/PageHeader.tsx
+++ b/src/components/common/header/PageHeader.tsx
@@ -5,10 +5,14 @@ import { ReactComponent as Close } from '../../../assets/icons/ic-close.svg'
import HelpNav from '../HelpNav'
import './pageHeader.css'
import LogoutCard from '../LogoutCard'
-import { getLoginInfo, getRandomColor } from '../helpers/Helpers'
+import { getLoginInfo, getRandomColor, setActionWithExpiry } from '../helpers/Helpers'
import { ServerInfo } from '../../v2/devtronStackManager/DevtronStackManager.type'
import { getServerInfo } from '../../v2/devtronStackManager/DevtronStackManager.service'
+import { useRouteMatch, useHistory, useLocation } from 'react-router'
+import GettingStartedCard from '../gettingStartedCard/GettingStarted'
+import { mainContext } from '../navigation/NavigationRoutes'
import ReactGA from 'react-ga4'
+import { handlePostHogEventUpdate, MAX_LOGIN_COUNT, POSTHOG_EVENT_ONBOARDING } from '../../onboardingGuide/onboarding.utils'
export interface PageHeaderType {
headerName?: string
additionalHeaderInfo?: () => JSX.Element
@@ -42,6 +46,14 @@ function PageHeader({
showCloseButton = false,
onClose,
}: PageHeaderType) {
+ const {
+ loginCount,
+ setLoginCount,
+ showGettingStartedCard,
+ setShowGettingStartedCard,
+ isGettingStartedClicked,
+ setGettingStartedClicked,
+ } = useContext(mainContext)
const [showHelpCard, setShowHelpCard] = useState(false)
const [showLogOutCard, setShowLogOutCard] = useState(false)
const loginInfo = getLoginInfo()
@@ -52,6 +64,7 @@ function PageHeader({
fetchingServerInfo: false,
},
)
+ const [expiryDate, setExpiryDate] = useState(0)
const getCurrentServerInfo = async () => {
try {
@@ -68,23 +81,44 @@ function PageHeader({
console.error('Error in fetching server info')
}
}
+
+ useEffect(() => {
+ setExpiryDate(+localStorage.getItem('clickedOkay'))
+ }, [])
+
useEffect(() => {
getCurrentServerInfo()
}, [])
+ const onClickLogoutButton = () => {
+ setShowLogOutCard(!showLogOutCard)
+ if (showHelpCard) {
+ setShowHelpCard(false)
+ }
+ setActionWithExpiry('clickedOkay', 1)
+ hideGettingStartedCard()
+ }
+
+ const onClickHelp = (e) => {
+ setShowHelpCard(!showHelpCard)
+ if (showLogOutCard) {
+ setShowLogOutCard(false)
+ }
+ setActionWithExpiry('clickedOkay', 1)
+ hideGettingStartedCard()
+ handlePostHogEventUpdate(e, POSTHOG_EVENT_ONBOARDING.HELP)
+ ReactGA.event({
+ category: 'Main Navigation',
+ action: `Help Clicked`,
+ })
+ }
+
const renderLogoutHelpSection = () => {
return (
<>
{
- setShowHelpCard(!showHelpCard)
- showLogOutCard && setShowLogOutCard(false)
- ReactGA.event({
- category: 'Main Navigation',
- action: `Help Clicked`,
- })
- }}
+ onClick={onClickHelp}
>
@@ -93,10 +127,7 @@ function PageHeader({
{
- setShowLogOutCard(!showLogOutCard)
- showHelpCard && setShowHelpCard(false)
- }}
+ onClick={onClickLogoutButton}
style={{ backgroundColor: getRandomColor(email) }}
>
{email[0]}
@@ -105,6 +136,19 @@ function PageHeader({
)
}
+ const hideGettingStartedCard = (count?: string) => {
+ setShowGettingStartedCard(false)
+ if (count) {
+ setLoginCount(+count)
+ }
+ }
+
+ const getExpired = (): boolean => {
+ // Render Getting started tippy card if the time gets expired
+ const now = new Date().valueOf()
+ return now > expiryDate
+ }
+
return (
+ )}
+ {showGettingStartedCard && loginCount >= 0 && loginCount < MAX_LOGIN_COUNT && getExpired() && (
+
)}
{showLogOutCard && (
diff --git a/src/components/common/helpers/Helpers.tsx b/src/components/common/helpers/Helpers.tsx
index f4c7c0f79a..a27acd7d9a 100644
--- a/src/components/common/helpers/Helpers.tsx
+++ b/src/components/common/helpers/Helpers.tsx
@@ -6,6 +6,7 @@ import YAML from 'yaml';
import { useWindowSize } from './UseWindowSize';
import { useLocation } from 'react-router'
import { Link } from 'react-router-dom';
+import { getDateInMilliseconds } from '../../apiTokens/authorization.utils';
const commandLineParser = require('command-line-parser');
export type IntersectionChangeHandler = (entry: IntersectionObserverEntry) => void;
@@ -906,7 +907,7 @@ export const sortOptionsByValue = (optionA, optionB) => {
return 0
}
-// Create instance of MutationObserver & watch for DOM changes until
+// Create instance of MutationObserver & watch for DOM changes until
// disconnect() is called.
export const watchDOMForChanges = (callback: (observer: MutationObserver) => void) => {
const observer = new MutationObserver(() => {
@@ -937,3 +938,8 @@ export const elementDidMount = (identifier: string): Promise
=> {
})
})
}
+
+// Setting expiry time in local storage for specified action key
+export const setActionWithExpiry = (key: string, days: number): void => {
+ localStorage.setItem(key, `${getDateInMilliseconds(days)}`)
+}
diff --git a/src/components/common/navigation/NavigationRoutes.tsx b/src/components/common/navigation/NavigationRoutes.tsx
index 4157b9a769..52e30c8d72 100644
--- a/src/components/common/navigation/NavigationRoutes.tsx
+++ b/src/components/common/navigation/NavigationRoutes.tsx
@@ -7,13 +7,24 @@ import { useRouteMatch, useHistory, useLocation } from 'react-router'
import * as Sentry from '@sentry/browser'
import ReactGA from 'react-ga4'
import { Security } from '../../security/Security'
-import { dashboardLoggedIn, getVersionConfig } from '../../../services/service'
+import {
+ dashboardLoggedIn,
+ getAppListMin,
+ getLoginData,
+ getVersionConfig,
+ updateLoginCount,
+} from '../../../services/service'
import Reload from '../../Reload/Reload'
import { EnvType } from '../../v2/appDetails/appDetails.type'
import DevtronStackManager from '../../v2/devtronStackManager/DevtronStackManager'
import { ServerInfo } from '../../v2/devtronStackManager/DevtronStackManager.type'
import { getServerInfo } from '../../v2/devtronStackManager/DevtronStackManager.service'
import ClusterNodeContainer from '../../ClusterNodes/ClusterNodeContainer'
+import DeployManageGuide from '../../onboardingGuide/DeployManageGuide'
+import { showError } from '../helpers/Helpers'
+import { AppRouterType } from '../../../services/service.types'
+import { getUserRole } from '../../userGroups/userGroup.service'
+import { LOGIN_COUNT, MAX_LOGIN_COUNT } from '../../onboardingGuide/onboarding.utils'
const Charts = lazy(() => import('../../charts/Charts'))
const ExternalApps = lazy(() => import('../../external-apps/ExternalApps'))
@@ -23,6 +34,8 @@ const V2Details = lazy(() => import('../../v2/index'))
const GlobalConfig = lazy(() => import('../../globalConfigurations/GlobalConfiguration'))
const BulkActions = lazy(() => import('../../deploymentGroups/BulkActions'))
const BulkEdit = lazy(() => import('../../bulkEdits/BulkEdits'))
+const OnboardingGuide = lazy(() => import('../../onboardingGuide/OnboardingGuide'))
+
export const mainContext = createContext(null)
export default function NavigationRoutes() {
@@ -38,10 +51,71 @@ export default function NavigationRoutes() {
fetchingServerInfo: false,
},
)
+ const [isHelpGettingStartedClicked, setHelpGettingStartedClicked] = useState(false)
+ const [loginCount, setLoginCount] = useState(0)
+ const [expiryDate, setExpiryDate] = useState(0)
+ const [isSuperAdmin, setSuperAdmin] = useState(false)
+ const [appListCount, setAppListCount] = useState(0)
+ const [loginLoader, setLoginLoader] = useState(true)
+ const [isDeployManageCardClicked, setDeployManageCardClicked] = useState(false)
+ const [showGettingStartedCard, setShowGettingStartedCard] = useState(true)
+ const [isGettingStartedClicked, setGettingStartedClicked] = useState(false)
+
+ const showCloseButtonAfterGettingStartedClicked = () => {
+ setHelpGettingStartedClicked(true)
+ }
+
+ useEffect(() => {
+ getInit()
+ setExpiryDate(+localStorage.getItem('clickedOkay'))
+ }, [])
+
+ const getInit = () => {
+ setLoginLoader(true)
+ Promise.all([getUserRole(), serverMode === SERVER_MODE.FULL && getAppListMin(), getLoginData()]).then(
+ (response) => {
+ const superAdmin = response[0]?.result?.roles.includes('role:super-admin___')
+ setSuperAdmin(superAdmin)
+ setAppListCount(response[1]?.result?.length)
+ processLoginData(response[2], superAdmin)
+ setLoginLoader(false)
+ },
+ (err) => {
+ setLoginLoader(false)
+ showError(err)
+ },
+ )
+ }
+
+ const processLoginData = (response, superAdmin) => {
+ const count = response.result?.value ? parseInt(response.result.value) : 0
+ setLoginCount(count)
+ if (
+ typeof Storage !== 'undefined' &&
+ (localStorage.getItem('isSSOLogin') || localStorage.getItem('isAdminLogin'))
+ ) {
+ localStorage.removeItem('isSSOLogin')
+ localStorage.removeItem('isAdminLogin')
+ if (count < MAX_LOGIN_COUNT) {
+ const updatedPayload = {
+ key: LOGIN_COUNT,
+ value: `${count + 1}`,
+ }
+ updateLoginCount(updatedPayload)
+ }
+ }
+ if (!count && superAdmin) {
+ history.push(`/${URLS.GETTING_STARTED}`)
+ } else if (!count) {
+ history.push(URLS.APP)
+ }
+ }
useEffect(() => {
const loginInfo = getLoginInfo()
+
if (!loginInfo) return
+
if (process.env.NODE_ENV === 'production' && window._env_) {
if (window._env_.SENTRY_ERROR_ENABLED) {
Sentry.configureScope(function (scope) {
@@ -73,6 +147,7 @@ export default function NavigationRoutes() {
})
}
}
+
if (typeof Storage !== 'undefined') {
if (localStorage.isDashboardLoggedIn) return
dashboardLoggedIn()
@@ -130,29 +205,59 @@ export default function NavigationRoutes() {
}
}
- if (pageState === ViewType.LOADING) {
+ const onClickedDeployManageCardClicked = () =>{
+ setDeployManageCardClicked(true)
+ }
+
+ if (pageState === ViewType.LOADING || loginLoader) {
return
} else if (pageState === ViewType.ERROR) {
return
} else {
return (
-
-
-
+
+
+ {!window.location.href.includes(URLS.GETTING_STARTED) && (
+
+ )}
+
{serverMode && (
}>
- } />
+ (
+
+ )}
+ />
} />
+
+
+
+
+
+
+
-
+
@@ -193,14 +311,23 @@ export default function NavigationRoutes() {
}
}
-export function AppRouter() {
+export function AppRouter({ isSuperAdmin, appListCount, loginCount }: AppRouterType) {
const { path } = useRouteMatch()
const [environmentId, setEnvironmentId] = useState(null)
return (
- } />
+ (
+
+ )}
+ />
} />
} />
} />
+
-
+
@@ -220,19 +350,19 @@ export function AppRouter() {
)
}
-export function AppListRouter() {
+export function AppListRouter({ isSuperAdmin, appListCount, loginCount }: AppRouterType) {
const { path } = useRouteMatch()
const [environmentId, setEnvironmentId] = useState(null)
return (
- } />
+ } />
-
+
@@ -240,19 +370,22 @@ export function AppListRouter() {
)
}
-export function RedirectWithSentry() {
+export function RedirectUserWithSentry({ isFirstLoginUser }) {
const { push } = useHistory()
const { pathname } = useLocation()
useEffect(() => {
- if (pathname && pathname !== '/') Sentry.captureMessage(`redirecting to app-list from ${pathname}`, 'warning')
- push(`${URLS.APP}/${URLS.APP_LIST}`)
+ if (pathname && pathname !== '/') Sentry.captureMessage(`redirecting to app-list from ${pathname}`, 'warning')
+ if (isFirstLoginUser) {
+ push(`${URLS.GETTING_STARTED}`)
+ } else {
+ push(`${URLS.APP}/${URLS.APP_LIST}`)
+ }
}, [])
return null
}
export function RedirectToAppList() {
const { push } = useHistory()
- const { pathname } = useLocation()
const { serverMode } = useContext(mainContext)
useEffect(() => {
let baseUrl = `${URLS.APP}/${URLS.APP_LIST}`
diff --git a/src/components/common/navigation/navigation.scss b/src/components/common/navigation/navigation.scss
index 6eac626d95..75ca17ae90 100644
--- a/src/components/common/navigation/navigation.scss
+++ b/src/components/common/navigation/navigation.scss
@@ -4,6 +4,9 @@
display: grid;
grid-template-columns: 64px 1fr;
overflow-y: hidden;
+ &.no-nav{
+ grid-template-columns: 1fr;
+ }
}
.main {
@@ -255,9 +258,9 @@
.help-card {
display: grid;
- grid-template-rows: 36px 1px 36px 36px 1px 36px auto;
+ grid-template-rows: 36px 36px 1px 36px 1px 36px 1px 36px auto;
align-items: center;
-
+ z-index: var(--modal-index);
.help-card__option {
padding: 6px 12px;
height: 36px;
@@ -322,4 +325,4 @@
.expandable-active-nav {
padding: 0px 0 0 12px;
-}
+}
\ No newline at end of file
diff --git a/src/components/login/Login.tsx b/src/components/login/Login.tsx
index bb16159537..86e760d5a3 100644
--- a/src/components/login/Login.tsx
+++ b/src/components/login/Login.tsx
@@ -10,7 +10,7 @@ import { Progressing, showError } from '../common'
import { LoginProps, LoginFormState } from './login.types'
import { getSSOConfigList, loginAsAdmin } from './login.service'
import './login.css'
-import { dashboardAccessed } from '../../services/service'
+import { dashboardAccessed, getLoginData, updateLoginCount } from '../../services/service'
export default class Login extends Component {
constructor(props) {
@@ -23,6 +23,7 @@ export default class Login extends Component {
username: 'admin',
password: '',
},
+ loginCount: 0,
}
this.handleChange = this.handleChange.bind(this)
this.autoFillLogin = this.autoFillLogin.bind(this)
@@ -62,6 +63,7 @@ export default class Login extends Component {
})
.catch((errors) => {})
}
+
}
handleChange(e: React.ChangeEvent): void {
@@ -91,6 +93,12 @@ export default class Login extends Component {
return !isValid
}
+ onClickSSO = (): void => {
+ if (typeof Storage !== 'undefined') {
+ localStorage.setItem('isSSOLogin', 'true')
+ }
+ }
+
login(e): void {
e.preventDefault()
let data = this.state.form
@@ -101,7 +109,14 @@ export default class Login extends Component {
this.setState({ loading: false })
let queryString = this.props.location.search.split('continue=')[1]
let url = queryString ? `${queryString}` : URLS.APP
- this.props.history.push(`${url}`)
+
+ getLoginData().then((response) => {
+ const count = response.result?.value ? parseInt(response.result.value) : 0
+ this.setState({ loginCount: count ?? 1 })
+ localStorage.setItem('isAdminLogin', 'true')
+
+ this.props.history.push(url)
+ })
}
})
.catch((errors: ServerErrors) => {
@@ -124,6 +139,7 @@ export default class Login extends Component {