diff --git a/spug_api/apps/host/urls.py b/spug_api/apps/host/urls.py index 939195d..9c99081 100644 --- a/spug_api/apps/host/urls.py +++ b/spug_api/apps/host/urls.py @@ -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), diff --git a/spug_api/apps/host/views.py b/spug_api/apps/host/views.py index 377da26..b2ce821 100644 --- a/spug_api/apps/host/views.py +++ b/spug_api/apps/host/views.py @@ -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'] diff --git a/spug_web/src/libs/functools.js b/spug_web/src/libs/functools.js index 0426d84..4a54d00 100644 --- a/spug_web/src/libs/functools.js +++ b/spug_web/src/libs/functools.js @@ -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 @@ -112,4 +112,16 @@ export function uniqueId() { const r = Math.random() * 16 | 0; 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); } \ No newline at end of file diff --git a/spug_web/src/pages/host/Table.js b/spug_web/src/pages/host/Table.js index 6bbb29f..3eb95d7 100644 --- a/spug_web/src/pages/host/Table.js +++ b/spug_web/src/pages/host/Table.js @@ -3,17 +3,19 @@ * Copyright (c) * 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 ( }>新建 , + } + onClick={handleExport}>导出,