!582 前端添加迁移实施页面

Merge pull request !582 from sunkeai/master
This commit is contained in:
SunnyQjm 2022-11-30 09:30:58 +00:00 committed by Gitee
commit 09e8b61bc7
26 changed files with 1810 additions and 1 deletions

View File

@ -48,6 +48,22 @@ export default [
}
],
},
{
path: '/migrate',
name: 'migrate',
routes: [
{
path: '/migrate',
redirect: '/migrate/implement',
},
{
path: '/migrate/implement',
name: 'implement',
hideInBreadcrumb: true,
component: './migrate/implement',
},
]
},
{
path: '/monitor',
name: 'monitor',

View File

@ -4,6 +4,8 @@ export default {
'menu.host.list': '主机列表',
'menu.host.cluster': '集群列表',
'menu.host.terminal': '主机终端',
'menu.migrate': '操作系统迁移',
'menu.migrate.implement': '迁移实施',
'menu.monitor': '监控中心',
'menu.monitor.': '监控中心',
'menu.monitor.dashboard': '系统监控',

View File

@ -30,6 +30,9 @@ const Welcome = () => {
<Menu.Item key="hostlist"><a href="/host/list">主机列表</a></Menu.Item>
<Menu.Item key="cluster"><a href="/host/cluster">集群列表</a></Menu.Item>
</SubMenu>
<SubMenu key="migrate" title="操作系统迁移">
<Menu.Item key="implement"><a href="/migrate/implement">迁移实施</a></Menu.Item>
</SubMenu>
<SubMenu key="monitor" title="监控中心">
<Menu.Item key="dashboard"><a href="/monitor/dashboard">系统监控</a></Menu.Item>
</SubMenu>

View File

@ -24,7 +24,7 @@
}
.menuCenter{
position: absolute;
width: 40%;
width: 46%;
left: 50%;
transform: translate(-50%,-5%);
line-height: 48px!important;

View File

@ -0,0 +1,200 @@
/* eslint-disable prefer-promise-reject-errors */
import React, { useState, useContext } from 'react';
import { Form, Input, Modal, Select, Button, Radio, message } from 'antd';
import { withRouter } from 'umi';
import { WrapperContext } from '../../containers';
import { SET_DATA } from '../../containers/constants';
import { getNodesList,BulkMigration } from '../../../service';
const { TextArea } = Input;
const { Option } = Select;
const { Item: FormItem } = Form;
export default withRouter(
() => {
const {
dispatch,
state: { allMoveVisible, machineList, startMigrateIp, activeMachineGroupId },
} = useContext(WrapperContext);
// Repo
const [repoType, setRepoType] = useState(false);
const [form] = Form.useForm();
const initialValues = {
kernel: 'ANCK',
repo_type: 'public',
verson: 'Anolis OS 8',
};
const handleAdd = async () => {
form.validateFields().then(async (values) => {
if (typeof values === 'undefined') return true;
const hide = message.loading('开始迁移中...', 0);
let params = {...values}
if(startMigrateIp !== ""){
params.ip = [startMigrateIp];
}
try {
const { code } = await BulkMigration(params);
if (code === 200) {
try {
const {
code: queryCode,
data: nodeList,
} = await getNodesList({ id: activeMachineGroupId });
if (queryCode === 200) {
message.success('开始迁移成功');
dispatch({
type: SET_DATA,
payload: {
machineList: nodeList && nodeList.length !== 0 ? nodeList : [],
nodeTotal: nodeList ? nodeList.length : 0,
allMoveVisible: false,
},
});
return true;
}
return false;
} catch (e) {
console.log(`更新数据获取失败,错误信息:${e}`);
return false;
}
}
message.error('开始迁移失败,请重试!');
return false;
} catch (error) {
message.error(error);
return false;
} finally {
hide();
form.resetFields();
}
})
};
const handleRepo = (e) => {
if(e.target.value === "public"){
setRepoType(false);
}else if(e.target.value === "private"){
setRepoType(true);
}
}
return (
<Modal
wrapClassName="addModal"
centered
width={960}
destroyOnClose
afterClose={() => form.resetFields()}
title="批量迁移机器"
visible={allMoveVisible}
onCancel={() => {
dispatch({
type: SET_DATA,
payload: { allMoveVisible: false },
});
}}
footer={[
<Button
key="back"
onClick={() =>{
form.resetFields();
dispatch({
type: SET_DATA,
payload: { allMoveVisible: false },
})
}}
>
取消
</Button>,
<Button key="submit" type="primary" onClick={handleAdd}>
确定
</Button>,
]}
>
<Form
layout="horizontal"
form={form}
initialValues={initialValues}
labelCol={{
span: 4,
}}
wrapperCol={{
span: 18,
}}
>
{
startMigrateIp === '' ?
<FormItem
name="ip"
label="选择机器"
rules={[{ required: true, message: "机器不能为空" }]}
>
<Select placeholder="请选择机器" mode="multiple">
{
machineList && machineList.map((item)=>{
return(
<Option key={item.ip}>{item.ip}</Option>
)
})
}
</Select>
</FormItem>
:
<FormItem
name="ip"
label="选择机器"
required
>
<Input value={startMigrateIp} defaultValue={startMigrateIp} disabled/>
</FormItem>
}
<FormItem
name="verson"
label="迁移版本"
rules={[{ required: true, message: "迁移版本不能为空" }]}
>
<Select placeholder="请选择要迁移的版本">
{/* <Option key="Anolis OS 7">Anolis OS 7</Option> */}
<Option key="Anolis OS 8">Anolis OS 8</Option>
</Select>
</FormItem>
<FormItem
name="kernel"
label="选择内核"
rules={[{ required: true, message: "内核不能为空" }]}
>
<Radio.Group >
<Radio value="ANCK">ANCK</Radio>
{/* <Radio value="RHCK">RHCK</Radio> */}
</Radio.Group>
</FormItem>
<FormItem
name="repo_type"
label="Repo配置"
rules={[{ required: true, message: "Repo配置不能为空" }]}
>
<Radio.Group onChange={handleRepo}>
<Radio value="public">公网地址</Radio>
<Radio value="private">内网地址</Radio>
</Radio.Group>
</FormItem>
{
repoType &&
<FormItem name="repo_url" label colon={false}>
<TextArea rows={3} placeholder="请输入内网地址" maxLength={150} />
</FormItem>
}
<FormItem name="backup_pwd" label="备份路径">
<TextArea rows={3} placeholder="请输入备份路径" maxLength={150} />
</FormItem>
<FormItem name="backup_dir" label="备份目录">
<TextArea rows={3} placeholder="请输入备份目录" maxLength={150} />
</FormItem>
</Form>
</Modal>
);
},
);

View File

@ -0,0 +1,287 @@
import React, { useContext, useEffect, useState } from 'react';
import { Statistic, Row, Col, Skeleton, message, Card, Select } from 'antd';
import { WrapperContext } from '../../containers';
import { SET_DATA } from '../../containers/constants';
import {
getBannerList,
getNodesList,
qyeryMachineInfo,
qyeryLog,
qyeryReport,
} from '../../../service';
import styles from './style.less';
const machineGroup = (props) => {
const {
dispatch,
state: { loadingVisible, nodeTotal, abnormalNodeTotal, machineGroupsList, activeMachineGroupId },
} = useContext(WrapperContext);
useEffect(()=>{
getGroupsList()
},[])
//
const getGroupsList = async () => {
dispatch({
type: SET_DATA,
payload: {
loadingVisible: true,
},
});
const {code, data} = await getBannerList();
if (code === 200) {
if(data && data.length !== 0){
getMachineList(data[0].id);
dispatch({
type: SET_DATA,
payload: {
machineGroupsList: data,
activeMachineGroupId: data[0].id,
},
});
}else{
getNoData()
}
dispatch({
type: SET_DATA,
payload: {
loadingVisible: false,
},
});
}
}
//
const getMachineList = async (myid) => {
dispatch({
type: SET_DATA,
payload: {
machineTableLoading: true,
},
});
const {code, data} = await getNodesList({id:myid});
if (code === 200) {
if(data && data.length !== 0){
handleMachineName(data[0])
dispatch({
type: SET_DATA,
payload: {
machineList: data,
nodeTotal: data.length,
abnormalNodeTotal: data.filter((i)=>i.status === "waiting").length,
},
});
}else{
getNoData()
}
dispatch({
type: SET_DATA,
payload: {
machineTableLoading: false,
},
});
}
}
//
const getNoData = () => {
dispatch({
type: SET_DATA,
payload: {
machineList: [],
nodeTotal: 0,
abnormalNodeTotal: 0,
systemMessage: {},
logtMessage: '',
reportMessage: {},
tableIp: '',
tableIpVersion: '',
},
});
}
const handleMachineName = (r) => {
dispatch({
type: SET_DATA,
payload: {
machineDetailLoading: true,
},
});
const hide = message.loading('loading...', 0);
Promise.all([
getMachineInfo(r.ip),
getLog(r.ip),
getReport(r.ip),
]).then((res) => {
console.log(res,'jaja')
const [
{ data: systemMessage },
{ data: logtMessage },
{ data: reportMessage },
] = res;
dispatch({
type: SET_DATA,
payload: {
tableIp: r.ip,
tableIpVersion: r.version ? `${r.ip} (${r.version})` : `${r.ip}`,
machineDetailLoading: false,
},
});
hide();
}).catch((error)=>{
hide();
console.log(error,'error')
})
// Promise.all([
// qyeryMachineInfo({ ip: r.ip}),
// qyeryLog({ ip: r.ip }),
// qyeryReport({ ip: r.ip }),
// ]).then((res) => {
// hide();
// const [
// { data: systemMessage },
// { data: logtMessage },
// { data: reportMessage },
// ] = res;
// dispatch({
// type: SET_DATA,
// payload: {
// systemMessage,
// logtMessage,
// reportMessage,
// tableIp: r.ip,
// tableIpVersion: r.version ? `${r.ip} (${r.version})` : `${r.ip}`,
// machineDetailLoading: false,
// },
// });
// })
}
const getMachineInfo = async (ip) => {
try {
const { code,data } = await qyeryMachineInfo({ ip });
if (code === 200) {
dispatch({
type: SET_DATA,
payload: {
systemMessage: data,
},
});
return true;
}
return false;
} catch (e) {
dispatch({
type: SET_DATA,
payload: {
systemMessage: {},
},
});
return false;
}
}
const getLog = async (ip) => {
try {
const { code,data } = await qyeryLog({ ip });
if (code === 200) {
dispatch({
type: SET_DATA,
payload: {
logtMessage: data,
},
});
return true;
}
return false;
} catch (e) {
dispatch({
type: SET_DATA,
payload: {
logtMessage: '',
},
});
return false;
}
}
const getReport = async (ip) => {
try {
const { code,data } = await qyeryReport({ ip });
if (code === 200) {
dispatch({
type: SET_DATA,
payload: {
reportMessage: data,
},
});
return true;
}
return false;
} catch (e) {
dispatch({
type: SET_DATA,
payload: {
reportMessage: {},
},
});
return false;
}
}
return (
<div className={styles.banner}>
<Skeleton loading={loadingVisible}>
<Card
title={
<div>
{
machineGroupsList && machineGroupsList.length !== 0 ?
<Select
key="banner-select"
defaultValue={activeMachineGroupId}
style={{ width: 150 }}
dropdownStyle={{background:"#333"}}
bordered={false}
// open={true}
onChange={(key)=>getMachineList(key)}
>
{ machineGroupsList.map((item)=>{
return(
<Option key={item.id} value={item.id}>{item.cluster_name}</Option>
)
})
}
</Select>
:
<div>暂无机器组</div>
}
</div>
}
>
<Row>
<Col span={8}>
<Statistic
title="机器总数:"
value={nodeTotal}
valueStyle={{ color: 'rgba(255,255,255,0.85)', fontSize: 30 }}
/>
</Col>
<Col span={8}>
<Statistic
title="待迁移机器数:"
value={abnormalNodeTotal}
valueStyle={{ color: '#FF4D4F', fontSize: 30 }}
/>
</Col>
</Row>
</Card>
</Skeleton>
</div>
);
}
export default machineGroup;

View File

@ -0,0 +1,54 @@
.banner {
background-color: #141414;
border-radius: 4px;
:global {
.ant-card-body {
padding: 0;
}
.ant-row {
padding: 24px;
padding-bottom: 14px;
}
.ant-statistic {
.ant-statistic-title {
color: rgba(255, 255, 255, 0.45);
font-size: 16px;
}
.ant-statistic-content {
text-indent: 2px;
}
}
// .ant-card-head{
// padding: 0;
// }
.operate-btn {
text-align: right;
.ant-statistic-title {
.ant-btn {
height: 20px;
padding: 0;
line-height: 20px;
}
}
}
}
}
:global {
.dashboard-operate-cluster.ant-dropdown {
.ant-dropdown-menu {
background-color: #333;
.ant-dropdown-menu-item {
color: #fff;
}
.ant-dropdown-menu-item:hover,
.ant-dropdown-menu-submenu-title:hover {
background-color: #1a1a1a;
}
}
}
.ant-select-item-option-selected:not(.ant-select-item-option-disabled){
background: rgba(255, 255, 255, 0.08);
}
}

View File

@ -0,0 +1,20 @@
import React, { useContext } from 'react';
import { Skeleton, Empty } from 'antd';
import { WrapperContext } from '../../../../containers';
export default () => {
const {
state: { machineDetailLoading, logtMessage },
} = useContext(WrapperContext);
return (
<div style={{ height: 'calc(100vh - 48px)', overflow: 'scroll' }}>
<Skeleton loading={machineDetailLoading}>
{logtMessage ? (
<pre style={{ color: 'rgba(255,255,255,0.65)' }}>{logtMessage}</pre>
) : (
<Empty image={Empty.PRESENTED_IMAGE_SIMPLE} style={{ marginTop: 100 }} />
)}
</Skeleton>
</div>
);
};

View File

@ -0,0 +1,119 @@
import React, { useContext } from 'react';
import { Card, Col, Row, Empty, Skeleton } from 'antd';
import { AppstoreOutlined, HddOutlined } from '@ant-design/icons';
import { WrapperContext } from '../../../../containers';
import { ReactComponent as SoftIcon } from '@/pages/migrate/static/softIcon.svg';
import styles from './style.less';
import { nanoid } from 'nanoid';
export default function MachineMessage() {
const {
state: { systemMessage, machineDetailLoading },
} = useContext(WrapperContext);
return (
<div className={styles.content}>
<Skeleton loading={machineDetailLoading}>
<Card
className={styles.card}
title={
<>
<AppstoreOutlined style={{ marginRight: 12, color: '#1890ff' }} />
基本信息
</>
}
>
<Row>
{systemMessage.base_info && systemMessage.base_info.length !== 0 ? (
<>
{systemMessage.base_info.map((item) => (
<Col span={12} key={nanoid()}>
{item.name}{item.value}
</Col>
))}
</>
) : (
<Col span={24} key={nanoid()}>
<Empty image={Empty.PRESENTED_IMAGE_SIMPLE} />
</Col>
)}
</Row>
</Card>
<Card
className={styles.card}
title={
<>
<HddOutlined style={{ marginRight: 12, color: '#1890ff' }} />
硬件信息
</>
}
>
<Row gutter={[0, 20]}>
{systemMessage.hard_info && systemMessage.hard_info.length !== 0 ? (
<>
{systemMessage.hard_info.map((item) => (
<Col span={12} key={nanoid()}>
{item.name}{item.value}
</Col>
))}
</>
) : (
<Col span={24} key={nanoid()}>
<Empty image={Empty.PRESENTED_IMAGE_SIMPLE} />
</Col>
)}
</Row>
</Card>
<Card
title={
<div style={{ display: 'flex', alignItems: 'center' }}>
<SoftIcon style={{ marginRight: 12 }} />
软件信息
</div>
}
>
<Row gutter={[0, 20]}>
{systemMessage.soft_info && systemMessage.soft_info.length !== 0 ? (
<>
{systemMessage.soft_info.map((item) => (
<Col span={12} key={nanoid()}>
{item.name}{item.value}
</Col>
))}
</>
) : (
<Col span={24} key={nanoid()}>
<Empty image={Empty.PRESENTED_IMAGE_SIMPLE} />
</Col>
)}
</Row>
</Card>
<Card
className={styles.card}
title={
<>
<HddOutlined style={{ marginRight: 12, color: '#1890ff' }} />
迁移信息
</>
}
>
<Row gutter={[0, 20]}>
{systemMessage.migration_info && systemMessage.migration_info.length !== 0 ? (
<>
{systemMessage.migration_info.map((item) => (
<Col span={12} key={nanoid()}>
{item.name}{item.value}
</Col>
))}
</>
) : (
<Col span={24} key={nanoid()}>
<Empty image={Empty.PRESENTED_IMAGE_SIMPLE} />
</Col>
)}
</Row>
</Card>
</Skeleton>
</div>
);
}

View File

@ -0,0 +1,10 @@
.content {
.card {
margin-bottom: 20px;
}
:global {
.ant-card-body {
padding: 30px 50px;
}
}
}

View File

@ -0,0 +1,78 @@
import React, { useContext, useState } from 'react';
import { Collapse, Card, Row, Col, Empty, Skeleton } from 'antd';
import {
BankOutlined,
RotateLeftOutlined,
RotateRightOutlined,
} from '@ant-design/icons';
import styles from './style.less';
import { WrapperContext } from '../../../../containers';
import { nanoid } from 'nanoid';
const { Panel } = Collapse;
export default function Report() {
const {
dispatch,
state: { reportMessage, machineDetailLoading },
} = useContext(WrapperContext);
return (
<div className={styles.content}>
<Skeleton loading={machineDetailLoading}>
{reportMessage && reportMessage.before === undefined ? (
<Empty image={Empty.PRESENTED_IMAGE_SIMPLE} style={{ marginTop: 100 }} />
) : (
<>
<Collapse defaultActiveKey={['1']} style={{ marginBottom: 24 }}>
<Panel
header={
<div style={{ fontSize: 16 }}>
<BankOutlined style={{ color: '#1890ff', marginRight: '13px' }} />
<span>系统信息迁移对比</span>
</div>
}
key="1"
>
<Row>
<Col span={12}>
<Card
title={
<>
<RotateLeftOutlined style={{ color: '#FCC00B', marginRight: '9px' }} />
迁移前
</>
}
>
{reportMessage && reportMessage.before && reportMessage.before.length !== 0 && reportMessage.before.map((item) => (
<div className={styles.rowLine} key={nanoid()}>
{item.name}{item.value}
</div>
))}
</Card>
</Col>
<Col span={12}>
<Card
title={
<>
<RotateRightOutlined style={{ color: '#FCC00B', marginRight: '9px' }} />
迁移后
</>
}
>
{reportMessage && reportMessage.after && reportMessage.after.length !== 0 && reportMessage.after.map((item) => (
<div className={styles.rowLine} key={nanoid()}>
{item.name}{item.value}
</div>
))}
</Card>
</Col>
</Row>
</Panel>
</Collapse>
</>
)}
</Skeleton>
</div>
);
}

View File

@ -0,0 +1,66 @@
.content {
.rowLine {
margin-bottom: 20px;
}
.contrast {
:global {
.ant-card-body {
padding: 0;
}
}
}
:global {
.ant-table-tbody :hover {
background-color: #141414 !important;
}
.ant-card-head {
font-size: 15px;
}
.ant-table table {
padding: 0px 26px;
}
.ant-table-tbody > tr > td {
border-bottom: 1px solid #2a2a2a;
}
.ant-collapse-content > .ant-collapse-content-box {
padding: 24px 30px;
}
.ant-card-body {
padding: 30px 0px 30px 60px;
}
.ant-card {
background-color: #141414;
}
.ant-table-tbody > tr > td.ant-table-cell-row-hover {
background-color: #141414;
}
.ant-pagination-disabled .ant-pagination-item-link,
.ant-pagination-disabled:hover .ant-pagination-item-link {
color: rgba(255, 255, 255, 0.65);
background: #141414;
border: 1px solid #2a2a2a;
}
.ant-pagination-item-active {
background: #141414;
// border: 1px solid #389e0d;
// a {
// color: #389e0d;
// }
}
.ant-collapse-expand-icon {
position: absolute;
right: 20px;
}
.ant-collapse-header-text {
padding-left: 3px;
}
}
}
:global {
.ant-table-filter-dropdown-btns {
.ant-btn-primary {
// color: #389e0d;
background-color: transparent !important;
}
}
}

View File

@ -0,0 +1,78 @@
import React, { useState, useEffect, useContext } from 'react';
import { Tabs, Card } from 'antd';
import styles from './style.less';
import { ReactComponent as TabIcon } from '@/pages/migrate/static/tab.svg';
import { ReactComponent as LiveTabIcon } from '@/pages/migrate/static/liveTab.svg';
import MachineMessage from './components/MachineMessage';
import LogMessage from './components/Log';
import { WrapperContext } from '../../containers';
import Report from './components/Report';
const { TabPane } = Tabs;
export default function Message() {
const {
dispatch,
state: { tableIpVersion },
} = useContext(WrapperContext);
const [activeKey, setActiveKey] = useState('1');
return (
<div className={styles.content}>
<Card title={tableIpVersion}>
<Tabs
defaultActiveKey="1"
activeKey={activeKey}
onChange={(key) => {
setActiveKey(key);
}}
>
<TabPane
key="1"
tab={
<div style={{ display: 'flex', alignItems: 'center' }}>
{activeKey === '1' ? (
<LiveTabIcon style={{ marginRight: 6 }} />
) : (
<TabIcon style={{ marginRight: 6 }} />
)}
机器信息
</div>
}
>
<MachineMessage />
</TabPane>
<TabPane
key="3"
tab={
<div style={{ display: 'flex', alignItems: 'center' }}>
{activeKey === '3' ? (
<LiveTabIcon style={{ marginRight: 6 }} />
) : (
<TabIcon style={{ marginRight: 6 }} />
)}
迁移日志
</div>
}
>
<LogMessage />
</TabPane>
<TabPane
key="4"
tab={
<div style={{ display: 'flex', alignItems: 'center' }}>
{activeKey === '4' ? (
<LiveTabIcon style={{ marginRight: 6 }} />
) : (
<TabIcon style={{ marginRight: 6 }} />
)}
迁移报告
</div>
}
>
<Report />
</TabPane>
</Tabs>
</Card>
</div>
);
}

View File

@ -0,0 +1,5 @@
.content {
min-height: calc(100vh - 90px);
background-color: #141414;
border-radius: 4px;
}

View File

@ -0,0 +1,422 @@
/* eslint-disable no-case-declarations */
import React, { Fragment, useContext, useEffect } from 'react';
import { Card, Tooltip, Skeleton, Modal, message, Menu, Progress, Button } from 'antd';
import ProTable, { TableDropdown } from '@ant-design/pro-table';
import { withRouter } from 'umi';
import { WrapperContext } from '../../containers';
import { SET_DATA } from '../../containers/constants';
import {
qyeryMachineInfo,
qyeryLog,
qyeryReport,
rebootMachine,
stopMigration,
getNodesList,
} from '../../../service';
import styles from './style.less';
export default withRouter(
() => {
const {
dispatch,
state: { loadingVisible, machineList, nodeTotal, activeMachineGroupId, machineTableLoading },
} = useContext(WrapperContext);
const columns = [
{
title: '机器 IP系统版本',
dataIndex: 'ip',
ellipsis: true,
render: (t, r) => (
// <Tooltip trigger="hover" placement="topLeft" title={r.ip+' '+(r.version && `(${r.version})`)}>
<span style={{cursor:"pointer"}} title={r.ip+' '+(r.version && `(${r.version})`)} onClick={()=>handleMachineName(r)}>{r.ip} {r.version ? `(${r.version})` : ''}</span>
// </Tooltip>
),
},
{
title: '迁移状态',
dataIndex: 'status',
width: 80,
filters: false,
valueEnum: {
waiting: {
text: <span style={{ fontSize: 13, color: 'rgba(255,255,255,0.4)' }}>未迁移</span>,
},
running: {
text: <span style={{ fontSize: 13, color: '#FCC00B' }}>迁移中</span>,
},
success: {
text: <span style={{ fontSize: 13, color: '#52C41A' }}>迁移完</span>,
},
},
},
{
title: '进度',
dataIndex: 'rate',
width: 110,
filters: false,
renderText: (_, r) => (
<Progress
percent={r.rate || 0}
className={
r.rate === 100
? styles.numGreen
: r.rate > 0 && r.rate < 99
? styles.numRed
: styles.numZero
}
size="small"
steps={10}
format={(percent) => `${percent}%`}
strokeColor={r.rate === 100 ? '#52C41A' : '#FF4D4F'}
/>
),
},
{
title: '操作',
dataIndex: 'option',
valueType: 'option',
width: 30,
fixed: 'right',
render: (t, r) => {
const menus = [
{
key: 'start',
name: '开始迁移',
disabled: r.status === "success"?true:false,
},
{
key: 'destroy',
name: '停止迁移',
disabled: r.status === "waiting"?false:true,
},
{
key: 'reboot',
name: '重启机器',
},
];
return (
<>
<TableDropdown
onSelect={async (key, e) => {
switch (key) {
case 'start':
dispatch({
type: SET_DATA,
payload: {
startMigrateIp: r.ip,
allMoveVisible: true,
},
})
break;
case 'destroy':
Modal.confirm({
title: (
<span style={{ fontWeight: 'normal', fontSize: 14 }}>
确定要停止迁移吗
</span>
),
okText: '确定',
cancelText: '取消',
onOk: async () => {
const destroyHide = message.loading('正在停止迁移...', 0);
try {
const { code } = await stopMigration({
ip: r.ip,
});
if (code === 200) {
try {
const {
code: queryCode,
data: nodeList,
} = await getNodesList({ id: activeMachineGroupId });
if (queryCode === 200) {
message.success('停止迁移成功');
dispatch({
type: SET_DATA,
payload: {
machineList: nodeList,
nodeTotal: nodeList ? nodeList.length : 0,
},
});
return true;
}
return false;
} catch (e) {
console.log(`更新数据获取失败,错误信息:${e}`);
return false;
}
}
message.error('停止迁移失败');
return false;
} catch (error) {
return false;
} finally {
destroyHide();
}
},
});
break;
case 'reboot':
Modal.confirm({
title: (
<span style={{ fontWeight: 'normal', fontSize: 14 }}>
确定要重启机器吗
</span>
),
okText: '确定',
cancelText: '取消',
onOk: async () => {
const rebootHide = message.loading('正在重启机器...', 0);
try {
const { code } = await rebootMachine({
ip: r.ip,
});
if (code === 200) {
try {
const {
code: queryCode,
data: nodeList,
} = await getNodesList({ id: activeMachineGroupId });
if (queryCode === 200) {
message.success('重启机器成功');
console.log(nodeList,"nodeList")
dispatch({
type: SET_DATA,
payload: {
machineList: nodeList,
nodeTotal: nodeList ? nodeList.length : 0,
},
});
return true;
}
return false;
} catch (e) {
console.log(`更新数据获取失败,错误信息:${e}`);
return false;
}
}
message.error('重启机器失败');
return false;
} catch (error) {
return false;
} finally {
rebootHide();
}
},
});
break;
default:
break;
}
}}
menus={menus}
/>
</>
);
},
},
];
const handleMachineName = async (r) => {
dispatch({
type: SET_DATA,
payload: {
machineDetailLoading: true,
},
});
const hide = message.loading('loading...', 0);
Promise.all([
getMachineInfo(r.ip),
getLog(r.ip),
getReport(r.ip),
]).then((res) => {
console.log(res,'jaja')
const [
{ data: systemMessage },
{ data: logtMessage },
{ data: reportMessage },
] = res;
dispatch({
type: SET_DATA,
payload: {
tableIp: r.ip,
tableIpVersion: r.version ? `${r.ip} (${r.version})` : `${r.ip}`,
machineDetailLoading: false,
},
});
hide();
}).catch((error)=>{
hide();
console.log(error,'error')
})
// Promise.all([
// qyeryMachineInfo({ ip: r.ip}),
// qyeryLog({ ip: r.ip }),
// qyeryReport({ ip: r.ip }),
// ]).then((res) => {
// console.log(res,'jaja')
// const [
// { data: systemMessage },
// { data: logtMessage },
// { data: reportMessage },
// ] = res;
// dispatch({
// type: SET_DATA,
// payload: {
// systemMessage,
// logtMessage,
// reportMessage,
// tableIp: r.ip,
// tableIpVersion: r.version ? `${r.ip} (${r.version})` : `${r.ip}`,
// machineDetailLoading: false,
// },
// });
// hide();
// }).catch((error)=>{
// hide();
// console.log(error,'error')
// })
// dispatch({
// type: SET_DATA,
// payload: {
// tableIp: r.ip,
// tableIpVersion: r.version ? `${r.ip} (${r.version})` : `${r.ip}`,
// machineDetailLoading: false,
// },
// });
// hide();
}
const getMachineInfo = async (ip) => {
try {
const { code,data } = await qyeryMachineInfo({ ip });
if (code === 200) {
dispatch({
type: SET_DATA,
payload: {
systemMessage: data,
},
});
return true;
}
return false;
} catch (e) {
dispatch({
type: SET_DATA,
payload: {
systemMessage: {},
},
});
return false;
}
}
const getLog = async (ip) => {
try {
const { code,data } = await qyeryLog({ ip });
if (code === 200) {
dispatch({
type: SET_DATA,
payload: {
logtMessage: data,
},
});
return true;
}
return false;
} catch (e) {
dispatch({
type: SET_DATA,
payload: {
logtMessage: '',
},
});
return false;
}
}
const getReport = async (ip) => {
try {
const { code,data } = await qyeryReport({ ip });
if (code === 200) {
dispatch({
type: SET_DATA,
payload: {
reportMessage: data,
},
});
return true;
}
return false;
} catch (e) {
dispatch({
type: SET_DATA,
payload: {
reportMessage: {},
},
});
return false;
}
}
return (
<div className={styles.nodes}>
<Card
bordered={false}
title={
<span>
<em>机器列表</em>
<i> {nodeTotal} </i>
</span>
}
extra={
<>
<Button
style={{ marginRight: 20 }}
type="primary"
ghost
onClick={() => {
dispatch({
type: SET_DATA,
payload: {
startMigrateIp: '',
allMoveVisible: true,
},
})
}}
>
批量迁移
</Button>
</>
}
>
<Skeleton loading={machineTableLoading}>
<ProTable
rowKey="ip"
tableLayout="fixed"
size="small"
rowClassName={() => 'node-row'}
search={false}
pagination={false}
tableAlertRender={false}
toolBarRender={false}
options={false}
columns={columns}
dataSource={machineList}
onRow={(r) => ({
})}
/>
</Skeleton>
{/* <Skeleton loading={loadingVisible} />
<Skeleton loading={loadingVisible} />
<Skeleton loading={loadingVisible} /> */}
</Card>
</div>
);
},
);

View File

@ -0,0 +1,143 @@
.nodes {
background-color: #141414;
border-radius: 4px;
.numGreen {
:global {
.ant-progress-text {
color: #52c41a;
}
}
}
.numRed {
:global {
.ant-progress-text {
color: #ff4d4f;
}
}
}
.numZero {
:global {
.ant-progress-text {
color: #999999;
}
}
}
:global {
.ant-card-extra {
display: flex;
align-items: center;
}
.ant-card {
background-color: #141414;
border-radius: 4px;
.ant-card-head {
// padding: 0 15px;
padding-right: 24px;
color: #fff;
border-bottom: 1px solid #2a2a2a;
.ant-card-head-title {
padding: 19px 0;
em {
font-size: 16px;
font-style: normal;
}
i {
font-weight: normal;
font-size: 13px;
font-style: normal;
}
}
}
.ant-card-body {
padding: 0;
.ant-pro-table {
margin: 10px;
margin-top: 5px;
margin-bottom: 30px;
background-color: #141414;
.ant-dropdown {
.ant-dropdown-menu {
background-color: #333 !important;
.ant-dropdown-menu-item {
color: #fff;
}
.ant-dropdown-menu-item:last-child {
color: #ff4d4f;
}
.ant-dropdown-menu-item:hover,
.ant-dropdown-menu-submenu-title:hover {
background-color: #1a1a1a;
}
}
}
.ant-table-row .ant-table-cell:first-child {
padding-right: 0;
padding-left: 5px;
}
.ant-table-content {
background-color: #141414;
tr:first-child > th:first-child {
padding-right: 0;
padding-left: 5px;
}
.ant-table-thead > tr > th {
padding-right: 0;
padding-left: 0;
color: rgba(255, 255, 255, 0.85);
background-color: #141414;
border-bottom: 1px solid #2a2a2a;
}
.ant-table-tbody > tr > td {
padding-right: 0;
padding-left: 0;
color: rgba(255, 255, 255, 0.65);
background-color: #141414;
border-bottom: 1px solid #2a2a2a;
.ant-typography {
color: rgba(255, 255, 255, 0.65);
}
// .ant-pro-table-dropdown {
// .anticon {
// color: #389e0d;
// }
// }
}
}
}
}
}
}
}
:global {
.node-row:hover > td {
background-color: #2a2a2a !important;
}
.dashboard-operate-node.ant-dropdown {
.ant-dropdown-menu {
background-color: #333;
.ant-dropdown-menu-item {
color: #fff;
}
.ant-dropdown-menu-item:hover,
.ant-dropdown-menu-submenu-title:hover {
background-color: #1a1a1a;
}
}
}
// overlayClassName 失效
.ant-dropdown-menu {
background-color: #333;
.ant-dropdown-menu-item {
color: #fff;
}
.ant-dropdown-menu-item-disabled {
color: rgba(255, 255, 255, 0.4);
}
.ant-dropdown-menu-item:hover,
.ant-dropdown-menu-submenu-title:hover {
background-color: #1a1a1a;
}
}
}

View File

@ -0,0 +1,2 @@
// 常量: actionTypes
export const SET_DATA = 'SET_DATA';

View File

@ -0,0 +1,58 @@
import React, { useEffect, useReducer, createContext } from 'react';
import { withRouter } from 'umi';
import rootReducer from './reducers';
// import { SET_DATA } from './constants';
// import { qyeryMachineInfo, qyeryLog, qyeryReport,getBannerList } from '../../service';
// 初始化全量数据(全部的分类、应用数据)
const initState = {
loadingVisible: true,
machineTableLoading: true,
machineDetailLoading: true,
// 机器总数
nodeTotal: 0,
// 待迁移机器数
abnormalNodeTotal: 0,
isFocused: false,
isDefault: false,
// 机器信息
systemMessage: {},
// 迁移日志
logtMessage: '',
// 迁移报告
reportMessage: {},
// 当前展示的机器ip(系统版本)
tableIpVersion: '',
// 当前展示的机器ip
tableIp: '',
// 批量迁移
allMoveVisible: false,
// 机器组列表
machineGroupsList: [],
// 机器列表
machineList:[],
// 选中机器组id
activeMachineGroupId: '',
// 开始迁移弹窗机器ip
startMigrateIp: '',
};
// 创建需要共享全量数据的 Context
export const WrapperContext = createContext();
export default withRouter((props) => {
// 使用 useReducer 取代 useState 集中管理数据
const [state, dispatch] = useReducer(rootReducer, initState);
useEffect(() => {
(async () => {
})();
}, []);
// 使用 Provider 提供 Context 的值Provider所包含的子树都可以直接访问 Context 的值
return (
<WrapperContext.Provider value={{ state, dispatch }}>{props.children}</WrapperContext.Provider>
);
});

View File

@ -0,0 +1,14 @@
import * as types from './constants';
// 全量数据变化的处理逻辑 reducer函数
export default (state, { type, payload }) => {
switch (type) {
case types.SET_DATA:
return {
...state,
...payload,
};
default:
return state;
}
};

View File

@ -0,0 +1,39 @@
import React, { useRef, useState, useEffect } from 'react';
import { Row, Col } from "antd";
// import { connect } from 'dva';
import "./index.less";
import Wrapper from './containers';
import Banner from './components/Banner';
import Nodes from './components/Nodes';
import Message from './components/Message';
import AllMoveModel from './components/AllMove';
const migrate = (props) => {
return (
<div className="container">
<Wrapper>
<Row gutter={[16, 16]}>
<Col span={8}>
<Row gutter={[16, 16]} style={{ marginBottom: 15 }}>
<Col span={24}>
<Banner />
</Col>
</Row>
<Row gutter={[16, 16]}>
<Col span={24}>
<Nodes />
</Col>
</Row>
</Col>
<Col span={16} style={{ overflow: 'auto' }}>
<Message />
</Col>
</Row>
<AllMoveModel />
</Wrapper>
</div>
);
}
export default migrate;

View File

@ -0,0 +1,79 @@
.container {
position: relative;
width: 100vw;
min-height: calc(100vh - 48px);
padding: 20px 25px 10px 25px;
background-color: #000;
:global {
.ant-skeleton-content {
padding: 20px;
padding-bottom: 6px;
.ant-skeleton-title {
height: 12px;
margin-top: 0;
background-color: #2a2a2a;
}
.ant-skeleton-paragraph {
margin-top: 12px;
li {
height: 10px;
margin-top: 10px;
background-color: #2a2a2a;
}
}
}
.ant-empty {
color: rgba(255, 255, 255, 0.65);
}
}
}
:global {
.updateModal {
.ant-modal-body {
padding: 20px 30px;
background-color: #fff;
.ant-descriptions {
margin-bottom: 24px;
.ant-descriptions-item-label {
width: 20%;
background-color: #fafafa;
}
.ant-descriptions-item-content {
padding: 4px 16px;
.ant-input-disabled {
color: rgba(0, 0, 0, 0.85);
background-color: #fff;
border: none;
cursor: text;
}
.ant-select-disabled {
.ant-select-selection-item {
color: rgba(0, 0, 0, 0.85);
}
.ant-select-selector {
cursor: text;
.ant-select-selection-search-input {
cursor: text;
}
}
}
}
}
}
}
.switch-is-default {
width: 300px;
.ant-popover-title {
padding: 20px 20px 0;
border-bottom: none;
}
.ant-popover-inner-content {
padding: 0 20px 20px 40px;
color: #666;
font-size: 12px;
line-height: 22px;
}
}
}

View File

@ -0,0 +1,66 @@
import { request } from 'umi';
import { stringify } from 'qs';
const token = localStorage.getItem('token');
/* *************** 操作系统迁移--迁移实施 *************** */
// 迁移机器组列表
export async function getBannerList(params) {
return request(`/api/v1/migration/group/`, {
method: 'GET',
});
}
// 机器列表
export async function getNodesList(params) { //id=1
return request(`/api/v1/implementation/list/?${stringify(params)}`, {
method: 'GET',
});
}
// 机器信息
export async function qyeryMachineInfo(params) {
return request(`/api/v1/implementation/info/?${stringify(params)}`, {
method: 'GET',
});
}
// 迁移日志
export async function qyeryLog(params) {
return request(`/api/v1/implementation/log/?${stringify(params)}`, {
method: 'GET',
});
}
// 迁移报告
export async function qyeryReport(params) {
return request(`/api/v1/implementation/report/?${stringify(params)}`, {
method: 'GET',
});
}
// 停止迁移
export async function stopMigration(params) {
return request(`/api/v1/implementation/stop/`, {
method: 'POST',
data: params,
});
}
// 批量迁移 & 开始迁移
export async function BulkMigration(params) {
return request(`/api/v1/implementation/migrate/`, {
method: 'POST',
data: params,
});
}
// 重启机器
export async function rebootMachine(params) {
return request(`/api/v1/implementation/reboot/`, {
method: 'post',
data: params,
});
}

View File

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="16px" height="16px" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>
图标
</title>
<g id="OS迁移平台-UI-Beta1.0备份" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="0机器组详情-弹窗位置备份" transform="translate(-487.000000, -95.000000)">
<g id="展示-带图标" transform="translate(486.000000, 92.000000)">
<g id="图标" transform="translate(1.000000, 3.000000)">
<rect id="矩形" fill="#000000" fill-rule="nonzero" opacity="0" x="0" y="0" width="16" height="16">
</rect>
<rect id="矩形" fill="#000000" fill-rule="nonzero" opacity="0" x="2" y="2" width="11.9882927" height="11.9882927">
</rect>
<path d="M10.7276376,5.34227177 C10.9818537,5.08939372 10.9818537,4.67729616 10.7276376,4.42441812 L9.79239024,3.48917073 L9.79372823,3.49050871 C9.5462021,3.24298258 9.12072475,2.81750523 8.51863415,2.21675262 C8.23498258,1.93979094 7.77605575,1.94246691 7.49508014,2.22344251 L2.22611847,7.48839024 C2.08996122,7.62362369 2.01339073,7.80759494 2.01339073,7.99949826 C2.01339073,8.19140157 2.08996122,8.37537283 2.22611847,8.51060628 L7.49374216,13.774216 C7.77653032,14.0565208 8.23450801,14.0565208 8.51729616,13.774216 L10.7262997,11.5665505 C10.9805157,11.3136725 10.9805157,10.9015749 10.7262997,10.6486969 C10.4721014,10.395765 10.0613062,10.395765 9.80710802,10.6486969 L8.13463415,12.3238467 C8.06505924,12.3934216 7.95668293,12.3934216 7.88710802,12.3238467 L3.67916376,8.11991637 C3.60958885,8.05034146 3.60958885,7.94196516 3.67916376,7.87239024 L7.88577003,3.66845993 C7.89112195,3.66310802 7.89781184,3.65909408 7.90316376,3.65374216 C7.97273867,3.59888502 8.06907317,3.60423694 8.13329616,3.66845993 L9.808446,5.34227177 C10.062662,5.5964878 10.4747596,5.5964878 10.7276376,5.34227177 Z M6.61067595,8.02625784 C6.61067595,8.5339086 6.88175941,9.00299725 7.32181183,9.25682264 C7.76186426,9.51064802 8.30403121,9.51064802 8.74408363,9.25682264 C9.18413606,9.00299725 9.45521951,8.5339086 9.45521951,8.02625784 C9.45521951,7.24149776 8.81844675,6.60532403 8.03294773,6.60532403 C7.24744872,6.60532403 6.61067595,7.24149776 6.61067595,8.02625784 L6.61067595,8.02625784 Z M13.7849199,7.5097979 L12.1405436,5.87344948 C11.8863275,5.62057143 11.47423,5.62057143 11.2213519,5.87478746 C11.0992832,5.99631438 11.03066,6.16146576 11.03066,6.33371429 C11.03066,6.50596282 11.0992832,6.6711142 11.2213519,6.79264112 L12.3318746,7.90182579 C12.4014495,7.9714007 12.4014495,8.07977701 12.3318746,8.14935192 L11.2374077,9.24248083 C11.115339,9.36400775 11.0467158,9.52915913 11.0467158,9.70140766 C11.0467158,9.87365619 11.115339,10.0388076 11.2374077,10.1603345 C11.4916059,10.4132663 11.9024011,10.4132663 12.1565993,10.1603345 L13.7862578,8.53201394 C13.9219406,8.3964443 13.9980647,8.21243242 13.9978143,8.02062814 C13.9975626,7.82882386 13.9209571,7.64501189 13.7849199,7.5097979 L13.7849199,7.5097979 Z" id="形状" fill-opacity="1" fill="#1890ff">
</path>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.0 KiB

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="16px" height="16px" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>待迁移机器图标</title>
<g id="OS迁移平台-UI-Beta1.1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="OS迁移平台-Dashboard" transform="translate(-1030.000000, -224.000000)" fill="#FF4D4F" fill-rule="nonzero">
<path d="M1042,232.678571 L1034,232.678571 C1033.84286,232.678571 1033.71429,232.807143 1033.71429,232.964286 L1033.71429,236.107143 C1033.71429,236.264286 1033.84286,236.392857 1034,236.392857 L1042,236.392857 C1042.15714,236.392857 1042.28571,236.264286 1042.28571,236.107143 L1042.28571,232.964286 C1042.28571,232.807143 1042.15714,232.678571 1042,232.678571 Z M1041,235.107143 L1035,235.107143 L1035,233.964286 L1041,233.964286 L1041,235.107143 Z M1044.71429,237.428571 C1044.00357,237.428571 1043.42857,238.003571 1043.42857,238.714286 C1043.42857,239.425 1044.00357,240 1044.71429,240 C1045.425,240 1046,239.425 1046,238.714286 C1046,238.003571 1045.425,237.428571 1044.71429,237.428571 Z M1044.71429,239.142857 C1044.47679,239.142857 1044.28571,238.951786 1044.28571,238.714286 C1044.28571,238.476786 1044.47679,238.285714 1044.71429,238.285714 C1044.95179,238.285714 1045.14286,238.476786 1045.14286,238.714286 C1045.14286,238.951786 1044.95179,239.142857 1044.71429,239.142857 Z M1042,227.607143 L1034,227.607143 C1033.84286,227.607143 1033.71429,227.735714 1033.71429,227.892857 L1033.71429,231.035714 C1033.71429,231.192857 1033.84286,231.321429 1034,231.321429 L1042,231.321429 C1042.15714,231.321429 1042.28571,231.192857 1042.28571,231.035714 L1042.28571,227.892857 C1042.28571,227.735714 1042.15714,227.607143 1042,227.607143 Z M1041,230.035714 L1035,230.035714 L1035,228.892857 L1041,228.892857 L1041,230.035714 Z M1044.71429,226.571429 C1045.425,226.571429 1046,225.996429 1046,225.285714 C1046,224.575 1045.425,224 1044.71429,224 C1044.00357,224 1043.42857,224.575 1043.42857,225.285714 C1043.42857,225.996429 1044.00357,226.571429 1044.71429,226.571429 Z M1044.71429,224.857143 C1044.95179,224.857143 1045.14286,225.048214 1045.14286,225.285714 C1045.14286,225.523214 1044.95179,225.714286 1044.71429,225.714286 C1044.47679,225.714286 1044.28571,225.523214 1044.28571,225.285714 C1044.28571,225.048214 1044.47679,224.857143 1044.71429,224.857143 Z M1031.28571,224 C1030.575,224 1030,224.575 1030,225.285714 C1030,225.996429 1030.575,226.571429 1031.28571,226.571429 C1031.99643,226.571429 1032.57143,225.996429 1032.57143,225.285714 C1032.57143,224.575 1031.99643,224 1031.28571,224 Z M1031.28571,225.714286 C1031.04821,225.714286 1030.85714,225.523214 1030.85714,225.285714 C1030.85714,225.048214 1031.04821,224.857143 1031.28571,224.857143 C1031.52321,224.857143 1031.71429,225.048214 1031.71429,225.285714 C1031.71429,225.523214 1031.52321,225.714286 1031.28571,225.714286 Z M1031.28571,237.428571 C1030.575,237.428571 1030,238.003571 1030,238.714286 C1030,239.425 1030.575,240 1031.28571,240 C1031.99643,240 1032.57143,239.425 1032.57143,238.714286 C1032.57143,238.003571 1031.99643,237.428571 1031.28571,237.428571 Z M1031.28571,239.142857 C1031.04821,239.142857 1030.85714,238.951786 1030.85714,238.714286 C1030.85714,238.476786 1031.04821,238.285714 1031.28571,238.285714 C1031.52321,238.285714 1031.71429,238.476786 1031.71429,238.714286 C1031.71429,238.951786 1031.52321,239.142857 1031.28571,239.142857 Z" id="待迁移机器图标"></path>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.5 KiB

View File

@ -0,0 +1,4 @@
<svg t="1659606311721" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="25834" width="20" height="20">
<path d="M169 169v686h686V169H169z m640 46v146H215V215h594zM215 407h145.8v402H215V407z m191.8 402V407H809v402H406.8z" p-id="25835" fill="#1890ff">
</path>
</svg>

After

Width:  |  Height:  |  Size: 310 B

View File

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="16px" height="16px" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>图标</title>
<g id="OS迁移平台-UI-Beta1.0备份" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="0机器组详情-弹窗位置备份" transform="translate(-487.000000, -95.000000)">
<g id="展示-带图标" transform="translate(486.000000, 92.000000)">
<g id="图标" transform="translate(1.000000, 3.000000)">
<rect id="矩形" fill="#000000" fill-rule="nonzero" opacity="0" x="0" y="0" width="16" height="16"></rect>
<rect id="矩形" fill="#000000" fill-rule="nonzero" opacity="0" x="2" y="2" width="11.9882927" height="11.9882927"></rect>
<path d="M10.7276376,5.34227177 C10.9818537,5.08939372 10.9818537,4.67729616 10.7276376,4.42441812 L9.79239024,3.48917073 L9.79372823,3.49050871 C9.5462021,3.24298258 9.12072475,2.81750523 8.51863415,2.21675262 C8.23498258,1.93979094 7.77605575,1.94246691 7.49508014,2.22344251 L2.22611847,7.48839024 C2.08996122,7.62362369 2.01339073,7.80759494 2.01339073,7.99949826 C2.01339073,8.19140157 2.08996122,8.37537283 2.22611847,8.51060628 L7.49374216,13.774216 C7.77653032,14.0565208 8.23450801,14.0565208 8.51729616,13.774216 L10.7262997,11.5665505 C10.9805157,11.3136725 10.9805157,10.9015749 10.7262997,10.6486969 C10.4721014,10.395765 10.0613062,10.395765 9.80710802,10.6486969 L8.13463415,12.3238467 C8.06505924,12.3934216 7.95668293,12.3934216 7.88710802,12.3238467 L3.67916376,8.11991637 C3.60958885,8.05034146 3.60958885,7.94196516 3.67916376,7.87239024 L7.88577003,3.66845993 C7.89112195,3.66310802 7.89781184,3.65909408 7.90316376,3.65374216 C7.97273867,3.59888502 8.06907317,3.60423694 8.13329616,3.66845993 L9.808446,5.34227177 C10.062662,5.5964878 10.4747596,5.5964878 10.7276376,5.34227177 Z M6.61067595,8.02625784 C6.61067595,8.5339086 6.88175941,9.00299725 7.32181183,9.25682264 C7.76186426,9.51064802 8.30403121,9.51064802 8.74408363,9.25682264 C9.18413606,9.00299725 9.45521951,8.5339086 9.45521951,8.02625784 C9.45521951,7.24149776 8.81844675,6.60532403 8.03294773,6.60532403 C7.24744872,6.60532403 6.61067595,7.24149776 6.61067595,8.02625784 L6.61067595,8.02625784 Z M13.7849199,7.5097979 L12.1405436,5.87344948 C11.8863275,5.62057143 11.47423,5.62057143 11.2213519,5.87478746 C11.0992832,5.99631438 11.03066,6.16146576 11.03066,6.33371429 C11.03066,6.50596282 11.0992832,6.6711142 11.2213519,6.79264112 L12.3318746,7.90182579 C12.4014495,7.9714007 12.4014495,8.07977701 12.3318746,8.14935192 L11.2374077,9.24248083 C11.115339,9.36400775 11.0467158,9.52915913 11.0467158,9.70140766 C11.0467158,9.87365619 11.115339,10.0388076 11.2374077,10.1603345 C11.4916059,10.4132663 11.9024011,10.4132663 12.1565993,10.1603345 L13.7862578,8.53201394 C13.9219406,8.3964443 13.9980647,8.21243242 13.9978143,8.02062814 C13.9975626,7.82882386 13.9209571,7.64501189 13.7849199,7.5097979 L13.7849199,7.5097979 Z" id="形状" fill-opacity="0.65" fill="#FFFFFF"></path>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.1 KiB