添加依赖管理

This commit is contained in:
whyour
2021-10-23 18:23:32 +08:00
parent bad247c51c
commit 795d1b938d
19 changed files with 1170 additions and 17 deletions
+6
View File
@@ -48,6 +48,12 @@ export default {
icon: <FormOutlined />,
component: '@/pages/script/index',
},
{
path: '/dependence',
name: '依赖管理',
icon: <FormOutlined />,
component: '@/pages/dependence/index',
},
{
path: '/diff',
name: '对比工具',
+8 -3
View File
@@ -129,9 +129,14 @@ export default function (props: any) {
);
ws.current.onmessage = (e: any) => {
if (e.data === 'hanhh') {
console.log('websocket连接成功', e);
} else {
try {
const data = JSON.parse(e.data);
if (data && data.message === 'hanhh') {
console.log('websocket连接成功', e);
} else {
console.log('websocket连接失败', e);
}
} catch (error) {
console.log('websocket连接失败', e);
}
};
+4 -4
View File
@@ -27,7 +27,7 @@ const CronLogModal = ({
}) => {
const [value, setValue] = useState<string>('启动中...');
const [loading, setLoading] = useState<any>(true);
const [excuting, setExcuting] = useState<any>(true);
const [executing, setExecuting] = useState<any>(true);
const [isPhone, setIsPhone] = useState(false);
const [theme, setTheme] = useState<string>('');
@@ -41,7 +41,7 @@ const CronLogModal = ({
if (localStorage.getItem('logCron') === cron._id) {
const log = data.data as string;
setValue(log || '暂无日志');
setExcuting(
setExecuting(
log && !log.includes('执行结束') && !log.includes('重启面板'),
);
if (log && !log.includes('执行结束') && !log.includes('重启面板')) {
@@ -89,8 +89,8 @@ const CronLogModal = ({
const titleElement = () => {
return (
<>
{(excuting || loading) && <Loading3QuartersOutlined spin />}
{!excuting && <CheckCircleOutlined />}
{(executing || loading) && <Loading3QuartersOutlined spin />}
{!executing && <CheckCircleOutlined />}
<span style={{ marginLeft: 5 }}>-{cron && cron.name}</span>{' '}
</>
);
View File
+424
View File
@@ -0,0 +1,424 @@
import React, { useCallback, useRef, useState, useEffect } from 'react';
import {
Button,
message,
Modal,
Table,
Tag,
Space,
Typography,
Tooltip,
Input,
Tabs,
} from 'antd';
import {
EditOutlined,
DeleteOutlined,
SyncOutlined,
CheckCircleOutlined,
StopOutlined,
BugOutlined,
FileTextOutlined,
} from '@ant-design/icons';
import config from '@/utils/config';
import { PageContainer } from '@ant-design/pro-layout';
import { request } from '@/utils/http';
import DependenceModal from './modal';
import { DndProvider, useDrag, useDrop } from 'react-dnd';
import { HTML5Backend } from 'react-dnd-html5-backend';
import './index.less';
import { getTableScroll } from '@/utils/index';
import DependenceLogModal from './logModal';
const { Text } = Typography;
const { Search } = Input;
enum Status {
'安装中',
'已安装',
'安装失败',
}
enum StatusColor {
'processing',
'success',
'error',
}
const Dependence = ({ headerStyle, isPhone, ws }: any) => {
const columns: any = [
{
title: '序号',
align: 'center' as const,
width: 50,
render: (text: string, record: any, index: number) => {
return <span style={{ cursor: 'text' }}>{index + 1} </span>;
},
},
{
title: '名称',
dataIndex: 'name',
key: 'name',
align: 'center' as const,
},
{
title: '状态',
key: 'status',
dataIndex: 'status',
align: 'center' as const,
render: (text: string, record: any, index: number) => {
return (
<Space size="middle" style={{ cursor: 'text' }}>
<Tag color={StatusColor[record.status]} style={{ marginRight: 0 }}>
{Status[record.status]}
</Tag>
</Space>
);
},
},
{
title: '创建时间',
key: 'created',
dataIndex: 'created',
align: 'center' as const,
render: (text: string, record: any) => {
return <span>{new Date(record.created).toLocaleString()}</span>;
},
},
{
title: '操作',
key: 'action',
align: 'center' as const,
render: (text: string, record: any, index: number) => {
const isPc = !isPhone;
return (
<Space size="middle">
{record.status !== Status. && (
<>
<Tooltip title={isPc ? '重新安装' : ''}>
<a onClick={() => reInstallDependence(record, index)}>
<BugOutlined />
</a>
</Tooltip>
<Tooltip title={isPc ? '删除' : ''}>
<a onClick={() => deleteDependence(record, index)}>
<DeleteOutlined />
</a>
</Tooltip>
</>
)}
<Tooltip title={isPc ? '日志' : ''}>
<a
onClick={() => {
setLogDependence({ ...record, timestamp: Date.now() });
}}
>
<FileTextOutlined />
</a>
</Tooltip>
</Space>
);
},
},
];
const [value, setValue] = useState<any[]>([]);
const [loading, setLoading] = useState(true);
const [isModalVisible, setIsModalVisible] = useState(false);
const [editedDependence, setEditedDependence] = useState();
const [selectedRowIds, setSelectedRowIds] = useState<string[]>([]);
const [searchText, setSearchText] = useState('');
const [tableScrollHeight, setTableScrollHeight] = useState<number>();
const [logDependence, setLogDependence] = useState<any>();
const [isLogModalVisible, setIsLogModalVisible] = useState(false);
const [type, setType] = useState('nodejs');
const getDependencies = () => {
setLoading(true);
request
.get(
`${config.apiPrefix}dependencies?searchValue=${searchText}&type=${type}`,
)
.then((data: any) => {
setValue(data.data);
})
.finally(() => setLoading(false));
};
const addDependence = () => {
setEditedDependence(null as any);
setIsModalVisible(true);
};
const editDependence = (record: any, index: number) => {
setEditedDependence(record);
setIsModalVisible(true);
};
const deleteDependence = (record: any, index: number) => {
Modal.confirm({
title: '确认删除',
content: (
<>
{' '}
<Text style={{ wordBreak: 'break-all' }} type="warning">
{record.name}
</Text>{' '}
</>
),
onOk() {
request
.delete(`${config.apiPrefix}dependencies`, { data: [record._id] })
.then((data: any) => {
if (data.code === 200) {
message.success('删除成功');
const result = [...value];
result.splice(index, 1);
setValue(result);
} else {
message.error(data);
}
});
},
onCancel() {
console.log('Cancel');
},
});
};
const reInstallDependence = (record: any, index: number) => {
Modal.confirm({
title: '确认重新安装',
content: (
<>
{' '}
<Text style={{ wordBreak: 'break-all' }} type="warning">
{record.name}
</Text>{' '}
</>
),
onOk() {
request
.put(`${config.apiPrefix}dependencies/reinstall`, {
data: [record._id],
})
.then((data: any) => {
if (data.code === 200) {
handleDependence(data.data[0]);
} else {
message.error(data);
}
});
},
onCancel() {
console.log('Cancel');
},
});
};
const handleCancel = (dependence?: any[]) => {
setIsModalVisible(false);
dependence && handleDependence(dependence);
};
const handleDependence = (dependence: any) => {
const result = [...value];
if (Array.isArray(dependence)) {
result.push(...dependence);
} else {
const index = value.findIndex((x) => x._id === dependence._id);
result.splice(index, 1, {
...dependence,
});
}
setValue(result);
};
const onSelectChange = (selectedIds: any[]) => {
setSelectedRowIds(selectedIds);
setTimeout(() => {
if (selectedRowIds.length === 0 || selectedIds.length === 0) {
const offset = isPhone ? 40 : 0;
setTableScrollHeight(getTableScroll({ extraHeight: 87 }) - offset);
}
});
};
const rowSelection = {
selectedRowIds,
onChange: onSelectChange,
};
const delDependencies = () => {
Modal.confirm({
title: '确认删除',
content: <></>,
onOk() {
request
.delete(`${config.apiPrefix}dependencies`, { data: selectedRowIds })
.then((data: any) => {
if (data.code === 200) {
message.success('批量删除成功');
setSelectedRowIds([]);
getDependencies();
} else {
message.error(data);
}
});
},
onCancel() {
console.log('Cancel');
},
});
};
const getDependenceDetail = (dependence: any) => {
request
.get(`${config.apiPrefix}dependencies/${dependence._id}`)
.then((data: any) => {
const index = value.findIndex((x) => x._id === dependence._id);
const result = [...value];
result.splice(index, 1, {
...dependence,
...data.data,
});
setValue(result);
})
.finally(() => setLoading(false));
};
const onSearch = (value: string) => {
setSearchText(value.trim());
};
useEffect(() => {
getDependencies();
}, [searchText, type]);
useEffect(() => {
const offset = isPhone ? 40 : 0;
setTableScrollHeight(getTableScroll({ extraHeight: 87 }) - offset);
}, []);
useEffect(() => {
if (logDependence) {
localStorage.setItem('logDependence', logDependence._id);
setIsLogModalVisible(true);
}
}, [logDependence]);
useEffect(() => {
ws.onmessage = (e: any) => {
const { type, message, references } = JSON.parse(e.data);
if (
type === 'installDependence' &&
message === '安装结束' &&
references.length > 0
) {
const result = [...value];
for (let i = 0; i < references.length; i++) {
const index = value.findIndex((x) => x._id === references[i]);
result.splice(index, 1, {
...result[index],
status: Status.已安装,
});
}
setValue(result);
}
};
}, [value]);
const panelContent = () => (
<>
{selectedRowIds.length > 0 && (
<div style={{ marginBottom: 16 }}>
<Button
type="primary"
style={{ marginBottom: 5, marginLeft: 8 }}
onClick={delDependencies}
>
</Button>
<span style={{ marginLeft: 8 }}>
<a>{selectedRowIds?.length}</a>
</span>
</div>
)}
<DndProvider backend={HTML5Backend}>
<Table
columns={columns}
rowSelection={rowSelection}
pagination={false}
dataSource={value}
rowKey="_id"
size="middle"
scroll={{ x: 768, y: tableScrollHeight }}
loading={loading}
/>
</DndProvider>
</>
);
const onTabChange = (activeKey: string) => {
setType(activeKey);
};
return (
<PageContainer
className="ql-container-wrapper dependence-wrapper"
title="依赖管理"
extra={[
<Search
placeholder="请输入名称"
style={{ width: 'auto' }}
enterButton
loading={loading}
onSearch={onSearch}
/>,
<Button key="2" type="primary" onClick={() => addDependence()}>
</Button>,
]}
header={{
style: headerStyle,
}}
>
<Tabs
defaultActiveKey="nodejs"
size="small"
tabPosition="top"
onChange={onTabChange}
>
<Tabs.TabPane tab="nodejs" key="nodejs">
{panelContent()}
</Tabs.TabPane>
<Tabs.TabPane tab="python3" key="python3">
{panelContent()}
</Tabs.TabPane>
<Tabs.TabPane tab="linux" key="linux">
{panelContent()}
</Tabs.TabPane>
</Tabs>
<DependenceModal
visible={isModalVisible}
handleCancel={handleCancel}
dependence={editedDependence}
defaultType={type}
/>
<DependenceLogModal
visible={isLogModalVisible}
handleCancel={() => {
setIsLogModalVisible(false);
getDependenceDetail(logDependence);
}}
ws={ws}
dependence={logDependence}
/>
</PageContainer>
);
};
export default Dependence;
+122
View File
@@ -0,0 +1,122 @@
import React, { useEffect, useState } from 'react';
import { Modal, message, Input, Form, Statistic, Button } from 'antd';
import { request } from '@/utils/http';
import config from '@/utils/config';
import {
Loading3QuartersOutlined,
CheckCircleOutlined,
} from '@ant-design/icons';
import { PageLoading } from '@ant-design/pro-layout';
const DependenceLogModal = ({
dependence,
handleCancel,
visible,
ws,
}: {
dependence?: any;
visible: boolean;
handleCancel: () => void;
ws: any;
}) => {
const [value, setValue] = useState<string>('');
const [executing, setExecuting] = useState<any>(true);
const [isPhone, setIsPhone] = useState(false);
const [loading, setLoading] = useState<any>(true);
const cancel = () => {
localStorage.removeItem('logDependence');
handleCancel();
};
const titleElement = () => {
return (
<>
{executing && <Loading3QuartersOutlined spin />}
{!executing && <CheckCircleOutlined />}
<span style={{ marginLeft: 5 }}>
- {dependence && dependence.name}
</span>{' '}
</>
);
};
const getDependenceLog = () => {
setLoading(true);
request
.get(`${config.apiPrefix}dependencies/${dependence._id}`)
.then((data: any) => {
if (localStorage.getItem('logDependence') === dependence._id) {
const log = (data.data.log || []).join('\n') as string;
setValue(log);
setExecuting(!log.includes('安装结束'));
}
})
.finally(() => {
setLoading(false);
});
};
useEffect(() => {
if (dependence) {
getDependenceLog();
ws.onmessage = (e: any) => {
const { type, message, references } = JSON.parse(e.data);
if (
type === 'installDependence' &&
message === '安装结束' &&
references.length > 0
) {
setExecuting(false);
}
setValue(`${value} \n ${message}`);
};
}
}, [dependence]);
useEffect(() => {
setIsPhone(document.body.clientWidth < 768);
}, []);
return (
<Modal
title={titleElement()}
visible={visible}
centered
className="log-modal"
bodyStyle={{
overflowY: 'auto',
maxHeight: 'calc(70vh - var(--vh-offset, 0px))',
minHeight: '300px',
}}
forceRender
onOk={() => cancel()}
onCancel={() => cancel()}
footer={[
<Button type="primary" onClick={() => cancel()}>
</Button>,
]}
>
{loading ? (
<PageLoading />
) : (
<pre
style={
isPhone
? {
fontFamily: 'Source Code Pro',
width: 375,
zoom: 0.83,
}
: {}
}
>
{value}
</pre>
)}
</Modal>
);
};
export default DependenceLogModal;
+139
View File
@@ -0,0 +1,139 @@
import React, { useEffect, useState } from 'react';
import { Modal, message, Input, Form, Radio, Select } from 'antd';
import { request } from '@/utils/http';
import config from '@/utils/config';
const { Option } = Select;
enum DependenceTypes {
'nodejs',
'python3',
'linux',
}
const DependenceModal = ({
dependence,
handleCancel,
visible,
defaultType,
}: {
dependence?: any;
visible: boolean;
handleCancel: (cks?: any[]) => void;
defaultType: string;
}) => {
const [form] = Form.useForm();
const [loading, setLoading] = useState(false);
const handleOk = async (values: any) => {
setLoading(true);
const { name, split, type } = values;
const method = dependence ? 'put' : 'post';
let payload;
if (!dependence) {
if (split === '1') {
const symbol = name.includes('&') ? '&' : '\n';
payload = name.split(symbol).map((x: any) => {
return {
name: x,
type,
};
});
} else {
payload = [{ name, type }];
}
} else {
payload = { ...values, _id: dependence._id };
}
try {
const { code, data } = await request[method](
`${config.apiPrefix}dependencies`,
{
data: payload,
},
);
if (code === 200) {
message.success(dependence ? '更新依赖成功' : '添加依赖成功');
} else {
message.error(data);
}
setLoading(false);
handleCancel(data);
} catch (error) {
setLoading(false);
}
};
useEffect(() => {
form.resetFields();
}, [dependence, visible]);
return (
<Modal
title={dependence ? '编辑依赖' : '新建依赖'}
visible={visible}
forceRender
onOk={() => {
form
.validateFields()
.then((values) => {
handleOk(values);
})
.catch((info) => {
console.log('Validate Failed:', info);
});
}}
onCancel={() => handleCancel()}
confirmLoading={loading}
>
<Form
form={form}
layout="vertical"
name="dependence_modal"
initialValues={dependence}
>
<Form.Item
name="type"
label="依赖类型"
initialValue={DependenceTypes[defaultType as any]}
>
<Select>
{config.dependenceTypes.map((x, i) => (
<Option value={i}>{x}</Option>
))}
</Select>
</Form.Item>
{!dependence && (
<Form.Item
name="split"
label="自动拆分"
initialValue="0"
tooltip="多个依赖是否换行分割"
>
<Radio.Group>
<Radio value="1"></Radio>
<Radio value="0"></Radio>
</Radio.Group>
</Form.Item>
)}
<Form.Item
name="name"
label="名称"
rules={[
{ required: true, message: '请输入依赖名称', whitespace: true },
]}
>
<Input.TextArea
rows={4}
autoSize={true}
placeholder="请输入依赖名称"
/>
</Form.Item>
<Form.Item name="remark" label="备注">
<Input placeholder="请输入备注" />
</Form.Item>
</Form>
</Modal>
);
};
export default DependenceModal;
+2 -2
View File
@@ -368,7 +368,7 @@ const Env = ({ headerStyle, isPhone, theme }: any) => {
setTimeout(() => {
if (selectedRowIds.length === 0 || selectedIds.length === 0) {
const offset = isPhone ? 40 : 0;
setTableScrollHeight(getTableScroll({ extraHeight: 127 }) - offset);
setTableScrollHeight(getTableScroll({ extraHeight: 87 }) - offset);
}
});
};
@@ -438,7 +438,7 @@ const Env = ({ headerStyle, isPhone, theme }: any) => {
useEffect(() => {
const offset = isPhone ? 40 : 0;
setTableScrollHeight(getTableScroll({ extraHeight: 127 }) - offset);
setTableScrollHeight(getTableScroll({ extraHeight: 87 }) - offset);
}, []);
return (
+7 -2
View File
@@ -85,7 +85,12 @@ const EnvModal = ({
<Input placeholder="请输入环境变量名称" />
</Form.Item>
{!env && (
<Form.Item name="split" label="自动拆分" initialValue="0">
<Form.Item
name="split"
label="自动拆分"
initialValue="0"
tooltip="多个依赖是否换行分割"
>
<Radio.Group>
<Radio value="1"></Radio>
<Radio value="0"></Radio>
@@ -106,7 +111,7 @@ const EnvModal = ({
/>
</Form.Item>
<Form.Item name="remarks" label="备注">
<Input />
<Input placeholder="请输入备注" />
</Form.Item>
</Form>
</Modal>
+1
View File
@@ -188,4 +188,5 @@ export default {
'/log': '任务日志',
'/setting': '系统设置',
},
dependenceTypes: ['nodejs', 'python3', 'linux'],
};