A 添加主机批量导入功能

pull/103/head
vapao 2020-05-15 10:06:41 +08:00
parent 0b870654d1
commit ac30b34e54
8 changed files with 133 additions and 1 deletions

View File

@ -7,5 +7,6 @@ from .views import *
urlpatterns = [
path('', HostView.as_view()),
path('import/', post_import),
path('ssh/<int:h_id>/', web_ssh),
]

View File

@ -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:

View File

@ -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.

View File

@ -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

View File

@ -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>
)
}

View File

@ -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>

View File

@ -13,6 +13,7 @@ class Store {
@observable idMap = {};
@observable isFetching = false;
@observable formVisible = false;
@observable importVisible = false;
@observable f_name;
@observable f_zone;