From e026ce09bfb12847d0f452a1f22f0117cfae1b12 Mon Sep 17 00:00:00 2001 From: vapao Date: Thu, 31 Mar 2022 00:52:40 +0800 Subject: [PATCH] =?UTF-8?q?A=20=E4=B8=BB=E6=9C=BAexcel=E5=AF=BC=E5=85=A5?= =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E5=AF=86=E7=A0=81=E5=AD=97=E6=AE=B5=E5=B9=B6?= =?UTF-8?q?=E4=BC=98=E5=8C=96=E5=AF=BC=E5=85=A5=E4=BD=93=E9=AA=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- spug_api/apps/host/utils.py | 4 +- spug_api/apps/host/views.py | 21 ++++-- spug_web/public/resource/主机导入模板.xlsx | Bin 8760 -> 9119 bytes spug_web/src/pages/host/BatchSync.js | 42 ++---------- spug_web/src/pages/host/Import.js | 75 ++++++++++++--------- spug_web/src/pages/host/Sync.js | 46 +++++++++++++ spug_web/src/pages/host/index.module.less | 13 ++++ 7 files changed, 128 insertions(+), 73 deletions(-) create mode 100644 spug_web/src/pages/host/Sync.js diff --git a/spug_api/apps/host/utils.py b/spug_api/apps/host/utils.py index 463a33c..d550e95 100644 --- a/spug_api/apps/host/utils.py +++ b/spug_api/apps/host/utils.py @@ -248,12 +248,14 @@ def fetch_host_extend(ssh): return response -def batch_sync_host(token, hosts, password): +def batch_sync_host(token, hosts, password=None): private_key, public_key = AppSetting.get_ssh_key() threads, latest_exception, rds = [], None, get_redis_connection() max_workers = max(10, os.cpu_count() * 5) with futures.ThreadPoolExecutor(max_workers=max_workers) as executor: for host in hosts: + if hasattr(host, 'password'): + password = host.password t = executor.submit(_sync_host_extend, host, private_key, public_key, password) t.host = host threads.append(t) diff --git a/spug_api/apps/host/views.py b/spug_api/apps/host/views.py index db7c963..8fb6e36 100644 --- a/spug_api/apps/host/views.py +++ b/spug_api/apps/host/views.py @@ -129,31 +129,40 @@ class HostView(View): def post_import(request): group_id = request.POST.get('group_id') file = request.FILES['file'] + hosts = [] ws = load_workbook(file, read_only=True)['Sheet1'] - summary = {'invalid': [], 'skip': [], 'repeat': [], 'success': []} - for i, row in enumerate(ws.rows): - if i == 0: # 第1行是表头 略过 + summary = {'fail': 0, 'success': 0, 'invalid': [], 'skip': [], 'repeat': []} + for i, row in enumerate(ws.rows, start=1): + if i == 1: # 第1行是表头 略过 continue if not all([row[x].value for x in range(4)]): summary['invalid'].append(i) + summary['fail'] += 1 continue data = AttrDict( name=row[0].value, hostname=row[1].value, port=row[2].value, username=row[3].value, - desc=row[4].value + desc=row[5].value ) if Host.objects.filter(hostname=data.hostname, port=data.port, username=data.username).exists(): summary['skip'].append(i) + summary['fail'] += 1 continue if Host.objects.filter(name=data.name).exists(): summary['repeat'].append(i) + summary['fail'] += 1 continue host = Host.objects.create(created_by=request.user, **data) host.groups.add(group_id) - summary['success'].append(i) - return json_response(summary) + summary['success'] += 1 + host.password = row[4].value + hosts.append(host) + token = uuid.uuid4().hex + if hosts: + Thread(target=batch_sync_host, args=(token, hosts)).start() + return json_response({'summary': summary, 'token': token, 'hosts': {x.id: {'name': x.name} for x in hosts}}) @auth('host.host.add') diff --git a/spug_web/public/resource/主机导入模板.xlsx b/spug_web/public/resource/主机导入模板.xlsx index 415e78ace78e066b6cae071e7d2bc7195193bf89..4ca6e2f69afca540360957983397dc508edd060f 100644 GIT binary patch literal 9119 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 Vu+i}N-qj}nw4+1lJLd0q{|DeWCt&~p literal 8760 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 * Released under the AGPL-3.0 License. */ -import React, { useState, useEffect } from 'react'; +import React, { useState } from 'react'; import { observer } from 'mobx-react'; import { Modal, Form, Input, Button, Radio } from 'antd'; -import { LoadingOutlined } from '@ant-design/icons'; -import { http, X_TOKEN } from 'libs'; +import Sync from './Sync'; +import { http } from 'libs'; import store from './store'; export default observer(function () { const [loading, setLoading] = useState(false); const [password, setPassword] = useState(); const [range, setRange] = useState('2'); - const [hosts, setHosts] = useState({}); + const [hosts, setHosts] = useState(); const [token, setToken] = useState(); - useEffect(() => { - if (token) { - let index = 0; - const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'; - const socket = new WebSocket(`${protocol}//${window.location.host}/api/ws/host/${token}/?x-token=${X_TOKEN}`); - socket.onopen = () => socket.send(String(index)); - socket.onmessage = e => { - if (e.data === 'pong') { - socket.send(String(index)) - } else { - index += 1; - const {key, status, message} = JSON.parse(e.data); - hosts[key]['status'] = status; - hosts[key]['message'] = message; - setHosts({...hosts}) - } - } - return () => socket && socket.close() - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [token]) - function handleSubmit() { setLoading(true); http.post('/api/host/valid/', {password, range}) @@ -82,15 +60,9 @@ export default observer(function () { - + {token && hosts ? ( + + ) : null} ); }) diff --git a/spug_web/src/pages/host/Import.js b/spug_web/src/pages/host/Import.js index d58635f..e77c3e6 100644 --- a/spug_web/src/pages/host/Import.js +++ b/spug_web/src/pages/host/Import.js @@ -5,8 +5,9 @@ */ import React, { useState } from 'react'; import { observer } from 'mobx-react'; +import { Modal, Form, Upload, Button, Tooltip, Divider, Cascader, message } from 'antd'; import { UploadOutlined } from '@ant-design/icons'; -import { Modal, Form, Upload, Button, Tooltip, Alert, Cascader, message } from 'antd'; +import Sync from './Sync'; import http from 'libs/http'; import store from './store'; @@ -14,6 +15,9 @@ export default observer(function () { const [loading, setLoading] = useState(false); const [fileList, setFileList] = useState([]); const [groupId, setGroupId] = useState([]); + const [summary, setSummary] = useState({}); + const [token, setToken] = useState(); + const [hosts, setHosts] = useState(); function handleSubmit() { if (groupId.length === 0) return message.error('请选择要导入的分组'); @@ -23,25 +27,9 @@ export default observer(function () { formData.append('group_id', groupId[groupId.length - 1]); http.post('/api/host/import/', formData, {timeout: 120000}) .then(res => { - Modal.info({ - title: '导入结果', - content:
- {res.success.length} - {res['skip'].length > 0 && - {res['skip'].length} - } - {res['invalid'].length > 0 && - {res['invalid'].length} - } - {res['repeat'].length > 0 && - {res['repeat'].length} - } -
, - onOk: () => { - store.fetchRecords(); - store.importVisible = false - } - }) + setToken(res.token) + setHosts(res.hosts) + setSummary(res.summary) }) .finally(() => setLoading(false)) } @@ -54,22 +42,20 @@ export default observer(function () { } } + function handleClose() { + store.importVisible = false; + store.fetchRecords() + } + return ( store.importVisible = false} - confirmLoading={loading} - okButtonProps={{disabled: !fileList.length}} - onOk={handleSubmit}> - -
+ onCancel={handleClose} + footer={null}> + 主机导入模板.xlsx @@ -81,7 +67,7 @@ export default observer(function () { fieldNames={{label: 'title'}} placeholder="请选择"/> - + + + + + + {token && hosts ? ( +
+ 导入结果 +
+
成功:{summary.success}
+
失败:{summary.fail > 0 ? ( + + {summary.skip.map(x =>
第 {x} 行,重复的服务器信息
)} + {summary.repeat.map(x =>
第 {x} 行,重复的主机名称
)} + {summary.invalid.map(x =>
第 {x} 行,无效的数据
)} +
+ )}>{summary.fail} + ) : 0}
+
+ {Object.keys(hosts).length > 0 && ( + <> + 验证及同步 + + + )} + + ) : null}
); }) diff --git a/spug_web/src/pages/host/Sync.js b/spug_web/src/pages/host/Sync.js new file mode 100644 index 0000000..1d7d13c --- /dev/null +++ b/spug_web/src/pages/host/Sync.js @@ -0,0 +1,46 @@ +/** + * Copyright (c) OpenSpug Organization. https://github.com/openspug/spug + * Copyright (c) + * Released under the AGPL-3.0 License. + */ +import React, { useState, useEffect } from 'react'; +import { Form } from 'antd'; +import { LoadingOutlined } from '@ant-design/icons'; +import { X_TOKEN } from 'libs'; +import styles from './index.module.less'; + +export default function (props) { + const [hosts, setHosts] = useState(props.hosts); + + useEffect(() => { + let index = 0; + const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'; + const socket = new WebSocket(`${protocol}//${window.location.host}/api/ws/host/${props.token}/?x-token=${X_TOKEN}`); + socket.onopen = () => socket.send(String(index)); + socket.onmessage = e => { + if (e.data === 'pong') { + socket.send(String(index)) + } else { + index += 1; + const {key, status, message} = JSON.parse(e.data); + hosts[key]['status'] = status; + hosts[key]['message'] = message; + setHosts({...hosts}) + } + } + return () => socket && socket.close() + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []) + + return ( +
+ {Object.entries(hosts).map(([key, item]) => ( + + {item.status === 'ok' && 成功} + {item.status === 'fail' && 失败} + {item.status === undefined && } + + ))} +
+ ) +} diff --git a/spug_web/src/pages/host/index.module.less b/spug_web/src/pages/host/index.module.less index 30d4517..27c2ccd 100644 --- a/spug_web/src/pages/host/index.module.less +++ b/spug_web/src/pages/host/index.module.less @@ -68,4 +68,17 @@ text-overflow: ellipsis; white-space: nowrap; } +} + +.batchSync { + max-height: calc(100vh - 300px); + overflow: auto; + + :global(.ant-form-item) { + margin-bottom: 4px; + } + + :global(.ant-form-item-extra) { + padding-top: 0; + } } \ No newline at end of file