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 = [ urlpatterns = [
path('', HostView.as_view()), path('', HostView.as_view()),
path('import/', post_import),
path('ssh/<int:h_id>/', web_ssh), 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.schedule.models import Task
from apps.monitor.models import Detection from apps.monitor.models import Detection
from libs.ssh import SSH, AuthenticationException 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): class HostView(View):
@ -69,6 +70,38 @@ class HostView(View):
return json_response(error=error) 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): def web_ssh(request, h_id):
host = Host.objects.filter(pk=h_id).first() host = Host.objects.filter(pk=h_id).first()
if not host: if not host:

View File

@ -7,3 +7,4 @@ django-redis==4.10.0
requests==2.22.0 requests==2.22.0
GitPython==3.0.8 GitPython==3.0.8
python-ldap==3.2.0 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 { Table, Divider, Modal, message } from 'antd';
import { LinkButton } from 'components'; import { LinkButton } from 'components';
import ComForm from './Form'; import ComForm from './Form';
import ComImport from './Import';
import http from 'libs/http'; import http from 'libs/http';
import store from './store'; import store from './store';
@ -85,6 +86,7 @@ class ComTable extends React.Component {
<React.Fragment> <React.Fragment>
<Table rowKey="id" loading={store.isFetching} dataSource={data} columns={this.columns}/> <Table rowKey="id" loading={store.isFetching} dataSource={data} columns={this.columns}/>
{store.formVisible && <ComForm/>} {store.formVisible && <ComForm/>}
{store.importVisible && <ComImport/>}
</React.Fragment> </React.Fragment>
) )
} }

View File

@ -33,6 +33,8 @@ export default observer(function () {
</SearchForm> </SearchForm>
<AuthDiv auth="host.host.add" style={{marginBottom: 16}}> <AuthDiv auth="host.host.add" style={{marginBottom: 16}}>
<Button type="primary" icon="plus" onClick={() => store.showForm()}>新建</Button> <Button type="primary" icon="plus" onClick={() => store.showForm()}>新建</Button>
<Button style={{marginLeft: 20}} type="primary" icon="import"
onClick={() => store.importVisible = true}>批量导入</Button>
</AuthDiv> </AuthDiv>
<ComTable/> <ComTable/>
</AuthCard> </AuthCard>

View File

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