mirror of https://github.com/openspug/spug
upgrade host module
parent
e9c0a04218
commit
ef84cf6bff
|
@ -0,0 +1,81 @@
|
|||
# Copyright: (c) OpenSpug Organization. https://github.com/openspug/spug
|
||||
# Copyright: (c) <spug.dev@gmail.com>
|
||||
# Released under the AGPL-3.0 License.
|
||||
from django.views.generic import View
|
||||
from libs import json_response, JsonParser, Argument, human_datetime
|
||||
from apps.host.models import Host, HostExtend
|
||||
from apps.host.utils import check_os_type
|
||||
import ipaddress
|
||||
import json
|
||||
|
||||
|
||||
class ExtendView(View):
|
||||
def get(self, request):
|
||||
form, error = JsonParser(
|
||||
Argument('host_id', type=int, help='参数错误')
|
||||
).parse(request.GET)
|
||||
if error is None:
|
||||
host = Host.objects.filter(pk=form.host_id).first()
|
||||
if not host:
|
||||
return json_response(error='未找到指定主机')
|
||||
if not host.is_verified:
|
||||
return json_response(error='该主机还未验证')
|
||||
cli = host.get_ssh()
|
||||
commands = [
|
||||
"lscpu | grep '^CPU(s)' | awk '{print $2}'",
|
||||
"free -m | awk 'NR==2{print $2}'",
|
||||
"hostname -I",
|
||||
"cat /etc/os-release | grep PRETTY_NAME | awk -F \\\" '{print $2}'",
|
||||
"fdisk -l | grep '^Disk /' | awk '{print $5}'"
|
||||
]
|
||||
code, out = cli.exec_command(';'.join(commands))
|
||||
if code != 0:
|
||||
return json_response(error=f'Exception: {out}')
|
||||
response = {'disk': [], 'public_ip_address': [], 'private_ip_address': []}
|
||||
for index, line in enumerate(out.strip().split('\n')):
|
||||
if index == 0:
|
||||
response['cpu'] = int(line)
|
||||
elif index == 1:
|
||||
response['memory'] = round(int(line) / 1000, 1)
|
||||
elif index == 2:
|
||||
for ip in line.split():
|
||||
if ipaddress.ip_address(ip).is_global:
|
||||
response['public_ip_address'].append(ip)
|
||||
else:
|
||||
response['private_ip_address'].append(ip)
|
||||
elif index == 3:
|
||||
response['os_name'] = line
|
||||
else:
|
||||
response['disk'].append(round(int(line) / 1024 / 1024 / 1024, 0))
|
||||
return json_response(response)
|
||||
return json_response(error=error)
|
||||
|
||||
def post(self, request):
|
||||
form, error = JsonParser(
|
||||
Argument('host_id', type=int, help='参数错误'),
|
||||
Argument('instance_id', required=False),
|
||||
Argument('os_name', help='请输入操作系统'),
|
||||
Argument('cpu', type=int, help='请输入CPU核心数'),
|
||||
Argument('memory', type=float, help='请输入内存大小'),
|
||||
Argument('disk', type=list, filter=lambda x: len(x), help='请添加磁盘'),
|
||||
Argument('private_ip_address', type=list, filter=lambda x: len(x), help='请添加内网IP'),
|
||||
Argument('public_ip_address', type=list, required=False),
|
||||
Argument('instance_charge_type', default='Other'),
|
||||
Argument('internet_charge_type', default='Other'),
|
||||
Argument('created_time', required=False),
|
||||
Argument('expired_time', required=False)
|
||||
).parse(request.body)
|
||||
if error is None:
|
||||
host = Host.objects.filter(pk=form.host_id).first()
|
||||
form.disk = json.dumps(form.disk)
|
||||
form.public_ip_address = json.dumps(form.public_ip_address) if form.public_ip_address else '[]'
|
||||
form.private_ip_address = json.dumps(form.private_ip_address)
|
||||
form.updated_at = human_datetime()
|
||||
form.os_type = check_os_type(form.os_name)
|
||||
if hasattr(host, 'hostextend'):
|
||||
extend = host.hostextend
|
||||
extend.update_by_dict(form)
|
||||
else:
|
||||
extend = HostExtend.objects.create(host=host, **form)
|
||||
return json_response(extend.to_view())
|
||||
return json_response(error=error)
|
|
@ -46,7 +46,7 @@ class Host(models.Model, ModelMixin):
|
|||
class HostExtend(models.Model, ModelMixin):
|
||||
INSTANCE_CHARGE_TYPES = (
|
||||
('PrePaid', '包年包月'),
|
||||
('PostPaid', '按量付费'),
|
||||
('PostPaid', '按量计费'),
|
||||
('Other', '其他')
|
||||
)
|
||||
INTERNET_CHARGE_TYPES = (
|
||||
|
@ -55,8 +55,8 @@ class HostExtend(models.Model, ModelMixin):
|
|||
('Other', '其他')
|
||||
)
|
||||
host = models.OneToOneField(Host, on_delete=models.CASCADE)
|
||||
instance_id = models.CharField(max_length=64)
|
||||
zone_id = models.CharField(max_length=30)
|
||||
instance_id = models.CharField(max_length=64, null=True)
|
||||
zone_id = models.CharField(max_length=30, null=True)
|
||||
cpu = models.IntegerField()
|
||||
memory = models.FloatField()
|
||||
disk = models.CharField(max_length=255, default='[]')
|
||||
|
@ -66,7 +66,7 @@ class HostExtend(models.Model, ModelMixin):
|
|||
public_ip_address = models.CharField(max_length=255)
|
||||
instance_charge_type = models.CharField(max_length=20, choices=INSTANCE_CHARGE_TYPES)
|
||||
internet_charge_type = models.CharField(max_length=20, choices=INTERNET_CHARGE_TYPES)
|
||||
created_time = models.CharField(max_length=20)
|
||||
created_time = models.CharField(max_length=20, null=True)
|
||||
expired_time = models.CharField(max_length=20, null=True)
|
||||
updated_at = models.CharField(max_length=20, default=human_datetime)
|
||||
|
||||
|
|
|
@ -5,10 +5,12 @@ from django.urls import path
|
|||
|
||||
from apps.host.views import *
|
||||
from apps.host.group import GroupView
|
||||
from apps.host.extend import ExtendView
|
||||
from apps.host.add import get_regions, cloud_import
|
||||
|
||||
urlpatterns = [
|
||||
path('', HostView.as_view()),
|
||||
path('extend/', ExtendView.as_view()),
|
||||
path('group/', GroupView.as_view()),
|
||||
path('import/', post_import),
|
||||
path('import/cloud/', cloud_import),
|
||||
|
|
|
@ -51,11 +51,14 @@ class HostView(View):
|
|||
if other and (not form.id or other.id != form.id):
|
||||
return json_response(error=f'已存在的主机名称【{form.name}】')
|
||||
if form.id:
|
||||
Host.objects.filter(pk=form.id).update(**form)
|
||||
Host.objects.filter(pk=form.id).update(is_verified=True, **form)
|
||||
host = Host.objects.get(pk=form.id)
|
||||
else:
|
||||
host = Host.objects.create(created_by=request.user, **form)
|
||||
host = Host.objects.create(created_by=request.user, is_verified=True, **form)
|
||||
host.groups.set(group_ids)
|
||||
response = host.to_view()
|
||||
response['group_ids'] = group_ids
|
||||
return json_response(response)
|
||||
return json_response(error=error)
|
||||
|
||||
def patch(self, request):
|
||||
|
@ -91,7 +94,6 @@ class HostView(View):
|
|||
if detection:
|
||||
return json_response(error=f'监控中心的任务【{detection.name}】关联了该主机,请解除关联后再尝试删除该主机')
|
||||
Host.objects.filter(pk=form.id).delete()
|
||||
print('pk: ', form.id)
|
||||
return json_response(error=error)
|
||||
|
||||
|
||||
|
@ -143,13 +145,7 @@ def post_import(request):
|
|||
|
||||
|
||||
def valid_ssh(hostname, port, username, password=None, pkey=None, with_expect=True):
|
||||
try:
|
||||
private_key = AppSetting.get('private_key')
|
||||
public_key = AppSetting.get('public_key')
|
||||
except KeyError:
|
||||
private_key, public_key = SSH.generate_key()
|
||||
AppSetting.set('private_key', private_key, 'ssh private key')
|
||||
AppSetting.set('public_key', public_key, 'ssh public key')
|
||||
private_key, public_key = AppSetting.get_ssh_key()
|
||||
if password:
|
||||
_cli = SSH(hostname, port, username, password=str(password))
|
||||
_cli.add_public_key(public_key)
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
# Released under the AGPL-3.0 License.
|
||||
from functools import lru_cache
|
||||
from apps.setting.models import Setting, KEYS_DEFAULT
|
||||
from libs.ssh import SSH
|
||||
import json
|
||||
|
||||
|
||||
|
@ -29,3 +30,13 @@ class AppSetting:
|
|||
Setting.objects.update_or_create(key=key, defaults={'value': value, 'desc': desc})
|
||||
else:
|
||||
raise KeyError('invalid key')
|
||||
|
||||
@classmethod
|
||||
def get_ssh_key(cls):
|
||||
public_key = cls.get_default('public_key')
|
||||
private_key = cls.get_default('private_key')
|
||||
if not private_key or not public_key:
|
||||
private_key, public_key = SSH.generate_key()
|
||||
cls.set('private_key', private_key)
|
||||
cls.set('public_key', public_key)
|
||||
return private_key, public_key
|
||||
|
|
|
@ -18,6 +18,11 @@ class ModelMixin(object):
|
|||
else:
|
||||
return {f.attname: getattr(self, f.attname) for f in self._meta.fields}
|
||||
|
||||
def update_by_dict(self, data):
|
||||
for key, value in data.items():
|
||||
setattr(self, key, value)
|
||||
self.save()
|
||||
|
||||
|
||||
# 使用该混入类,需要request.user对象实现has_perms方法
|
||||
class PermissionMixin(object):
|
||||
|
|
|
@ -1,19 +1,117 @@
|
|||
import React from 'react';
|
||||
import React, { useState, useEffect, useRef } from 'react';
|
||||
import { observer } from 'mobx-react';
|
||||
import { Drawer, Descriptions, List } from 'antd';
|
||||
import { Drawer, Descriptions, List, Button, Input, Select, DatePicker, Tag, message } from 'antd';
|
||||
import { EditOutlined, SaveOutlined, PlusOutlined, SyncOutlined } from '@ant-design/icons';
|
||||
import { http } from 'libs';
|
||||
import store from './store';
|
||||
import lds from 'lodash';
|
||||
import moment from 'moment';
|
||||
import styles from './index.module.less';
|
||||
|
||||
export default observer(function () {
|
||||
const host = store.record;
|
||||
const group_ids = host.group_ids || [];
|
||||
const [edit, setEdit] = useState(false);
|
||||
const [host, setHost] = useState(store.record);
|
||||
const diskInput = useRef();
|
||||
const sipInput = useRef();
|
||||
const gipInput = useRef();
|
||||
const [tag, setTag] = useState();
|
||||
const [inputVisible, setInputVisible] = useState(null);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [fetching, setFetching] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (store.detailVisible) {
|
||||
setHost(lds.cloneDeep(store.record))
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [store.detailVisible])
|
||||
|
||||
useEffect(() => {
|
||||
if (inputVisible === 'disk') {
|
||||
diskInput.current.focus()
|
||||
} else if (inputVisible === 'sip') {
|
||||
sipInput.current.focus()
|
||||
} else if (inputVisible === 'gip') {
|
||||
gipInput.current.focus()
|
||||
}
|
||||
}, [inputVisible])
|
||||
|
||||
function handleSubmit() {
|
||||
setLoading(true)
|
||||
if (host.created_time) host.created_time = moment(host.created_time).format('YYYY-MM-DD HH:mm:ss')
|
||||
if (host.expired_time) host.expired_time = moment(host.expired_time).format('YYYY-MM-DD HH:mm:ss')
|
||||
http.post('/api/host/extend/', {host_id: host.id, ...host})
|
||||
.then(res => {
|
||||
Object.assign(host, res);
|
||||
setEdit(false);
|
||||
setHost(lds.cloneDeep(host));
|
||||
store.fetchRecords()
|
||||
})
|
||||
.finally(() => setLoading(false))
|
||||
}
|
||||
|
||||
function handleFetch() {
|
||||
setFetching(true);
|
||||
http.get('/api/host/extend/', {params: {host_id: host.id}})
|
||||
.then(res => {
|
||||
Object.assign(host, res);
|
||||
setHost(lds.cloneDeep(host));
|
||||
message.success('同步成功')
|
||||
})
|
||||
.finally(() => setFetching(false))
|
||||
}
|
||||
|
||||
function handleChange(e, key) {
|
||||
host[key] = e && e.target ? e.target.value : e;
|
||||
setHost({...host})
|
||||
}
|
||||
|
||||
function handleClose() {
|
||||
store.detailVisible = false;
|
||||
setEdit(false)
|
||||
}
|
||||
|
||||
function handleTagConfirm(key) {
|
||||
if (tag) {
|
||||
if (key === 'disk') {
|
||||
const value = Number(tag);
|
||||
if (lds.isNaN(value)) return message.error('请输入数字');
|
||||
host.disk ? host.disk.push(value) : host.disk = [value]
|
||||
} else if (key === 'sip') {
|
||||
host.private_ip_address ? host.private_ip_address.push(tag) : host.private_ip_address = [tag]
|
||||
} else if (key === 'gip') {
|
||||
host.public_ip_address ? host.public_ip_address.push(tag) : host.public_ip_address = [tag]
|
||||
}
|
||||
setHost(lds.cloneDeep(host))
|
||||
}
|
||||
setTag(undefined);
|
||||
setInputVisible(false)
|
||||
}
|
||||
|
||||
function handleTagRemove(key, index) {
|
||||
if (key === 'disk') {
|
||||
host.disk.splice(index, 1)
|
||||
} else if (key === 'sip') {
|
||||
host.private_ip_address.splice(index, 1)
|
||||
} else if (key === 'gip') {
|
||||
host.public_ip_address.splice(index, 1)
|
||||
}
|
||||
setHost(lds.cloneDeep(host))
|
||||
}
|
||||
|
||||
return (
|
||||
<Drawer
|
||||
width={500}
|
||||
width={550}
|
||||
title={host.name}
|
||||
placement="right"
|
||||
onClose={() => store.detailVisible = false}
|
||||
onClose={handleClose}
|
||||
visible={store.detailVisible}>
|
||||
<Descriptions bordered size="small" title={<span style={{fontWeight: 500}}>基本信息</span>} column={1}>
|
||||
<Descriptions
|
||||
bordered
|
||||
size="small"
|
||||
labelStyle={{width: 150}}
|
||||
title={<span style={{fontWeight: 500}}>基本信息</span>}
|
||||
column={1}>
|
||||
<Descriptions.Item label="主机名称">{host.name}</Descriptions.Item>
|
||||
<Descriptions.Item label="连接地址">{host.username}@{host.hostname}</Descriptions.Item>
|
||||
<Descriptions.Item label="连接端口">{host.port}</Descriptions.Item>
|
||||
|
@ -21,34 +119,148 @@ export default observer(function () {
|
|||
<Descriptions.Item label="描述信息">{host.desc}</Descriptions.Item>
|
||||
<Descriptions.Item label="所属分组">
|
||||
<List>
|
||||
{group_ids.map(g_id => (
|
||||
{lds.get(host, 'group_ids', []).map(g_id => (
|
||||
<List.Item key={g_id} style={{padding: '6px 0'}}>{store.groups[g_id]}</List.Item>
|
||||
))}
|
||||
</List>
|
||||
</Descriptions.Item>
|
||||
</Descriptions>
|
||||
{host.id ? (
|
||||
<Descriptions
|
||||
bordered
|
||||
size="small"
|
||||
column={1}
|
||||
style={{marginTop: 24}}
|
||||
title={<span style={{fontWeight: 500}}>扩展信息</span>}>
|
||||
<Descriptions.Item label="实例ID">{host.instance_id}</Descriptions.Item>
|
||||
<Descriptions.Item label="操作系统">{host.os_name}</Descriptions.Item>
|
||||
<Descriptions.Item label="CPU">{host.cpu}核</Descriptions.Item>
|
||||
<Descriptions.Item label="内存">{host.memory}GB</Descriptions.Item>
|
||||
<Descriptions.Item label="磁盘">{host.disk.map(x => `${x}GB`).join(', ')}</Descriptions.Item>
|
||||
<Descriptions.Item label="内网IP">{host.private_ip_address.join(', ')}</Descriptions.Item>
|
||||
<Descriptions.Item label="公网IP">{host.public_ip_address.join(', ')}</Descriptions.Item>
|
||||
<Descriptions.Item label="实例付费方式">{host.instance_charge_type_alias}</Descriptions.Item>
|
||||
<Descriptions.Item label="网络付费方式">{host.internet_charge_type_alisa}</Descriptions.Item>
|
||||
<Descriptions.Item label="创建时间">{host.created_time}</Descriptions.Item>
|
||||
<Descriptions.Item label="到期时间">{host.expired_time || 'N/A'}</Descriptions.Item>
|
||||
<Descriptions.Item label="更新时间">{host.updated_at}</Descriptions.Item>
|
||||
</Descriptions>
|
||||
) : null}
|
||||
|
||||
<Descriptions
|
||||
bordered
|
||||
size="small"
|
||||
column={1}
|
||||
className={edit ? styles.hostExtendEdit : null}
|
||||
labelStyle={{width: 150}}
|
||||
style={{marginTop: 24}}
|
||||
extra={edit ? ([
|
||||
<Button key="1" type="link" loading={fetching} icon={<SyncOutlined/>} onClick={handleFetch}>同步</Button>,
|
||||
<Button key="2" type="link" loading={loading} icon={<SaveOutlined/>} onClick={handleSubmit}>保存</Button>
|
||||
]) : (
|
||||
<Button type="link" icon={<EditOutlined/>} onClick={() => setEdit(true)}>编辑</Button>
|
||||
)}
|
||||
title={<span style={{fontWeight: 500}}>扩展信息</span>}>
|
||||
<Descriptions.Item label="实例ID">
|
||||
{edit ? (
|
||||
<Input value={host.instance_id} onChange={e => handleChange(e, 'instance_id')} placeholder="选填"/>
|
||||
) : host.instance_id}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="操作系统">
|
||||
{edit ? (
|
||||
<Input value={host.os_name} onChange={e => handleChange(e, 'os_name')}
|
||||
placeholder="例如:Ubuntu Server 16.04.1 LTS"/>
|
||||
) : host.os_name}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="CPU">
|
||||
{edit ? (
|
||||
<Input suffix="核" style={{width: 100}} value={host.cpu} onChange={e => handleChange(e, 'cpu')}
|
||||
placeholder="数字"/>
|
||||
) : host.cpu ? `${host.cpu}核` : null}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="内存">
|
||||
{edit ? (
|
||||
<Input suffix="GB" style={{width: 100}} value={host.memory} onChange={e => handleChange(e, 'memory')}
|
||||
placeholder="数字"/>
|
||||
) : host.memory ? `${host.memory}GB` : null}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="磁盘">
|
||||
{lds.get(host, 'disk', []).map((item, index) => (
|
||||
<Tag visible closable={edit} key={index} onClose={() => handleTagRemove('disk', index)}>{item}GB</Tag>
|
||||
))}
|
||||
{edit && (inputVisible === 'disk' ? (
|
||||
<Input
|
||||
ref={diskInput}
|
||||
type="text"
|
||||
size="small"
|
||||
value={tag}
|
||||
className={styles.tagNumberInput}
|
||||
onChange={e => setTag(e.target.value)}
|
||||
onBlur={() => handleTagConfirm('disk')}
|
||||
onPressEnter={() => handleTagConfirm('disk')}
|
||||
/>
|
||||
) : (
|
||||
<Tag className={styles.tagAdd} onClick={() => setInputVisible('disk')}><PlusOutlined/> 新建</Tag>
|
||||
))}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="内网IP">
|
||||
{lds.get(host, 'private_ip_address', []).map((item, index) => (
|
||||
<Tag visible closable={edit} key={index} onClose={() => handleTagRemove('sip', index)}>{item}</Tag>
|
||||
))}
|
||||
{edit && (inputVisible === 'sip' ? (
|
||||
<Input
|
||||
ref={sipInput}
|
||||
type="text"
|
||||
size="small"
|
||||
value={tag}
|
||||
className={styles.tagInput}
|
||||
onChange={e => setTag(e.target.value)}
|
||||
onBlur={() => handleTagConfirm('sip')}
|
||||
onPressEnter={() => handleTagConfirm('sip')}
|
||||
/>
|
||||
) : (
|
||||
<Tag className={styles.tagAdd} onClick={() => setInputVisible('sip')}><PlusOutlined/> 新建</Tag>
|
||||
))}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="公网IP">
|
||||
{lds.get(host, 'public_ip_address', []).map((item, index) => (
|
||||
<Tag visible closable={edit} key={index} onClose={() => handleTagRemove('gip', index)}>{item}</Tag>
|
||||
))}
|
||||
{edit && (inputVisible === 'gip' ? (
|
||||
<Input
|
||||
ref={gipInput}
|
||||
type="text"
|
||||
size="small"
|
||||
value={tag}
|
||||
className={styles.tagInput}
|
||||
onChange={e => setTag(e.target.value)}
|
||||
onBlur={() => handleTagConfirm('gip')}
|
||||
onPressEnter={() => handleTagConfirm('gip')}
|
||||
/>
|
||||
) : (
|
||||
<Tag className={styles.tagAdd} onClick={() => setInputVisible('gip')}><PlusOutlined/> 新建</Tag>
|
||||
))}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="实例计费方式">
|
||||
{edit ? (
|
||||
<Select
|
||||
style={{width: 150}}
|
||||
value={host.instance_charge_type}
|
||||
placeholder="请选择"
|
||||
onChange={v => handleChange(v, 'instance_charge_type')}>
|
||||
<Select.Option value="PrePaid">包年包月</Select.Option>
|
||||
<Select.Option value="PostPaid">按量计费</Select.Option>
|
||||
<Select.Option value="Other">其他</Select.Option>
|
||||
</Select>
|
||||
) : host.instance_charge_type_alias}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="网络计费方式">
|
||||
{edit ? (
|
||||
<Select
|
||||
style={{width: 150}}
|
||||
value={host.internet_charge_type}
|
||||
placeholder="请选择"
|
||||
onChange={v => handleChange(v, 'internet_charge_type')}>
|
||||
<Select.Option value="PayByBandwidth">按带宽计费</Select.Option>
|
||||
<Select.Option value="PayByTraffic">按流量计费</Select.Option>
|
||||
<Select.Option value="Other">其他</Select.Option>
|
||||
</Select>
|
||||
) : host.internet_charge_type_alisa}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="创建时间">
|
||||
{edit ? (
|
||||
<DatePicker
|
||||
value={host.created_time ? moment(host.created_time) : undefined}
|
||||
onChange={v => handleChange(v, 'created_time')}/>
|
||||
) : host.created_time}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="到期时间">
|
||||
{edit ? (
|
||||
<DatePicker
|
||||
value={host.expired_time ? moment(host.expired_time) : undefined}
|
||||
onChange={v => handleChange(v, 'expired_time')}/>
|
||||
) : host.expired_time}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="更新时间">{host.updated_at}</Descriptions.Item>
|
||||
</Descriptions>
|
||||
</Drawer>
|
||||
)
|
||||
})
|
|
@ -46,7 +46,8 @@ export default observer(function () {
|
|||
} else {
|
||||
message.success('操作成功');
|
||||
store.formVisible = false;
|
||||
store.fetchRecords()
|
||||
store.fetchRecords();
|
||||
if (!store.record.id) handleNext(res)
|
||||
}
|
||||
}, () => setLoading(false))
|
||||
}
|
||||
|
@ -57,12 +58,24 @@ export default observer(function () {
|
|||
return http.post('/api/host/', formData).then(res => {
|
||||
message.success('验证成功');
|
||||
store.formVisible = false;
|
||||
store.fetchRecords()
|
||||
store.fetchRecords();
|
||||
if (!store.record.id) handleNext(res)
|
||||
})
|
||||
}
|
||||
message.error('请输入授权密码')
|
||||
}
|
||||
|
||||
function handleNext(res) {
|
||||
Modal.confirm({
|
||||
title: '提示信息',
|
||||
content: '是否继续完善主机的扩展信息?',
|
||||
onOk: () => {
|
||||
store.record = res;
|
||||
store.detailVisible = true
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const ConfirmForm = (props) => (
|
||||
<Form layout="vertical" style={{marginTop: 24}}>
|
||||
<Form.Item required label="授权密码" help={`用户 ${props.username} 的密码, 该密码仅做首次验证使用,不会存储该密码。`}>
|
||||
|
@ -94,14 +107,14 @@ export default observer(function () {
|
|||
return (
|
||||
<Modal
|
||||
visible
|
||||
width={800}
|
||||
width={700}
|
||||
maskClosable={false}
|
||||
title={store.record.id ? '编辑主机' : '新建主机'}
|
||||
okText="验证"
|
||||
onCancel={() => store.formVisible = false}
|
||||
confirmLoading={loading}
|
||||
onOk={handleSubmit}>
|
||||
<Form form={form} labelCol={{span: 6}} wrapperCol={{span: 14}} initialValues={info}>
|
||||
<Form form={form} labelCol={{span: 5}} wrapperCol={{span: 17}} initialValues={info}>
|
||||
<Form.Item required name="group_ids" label="主机分组">
|
||||
<TreeSelect
|
||||
multiple
|
||||
|
|
|
@ -1,4 +1,27 @@
|
|||
.steps {
|
||||
width: 350px;
|
||||
margin: 0 auto 30px;
|
||||
}
|
||||
|
||||
.tagAdd {
|
||||
background: #fff;
|
||||
border-style: dashed;
|
||||
}
|
||||
|
||||
.tagNumberInput {
|
||||
width: 78px;
|
||||
margin-right: 8px;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.tagInput {
|
||||
width: 140px;
|
||||
margin-right: 8px;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.hostExtendEdit {
|
||||
:global(.ant-descriptions-item-content) {
|
||||
padding: 4px 16px !important;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue