1
import {DataLoader, NavigationManager, Notifications, NotificationsManager, PageContext, Popup, PopupManager, PopupProps} from 'argo-ui';
2
import {createBrowserHistory} from 'history';
3
import * as PropTypes from 'prop-types';
4
import * as React from 'react';
5
import {Helmet} from 'react-helmet';
6
import {Redirect, Route, RouteComponentProps, Router, Switch} from 'react-router';
7
import applications from './applications';
8
import help from './help';
9
import login from './login';
10
import settings from './settings';
11
import {Layout} from './shared/components/layout/layout';
12
import {Page} from './shared/components/page/page';
13
import {VersionPanel} from './shared/components/version-info/version-info-panel';
14
import {AuthSettingsCtx, Provider} from './shared/context';
15
import {services} from './shared/services';
16
import requests from './shared/services/requests';
17
import {hashCode} from './shared/utils';
18
import {Banner} from './ui-banner/ui-banner';
19
import userInfo from './user-info';
20
import {AuthSettings} from './shared/models';
21
import {PKCEVerification} from './login/components/pkce-verify';
23
services.viewPreferences.init();
24
const bases = document.getElementsByTagName('base');
25
const base = bases.length > 0 ? bases[0].getAttribute('href') || '/' : '/';
26
export const history = createBrowserHistory({basename: base});
27
requests.setBaseHRef(base);
29
type Routes = {[path: string]: {component: React.ComponentType<RouteComponentProps<any>>; noLayout?: boolean; extension?: boolean}};
31
const routes: Routes = {
32
'/login': {component: login.component as any, noLayout: true},
33
'/applications': {component: applications.component},
34
'/settings': {component: settings.component},
35
'/user-info': {component: userInfo.component},
36
'/help': {component: help.component},
37
'/pkce/verify': {component: PKCEVerification, noLayout: true}
44
iconClassName: string;
47
const navItems: NavItem[] = [
49
title: 'Applications',
50
tooltip: 'Manage your applications, and diagnose health problems.',
51
path: '/applications',
52
iconClassName: 'argo-icon argo-icon-application'
56
tooltip: 'Manage your repositories, projects, settings',
58
iconClassName: 'argo-icon argo-icon-settings'
63
iconClassName: 'fa fa-user-circle'
66
title: 'Documentation',
67
tooltip: 'Read the documentation, and get help and assistance.',
69
iconClassName: 'argo-icon argo-icon-docs'
73
const versionLoader = services.version.version();
75
async function isExpiredSSO() {
77
const {iss} = await services.users.get();
78
const authSettings = await services.authService.settings();
79
if (iss && iss !== 'argocd') {
80
return ((authSettings.dexConfig && authSettings.dexConfig.connectors) || []).length > 0 || authSettings.oidcConfig;
88
requests.onError.subscribe(async err => {
89
if (err.status === 401) {
90
if (history.location.pathname.startsWith('/login')) {
94
const isSSO = await isExpiredSSO();
95
// location might change after async method call, so we need to check again.
96
if (history.location.pathname.startsWith('/login')) {
99
// Query for basehref and remove trailing /.
100
// If basehref is the default `/` it will become an empty string.
101
const basehref = document
102
.querySelector('head > base')
103
.getAttribute('href')
106
window.location.href = `${basehref}/auth/login?return_url=${encodeURIComponent(location.href)}`;
108
history.push(`/login?return_url=${encodeURIComponent(location.href)}`);
113
export class App extends React.Component<
115
{popupProps: PopupProps; showVersionPanel: boolean; error: Error; navItems: NavItem[]; routes: Routes; extensionsLoaded: boolean; authSettings: AuthSettings}
117
public static childContextTypes = {
118
history: PropTypes.object,
119
apis: PropTypes.object
122
public static getDerivedStateFromError(error: Error) {
126
private popupManager: PopupManager;
127
private notificationsManager: NotificationsManager;
128
private navigationManager: NavigationManager;
129
private navItems: NavItem[];
130
private routes: Routes;
132
constructor(props: {}) {
134
this.state = {popupProps: null, error: null, showVersionPanel: false, navItems: [], routes: null, extensionsLoaded: false, authSettings: null};
135
this.popupManager = new PopupManager();
136
this.notificationsManager = new NotificationsManager();
137
this.navigationManager = new NavigationManager(history);
138
this.navItems = navItems;
139
this.routes = routes;
142
public async componentDidMount() {
143
this.popupManager.popupProps.subscribe(popupProps => this.setState({popupProps}));
144
const authSettings = await services.authService.settings();
145
const {trackingID, anonymizeUsers} = authSettings.googleAnalytics || {trackingID: '', anonymizeUsers: true};
146
const {loggedIn, username} = await services.users.get();
148
const ga = await import('react-ga');
149
ga.initialize(trackingID);
150
const trackPageView = () => {
151
if (loggedIn && username) {
152
const userId = !anonymizeUsers ? username : hashCode(username).toString();
155
ga.pageview(location.pathname + location.search);
158
history.listen(trackPageView);
160
if (authSettings.uiCssURL) {
161
const link = document.createElement('link');
162
link.href = authSettings.uiCssURL;
163
link.rel = 'stylesheet';
164
link.type = 'text/css';
165
document.head.appendChild(link);
168
const systemExtensions = services.extensions.getSystemExtensions();
169
const extendedNavItems = this.navItems;
170
const extendedRoutes = this.routes;
171
for (const extension of systemExtensions) {
172
extendedNavItems.push({
173
title: extension.title,
174
path: extension.path,
175
iconClassName: `fa ${extension.icon}`
177
const component = () => (
180
<title>{extension.title} - Argo CD</title>
182
<Page title={extension.title}>
183
<extension.component />
187
extendedRoutes[extension.path] = {
188
component: component as React.ComponentType<React.ComponentProps<any>>,
193
this.setState({...this.state, navItems: extendedNavItems, routes: extendedRoutes, extensionsLoaded: true, authSettings});
197
if (this.state.error != null) {
198
const stack = this.state.error.stack;
199
const url = 'https://github.com/argoproj/argo-cd/issues/new?labels=bug&template=bug_report.md';
203
<p>Something went wrong!</p>
205
Consider submitting an issue <a href={url}>here</a>.
217
<link rel='icon' type='image/png' href={`${base}assets/favicon/favicon-32x32.png`} sizes='32x32' />
218
<link rel='icon' type='image/png' href={`${base}assets/favicon/favicon-16x16.png`} sizes='16x16' />
220
<PageContext.Provider value={{title: 'Argo CD'}}>
221
<Provider value={{history, popup: this.popupManager, notifications: this.notificationsManager, navigation: this.navigationManager, baseHref: base}}>
222
<DataLoader load={() => services.viewPreferences.getPreferences()}>
223
{pref => <div className={pref.theme ? 'theme-' + pref.theme : 'theme-light'}>{this.state.popupProps && <Popup {...this.state.popupProps} />}</div>}
225
<AuthSettingsCtx.Provider value={this.state.authSettings}>
226
<Router history={history}>
228
<Redirect exact={true} path='/' to='/applications' />
229
{Object.keys(this.routes).map(path => {
230
const route = this.routes[path];
235
render={routeProps =>
238
<route.component {...routeProps} />
241
<DataLoader load={() => services.viewPreferences.getPreferences()}>
244
onVersionClick={() => this.setState({showVersionPanel: true})}
245
navItems={this.navItems}
247
isExtension={route.extension}>
249
<route.component {...routeProps} />
259
{this.state.extensionsLoaded && <Redirect path='*' to='/' />}
262
</AuthSettingsCtx.Provider>
264
</PageContext.Provider>
265
<Notifications notifications={this.notificationsManager.notifications} />
266
<VersionPanel version={versionLoader} isShown={this.state.showVersionPanel} onClose={() => this.setState({showVersionPanel: false})} />
271
public getChildContext() {
272
return {history, apis: {popup: this.popupManager, notifications: this.notificationsManager, navigation: this.navigationManager}};