github.com/argoproj/argo-cd@v1.8.7/ui/src/app/app.tsx (about) 1 import {DataLoader, Layout, NavigationManager, Notifications, NotificationsManager, PageContext, Popup, PopupManager, PopupProps, Tooltip} 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 8 import applications from './applications'; 9 import help from './help'; 10 import login from './login'; 11 import settings from './settings'; 12 import {VersionPanel} from './shared/components/version-info/version-info-panel'; 13 import {Provider} from './shared/context'; 14 import {services} from './shared/services'; 15 import requests from './shared/services/requests'; 16 import {hashCode} from './shared/utils'; 17 import userInfo from './user-info'; 18 19 services.viewPreferences.init(); 20 const bases = document.getElementsByTagName('base'); 21 const base = bases.length > 0 ? bases[0].getAttribute('href') || '/' : '/'; 22 export const history = createBrowserHistory({basename: base}); 23 requests.setBaseHRef(base); 24 25 const routes: {[path: string]: {component: React.ComponentType<RouteComponentProps<any>>; noLayout?: boolean}} = { 26 '/login': {component: login.component as any, noLayout: true}, 27 '/applications': {component: applications.component}, 28 '/settings': {component: settings.component}, 29 '/user-info': {component: userInfo.component}, 30 '/help': {component: help.component} 31 }; 32 33 const navItems = [ 34 { 35 title: 'Manage your applications, and diagnose health problems.', 36 path: '/applications', 37 iconClassName: 'argo-icon-application' 38 }, 39 { 40 title: 'Manage your repositories, projects, settings', 41 path: '/settings', 42 iconClassName: 'argo-icon-settings' 43 }, 44 { 45 title: 'User Info', 46 path: '/user-info', 47 iconClassName: 'fa fa-user-circle' 48 }, 49 { 50 title: 'Read the documentation, and get help and assistance.', 51 path: '/help', 52 iconClassName: 'argo-icon-docs' 53 } 54 ]; 55 56 const versionLoader = services.version.version(); 57 58 async function isExpiredSSO() { 59 try { 60 const {loggedIn, iss} = await services.users.get(); 61 if (loggedIn && iss !== 'argocd') { 62 const authSettings = await services.authService.settings(); 63 return ((authSettings.dexConfig && authSettings.dexConfig.connectors) || []).length > 0 || authSettings.oidcConfig; 64 } 65 } catch { 66 return false; 67 } 68 return false; 69 } 70 71 requests.onError.subscribe(async err => { 72 if (err.status === 401) { 73 if (history.location.pathname.startsWith('/login')) { 74 return; 75 } 76 77 const isSSO = await isExpiredSSO(); 78 // location might change after async method call, so we need to check again. 79 if (history.location.pathname.startsWith('/login')) { 80 return; 81 } 82 // Query for basehref and remove trailing /. 83 // If basehref is the default `/` it will become an empty string. 84 const basehref = document 85 .querySelector('head > base') 86 .getAttribute('href') 87 .replace(/\/$/, ''); 88 if (isSSO) { 89 window.location.href = `${basehref}/auth/login?return_url=${encodeURIComponent(location.href)}`; 90 } else { 91 history.push(`/login?return_url=${encodeURIComponent(location.href)}`); 92 } 93 } 94 }); 95 96 export class App extends React.Component<{}, {popupProps: PopupProps; showVersionPanel: boolean; error: Error}> { 97 public static childContextTypes = { 98 history: PropTypes.object, 99 apis: PropTypes.object 100 }; 101 102 public static getDerivedStateFromError(error: Error) { 103 return {error}; 104 } 105 106 private popupManager: PopupManager; 107 private notificationsManager: NotificationsManager; 108 private navigationManager: NavigationManager; 109 110 constructor(props: {}) { 111 super(props); 112 this.state = {popupProps: null, error: null, showVersionPanel: false}; 113 this.popupManager = new PopupManager(); 114 this.notificationsManager = new NotificationsManager(); 115 this.navigationManager = new NavigationManager(history); 116 } 117 118 public async componentDidMount() { 119 this.popupManager.popupProps.subscribe(popupProps => this.setState({popupProps})); 120 const authSettings = await services.authService.settings(); 121 const {trackingID, anonymizeUsers} = authSettings.googleAnalytics || {trackingID: '', anonymizeUsers: true}; 122 const {loggedIn, username} = await services.users.get(); 123 if (trackingID) { 124 const ga = await import('react-ga'); 125 ga.initialize(trackingID); 126 const trackPageView = () => { 127 if (loggedIn && username) { 128 const userId = !anonymizeUsers ? username : hashCode(username).toString(); 129 ga.set({userId}); 130 } 131 ga.pageview(location.pathname + location.search); 132 }; 133 trackPageView(); 134 history.listen(trackPageView); 135 } 136 if (authSettings.uiCssURL) { 137 const link = document.createElement('link'); 138 link.href = authSettings.uiCssURL; 139 link.rel = 'stylesheet'; 140 link.type = 'text/css'; 141 document.head.appendChild(link); 142 } 143 } 144 145 public render() { 146 if (this.state.error != null) { 147 const stack = this.state.error.stack; 148 const url = 'https://github.com/argoproj/argo-cd/issues/new?labels=bug&template=bug_report.md'; 149 150 return ( 151 <React.Fragment> 152 <p>Something went wrong!</p> 153 <p> 154 Consider submitting an issue <a href={url}>here</a>. 155 </p> 156 <br /> 157 <p>Stacktrace:</p> 158 <pre>{stack}</pre> 159 </React.Fragment> 160 ); 161 } 162 163 return ( 164 <React.Fragment> 165 <Helmet> 166 <link rel='icon' type='image/png' href={`${base}assets/favicon/favicon-32x32.png`} sizes='32x32' /> 167 <link rel='icon' type='image/png' href={`${base}assets/favicon/favicon-16x16.png`} sizes='16x16' /> 168 </Helmet> 169 <PageContext.Provider value={{title: 'Argo CD'}}> 170 <Provider value={{history, popup: this.popupManager, notifications: this.notificationsManager, navigation: this.navigationManager, baseHref: base}}> 171 {this.state.popupProps && <Popup {...this.state.popupProps} />} 172 <Router history={history}> 173 <Switch> 174 <Redirect exact={true} path='/' to='/applications' /> 175 {Object.keys(routes).map(path => { 176 const route = routes[path]; 177 return ( 178 <Route 179 key={path} 180 path={path} 181 render={routeProps => 182 route.noLayout ? ( 183 <div> 184 <route.component {...routeProps} /> 185 </div> 186 ) : ( 187 <Layout 188 navItems={navItems} 189 version={() => ( 190 <DataLoader load={() => versionLoader}> 191 {version => ( 192 <React.Fragment> 193 <Tooltip content={version.Version}> 194 <a style={{color: 'white'}} onClick={() => this.setState({showVersionPanel: true})}> 195 {version.Version} 196 </a> 197 </Tooltip> 198 </React.Fragment> 199 )} 200 </DataLoader> 201 )}> 202 <route.component {...routeProps} /> 203 </Layout> 204 ) 205 } 206 /> 207 ); 208 })} 209 <Redirect path='*' to='/' /> 210 </Switch> 211 </Router> 212 <DataLoader load={() => services.authService.settings()}> 213 {s => 214 (s.help && s.help.chatUrl && ( 215 <div style={{position: 'fixed', right: 10, bottom: 10}}> 216 <a href={s.help.chatUrl} className='argo-button argo-button--special'> 217 <i className='fas fa-comment-alt' /> {s.help.chatText} 218 </a> 219 </div> 220 )) || 221 null 222 } 223 </DataLoader> 224 </Provider> 225 </PageContext.Provider> 226 <Notifications notifications={this.notificationsManager.notifications} /> 227 <VersionPanel version={versionLoader} isShown={this.state.showVersionPanel} onClose={() => this.setState({showVersionPanel: false})} /> 228 </React.Fragment> 229 ); 230 } 231 232 public getChildContext() { 233 return {history, apis: {popup: this.popupManager, notifications: this.notificationsManager, navigation: this.navigationManager}}; 234 } 235 }