Skip to content

Commit

Permalink
feat(console): support vm snapshot (tkestack#2289)
Browse files Browse the repository at this point in the history
* fix(console): 修复判断是否可见缺少了children的问题

* feat(console): 创建虚拟机快照

* feat(console): 支持虚拟机快照恢复

* feat(console): 添加了统一的接口错误提示

* feat(console): 支持在快照列表中显示恢复状态

* feat(console): 新建快照成功之后跳转到快照列表
  • Loading branch information
jo-hnny authored May 29, 2023
1 parent 4eca8b2 commit 91d2cb0
Show file tree
Hide file tree
Showing 15 changed files with 558 additions and 22 deletions.
9 changes: 9 additions & 0 deletions web/console/config/validateConfig.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { z } from 'zod';

export const nameRule = (key: string) => {
return z
.string()
.min(1, { message: `${key}不能为空` })
.max(63, { message: `${key}长度不能超过63个字符` })
.regex(/^([A-Za-z0-9][-A-Za-z0-9_./]*)?[A-Za-z0-9]$/, { message: `${key}格式不正确` });
};
2 changes: 2 additions & 0 deletions web/console/helpers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,3 +44,5 @@ export * from './csrf';
export * from './isInIframe';
export { satisfyClusterVersion } from './satisfyClusterVersion';
export { cutNsStartClusterId, parseQueryString, reduceK8sQueryString, reduceK8sRestfulPath, reduceNs } from './urlUtil';

export * from './path';
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ import { UpdateResourcePanel } from './resourceEdition/UpdateResourcePanel';
import { ResourceListPanel } from './ResourceListPanel';
import { HPAPanel } from '@src/modules/cluster/components/scale/hpa';
import { CronHpaPanel } from '@src/modules/cluster/components/scale/cronhpa';
import { VMDetailPanel } from './virtual-machine';
import { VMDetailPanel, SnapshotTablePanel } from './virtual-machine';

interface ResourceContainerPanelState {
/** 共享锁 */
Expand Down Expand Up @@ -173,6 +173,8 @@ export class ResourceContainerPanel extends React.Component<RootProps, ResourceC
}
} else if (mode === 'detail' && resourceName === 'virtual-machine') {
return <VMDetailPanel />;
} else if (mode === 'snapshot' && resourceName === 'virtual-machine') {
return <SnapshotTablePanel route={route} />;
} else {
// 判断应该展示什么组件
switch (mode) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ import { ResourceLogPanel } from './resourceTableOperation/ResourceLogPanel';
import { ResourceTablePanel } from './resourceTableOperation/ResourceTablePanel';
import { HPAPanel } from '../scale/hpa';
import { CronHpaPanel } from '../scale/cronhpa';
import { VMListPanel } from './virtual-machine';
import { VMListPanel, SnapshotTablePanel } from './virtual-machine';

const loadingElement: JSX.Element = (
<div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ interface ActionButtonProps {
onSuccess?: () => void;
children: React.ReactNode;
disabled?: boolean;
body?: React.ReactNode;
}

export const ActionButton = ({
Expand All @@ -16,7 +17,8 @@ export const ActionButton = ({
title,
confirm,
children,
disabled = false
disabled = false,
body
}: ActionButtonProps) => {
const [visible, setVisible] = useState(false);

Expand All @@ -35,6 +37,8 @@ export const ActionButton = ({
</Button>

<Modal caption={title} visible={visible} onClose={() => setVisible(false)}>
{body && <Modal.Body>{body}</Modal.Body>}

<Modal.Footer>
<Button type="primary" onClick={handleOk}>
确定
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import React, { useState } from 'react';
import { Button, Modal, Form, Input } from 'tea-component';
import { useForm, Controller } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { nameRule } from '@config/validateConfig';
import { z } from 'zod';
import { getReactHookFormStatusWithMessage } from '@helper';
import { virtualMachineAPI } from '@src/webApi';

export const CreateSnapshotButton = ({ clusterId, namespace, name, onSuccess = () => {}, disabled }) => {
const [visible, setVisible] = useState(false);

const { control, handleSubmit, reset } = useForm({
mode: 'onBlur',
defaultValues: {
snapshotName: ''
},
resolver: zodResolver(z.object({ snapshotName: nameRule('快照名称') }))
});

function onCancel() {
setVisible(false);

reset();
}

async function onSubmit({ snapshotName }) {
console.log('snapshot name', snapshotName);

try {
await virtualMachineAPI.createSnapshot({
clusterId,
namespace,
vmName: name,
name: snapshotName
});

onCancel();
onSuccess();
} catch (error) {
console.log('create snapshot error:', error);
}
}

return (
<>
<Button type="link" onClick={() => setVisible(true)} disabled={disabled}>
新建快照
</Button>

<Modal caption="新建虚拟机快照" visible={visible} onClose={onCancel}>
<Modal.Body>
<Form>
<Controller
control={control}
name="snapshotName"
render={({ field, ...other }) => (
<Form.Item
label="快照名称"
extra="快照名称不能超过63个字符"
{...getReactHookFormStatusWithMessage(other)}
>
<Input {...field} />
</Form.Item>
)}
/>
</Form>
</Modal.Body>

<Modal.Footer>
<Button type="primary" onClick={handleSubmit(onSubmit)}>
确认
</Button>
<Button onClick={onCancel}>取消</Button>
</Modal.Footer>
</Modal>
</>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import React from 'react';
import { ActionButton } from './actionButton';
import { virtualMachineAPI } from '@src/webApi';

export const DelSnapshotButton = ({ type, clusterId, namespace, name, onSuccess = () => {} }) => {
function del() {
return virtualMachineAPI.delSnapshot({ clusterId, namespace, name });
}

return (
<ActionButton type={type} title={`你确定要删除快照“${name}”吗?`} confirm={del} onSuccess={onSuccess}>
删除
</ActionButton>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,9 @@ export { ShutdownButton } from './shutdownButton';
export { VNCButton } from './vncButton';

export { VmMonitorDialog } from './vmMonitorDialog';

export { CreateSnapshotButton } from './createSnapshotButton';

export { DelSnapshotButton } from './delSnapshotButton';

export { RecoverySnapshotButton } from './recoverySnapshotButton';
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import React from 'react';
import { ActionButton } from './actionButton';
import { virtualMachineAPI } from '@src/webApi';
import { Text, Alert } from 'tea-component';

export const RecoverySnapshotButton = ({
type,
clusterId,
namespace,
name,
vmName,
disabled,
onSuccess = () => {}
}) => {
function recovery() {
return virtualMachineAPI.recoverySnapshot({ clusterId, namespace, name, vmName });
}

return (
<ActionButton
type={type}
title={`恢复快照`}
confirm={recovery}
onSuccess={onSuccess}
disabled={disabled}
body={
<>
<Alert type="warning">请确保虚拟机处于关机状态!</Alert>
<Text parent="p">
您将要对虚拟机<Text theme="warning">{vmName}</Text>恢复快照<Text theme="warning">{name}</Text>,
恢复操作将会覆盖当前状态下虚拟机数据
</Text>
<Text parent="p">您是否要继续执行?</Text>
</>
}
>
恢复
</ActionButton>
);
};
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
export { VMListPanel } from './pages/list';
export { VMCreatePanel } from './pages/create';
export { VMDetailPanel } from './pages/detail';

export { SnapshotTablePanel } from './pages//snapshotTablePanel/snapTablePanel';
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,16 @@ export const VMListActionPanel = ({ route, reFetch, vmList, onQueryChange }) =>
</Button>

<VmMonitorDialog clusterId={clusterId} namespace={namespaceSelection} vmList={vmList} />

<Button
type="primary"
onClick={() => {
const urlParams = router.resolve(route);
router.navigate(Object.assign({}, urlParams, { mode: 'snapshot' }), route.queries);
}}
>
快照
</Button>
</>
}
right={
Expand All @@ -46,7 +56,11 @@ export const VMListActionPanel = ({ route, reFetch, vmList, onQueryChange }) =>
? namespaceListLoadable?.contents?.map(value => ({ value }))
: []
}
onChange={value => setNamespaceSelection(value)}
onChange={value => {
setNamespaceSelection(value);
const urlParams = router.resolve(route);
router.navigate(urlParams, Object.assign({}, route.queries, { np: value }));
}}
/>

<TagSearchBox
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { useSetRecoilState, useRecoilState, useRecoilValue } from 'recoil';
import { clusterIdState, namespaceSelectionState, vmSelectionState } from '../../store/base';
import { virtualMachineAPI } from '@src/webApi';
import { router } from '@src/modules/cluster/router';
import { BootButton, ShutdownButton, DelButton, VNCButton } from '../../components';
import { BootButton, ShutdownButton, DelButton, VNCButton, CreateSnapshotButton } from '../../components';

const { autotip } = Table.addons;

Expand Down Expand Up @@ -93,7 +93,7 @@ export const VMListPanel = ({ route }) => {
{
key: 'actions',
header: '操作',
render({ name, status }) {
render({ name, status, supportSnapshot }) {
return (
<>
<VNCButton type="link" clusterId={clusterId} name={name} namespace={namespace} status={status} />
Expand Down Expand Up @@ -124,6 +124,19 @@ export const VMListPanel = ({ route }) => {
<List.Item>
<DelButton clusterId={clusterId} name={name} namespace={namespace} type="link" onSuccess={reFetch} />
</List.Item>

<List.Item>
<CreateSnapshotButton
clusterId={clusterId}
name={name}
namespace={namespace}
disabled={!supportSnapshot}
onSuccess={() => {
const urlParams = router.resolve(route);
router.navigate(Object.assign({}, urlParams, { mode: 'snapshot' }), route.queries);
}}
/>
</List.Item>
</List>
</Dropdown>
</>
Expand Down Expand Up @@ -172,7 +185,11 @@ export const VMListPanel = ({ route }) => {
}`,
createTime: metadata?.creationTimestamp,

id: metadata?.uid
id: metadata?.uid,

supportSnapshot:
metadata?.annotations?.['tkestack.io/support-snapshot'] === 'true' &&
(realStatus === 'Running' || realStatus === 'Stopped')
};
}) ?? [],

Expand Down
Loading

0 comments on commit 91d2cb0

Please sign in to comment.