feat(ws): call delete Workspace API from the frontend (#383)
Signed-off-by: Guilherme Caponetto <638737+caponetto@users.noreply.github.com>
This commit is contained in:
parent
c40a2e00e3
commit
3c8c8e0c2a
|
@ -16,6 +16,7 @@
|
|||
"@patternfly/react-styles": "^6.2.0",
|
||||
"@patternfly/react-table": "^6.2.0",
|
||||
"@types/js-yaml": "^4.0.9",
|
||||
"date-fns": "^4.1.0",
|
||||
"js-yaml": "^4.1.0",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"react": "^18",
|
||||
|
@ -8597,6 +8598,15 @@
|
|||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/date-fns": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-4.1.0.tgz",
|
||||
"integrity": "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/kossnocorp"
|
||||
}
|
||||
},
|
||||
"node_modules/dateformat": {
|
||||
"version": "4.6.3",
|
||||
"resolved": "https://registry.npmjs.org/dateformat/-/dateformat-4.6.3.tgz",
|
||||
|
|
|
@ -104,6 +104,7 @@
|
|||
"@patternfly/react-styles": "^6.2.0",
|
||||
"@patternfly/react-table": "^6.2.0",
|
||||
"@types/js-yaml": "^4.0.9",
|
||||
"date-fns": "^4.1.0",
|
||||
"js-yaml": "^4.1.0",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"react": "^18",
|
||||
|
|
|
@ -34,6 +34,7 @@ import {
|
|||
CodeIcon,
|
||||
} from '@patternfly/react-icons';
|
||||
import { useState } from 'react';
|
||||
import { formatDistanceToNow } from 'date-fns';
|
||||
import { Workspace, WorkspaceState } from '~/shared/api/backendApiTypes';
|
||||
import { WorkspaceDetails } from '~/app/pages/Workspaces/Details/WorkspaceDetails';
|
||||
import { ExpandedWorkspaceRow } from '~/app/pages/Workspaces/ExpandedWorkspaceRow';
|
||||
|
@ -115,7 +116,6 @@ export const Workspaces: React.FunctionComponent = () => {
|
|||
const [workspaces, setWorkspaces] = useState<Workspace[]>([]);
|
||||
const [expandedWorkspacesNames, setExpandedWorkspacesNames] = React.useState<string[]>([]);
|
||||
const [selectedWorkspace, setSelectedWorkspace] = 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 filterRef = React.useRef<FilterRef>(null);
|
||||
|
@ -285,10 +285,21 @@ export const Workspaces: React.FunctionComponent = () => {
|
|||
// setActiveActionType(ActionType.Edit);
|
||||
// }, []);
|
||||
|
||||
const deleteAction = React.useCallback((workspace: Workspace) => {
|
||||
setSelectedWorkspace(workspace);
|
||||
setActiveActionType(ActionType.Delete);
|
||||
}, []);
|
||||
const deleteAction = React.useCallback(async () => {
|
||||
if (!selectedWorkspace) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await api.deleteWorkspace({}, selectedNamespace, selectedWorkspace.name);
|
||||
// TODO: alert user about success
|
||||
console.info(`Workspace '${selectedWorkspace.name}' deleted successfully`);
|
||||
await initialWorkspacesRefresh();
|
||||
} catch (err) {
|
||||
// TODO: alert user about error
|
||||
console.error(`Error deleting workspace '${selectedWorkspace.name}': ${err}`);
|
||||
}
|
||||
}, [api, initialWorkspacesRefresh, selectedNamespace, selectedWorkspace]);
|
||||
|
||||
const startRestartAction = React.useCallback((workspace: Workspace, action: ActionType) => {
|
||||
setSelectedWorkspace(workspace);
|
||||
|
@ -305,7 +316,8 @@ export const Workspaces: React.FunctionComponent = () => {
|
|||
const handleDeleteClick = React.useCallback((workspace: Workspace) => {
|
||||
const buttonElement = document.activeElement as HTMLElement;
|
||||
buttonElement.blur(); // Remove focus from the currently focused button
|
||||
setWorkspaceToDelete(workspace); // Open the modal and set workspace to delete
|
||||
setSelectedWorkspace(workspace);
|
||||
setActiveActionType(ActionType.Delete);
|
||||
}, []);
|
||||
|
||||
const onCloseActionAlertDialog = () => {
|
||||
|
@ -610,7 +622,9 @@ export const Workspaces: React.FunctionComponent = () => {
|
|||
date={new Date(workspace.activity.lastActivity)}
|
||||
tooltip={{ variant: TimestampTooltipVariant.default }}
|
||||
>
|
||||
1 hour ago
|
||||
{formatDistanceToNow(new Date(workspace.activity.lastActivity), {
|
||||
addSuffix: true,
|
||||
})}
|
||||
</Timestamp>
|
||||
</Td>
|
||||
<Td>
|
||||
|
@ -641,14 +655,19 @@ export const Workspaces: React.FunctionComponent = () => {
|
|||
)}
|
||||
</Table>
|
||||
{isActionAlertModalOpen && chooseAlertModal()}
|
||||
<DeleteModal
|
||||
isOpen={workspaceToDelete != null}
|
||||
resourceName={workspaceToDelete?.name || ''}
|
||||
namespace={workspaceToDelete?.namespace || ''}
|
||||
title="Delete Workspace?"
|
||||
onClose={() => setWorkspaceToDelete(null)}
|
||||
onDelete={() => workspaceToDelete && deleteAction(workspaceToDelete)}
|
||||
/>
|
||||
{selectedWorkspace && (
|
||||
<DeleteModal
|
||||
isOpen={activeActionType === ActionType.Delete}
|
||||
resourceName={selectedWorkspace.name}
|
||||
namespace={selectedWorkspace.namespace}
|
||||
title="Delete Workspace?"
|
||||
onClose={() => {
|
||||
setSelectedWorkspace(null);
|
||||
setActiveActionType(null);
|
||||
}}
|
||||
onDelete={() => deleteAction()}
|
||||
/>
|
||||
)}
|
||||
<Pagination
|
||||
itemCount={333}
|
||||
widgetId="bottom-example"
|
||||
|
|
|
@ -7,9 +7,9 @@ import {
|
|||
ModalHeader,
|
||||
TabTitleText,
|
||||
} from '@patternfly/react-core';
|
||||
import { Workspace } from '~/shared/api/backendApiTypes';
|
||||
import { WorkspaceRedirectInformationView } from '~/app/pages/Workspaces/workspaceActions/WorkspaceRedirectInformationView';
|
||||
import { ActionButton } from '~/app/pages/Workspaces/workspaceActions/ActionButton';
|
||||
import { Workspace } from '~/shared/api/backendApiTypes';
|
||||
import { ActionButton } from '~/shared/components/ActionButton';
|
||||
|
||||
interface StartActionAlertProps {
|
||||
onClose: () => void;
|
||||
|
|
|
@ -1,16 +1,16 @@
|
|||
import * as React from 'react';
|
||||
import {
|
||||
Button,
|
||||
Content,
|
||||
Modal,
|
||||
ModalBody,
|
||||
ModalFooter,
|
||||
ModalHeader,
|
||||
TabTitleText,
|
||||
Content,
|
||||
} from '@patternfly/react-core';
|
||||
import { Workspace } from '~/shared/api/backendApiTypes';
|
||||
import { WorkspaceRedirectInformationView } from '~/app/pages/Workspaces/workspaceActions/WorkspaceRedirectInformationView';
|
||||
import { ActionButton } from '~/app/pages/Workspaces/workspaceActions/ActionButton';
|
||||
import { Workspace } from '~/shared/api/backendApiTypes';
|
||||
import { ActionButton } from '~/shared/components/ActionButton';
|
||||
|
||||
interface StopActionAlertProps {
|
||||
onClose: () => void;
|
||||
|
|
|
@ -68,9 +68,7 @@ export const patchWorkspace: PatchWorkspaceAPI = (hostPath) => (opts, namespace,
|
|||
);
|
||||
|
||||
export const deleteWorkspace: DeleteWorkspaceAPI = (hostPath) => (opts, namespace, workspace) =>
|
||||
handleRestFailures(
|
||||
restDELETE(hostPath, `/workspaces/${namespace}/${workspace}`, {}, {}, opts),
|
||||
).then((response) => extractNotebookResponse<void>(response));
|
||||
handleRestFailures(restDELETE(hostPath, `/workspaces/${namespace}/${workspace}`, {}, {}, opts));
|
||||
|
||||
export const pauseWorkspace: PauseWorkspaceAPI = (hostPath) => (opts, namespace, workspace) =>
|
||||
handleRestFailures(
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import React, { useState, useEffect } from 'react';
|
||||
import React, { useState, useEffect, useCallback } from 'react';
|
||||
import {
|
||||
Modal,
|
||||
ModalBody,
|
||||
|
@ -14,13 +14,14 @@ import {
|
|||
HelperTextItem,
|
||||
} from '@patternfly/react-core';
|
||||
import { default as ExclamationCircleIcon } from '@patternfly/react-icons/dist/esm/icons/exclamation-circle-icon';
|
||||
import { ActionButton } from '~/shared/components/ActionButton';
|
||||
|
||||
interface DeleteModalProps {
|
||||
isOpen: boolean;
|
||||
resourceName: string;
|
||||
namespace: string;
|
||||
onClose: () => void;
|
||||
onDelete: (resourceName: string) => void;
|
||||
onDelete: (resourceName: string) => Promise<void>;
|
||||
title: string;
|
||||
}
|
||||
|
||||
|
@ -33,6 +34,7 @@ const DeleteModal: React.FC<DeleteModalProps> = ({
|
|||
onDelete,
|
||||
}) => {
|
||||
const [inputValue, setInputValue] = useState('');
|
||||
const [isDeleting, setIsDeleting] = React.useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isOpen) {
|
||||
|
@ -40,14 +42,17 @@ const DeleteModal: React.FC<DeleteModalProps> = ({
|
|||
}
|
||||
}, [isOpen]);
|
||||
|
||||
const handleDelete = () => {
|
||||
const handleDelete = useCallback(async () => {
|
||||
if (inputValue === resourceName) {
|
||||
onDelete(resourceName);
|
||||
setIsDeleting(true);
|
||||
await onDelete(resourceName);
|
||||
setIsDeleting(false);
|
||||
|
||||
onClose();
|
||||
} else {
|
||||
alert('Resource name does not match.');
|
||||
}
|
||||
};
|
||||
}, [inputValue, onClose, onDelete, resourceName]);
|
||||
|
||||
const handleInputChange = (event: React.FormEvent<HTMLInputElement>, value: string) => {
|
||||
setInputValue(value);
|
||||
|
@ -94,17 +99,21 @@ const DeleteModal: React.FC<DeleteModalProps> = ({
|
|||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<div style={{ marginTop: '1rem' }}>
|
||||
<Button
|
||||
<ActionButton
|
||||
action="Delete"
|
||||
titleOnLoading="Deleting ..."
|
||||
onClick={handleDelete}
|
||||
variant="danger"
|
||||
isDisabled={inputValue !== resourceName}
|
||||
aria-disabled={inputValue !== resourceName}
|
||||
>
|
||||
Delete
|
||||
</Button>
|
||||
<Button onClick={onClose} variant="link" style={{ marginLeft: '1rem' }}>
|
||||
Cancel
|
||||
</Button>
|
||||
</ActionButton>
|
||||
{!isDeleting && (
|
||||
<Button onClick={onClose} variant="link" style={{ marginLeft: '1rem' }}>
|
||||
Cancel
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</ModalFooter>
|
||||
</Modal>
|
||||
|
|
|
@ -103,8 +103,8 @@ export const buildMockWorkspace = (workspace?: Partial<Workspace>): Workspace =>
|
|||
},
|
||||
},
|
||||
activity: {
|
||||
lastActivity: 1746551485113,
|
||||
lastUpdate: 1746551485113,
|
||||
lastActivity: new Date().getTime(),
|
||||
lastUpdate: new Date().getTime(),
|
||||
},
|
||||
pendingRestart: false,
|
||||
services: [
|
||||
|
|
|
@ -61,6 +61,10 @@ export const mockWorkspace2: Workspace = buildMockWorkspace({
|
|||
state: WorkspaceState.WorkspaceStatePaused,
|
||||
paused: false,
|
||||
deferUpdates: false,
|
||||
activity: {
|
||||
lastActivity: 1735603200000,
|
||||
lastUpdate: 1735603200000,
|
||||
},
|
||||
podTemplate: {
|
||||
podMetadata: {
|
||||
labels: { labelKey1: 'labelValue1', labelKey2: 'labelValue2' },
|
||||
|
@ -121,6 +125,10 @@ export const mockWorkspace3: Workspace = buildMockWorkspace({
|
|||
workspaceKind: mockWorkspaceKindInfo1,
|
||||
state: WorkspaceState.WorkspaceStateRunning,
|
||||
pendingRestart: true,
|
||||
activity: {
|
||||
lastActivity: 1744857600000,
|
||||
lastUpdate: 1744857600000,
|
||||
},
|
||||
});
|
||||
|
||||
export const mockWorkspace4 = buildMockWorkspace({
|
||||
|
|
Loading…
Reference in New Issue