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  };