mirror of https://github.com/openspug/spug
A 添加主机批量导入功能
parent
0b870654d1
commit
ac30b34e54
|
@ -7,5 +7,6 @@ from .views import *
|
|||
|
||||
urlpatterns = [
|
||||
path('', HostView.as_view()),
|
||||
path('import/', post_import),
|
||||
path('ssh/<int:h_id>/', web_ssh),
|
||||
]
|
||||
|
|
|
@ -12,7 +12,8 @@ from apps.app.models import Deploy
|
|||
from apps.schedule.models import Task
|
||||
from apps.monitor.models import Detection
|
||||
from libs.ssh import SSH, AuthenticationException
|
||||
from libs import human_datetime
|
||||
from libs import human_datetime, AttrDict
|
||||
from openpyxl import load_workbook
|
||||
|
||||
|
||||
class HostView(View):
|
||||
|
@ -69,6 +70,38 @@ class HostView(View):
|
|||
return json_response(error=error)
|
||||
|
||||
|
||||
def post_import(request):
|
||||
password = request.POST.get('password')
|
||||
file = request.FILES['file']
|
||||
ws = load_workbook(file, read_only=True)['Sheet1']
|
||||
summary = {'invalid': [], 'skip': [], 'fail': [], 'success': []}
|
||||
for i, row in enumerate(ws.rows):
|
||||
if i == 0: # 第1行是表头 略过
|
||||
continue
|
||||
if not all([row[x].value for x in range(5)]):
|
||||
summary['invalid'].append(i)
|
||||
continue
|
||||
data = AttrDict(
|
||||
zone=row[0].value,
|
||||
name=row[1].value,
|
||||
hostname=row[2].value,
|
||||
port=row[3].value,
|
||||
username=row[4].value,
|
||||
password=row[5].value,
|
||||
desc=row[6].value
|
||||
)
|
||||
if Host.objects.filter(hostname=data.hostname, port=data.port, username=data.username,
|
||||
deleted_by_id__isnull=True).exists():
|
||||
summary['skip'].append(i)
|
||||
continue
|
||||
if valid_ssh(data.hostname, data.port, data.username, data.pop('password') or password) is False:
|
||||
summary['fail'].append(i)
|
||||
continue
|
||||
Host.objects.create(created_by=request.user, **data)
|
||||
summary['success'].append(i)
|
||||
return json_response(summary)
|
||||
|
||||
|
||||
def web_ssh(request, h_id):
|
||||
host = Host.objects.filter(pk=h_id).first()
|
||||
if not host:
|
||||
|
|
|
@ -7,3 +7,4 @@ django-redis==4.10.0
|
|||
requests==2.22.0
|
||||
GitPython==3.0.8
|
||||
python-ldap==3.2.0
|
||||
openpyxl==3.0.3
|
Binary file not shown.
|
@ -0,0 +1,92 @@
|
|||
/**
|
||||
* Copyright (c) OpenSpug Organization. https://github.com/openspug/spug
|
||||
* Copyright (c) <spug.dev@gmail.com>
|
||||
* Released under the MIT License.
|
||||
*/
|
||||
import React from 'react';
|
||||
import { observer } from 'mobx-react';
|
||||
import { Modal, Form, Input, Upload, Icon, Button, Tooltip, Alert } from 'antd';
|
||||
import http from 'libs/http';
|
||||
import store from './store';
|
||||
|
||||
@observer
|
||||
class ComImport extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
loading: false,
|
||||
password: null,
|
||||
fileList: [],
|
||||
}
|
||||
}
|
||||
|
||||
handleSubmit = () => {
|
||||
this.setState({loading: true});
|
||||
const formData = new FormData();
|
||||
formData.append('file', this.state.fileList[0]);
|
||||
formData.append('password', this.state.password);
|
||||
http.post('/api/host/import/', formData)
|
||||
.then(res => {
|
||||
Modal.info({
|
||||
title: '导入结果',
|
||||
content: <Form labelCol={{span: 5}} wrapperCol={{span: 14}}>
|
||||
<Form.Item style={{margin: 0}} label="导入成功">{res.success.length}</Form.Item>
|
||||
{res['fail'].length > 0 && <Form.Item style={{margin: 0, color: '#1890ff'}} label="验证失败">
|
||||
<Tooltip title={`相关行:${res['fail'].join(', ')}`}>{res['fail'].length}</Tooltip>
|
||||
</Form.Item>}
|
||||
{res['skip'].length > 0 && <Form.Item style={{margin: 0, color: '#1890ff'}} label="重复数据">
|
||||
<Tooltip title={`相关行:${res['skip'].join(', ')}`}>{res['skip'].length}</Tooltip>
|
||||
</Form.Item>}
|
||||
{res['invalid'].length > 0 && <Form.Item style={{margin: 0, color: '#1890ff'}} label="无效数据">
|
||||
<Tooltip title={`相关行:${res['invalid'].join(', ')}`}>{res['invalid'].length}</Tooltip>
|
||||
</Form.Item>}
|
||||
</Form>
|
||||
})
|
||||
})
|
||||
.finally(() => this.setState({loading: false}))
|
||||
};
|
||||
|
||||
beforeUpload = (file) => {
|
||||
this.setState({fileList: [file]});
|
||||
return false
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Modal
|
||||
visible
|
||||
width={800}
|
||||
maskClosable={false}
|
||||
title="批量导入"
|
||||
okText="导入"
|
||||
onCancel={() => store.importVisible = false}
|
||||
confirmLoading={this.state.loading}
|
||||
okButtonProps={{disabled: !this.state.fileList.length}}
|
||||
onOk={this.handleSubmit}>
|
||||
<Alert closable showIcon type="info" message={null}
|
||||
style={{width: 600, margin: '0 auto 20px', color: '#31708f !important'}}
|
||||
description="导入或输入的密码仅作首次验证使用,并不会存储密码。"/>
|
||||
<Form labelCol={{span: 6}} wrapperCol={{span: 14}}>
|
||||
<Form.Item label="模板下载" help="请下载使用该模板填充数据后导入">
|
||||
<a href="/resource/主机导入模板.xlsx">主机导入模板.xlsx</a>
|
||||
</Form.Item>
|
||||
<Form.Item label="默认密码" help="如果excel中密码为空则使用该密码">
|
||||
<Input
|
||||
value={this.state.password}
|
||||
onChange={e => this.setState({password: e.target.value})}
|
||||
placeholder="请输入默认主机密码"/>
|
||||
</Form.Item>
|
||||
<Form.Item required label="导入数据">
|
||||
<Upload name="file" accept=".xls, .xlsx" fileList={this.state.fileList} beforeUpload={this.beforeUpload}>
|
||||
<Button>
|
||||
<Icon type="upload"/> 点击上传
|
||||
</Button>
|
||||
</Upload>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default ComImport
|
|
@ -8,6 +8,7 @@ import { observer } from 'mobx-react';
|
|||
import { Table, Divider, Modal, message } from 'antd';
|
||||
import { LinkButton } from 'components';
|
||||
import ComForm from './Form';
|
||||
import ComImport from './Import';
|
||||
import http from 'libs/http';
|
||||
import store from './store';
|
||||
|
||||
|
@ -85,6 +86,7 @@ class ComTable extends React.Component {
|
|||
<React.Fragment>
|
||||
<Table rowKey="id" loading={store.isFetching} dataSource={data} columns={this.columns}/>
|
||||
{store.formVisible && <ComForm/>}
|
||||
{store.importVisible && <ComImport/>}
|
||||
</React.Fragment>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -33,6 +33,8 @@ export default observer(function () {
|
|||
</SearchForm>
|
||||
<AuthDiv auth="host.host.add" style={{marginBottom: 16}}>
|
||||
<Button type="primary" icon="plus" onClick={() => store.showForm()}>新建</Button>
|
||||
<Button style={{marginLeft: 20}} type="primary" icon="import"
|
||||
onClick={() => store.importVisible = true}>批量导入</Button>
|
||||
</AuthDiv>
|
||||
<ComTable/>
|
||||
</AuthCard>
|
||||
|
|
|
@ -13,6 +13,7 @@ class Store {
|
|||
@observable idMap = {};
|
||||
@observable isFetching = false;
|
||||
@observable formVisible = false;
|
||||
@observable importVisible = false;
|
||||
|
||||
@observable f_name;
|
||||
@observable f_zone;
|
||||
|
|
Loading…
Reference in New Issue