From fc1249fdd3452d24c590f5f7d31b83f7da78f2cc Mon Sep 17 00:00:00 2001 From: Aditya Purandare Date: Mon, 23 Dec 2024 15:02:35 +0530 Subject: [PATCH] feat: Add restart apps button on Applications List view --- .../applications-list/applications-list.tsx | 13 +++ .../applications-restart-panel.tsx | 99 +++++++++++++++++++ 2 files changed, 112 insertions(+) create mode 100644 ui/src/app/applications/components/applications-restart-panel/applications-restart-panel.tsx diff --git a/ui/src/app/applications/components/applications-list/applications-list.tsx b/ui/src/app/applications/components/applications-list/applications-list.tsx index 764973e8106c4..a7f93ca661dfe 100644 --- a/ui/src/app/applications/components/applications-list/applications-list.tsx +++ b/ui/src/app/applications/components/applications-list/applications-list.tsx @@ -20,6 +20,7 @@ import {ApplicationsSummary} from './applications-summary'; import {ApplicationsTable} from './applications-table'; import {ApplicationTiles} from './applications-tiles'; import {ApplicationsRefreshPanel} from '../applications-refresh-panel/applications-refresh-panel'; +import {ApplicationsRestartPanel} from '../applications-restart-panel/applications-restart-panel'; import {useSidebarTarget} from '../../../sidebar/sidebar'; import './applications-list.scss'; @@ -314,6 +315,7 @@ export const ApplicationsList = (props: RouteComponentProps<{}>) => { const appInput = tryJsonParse(query.get('new')); const syncAppsInput = tryJsonParse(query.get('syncApps')); const refreshAppsInput = tryJsonParse(query.get('refreshApps')); + const restartAppsInput = tryJsonParse(query.get('restartApps')); const [createApi, setCreateApi] = React.useState(null); const clusters = React.useMemo(() => services.clusters.list(), []); const [isAppCreatePending, setAppCreatePending] = React.useState(false); @@ -477,6 +479,11 @@ export const ApplicationsList = (props: RouteComponentProps<{}>) => { title: 'Refresh Apps', iconClassName: 'fa fa-redo', action: () => ctx.navigation.goto('.', {refreshApps: true}, {replace: true}) + }, + { + title: 'Restart Apps', + iconClassName: 'fa fa-recycle', + action: () => ctx.navigation.goto('.', {restartApps: true}, {replace: true}) } ] } @@ -586,6 +593,12 @@ export const ApplicationsList = (props: RouteComponentProps<{}>) => { hide={() => ctx.navigation.goto('.', {refreshApps: null}, {replace: true})} apps={filteredApps} /> + ctx.navigation.goto('.', {restartApps: null}, {replace: true})} + apps={filteredApps} + /> {q => ( diff --git a/ui/src/app/applications/components/applications-restart-panel/applications-restart-panel.tsx b/ui/src/app/applications/components/applications-restart-panel/applications-restart-panel.tsx new file mode 100644 index 0000000000000..c58eb04afe8de --- /dev/null +++ b/ui/src/app/applications/components/applications-restart-panel/applications-restart-panel.tsx @@ -0,0 +1,99 @@ +import * as React from 'react'; +import {SlidingPanel, Spinner, ErrorNotification, NotificationType} from 'argo-ui'; +import {Form, FormApi} from 'react-form'; +import {ProgressPopup} from '../../../shared/components'; +import {Consumer} from '../../../shared/context'; +import * as models from '../../../shared/models'; +import {services} from '../../../shared/services'; +import {ApplicationSelector} from '../../../shared/components'; + +interface Progress { + percentage: number; + title: string; +} + +export const ApplicationsRestartPanel = ({show, apps, hide}: {show: boolean; apps: models.Application[]; hide: () => void}) => { + const [form, setForm] = React.useState(null); + const [progress, setProgress] = React.useState(null); + const [isRestartPending, setRestartPending] = React.useState(false); + + const getSelectedApps = (params: any) => apps.filter((_, i) => params['app/' + i]); + + return ( + + {ctx => ( + + {' '} + + + }> +
{ + const selectedApps = getSelectedApps(params); + if (selectedApps.length === 0) { + ctx.notifications.show({content: `No apps selected`, type: NotificationType.Error}); + return; + } + + setProgress({percentage: 0, title: 'Restarting applications'}); + setRestartPending(true); + let i = 0; + const restartActions = []; + for (const app of selectedApps) { + const restartAction = async () => { + try { + const tree = await services.applications.resourceTree(app.metadata.name, app.metadata.namespace); + const relevantResources = (tree?.nodes || []).filter( + n => (n.kind === 'Deployment' || n.kind === 'StatefulSet' || n.kind === 'DaemonSet') && n.group === 'apps' + ); + for (const resource of relevantResources) { + await services.applications.runResourceAction(app.metadata.name, app.metadata.namespace, resource, 'restart'); + } + } catch (e) { + ctx.notifications.show({ + content: , + type: NotificationType.Error + }); + } + i++; + setProgress({ + percentage: i / selectedApps.length, + title: `Restarted ${i} of ${selectedApps.length} applications` + }); + }; + restartActions.push(restartAction()); + + if (restartActions.length >= 20) { + await Promise.all(restartActions); + restartActions.length = 0; + } + } + await Promise.all(restartActions); + setRestartPending(false); + hide(); + }} + getApi={setForm}> + {formApi => ( + +
+

Restart app(s)

+ {progress !== null && setProgress(null)} percentage={progress.percentage} title={progress.title} />} + +
+
+ )} + +
+ )} +
+ ); +};