github.com/freiheit-com/kuberpult@v1.24.2-0.20240328135542-315d5630abe6/services/frontend-service/src/ui/App/index.tsx (about) 1 /*This file is part of kuberpult. 2 3 Kuberpult is free software: you can redistribute it and/or modify 4 it under the terms of the Expat(MIT) License as published by 5 the Free Software Foundation. 6 7 Kuberpult is distributed in the hope that it will be useful, 8 but WITHOUT ANY WARRANTY; without even the implied warranty of 9 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 10 MIT License for more details. 11 12 You should have received a copy of the MIT License 13 along with kuberpult. If not, see <https://directory.fsf.org/wiki/License:Expat>. 14 15 Copyright 2023 freiheit.com*/ 16 import { NavigationBar } from '../components/NavigationBar/NavigationBar'; 17 import { ReleaseDialog } from '../components/ReleaseDialog/ReleaseDialog'; 18 import { PageRoutes } from './PageRoutes'; 19 import '../../assets/app-v2.scss'; 20 import * as React from 'react'; 21 import { 22 EnableRolloutStatus, 23 FlushRolloutStatus, 24 PanicOverview, 25 showSnackbarWarn, 26 UpdateFrontendConfig, 27 UpdateOverview, 28 UpdateRolloutStatus, 29 useKuberpultVersion, 30 useReleaseDialogParams, 31 } from '../utils/store'; 32 import { useApi } from '../utils/GrpcApi'; 33 import { AzureAuthProvider, useAzureAuthSub } from '../utils/AzureAuthProvider'; 34 import { Snackbar } from '../components/snackbar/snackbar'; 35 import { mergeMap, retryWhen } from 'rxjs/operators'; 36 import { Observable, throwError, timer } from 'rxjs'; 37 import { GetFrontendConfigResponse } from '../../api/api'; 38 import { EnvironmentConfigDialog } from '../components/EnvironmentConfigDialog/EnvironmentConfigDialog'; 39 import { getOpenEnvironmentConfigDialog } from '../utils/Links'; 40 import { useSearchParams } from 'react-router-dom'; 41 import { TooltipProvider } from '../components/tooltip/tooltip'; 42 43 // retry strategy: retries the observable subscription with randomized exponential backoff 44 // source: https://www.learnrxjs.io/learn-rxjs/operators/error_handling/retrywhen#examples 45 function retryStrategy(maxRetryAttempts: number) { 46 return (attempts: Observable<any>): Observable<any> => 47 attempts.pipe( 48 mergeMap((error, retryAttempt) => { 49 if (retryAttempt >= maxRetryAttempts) { 50 return throwError(error); 51 } 52 // backoff time in seconds = 2^attempt number (exponential) + random 53 const backoffTime = 1000 * (2 ** retryAttempt + Math.random()); 54 return timer(backoffTime); 55 }) 56 ); 57 } 58 59 export const App: React.FC = () => { 60 const api = useApi; 61 const { authHeader, authReady } = useAzureAuthSub((auth) => auth); 62 63 const kuberpultVersion = useKuberpultVersion(); 64 React.useEffect(() => { 65 if (kuberpultVersion !== '') { 66 document.title = 'Kuberpult ' + kuberpultVersion; 67 } 68 }, [kuberpultVersion, api]); 69 70 React.useEffect(() => { 71 api.configService() 72 .GetConfig({}) // the config service does not require authorisation 73 .then( 74 (result: GetFrontendConfigResponse) => { 75 UpdateFrontendConfig.set({ configs: result, configReady: true }); 76 }, 77 (error) => { 78 // eslint-disable-next-line no-console 79 console.log('Error: Cannot connect to server!\n' + error); 80 } 81 ); 82 }, [api]); 83 84 React.useEffect(() => { 85 if (authReady) { 86 const subscription = api 87 .overviewService() 88 .StreamOverview({}, authHeader) 89 .pipe(retryWhen(retryStrategy(8))) 90 .subscribe( 91 (result) => { 92 UpdateOverview.set(result); 93 UpdateOverview.set({ loaded: true }); 94 PanicOverview.set({ error: '' }); 95 }, 96 (error) => { 97 PanicOverview.set({ error: JSON.stringify({ msg: 'error in streamoverview', error }) }); 98 showSnackbarWarn('Connection Error: Refresh the page'); 99 } 100 ); 101 return (): void => subscription.unsubscribe(); 102 } 103 }, [api, authHeader, authReady]); 104 105 React.useEffect(() => { 106 if (authReady) { 107 const subscription = api 108 .rolloutService() 109 .StreamStatus({}, authHeader) 110 .pipe(retryWhen(retryStrategy(8))) 111 .subscribe( 112 (result) => { 113 UpdateRolloutStatus(result); 114 }, 115 (error) => { 116 if (error.code === 12) { 117 // Error code 12 means "not implemented". That is what we get when the rollout service is not enabled. 118 FlushRolloutStatus(); 119 return; 120 } 121 PanicOverview.set({ error: JSON.stringify({ msg: 'error in rolloutstatus', error }) }); 122 EnableRolloutStatus(); 123 } 124 ); 125 return (): void => subscription.unsubscribe(); 126 } 127 }, [api, authHeader, authReady]); 128 129 PanicOverview.listen( 130 (err) => err.error, 131 (err) => { 132 // eslint-disable-next-line no-console 133 console.log('Error: Cannot connect to server!\n' + err); 134 } 135 ); 136 137 const [params] = useSearchParams(); 138 const { app, version } = useReleaseDialogParams(); 139 const currentOpenConfig = getOpenEnvironmentConfigDialog(params); 140 141 return ( 142 <AzureAuthProvider> 143 <div className={'app-container--v2'}> 144 {app && version ? <ReleaseDialog app={app} version={version} /> : null} 145 {currentOpenConfig.length > 0 ? <EnvironmentConfigDialog environmentName={currentOpenConfig} /> : null} 146 <NavigationBar /> 147 <div className="mdc-drawer-app-content"> 148 <PageRoutes /> 149 <Snackbar /> 150 </div> 151 <TooltipProvider /> 152 </div> 153 </AzureAuthProvider> 154 ); 155 };