diff --git a/spug_api/apps/host/urls.py b/spug_api/apps/host/urls.py index 7be76ea..2acdb2e 100644 --- a/spug_api/apps/host/urls.py +++ b/spug_api/apps/host/urls.py @@ -7,5 +7,6 @@ from .views import * urlpatterns = [ path('', HostView.as_view()), + path('import/', post_import), path('ssh//', web_ssh), ] diff --git a/spug_api/apps/host/views.py b/spug_api/apps/host/views.py index 83291fc..49b8d66 100644 --- a/spug_api/apps/host/views.py +++ b/spug_api/apps/host/views.py @@ -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: diff --git a/spug_api/requirements.txt b/spug_api/requirements.txt index 1532c93..4134d20 100644 --- a/spug_api/requirements.txt +++ b/spug_api/requirements.txt @@ -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 \ No newline at end of file diff --git a/spug_web/public/resource/主机导入模板.xlsx b/spug_web/public/resource/主机导入模板.xlsx new file mode 100644 index 0000000..1c3c1dd Binary files /dev/null and b/spug_web/public/resource/主机导入模板.xlsx differ diff --git a/spug_web/src/pages/host/Import.js b/spug_web/src/pages/host/Import.js new file mode 100644 index 0000000..0c5f718 --- /dev/null +++ b/spug_web/src/pages/host/Import.js @@ -0,0 +1,92 @@ +/** + * Copyright (c) OpenSpug Organization. https://github.com/openspug/spug + * Copyright (c) + * 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:
+ {res.success.length} + {res['fail'].length > 0 && + {res['fail'].length} + } + {res['skip'].length > 0 && + {res['skip'].length} + } + {res['invalid'].length > 0 && + {res['invalid'].length} + } +
+ }) + }) + .finally(() => this.setState({loading: false})) + }; + + beforeUpload = (file) => { + this.setState({fileList: [file]}); + return false + }; + + render() { + return ( + store.importVisible = false} + confirmLoading={this.state.loading} + okButtonProps={{disabled: !this.state.fileList.length}} + onOk={this.handleSubmit}> + +
+ + 主机导入模板.xlsx + + + this.setState({password: e.target.value})} + placeholder="请输入默认主机密码"/> + + + + + + +
+
+ ) + } +} + +export default ComImport diff --git a/spug_web/src/pages/host/Table.js b/spug_web/src/pages/host/Table.js index 5543c5b..0289dbb 100644 --- a/spug_web/src/pages/host/Table.js +++ b/spug_web/src/pages/host/Table.js @@ -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 { {store.formVisible && } + {store.importVisible && } ) } diff --git a/spug_web/src/pages/host/index.js b/spug_web/src/pages/host/index.js index 5f13a3a..536745e 100644 --- a/spug_web/src/pages/host/index.js +++ b/spug_web/src/pages/host/index.js @@ -33,6 +33,8 @@ export default observer(function () { + diff --git a/spug_web/src/pages/host/store.js b/spug_web/src/pages/host/store.js index 9bb731c..88a4273 100644 --- a/spug_web/src/pages/host/store.js +++ b/spug_web/src/pages/host/store.js @@ -13,6 +13,7 @@ class Store { @observable idMap = {}; @observable isFetching = false; @observable formVisible = false; + @observable importVisible = false; @observable f_name; @observable f_zone;