feat(ws): Implement Start restart and stop workspace actions (#162)
Signed-off-by: Elay Aharoni (EXT-Nokia) <elay.aharoni.ext@nokia.com> Co-authored-by: Elay Aharoni (EXT-Nokia) <elay.aharoni.ext@nokia.com>
This commit is contained in:
parent
f9da864e1d
commit
4cbc26eaf4
|
|
@ -11,8 +11,8 @@ import {
|
||||||
Pagination,
|
Pagination,
|
||||||
Button,
|
Button,
|
||||||
Content,
|
Content,
|
||||||
Tooltip,
|
|
||||||
Brand,
|
Brand,
|
||||||
|
Tooltip,
|
||||||
} from '@patternfly/react-core';
|
} from '@patternfly/react-core';
|
||||||
import {
|
import {
|
||||||
Table,
|
Table,
|
||||||
|
|
@ -34,9 +34,21 @@ import DeleteModal from '~/shared/components/DeleteModal';
|
||||||
import { buildKindLogoDictionary } from '~/app/actions/WorkspaceKindsActions';
|
import { buildKindLogoDictionary } from '~/app/actions/WorkspaceKindsActions';
|
||||||
import useWorkspaceKinds from '~/app/hooks/useWorkspaceKinds';
|
import useWorkspaceKinds from '~/app/hooks/useWorkspaceKinds';
|
||||||
import { WorkspaceConnectAction } from '~/app/pages/Workspaces/WorkspaceConnectAction';
|
import { WorkspaceConnectAction } from '~/app/pages/Workspaces/WorkspaceConnectAction';
|
||||||
|
import { WorkspaceStartActionModal } from '~/app/pages/Workspaces/workspaceActions/WorkspaceStartActionModal';
|
||||||
|
import { WorkspaceRestartActionModal } from '~/app/pages/Workspaces/workspaceActions/WorkspaceRestartActionModal';
|
||||||
|
import { WorkspaceStopActionModal } from '~/app/pages/Workspaces/workspaceActions/WorkspaceStopActionModal';
|
||||||
import Filter, { FilteredColumn } from 'shared/components/Filter';
|
import Filter, { FilteredColumn } from 'shared/components/Filter';
|
||||||
import { formatRam } from 'shared/utilities/WorkspaceResources';
|
import { formatRam } from 'shared/utilities/WorkspaceResources';
|
||||||
|
|
||||||
|
export enum ActionType {
|
||||||
|
ViewDetails,
|
||||||
|
Edit,
|
||||||
|
Delete,
|
||||||
|
Start,
|
||||||
|
Restart,
|
||||||
|
Stop,
|
||||||
|
}
|
||||||
|
|
||||||
export const Workspaces: React.FunctionComponent = () => {
|
export const Workspaces: React.FunctionComponent = () => {
|
||||||
/* Mocked workspaces, to be removed after fetching info from backend */
|
/* Mocked workspaces, to be removed after fetching info from backend */
|
||||||
const mockWorkspaces: Workspace[] = [
|
const mockWorkspaces: Workspace[] = [
|
||||||
|
|
@ -85,7 +97,7 @@ export const Workspaces: React.FunctionComponent = () => {
|
||||||
lastUpdate: 0,
|
lastUpdate: 0,
|
||||||
},
|
},
|
||||||
pauseTime: 0,
|
pauseTime: 0,
|
||||||
pendingRestart: false,
|
pendingRestart: true,
|
||||||
podTemplateOptions: {
|
podTemplateOptions: {
|
||||||
imageConfig: {
|
imageConfig: {
|
||||||
desired: '',
|
desired: '',
|
||||||
|
|
@ -170,7 +182,7 @@ export const Workspaces: React.FunctionComponent = () => {
|
||||||
lastActivity: 'Last Activity',
|
lastActivity: 'Last Activity',
|
||||||
};
|
};
|
||||||
|
|
||||||
const filterableColumns: WorkspacesColumnNames = {
|
const filterableColumns = {
|
||||||
name: 'Name',
|
name: 'Name',
|
||||||
kind: 'Kind',
|
kind: 'Kind',
|
||||||
image: 'Image',
|
image: 'Image',
|
||||||
|
|
@ -182,10 +194,12 @@ export const Workspaces: React.FunctionComponent = () => {
|
||||||
|
|
||||||
// change when fetch workspaces is implemented
|
// change when fetch workspaces is implemented
|
||||||
const initialWorkspaces = mockWorkspaces;
|
const initialWorkspaces = mockWorkspaces;
|
||||||
const [workspaces, setWorkspaces] = useState(initialWorkspaces);
|
const [workspaces, setWorkspaces] = useState<Workspace[]>(initialWorkspaces);
|
||||||
const [expandedWorkspacesNames, setExpandedWorkspacesNames] = React.useState<string[]>([]);
|
const [expandedWorkspacesNames, setExpandedWorkspacesNames] = React.useState<string[]>([]);
|
||||||
const [selectedWorkspace, setSelectedWorkspace] = React.useState<Workspace | null>(null);
|
const [selectedWorkspace, setSelectedWorkspace] = React.useState<Workspace | null>(null);
|
||||||
const [workspaceToDelete, setWorkspaceToDelete] = React.useState<Workspace | null>(null);
|
const [workspaceToDelete, setWorkspaceToDelete] = React.useState<Workspace | null>(null);
|
||||||
|
const [isActionAlertModalOpen, setIsActionAlertModalOpen] = React.useState(false);
|
||||||
|
const [activeActionType, setActiveActionType] = React.useState<ActionType | null>(null);
|
||||||
|
|
||||||
const selectWorkspace = React.useCallback(
|
const selectWorkspace = React.useCallback(
|
||||||
(newSelectedWorkspace) => {
|
(newSelectedWorkspace) => {
|
||||||
|
|
@ -197,6 +211,7 @@ export const Workspaces: React.FunctionComponent = () => {
|
||||||
},
|
},
|
||||||
[selectedWorkspace],
|
[selectedWorkspace],
|
||||||
);
|
);
|
||||||
|
|
||||||
const setWorkspaceExpanded = (workspace: Workspace, isExpanding = true) =>
|
const setWorkspaceExpanded = (workspace: Workspace, isExpanding = true) =>
|
||||||
setExpandedWorkspacesNames((prevExpanded) => {
|
setExpandedWorkspacesNames((prevExpanded) => {
|
||||||
const newExpandedWorkspacesNames = prevExpanded.filter((wsName) => wsName !== workspace.name);
|
const newExpandedWorkspacesNames = prevExpanded.filter((wsName) => wsName !== workspace.name);
|
||||||
|
|
@ -300,20 +315,31 @@ export const Workspaces: React.FunctionComponent = () => {
|
||||||
|
|
||||||
// Actions
|
// Actions
|
||||||
|
|
||||||
|
const viewDetailsClick = React.useCallback((workspace: Workspace) => {
|
||||||
|
setSelectedWorkspace(workspace);
|
||||||
|
setActiveActionType(ActionType.ViewDetails);
|
||||||
|
}, []);
|
||||||
|
|
||||||
const editAction = React.useCallback((workspace: Workspace) => {
|
const editAction = React.useCallback((workspace: Workspace) => {
|
||||||
console.log(`Clicked on edit, on row ${workspace.name}`);
|
setSelectedWorkspace(workspace);
|
||||||
|
setActiveActionType(ActionType.Edit);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const deleteAction = React.useCallback((workspace: Workspace) => {
|
const deleteAction = React.useCallback((workspace: Workspace) => {
|
||||||
console.log(`Clicked on delete, on row ${workspace.name}`);
|
setSelectedWorkspace(workspace);
|
||||||
|
setActiveActionType(ActionType.Delete);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const startRestartAction = React.useCallback((workspace: Workspace) => {
|
const startRestartAction = React.useCallback((workspace: Workspace, action: ActionType) => {
|
||||||
console.log(`Clicked on start/restart, on row ${workspace.name}`);
|
setSelectedWorkspace(workspace);
|
||||||
|
setActiveActionType(action);
|
||||||
|
setIsActionAlertModalOpen(true);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const stopAction = React.useCallback((workspace: Workspace) => {
|
const stopAction = React.useCallback((workspace: Workspace) => {
|
||||||
console.log(`Clicked on stop, on row ${workspace.name}`);
|
setSelectedWorkspace(workspace);
|
||||||
|
setActiveActionType(ActionType.Stop);
|
||||||
|
setIsActionAlertModalOpen(true);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const handleDeleteClick = React.useCallback((workspace: Workspace) => {
|
const handleDeleteClick = React.useCallback((workspace: Workspace) => {
|
||||||
|
|
@ -322,35 +348,79 @@ export const Workspaces: React.FunctionComponent = () => {
|
||||||
setWorkspaceToDelete(workspace); // Open the modal and set workspace to delete
|
setWorkspaceToDelete(workspace); // Open the modal and set workspace to delete
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const defaultActions = React.useCallback(
|
const onCloseActionAlertDialog = () => {
|
||||||
(workspace: Workspace): IActions =>
|
setIsActionAlertModalOpen(false);
|
||||||
[
|
setSelectedWorkspace(null);
|
||||||
{
|
setActiveActionType(null);
|
||||||
title: 'View Details',
|
};
|
||||||
onClick: () => selectWorkspace(workspace),
|
|
||||||
},
|
const workspaceDefaultActions = (workspace: Workspace): IActions => {
|
||||||
{
|
const workspaceState = workspace.status.state;
|
||||||
title: 'Edit',
|
const workspaceActions = [
|
||||||
onClick: () => editAction(workspace),
|
{
|
||||||
},
|
title: 'View Details',
|
||||||
{
|
onClick: () => viewDetailsClick(workspace),
|
||||||
title: 'Delete',
|
},
|
||||||
onClick: () => handleDeleteClick(workspace),
|
{
|
||||||
},
|
title: 'Edit',
|
||||||
{
|
onClick: () => editAction(workspace),
|
||||||
isSeparator: true,
|
},
|
||||||
},
|
{
|
||||||
{
|
title: 'Delete',
|
||||||
title: 'Start/restart',
|
onClick: () => handleDeleteClick(workspace),
|
||||||
onClick: () => startRestartAction(workspace),
|
},
|
||||||
},
|
{
|
||||||
{
|
isSeparator: true,
|
||||||
title: 'Stop',
|
},
|
||||||
onClick: () => stopAction(workspace),
|
workspaceState !== WorkspaceState.Running
|
||||||
},
|
? {
|
||||||
] as IActions,
|
title: 'Start',
|
||||||
[selectWorkspace, editAction, handleDeleteClick, startRestartAction, stopAction],
|
onClick: () => startRestartAction(workspace, ActionType.Start),
|
||||||
);
|
}
|
||||||
|
: {
|
||||||
|
title: 'Restart',
|
||||||
|
onClick: () => startRestartAction(workspace, ActionType.Restart),
|
||||||
|
},
|
||||||
|
] as IActions;
|
||||||
|
|
||||||
|
if (workspaceState === WorkspaceState.Running) {
|
||||||
|
workspaceActions.push({
|
||||||
|
title: 'Stop',
|
||||||
|
onClick: () => stopAction(workspace),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return workspaceActions;
|
||||||
|
};
|
||||||
|
|
||||||
|
const chooseAlertModal = () => {
|
||||||
|
switch (activeActionType) {
|
||||||
|
case ActionType.Start:
|
||||||
|
return (
|
||||||
|
<WorkspaceStartActionModal
|
||||||
|
onClose={onCloseActionAlertDialog}
|
||||||
|
isOpen={isActionAlertModalOpen}
|
||||||
|
workspace={selectedWorkspace}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
case ActionType.Restart:
|
||||||
|
return (
|
||||||
|
<WorkspaceRestartActionModal
|
||||||
|
onClose={onCloseActionAlertDialog}
|
||||||
|
isOpen={isActionAlertModalOpen}
|
||||||
|
workspace={selectedWorkspace}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
case ActionType.Stop:
|
||||||
|
return (
|
||||||
|
<WorkspaceStopActionModal
|
||||||
|
onClose={onCloseActionAlertDialog}
|
||||||
|
isOpen={isActionAlertModalOpen}
|
||||||
|
workspace={selectedWorkspace}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
};
|
||||||
|
|
||||||
// States
|
// States
|
||||||
|
|
||||||
|
|
@ -401,7 +471,10 @@ export const Workspaces: React.FunctionComponent = () => {
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Drawer isInline isExpanded={selectedWorkspace != null}>
|
<Drawer
|
||||||
|
isInline
|
||||||
|
isExpanded={selectedWorkspace != null && activeActionType === ActionType.ViewDetails}
|
||||||
|
>
|
||||||
<DrawerContent panelContent={workspaceDetailsContent}>
|
<DrawerContent panelContent={workspaceDetailsContent}>
|
||||||
<DrawerContentBody>
|
<DrawerContentBody>
|
||||||
<PageSection isFilled>
|
<PageSection isFilled>
|
||||||
|
|
@ -483,7 +556,7 @@ export const Workspaces: React.FunctionComponent = () => {
|
||||||
</Td>
|
</Td>
|
||||||
<Td isActionCell data-testid="action-column">
|
<Td isActionCell data-testid="action-column">
|
||||||
<ActionsColumn
|
<ActionsColumn
|
||||||
items={defaultActions(workspace).map((action) => ({
|
items={workspaceDefaultActions(workspace).map((action) => ({
|
||||||
...action,
|
...action,
|
||||||
'data-testid': `action-${typeof action.title === 'string' ? action.title.toLowerCase() : ''}`,
|
'data-testid': `action-${typeof action.title === 'string' ? action.title.toLowerCase() : ''}`,
|
||||||
}))}
|
}))}
|
||||||
|
|
@ -496,6 +569,7 @@ export const Workspaces: React.FunctionComponent = () => {
|
||||||
</Tbody>
|
</Tbody>
|
||||||
))}
|
))}
|
||||||
</Table>
|
</Table>
|
||||||
|
{isActionAlertModalOpen && chooseAlertModal()}
|
||||||
<DeleteModal
|
<DeleteModal
|
||||||
isOpen={workspaceToDelete != null}
|
isOpen={workspaceToDelete != null}
|
||||||
resourceName={workspaceToDelete?.name || ''}
|
resourceName={workspaceToDelete?.name || ''}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,188 @@
|
||||||
|
import { ExpandableSection, Icon, Tab, Tabs, TabTitleText, Content } from '@patternfly/react-core';
|
||||||
|
import {
|
||||||
|
ExclamationCircleIcon,
|
||||||
|
ExclamationTriangleIcon,
|
||||||
|
InfoCircleIcon,
|
||||||
|
} from '@patternfly/react-icons';
|
||||||
|
import * as React from 'react';
|
||||||
|
|
||||||
|
// remove when changing to fetch data from BE
|
||||||
|
const mockedWorkspaceKind = {
|
||||||
|
name: 'jupyter-lab',
|
||||||
|
displayName: 'JupyterLab Notebook',
|
||||||
|
description: 'A Workspace which runs JupyterLab in a Pod',
|
||||||
|
deprecated: false,
|
||||||
|
deprecationMessage: '',
|
||||||
|
hidden: false,
|
||||||
|
icon: {
|
||||||
|
url: 'https://jupyter.org/assets/favicons/apple-touch-icon-152x152.png',
|
||||||
|
},
|
||||||
|
logo: {
|
||||||
|
url: 'https://upload.wikimedia.org/wikipedia/commons/3/38/Jupyter_logo.svg',
|
||||||
|
},
|
||||||
|
podTemplate: {
|
||||||
|
podMetadata: {
|
||||||
|
labels: { myWorkspaceKindLabel: 'my-value' },
|
||||||
|
annotations: { myWorkspaceKindAnnotation: 'my-value' },
|
||||||
|
},
|
||||||
|
volumeMounts: { home: '/home/jovyan' },
|
||||||
|
options: {
|
||||||
|
imageConfig: {
|
||||||
|
default: 'jupyterlab_scipy_190',
|
||||||
|
values: [
|
||||||
|
{
|
||||||
|
id: 'jupyterlab_scipy_180',
|
||||||
|
displayName: 'jupyter-scipy:v1.8.0',
|
||||||
|
labels: { pythonVersion: '3.11' },
|
||||||
|
hidden: true,
|
||||||
|
redirect: {
|
||||||
|
to: 'jupyterlab_scipy_190',
|
||||||
|
message: {
|
||||||
|
text: 'This update will change...',
|
||||||
|
level: 'Info',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'jupyterlab_scipy_190',
|
||||||
|
displayName: 'jupyter-scipy:v1.9.0',
|
||||||
|
labels: { pythonVersion: '3.11' },
|
||||||
|
hidden: true,
|
||||||
|
redirect: {
|
||||||
|
to: 'jupyterlab_scipy_200',
|
||||||
|
message: {
|
||||||
|
text: 'This update will change...',
|
||||||
|
level: 'Warning',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
podConfig: {
|
||||||
|
default: 'tiny_cpu',
|
||||||
|
values: [
|
||||||
|
{
|
||||||
|
id: 'tiny_cpu',
|
||||||
|
displayName: 'Tiny CPU',
|
||||||
|
description: 'Pod with 0.1 CPU, 128 Mb RAM',
|
||||||
|
labels: { cpu: '100m', memory: '128Mi' },
|
||||||
|
redirect: {
|
||||||
|
to: 'small_cpu',
|
||||||
|
message: {
|
||||||
|
text: 'This update will change...',
|
||||||
|
level: 'Danger',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const getLevelIcon = (level: string) => {
|
||||||
|
switch (level) {
|
||||||
|
case 'Info':
|
||||||
|
return (
|
||||||
|
<Icon status="info">
|
||||||
|
<InfoCircleIcon />
|
||||||
|
</Icon>
|
||||||
|
);
|
||||||
|
case 'Warning':
|
||||||
|
return (
|
||||||
|
<Icon status="warning">
|
||||||
|
<ExclamationTriangleIcon />
|
||||||
|
</Icon>
|
||||||
|
);
|
||||||
|
case 'Danger':
|
||||||
|
return (
|
||||||
|
<Icon status="danger">
|
||||||
|
<ExclamationCircleIcon />
|
||||||
|
</Icon>
|
||||||
|
);
|
||||||
|
default:
|
||||||
|
return (
|
||||||
|
<Icon status="info">
|
||||||
|
<InfoCircleIcon />
|
||||||
|
</Icon>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const WorkspaceRedirectInformationView: React.FC = () => {
|
||||||
|
const [activeKey, setActiveKey] = React.useState<string | number>(0);
|
||||||
|
// change this to get from BE, and use the workspaceKinds API
|
||||||
|
const workspaceKind = mockedWorkspaceKind;
|
||||||
|
|
||||||
|
const { imageConfig } = workspaceKind.podTemplate.options;
|
||||||
|
const { podConfig } = workspaceKind.podTemplate.options;
|
||||||
|
|
||||||
|
const imageConfigRedirects = imageConfig.values.map((value) => ({
|
||||||
|
src: value.id,
|
||||||
|
dest: value.redirect.to,
|
||||||
|
message: value.redirect.message.text,
|
||||||
|
level: value.redirect.message.level,
|
||||||
|
}));
|
||||||
|
const podConfigRedirects = podConfig.values.map((value) => ({
|
||||||
|
src: value.id,
|
||||||
|
dest: value.redirect.to,
|
||||||
|
message: value.redirect.message.text,
|
||||||
|
level: value.redirect.message.level,
|
||||||
|
}));
|
||||||
|
|
||||||
|
const getMaxLevel = (
|
||||||
|
redirects: { dest: string; level: string; message: string; src: string }[],
|
||||||
|
) => {
|
||||||
|
let maxLevel = redirects[0].level;
|
||||||
|
redirects.forEach((redirect) => {
|
||||||
|
if (
|
||||||
|
(maxLevel === 'Info' && (redirect.level === 'Warning' || redirect.level === 'Danger')) ||
|
||||||
|
(maxLevel === 'Warning' && redirect.level === 'Danger')
|
||||||
|
) {
|
||||||
|
maxLevel = redirect.level;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return maxLevel;
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Tabs activeKey={activeKey} onSelect={(event, eventKey) => setActiveKey(eventKey)}>
|
||||||
|
{imageConfigRedirects.length > 0 && (
|
||||||
|
<Tab
|
||||||
|
eventKey={0}
|
||||||
|
title={
|
||||||
|
<TabTitleText>
|
||||||
|
Image Config {getLevelIcon(getMaxLevel(imageConfigRedirects))}
|
||||||
|
</TabTitleText>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{imageConfigRedirects.map((redirect, index) => (
|
||||||
|
<Content style={{ display: 'flex', alignItems: 'baseline' }} key={index}>
|
||||||
|
{getLevelIcon(redirect.level)}
|
||||||
|
<ExpandableSection toggleText={` ${redirect.src} -> ${redirect.dest}`}>
|
||||||
|
<Content>{redirect.message}</Content>
|
||||||
|
</ExpandableSection>
|
||||||
|
</Content>
|
||||||
|
))}
|
||||||
|
</Tab>
|
||||||
|
)}
|
||||||
|
{podConfigRedirects.length > 0 && (
|
||||||
|
<Tab
|
||||||
|
eventKey={1}
|
||||||
|
title={
|
||||||
|
<TabTitleText>Pod Config {getLevelIcon(getMaxLevel(podConfigRedirects))}</TabTitleText>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{podConfigRedirects.map((redirect, index) => (
|
||||||
|
<Content style={{ display: 'flex', alignItems: 'baseline' }} key={index}>
|
||||||
|
{getLevelIcon(redirect.level)}
|
||||||
|
<ExpandableSection toggleText={` ${redirect.src} -> ${redirect.dest}`}>
|
||||||
|
<Content>{redirect.message}</Content>
|
||||||
|
</ExpandableSection>
|
||||||
|
</Content>
|
||||||
|
))}
|
||||||
|
</Tab>
|
||||||
|
)}
|
||||||
|
</Tabs>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,71 @@
|
||||||
|
import * as React from 'react';
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
Content,
|
||||||
|
Modal,
|
||||||
|
ModalBody,
|
||||||
|
ModalFooter,
|
||||||
|
ModalHeader,
|
||||||
|
TabTitleText,
|
||||||
|
} from '@patternfly/react-core';
|
||||||
|
import { Workspace } from '~/shared/types';
|
||||||
|
import { WorkspaceRedirectInformationView } from '~/app/pages/Workspaces/workspaceActions/WorkspaceRedirectInformationView';
|
||||||
|
|
||||||
|
interface RestartActionAlertProps {
|
||||||
|
onClose: () => void;
|
||||||
|
isOpen: boolean;
|
||||||
|
workspace: Workspace | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const WorkspaceRestartActionModal: React.FC<RestartActionAlertProps> = ({
|
||||||
|
onClose,
|
||||||
|
isOpen,
|
||||||
|
workspace,
|
||||||
|
}) => {
|
||||||
|
const workspacePendingUpdate = workspace?.status.pendingRestart;
|
||||||
|
const handleClick = (isUpdate = false) => {
|
||||||
|
if (isUpdate) {
|
||||||
|
console.log(`Update ${workspace?.name}`);
|
||||||
|
}
|
||||||
|
console.log(`Restart ${workspace?.name}`);
|
||||||
|
onClose();
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
variant="medium"
|
||||||
|
isOpen={isOpen}
|
||||||
|
aria-describedby="modal-title-icon-description"
|
||||||
|
aria-labelledby="title-icon-modal-title"
|
||||||
|
onClose={onClose}
|
||||||
|
>
|
||||||
|
<ModalHeader title="Restart Workspace" />
|
||||||
|
<ModalBody>
|
||||||
|
{workspacePendingUpdate ? (
|
||||||
|
<>
|
||||||
|
<TabTitleText>
|
||||||
|
There are pending redirect updates for that workspace. Are you sure you want to
|
||||||
|
proceed?
|
||||||
|
</TabTitleText>
|
||||||
|
<WorkspaceRedirectInformationView />
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<Content>Are you sure you want to restart the workspace?</Content>
|
||||||
|
)}
|
||||||
|
</ModalBody>
|
||||||
|
<ModalFooter>
|
||||||
|
{workspacePendingUpdate && (
|
||||||
|
<Button onClick={() => handleClick(true)}>Update and Restart</Button>
|
||||||
|
)}
|
||||||
|
<Button
|
||||||
|
onClick={() => handleClick(false)}
|
||||||
|
variant={workspacePendingUpdate ? 'secondary' : 'primary'}
|
||||||
|
>
|
||||||
|
Restart
|
||||||
|
</Button>
|
||||||
|
<Button variant="link" onClick={onClose}>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
</ModalFooter>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,57 @@
|
||||||
|
import * as React from 'react';
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
Modal,
|
||||||
|
ModalBody,
|
||||||
|
ModalFooter,
|
||||||
|
ModalHeader,
|
||||||
|
TabTitleText,
|
||||||
|
} from '@patternfly/react-core';
|
||||||
|
import { Workspace } from '~/shared/types';
|
||||||
|
import { WorkspaceRedirectInformationView } from '~/app/pages/Workspaces/workspaceActions/WorkspaceRedirectInformationView';
|
||||||
|
|
||||||
|
interface StartActionAlertProps {
|
||||||
|
onClose: () => void;
|
||||||
|
isOpen: boolean;
|
||||||
|
workspace: Workspace | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const WorkspaceStartActionModal: React.FC<StartActionAlertProps> = ({
|
||||||
|
onClose,
|
||||||
|
isOpen,
|
||||||
|
workspace,
|
||||||
|
}) => {
|
||||||
|
const handleClick = (isUpdate = false) => {
|
||||||
|
if (isUpdate) {
|
||||||
|
console.log(`Update ${workspace?.name}`);
|
||||||
|
}
|
||||||
|
console.log(`Start ${workspace?.name}`);
|
||||||
|
onClose();
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
variant="medium"
|
||||||
|
isOpen={isOpen}
|
||||||
|
aria-describedby="modal-title-icon-description"
|
||||||
|
aria-labelledby="title-icon-modal-title"
|
||||||
|
onClose={onClose}
|
||||||
|
>
|
||||||
|
<ModalHeader title="Start Workspace" />
|
||||||
|
<ModalBody>
|
||||||
|
<TabTitleText>
|
||||||
|
There are pending redirect updates for that workspace. Are you sure you want to proceed?
|
||||||
|
</TabTitleText>
|
||||||
|
<WorkspaceRedirectInformationView />
|
||||||
|
</ModalBody>
|
||||||
|
<ModalFooter>
|
||||||
|
<Button onClick={() => handleClick(true)}>Update and Start</Button>
|
||||||
|
<Button onClick={() => handleClick(false)} variant="secondary">
|
||||||
|
Start
|
||||||
|
</Button>
|
||||||
|
<Button variant="link" onClick={onClose}>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
</ModalFooter>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,71 @@
|
||||||
|
import * as React from 'react';
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
Modal,
|
||||||
|
ModalBody,
|
||||||
|
ModalFooter,
|
||||||
|
ModalHeader,
|
||||||
|
TabTitleText,
|
||||||
|
Content,
|
||||||
|
} from '@patternfly/react-core';
|
||||||
|
import { Workspace } from '~/shared/types';
|
||||||
|
import { WorkspaceRedirectInformationView } from '~/app/pages/Workspaces/workspaceActions/WorkspaceRedirectInformationView';
|
||||||
|
|
||||||
|
interface StopActionAlertProps {
|
||||||
|
onClose: () => void;
|
||||||
|
isOpen: boolean;
|
||||||
|
workspace: Workspace | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const WorkspaceStopActionModal: React.FC<StopActionAlertProps> = ({
|
||||||
|
onClose,
|
||||||
|
isOpen,
|
||||||
|
workspace,
|
||||||
|
}) => {
|
||||||
|
const workspacePendingUpdate = workspace?.status.pendingRestart;
|
||||||
|
const handleClick = (isUpdate = false) => {
|
||||||
|
if (isUpdate) {
|
||||||
|
console.log(`Update ${workspace?.name}`);
|
||||||
|
}
|
||||||
|
console.log(`Stop ${workspace?.name}`);
|
||||||
|
onClose();
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
variant="medium"
|
||||||
|
isOpen={isOpen}
|
||||||
|
aria-describedby="modal-title-icon-description"
|
||||||
|
aria-labelledby="title-icon-modal-title"
|
||||||
|
onClose={onClose}
|
||||||
|
>
|
||||||
|
<ModalHeader title="Stop Workspace" />
|
||||||
|
<ModalBody>
|
||||||
|
{workspacePendingUpdate ? (
|
||||||
|
<>
|
||||||
|
<TabTitleText>
|
||||||
|
There are pending redirect updates for that workspace. Are you sure you want to
|
||||||
|
proceed?
|
||||||
|
</TabTitleText>
|
||||||
|
<WorkspaceRedirectInformationView />
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<Content>Are you sure you want to stop the workspace?</Content>
|
||||||
|
)}
|
||||||
|
</ModalBody>
|
||||||
|
<ModalFooter>
|
||||||
|
{workspacePendingUpdate && (
|
||||||
|
<Button onClick={() => handleClick(true)}>Update and Stop</Button>
|
||||||
|
)}
|
||||||
|
<Button
|
||||||
|
onClick={() => handleClick(false)}
|
||||||
|
variant={workspacePendingUpdate ? 'secondary' : 'primary'}
|
||||||
|
>
|
||||||
|
{workspacePendingUpdate ? 'Stop and defer updates' : 'Stop'}
|
||||||
|
</Button>
|
||||||
|
<Button variant="link" onClick={onClose}>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
</ModalFooter>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
Loading…
Reference in New Issue