A 主机管理新增导出功能 #563 #545 #535

4.0
vapao 2022-10-22 15:52:24 +08:00
parent b8fbb0e8eb
commit b42b12aa88
4 changed files with 75 additions and 12 deletions

View File

@ -13,6 +13,7 @@ urlpatterns = [
path('extend/', ExtendView.as_view()),
path('group/', GroupView.as_view()),
path('import/', post_import),
path('export/', post_export),
path('import/cloud/', cloud_import),
path('import/region/', get_regions),
path('parse/', post_parse),

View File

@ -3,7 +3,7 @@
# Released under the AGPL-3.0 License.
from django.views.generic import View
from django.db.models import F
from django.http.response import HttpResponseBadRequest
from django.http.response import HttpResponseBadRequest, HttpResponse
from libs import json_response, JsonParser, Argument, AttrDict, auth
from apps.setting.utils import AppSetting
from apps.account.utils import get_host_perms
@ -14,10 +14,11 @@ from apps.schedule.models import Task
from apps.monitor.models import Detection
from libs.ssh import SSH, AuthenticationException
from paramiko.ssh_exception import BadAuthenticationType
from openpyxl import load_workbook
from openpyxl import load_workbook, Workbook
from threading import Thread
import socket
import uuid
import json
class HostView(View):
@ -110,13 +111,16 @@ class HostView(View):
deploy = Deploy.objects.filter(host_ids__regex=regex) \
.annotate(app_name=F('app__name'), env_name=F('env__name')).first()
if deploy:
return json_response(error=f'应用【{deploy.app_name}】在【{deploy.env_name}】的发布配置关联了该主机,请解除关联后再尝试删除该主机')
return json_response(
error=f'应用【{deploy.app_name}】在【{deploy.env_name}】的发布配置关联了该主机,请解除关联后再尝试删除该主机')
task = Task.objects.filter(targets__regex=regex).first()
if task:
return json_response(error=f'任务计划中的任务【{task.name}】关联了该主机,请解除关联后再尝试删除该主机')
return json_response(
error=f'任务计划中的任务【{task.name}】关联了该主机,请解除关联后再尝试删除该主机')
detection = Detection.objects.filter(type__in=('3', '4'), targets__regex=regex).first()
if detection:
return json_response(error=f'监控中心的任务【{detection.name}】关联了该主机,请解除关联后再尝试删除该主机')
return json_response(
error=f'监控中心的任务【{detection.name}】关联了该主机,请解除关联后再尝试删除该主机')
Host.objects.filter(id__in=host_ids).delete()
return json_response(error=error)
@ -161,6 +165,37 @@ def post_import(request):
return json_response({'summary': summary, 'token': token, 'hosts': {x.id: {'name': x.name} for x in hosts}})
@auth('host.host.view')
def post_export(request):
hosts = Host.objects.select_related('hostextend')
if not request.user.is_supper:
hosts = hosts.filter(id__in=get_host_perms(request.user))
wb = Workbook()
ws = wb.active
ws.append(('主机名称', 'SSH地址', 'SSH端口', 'SSH用户', 'SSH密码', '备注信息', '实例ID', '操作系统', 'CPU核心数', '内存GB', '磁盘GB', '内网IP'
'公网IP', '实例计费方式', '网络计费方式', '创建时间', '到期时间'))
for item in hosts:
data = [item.name, item.hostname, item.port, item.username, '', item.desc]
if hasattr(item, 'hostextend'):
data.extend([
item.hostextend.instance_id,
item.hostextend.os_name,
item.hostextend.cpu,
item.hostextend.memory,
','.join(str(x) for x in json.loads(item.hostextend.disk)),
','.join(json.loads(item.hostextend.private_ip_address)),
','.join(json.loads(item.hostextend.public_ip_address)),
item.hostextend.get_instance_charge_type_display(),
item.hostextend.get_internet_charge_type_display(),
item.hostextend.created_time,
item.hostextend.expired_time
])
ws.append(data)
response = HttpResponse(content_type='application/octet-stream')
wb.save(response)
return response
@auth('host.host.add')
def post_parse(request):
file = request.FILES['file']

View File

@ -86,7 +86,7 @@ export function trimFixed(data, bit) {
}
// 日期
export function human_date(date) {
export function humanDate(date) {
const now = date || new Date();
let month = now.getMonth() + 1;
let day = now.getDate();
@ -94,7 +94,7 @@ export function human_date(date) {
}
// 时间
export function human_time(date) {
export function humanTime(date) {
const now = date || new Date();
const hour = now.getHours() < 10 ? '0' + now.getHours() : now.getHours();
const minute = now.getMinutes() < 10 ? '0' + now.getMinutes() : now.getMinutes();
@ -102,8 +102,8 @@ export function human_time(date) {
return `${hour}:${minute}:${second}`
}
export function human_datetime(date) {
return `${human_date(date)} ${human_time(date)}`
export function humanDatetime(date) {
return `${humanDate(date)} ${humanTime(date)}`
}
// 生成唯一id
@ -113,3 +113,15 @@ export function uniqueId() {
return (c === 'x' ? r : (r & 0x3) | 0x8).toString(16)
});
}
export function blobToExcel(data, filename) {
const blob = new Blob([data], {type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'});
const link = document.createElement('a');
link.href = URL.createObjectURL(blob);
link.download = filename;
document.body.appendChild(link);
const evt = document.createEvent("MouseEvents");
evt.initEvent("click", false, false);
link.dispatchEvent(evt);
document.body.removeChild(link);
}

View File

@ -3,17 +3,19 @@
* Copyright (c) <spug.dev@gmail.com>
* Released under the AGPL-3.0 License.
*/
import React from 'react';
import React, { useState } from 'react';
import { observer } from 'mobx-react';
import { Table, Modal, Dropdown, Button, Menu, Avatar, Tooltip, Space, Tag, Radio, Input, message } from 'antd';
import { PlusOutlined, DownOutlined, SyncOutlined, FormOutlined } from '@ant-design/icons';
import { PlusOutlined, DownOutlined, SyncOutlined, FormOutlined, ExportOutlined } from '@ant-design/icons';
import { Action, TableCard, AuthButton, AuthFragment } from 'components';
import IPAddress from './IPAddress';
import { http, hasPermission } from 'libs';
import { http, hasPermission, blobToExcel, humanDate } from 'libs';
import store from './store';
import icons from './icons';
function ComTable() {
const [loading, setLoading] = useState(false)
function handleDelete(text) {
Modal.confirm({
title: '删除确认',
@ -38,6 +40,13 @@ function ComTable() {
}
}
function handleExport() {
setLoading(true)
http.post('/api/host/export/', {ids: store.dataSource.map(x => x.id)}, {responseType: 'blob', timeout: 60000})
.then(res => blobToExcel(res.data, `${humanDate()}_主机列表.xlsx`))
.finally(() => setLoading(false))
}
return (
<TableCard
tKey="hi"
@ -80,6 +89,12 @@ function ComTable() {
<Button type="primary" icon={<PlusOutlined/>}>新建 <DownOutlined/></Button>
</Dropdown>
</AuthFragment>,
<AuthButton
auth="host.host.view"
type="primary"
loading={loading}
icon={<ExportOutlined/>}
onClick={handleExport}>导出</AuthButton>,
<AuthButton
auth="host.host.add"
type="primary"