diff --git a/spug_api/apps/app/views.py b/spug_api/apps/app/views.py index 94b640d7b3..a692574ca8 100644 --- a/spug_api/apps/app/views.py +++ b/spug_api/apps/app/views.py @@ -7,6 +7,7 @@ from apps.app.models import App, Deploy, DeployExtend1, DeployExtend2 from apps.config.models import Config, ConfigHistory, Service from apps.app.utils import fetch_versions, remove_repo +from apps.host.utils import parse_group_host_ids from apps.setting.utils import AppSetting import json import re @@ -125,17 +126,23 @@ def post(self, request): Argument('id', type=int, required=False), Argument('app_id', type=int, help='请选择应用'), Argument('env_id', type=int, help='请选择环境'), - Argument('host_ids', type=list, filter=lambda x: len(x), help='请选择要部署的主机'), + Argument('host_ids', type=list, required=False, default=[]), + Argument('group_ids', type=list, required=False, default=[]), Argument('rst_notify', type=dict, help='请选择发布结果通知方式'), Argument('extend', filter=lambda x: x in dict(Deploy.EXTENDS), help='请选择发布类型'), Argument('is_parallel', type=bool, default=True), Argument('is_audit', type=bool, default=False) ).parse(request.body) if error is None: + host_ids = set(form.host_ids) + host_ids.update(parse_group_host_ids(form.group_ids)) + if not host_ids: + return json_response(error='请选择要部署的主机') deploy = Deploy.objects.filter(app_id=form.app_id, env_id=form.env_id).first() if deploy and deploy.id != form.id: return json_response(error='应用在该环境下已经存在发布配置') - form.host_ids = json.dumps(form.host_ids) + form.pop('group_ids') + form.host_ids = json.dumps(sorted(host_ids)) form.rst_notify = json.dumps(form.rst_notify) if form.extend == '1': extend_form, error = JsonParser( diff --git a/spug_api/apps/deploy/utils.py b/spug_api/apps/deploy/utils.py index a8ac9b9782..8b6414fec7 100644 --- a/spug_api/apps/deploy/utils.py +++ b/spug_api/apps/deploy/utils.py @@ -13,6 +13,7 @@ from apps.deploy.helper import Helper, SpugError from concurrent import futures from functools import partial +import requests import json import uuid import os @@ -141,7 +142,10 @@ def _ext2_deploy(req, helper, env): helper.send_info('local', f'\033[32m完成√\033[0m\r\n') for action in server_actions: helper.send_step('local', step, f'{human_time()} {action["title"]}...\r\n') - helper.local(f'cd /tmp && {action["data"]}', env) + if action.get('type') == 'jenkins': + _deploy_jenkins(helper, action, env) + else: + helper.local(f'cd /tmp && {action["data"]}', env) step += 1 for action in host_actions: @@ -221,6 +225,39 @@ def _ext2_deploy(req, helper, env): helper.send_step('local', 100, f'\r\n{human_time()} ** 发布成功 **') +def _deploy_jenkins(helper, action, env): + url = (action.get('url') or '').strip() + if not url: + helper.send_error('local', 'Jenkins URL is required') + auth = None + user = action.get('user') + token = action.get('token') + if user and token: + auth = (user, token) + try: + timeout = int(action.get('timeout') or 20) + except (TypeError, ValueError): + timeout = 20 + params = {} + raw_params = action.get('params') or {} + if not isinstance(raw_params, dict): + raw_params = {} + for k, v in raw_params.items(): + params[k] = render_str(str(v), env) + build_url = url.rstrip('/') + if params: + if not build_url.endswith('/buildWithParameters'): + build_url = f'{build_url}/buildWithParameters' + elif not build_url.endswith('/build'): + build_url = f'{build_url}/build' + helper.send_info('local', f'{human_time()} Trigger Jenkins: {build_url}\\r\\n') + response = requests.post(build_url, data=params or None, auth=auth, timeout=timeout) + if response.status_code not in (200, 201, 202): + helper.send_error('local', f'Jenkins trigger failed with status {response.status_code}: {response.text[:500]}') + queue_url = response.headers.get('Location') or '-' + helper.send_info('local', f'{human_time()} Jenkins accepted, queue url: {queue_url}\\r\\n') + + def _deploy_ext1_host(req, helper, h_id, env): helper.send_step(h_id, 1, f'\033[32m就绪√\033[0m\r\n{human_time()} 数据准备... ') host = Host.objects.filter(pk=h_id).first() diff --git a/spug_api/apps/deploy/views.py b/spug_api/apps/deploy/views.py index 92eb1b864f..a2305656cf 100644 --- a/spug_api/apps/deploy/views.py +++ b/spug_api/apps/deploy/views.py @@ -11,6 +11,7 @@ from apps.app.models import Deploy, DeployExtend2 from apps.repository.models import Repository from apps.deploy.utils import dispatch, Helper +from apps.host.utils import parse_group_host_ids from apps.host.models import Host from collections import defaultdict from threading import Thread @@ -19,6 +20,13 @@ import json import os +def merge_host_ids(host_ids, group_ids, limited_host_ids=None): + ids = set(host_ids or []) + ids.update(parse_group_host_ids(group_ids)) + if limited_host_ids is not None: + ids = ids.intersection(set(limited_host_ids)) + return sorted(ids) + class RequestView(View): @auth('deploy.request.view') @@ -211,13 +219,17 @@ def post_request_ext1(request): Argument('deploy_id', type=int, help='参数错误'), Argument('name', help='请输入申请标题'), Argument('extra', type=list, help='请选择发布版本'), - Argument('host_ids', type=list, filter=lambda x: len(x), help='请选择要部署的主机'), + Argument('host_ids', type=list, required=False, default=[]), + Argument('group_ids', type=list, required=False, default=[]), Argument('type', default='1'), Argument('plan', required=False), Argument('desc', required=False), ).parse(request.body) if error is None: deploy = Deploy.objects.get(pk=form.deploy_id) + host_ids = merge_host_ids(form.host_ids, form.group_ids, json.loads(deploy.host_ids)) + if not host_ids: + return json_response(error='请选择要部署的主机') form.spug_version = Repository.make_spug_version(deploy.id) if form.extra[0] == 'tag': if not form.extra[1]: @@ -240,7 +252,8 @@ def post_request_ext1(request): form.extra = json.dumps(form.extra) form.status = '0' if deploy.is_audit else '1' - form.host_ids = json.dumps(sorted(form.host_ids)) + form.pop('group_ids') + form.host_ids = json.dumps(host_ids) if form.id: req = DeployRequest.objects.get(pk=form.id) is_required_notify = deploy.is_audit and req.status == '-1' @@ -258,18 +271,23 @@ def post_request_ext1_rollback(request): form, error = JsonParser( Argument('request_id', type=int, help='请选择要回滚的版本'), Argument('name', help='请输入申请标题'), - Argument('host_ids', type=list, filter=lambda x: len(x), help='请选择要部署的主机'), + Argument('host_ids', type=list, required=False, default=[]), + Argument('group_ids', type=list, required=False, default=[]), Argument('desc', required=False), ).parse(request.body) if error is None: req = DeployRequest.objects.get(pk=form.pop('request_id')) + host_ids = merge_host_ids(form.host_ids, form.group_ids, json.loads(req.deploy.host_ids)) + if not host_ids: + return json_response(error='请选择要部署的主机') requests = DeployRequest.objects.filter(deploy=req.deploy, status__in=('3', '-3')) versions = list({x.spug_version: 1 for x in requests}.keys()) if req.spug_version not in versions[:req.deploy.extend_obj.versions + 1]: return json_response(error='选择的版本超出了发布配置中设置的版本数量,无法快速回滚,可通过新建发布申请选择构建仓库里的该版本再次发布。') form.status = '0' if req.deploy.is_audit else '1' - form.host_ids = json.dumps(sorted(form.host_ids)) + form.pop('group_ids') + form.host_ids = json.dumps(host_ids) new_req = DeployRequest.objects.create( deploy_id=req.deploy_id, repository_id=req.repository_id, @@ -291,7 +309,8 @@ def post_request_ext2(request): Argument('id', type=int, required=False), Argument('deploy_id', type=int, help='缺少必要参数'), Argument('name', help='请输申请标题'), - Argument('host_ids', type=list, filter=lambda x: len(x), help='请选择要部署的主机'), + Argument('host_ids', type=list, required=False, default=[]), + Argument('group_ids', type=list, required=False, default=[]), Argument('extra', type=dict, required=False), Argument('version', default=''), Argument('type', default='1'), @@ -302,6 +321,9 @@ def post_request_ext2(request): deploy = Deploy.objects.filter(pk=form.deploy_id).first() if not deploy: return json_response(error='未找到该发布配置') + host_ids = merge_host_ids(form.host_ids, form.group_ids, json.loads(deploy.host_ids)) + if not host_ids: + return json_response(error='请选择要部署的主机') extra = form.pop('extra') if DeployExtend2.objects.filter(deploy=deploy, host_actions__contains='"src_mode": "1"').exists(): if not extra: @@ -312,7 +334,8 @@ def post_request_ext2(request): form.spug_version = Repository.make_spug_version(deploy.id) form.name = form.name.replace("'", '') form.status = '0' if deploy.is_audit else '1' - form.host_ids = json.dumps(form.host_ids) + form.pop('group_ids') + form.host_ids = json.dumps(host_ids) if form.id: req = DeployRequest.objects.get(pk=form.id) is_required_notify = deploy.is_audit and req.status == '-1' diff --git a/spug_api/apps/exec/views.py b/spug_api/apps/exec/views.py index a6db18cb6f..f854b0554d 100644 --- a/spug_api/apps/exec/views.py +++ b/spug_api/apps/exec/views.py @@ -7,6 +7,7 @@ from libs import json_response, JsonParser, Argument, human_datetime, auth from apps.exec.models import ExecTemplate, ExecHistory from apps.host.models import Host +from apps.host.utils import parse_group_host_ids from apps.account.utils import has_host_perm import uuid import json @@ -61,13 +62,18 @@ def get(self, request): @auth('exec.task.do') def post(self, request): form, error = JsonParser( - Argument('host_ids', type=list, filter=lambda x: len(x), help='请选择执行主机'), + Argument('host_ids', type=list, required=False, default=[]), + Argument('group_ids', type=list, required=False, default=[]), Argument('command', help='请输入执行命令内容'), Argument('interpreter', default='sh'), Argument('template_id', type=int, required=False), Argument('params', type=dict, handler=json.dumps, default={}) ).parse(request.body) if error is None: + form.host_ids = sorted(set(form.host_ids).union(parse_group_host_ids(form.group_ids))) + if not form.host_ids: + return json_response(error='请选择执行主机') + form.pop('group_ids') if not has_host_perm(request.user, form.host_ids): return json_response(error='无权访问主机,请联系管理员') token, rds = uuid.uuid4().hex, get_redis_connection() diff --git a/spug_api/apps/host/utils.py b/spug_api/apps/host/utils.py index a379f4df03..98f7dd9ecd 100644 --- a/spug_api/apps/host/utils.py +++ b/spug_api/apps/host/utils.py @@ -6,7 +6,7 @@ from libs.ssh import SSH, AuthenticationException from libs.utils import AttrDict, human_datetime from libs.validators import ip_validator -from apps.host.models import HostExtend +from apps.host.models import HostExtend, Group from apps.setting.utils import AppSetting from collections import defaultdict from datetime import datetime, timezone @@ -301,3 +301,14 @@ def _get_ssh(kwargs, pkey=None, private_key=None, public_key=None, password=None ssh.add_public_key(public_key) return _get_ssh(kwargs, private_key) raise e + + +def parse_group_host_ids(group_ids): + group_ids = {int(x) for x in (group_ids or [])} + if not group_ids: + return set() + all_ids, sub_ids = set(group_ids), set(group_ids) + while sub_ids: + sub_ids = {x.id for x in Group.objects.filter(parent_id__in=sub_ids)} + all_ids.update(sub_ids) + return set(x.host_id for x in Group.hosts.through.objects.filter(group_id__in=all_ids)) diff --git a/spug_web/src/pages/deploy/app/AddSelect.js b/spug_web/src/pages/deploy/app/AddSelect.js index 615dc0fdad..27ee2e6a54 100644 --- a/spug_web/src/pages/deploy/app/AddSelect.js +++ b/spug_web/src/pages/deploy/app/AddSelect.js @@ -20,6 +20,7 @@ class AddSelect extends React.Component { is_audit: false, rst_notify: {mode: '0'}, host_ids: [], + group_ids: [], filter_rule: {type: 'exclude', data: ''} } }; @@ -31,6 +32,7 @@ class AddSelect extends React.Component { is_audit: false, rst_notify: {mode: '0'}, host_ids: [], + group_ids: [], host_actions: [], server_actions: [] } diff --git a/spug_web/src/pages/deploy/app/Ext1Setup1.js b/spug_web/src/pages/deploy/app/Ext1Setup1.js index 4457fe0d63..8602905b68 100644 --- a/spug_web/src/pages/deploy/app/Ext1Setup1.js +++ b/spug_web/src/pages/deploy/app/Ext1Setup1.js @@ -10,6 +10,7 @@ import { Switch, Form, Input, Select, Button, Radio } from 'antd'; import Repo from './Repo'; import envStore from 'pages/config/environment/store'; import HostSelector from 'pages/host/Selector'; +import hostStore from 'pages/host/store'; import store from './store'; export default observer(function Ext1Setup1() { @@ -22,6 +23,10 @@ export default observer(function Ext1Setup1() { } useEffect(() => { + hostStore.initial() + if (!Array.isArray(store.deploy.group_ids)) { + store.deploy.group_ids = [] + } if (store.currentRecord['deploys'] === undefined) { store.loadDeploys(store.app_id).then(updateEnvs) } else { @@ -29,6 +34,18 @@ export default observer(function Ext1Setup1() { } }, []) + function handleChangeGroups(group_ids) { + info.group_ids = group_ids; + const host_ids = new Set(info.host_ids || []); + for (let id of group_ids) { + const counter = hostStore.counter[id]; + if (counter) { + counter.forEach(h_id => host_ids.add(h_id)) + } + } + info.host_ids = [...host_ids] + } + const info = store.deploy; let modePlaceholder; switch (info['rst_notify']['mode']) { @@ -64,6 +81,18 @@ export default observer(function Ext1Setup1() { info.host_ids = ids}/> + + + setVisible(true)}>私有仓库?}> info['git_repo'] = e.target.value} placeholder="请输入Git仓库地址"/> diff --git a/spug_web/src/pages/deploy/app/Ext2Setup1.js b/spug_web/src/pages/deploy/app/Ext2Setup1.js index 043929b670..0482bcd401 100644 --- a/spug_web/src/pages/deploy/app/Ext2Setup1.js +++ b/spug_web/src/pages/deploy/app/Ext2Setup1.js @@ -9,6 +9,7 @@ import { Link } from 'react-router-dom'; import { Form, Switch, Select, Button, Input, Radio } from 'antd'; import envStore from 'pages/config/environment/store'; import HostSelector from 'pages/host/Selector'; +import hostStore from 'pages/host/store'; import store from './store'; export default observer(function Ext2Setup1() { @@ -20,6 +21,10 @@ export default observer(function Ext2Setup1() { } useEffect(() => { + hostStore.initial() + if (!Array.isArray(store.deploy.group_ids)) { + store.deploy.group_ids = [] + } if (store.currentRecord['deploys'] === undefined) { store.loadDeploys(store.app_id).then(updateEnvs) } else { @@ -27,6 +32,18 @@ export default observer(function Ext2Setup1() { } }, []) + function handleChangeGroups(group_ids) { + info.group_ids = group_ids; + const host_ids = new Set(info.host_ids || []); + for (let id of group_ids) { + const counter = hostStore.counter[id]; + if (counter) { + counter.forEach(h_id => host_ids.add(h_id)) + } + } + info.host_ids = [...host_ids] + } + const info = store.deploy; let modePlaceholder; switch (info['rst_notify']['mode']) { @@ -62,6 +79,18 @@ export default observer(function Ext2Setup1() { info.host_ids = ids}/> + + + (x.title && x.data) || (x.title && (x.src || x.src_mode === '1') && x.dst)); - info['server_actions'] = info['server_actions'].filter(x => x.title && x.data); + const serverActions = []; + for (let item of info['server_actions']) { + if (item.type === 'jenkins') { + if (!item.title || !item.url) continue + let params = {}; + if (item.params_text) { + try { + params = JSON.parse(item.params_text) + } catch (e) { + message.error(`Jenkins动作【${item.title || '未命名'}】参数必须是JSON对象`) + this.setState({loading: false}); + return + } + } + serverActions.push({...item, params}) + } else if (item.title && item.data) { + serverActions.push(item) + } + } + info['server_actions'] = serverActions; http.post('/api/app/deploy/', info) .then(res => { message.success('保存成功'); @@ -87,21 +106,73 @@ class Ext2Setup2 extends React.Component { {server_actions.map((item, index) => (
- item['title'] = e.target.value} - placeholder="请输入"/> - - - - item['data'] = cleanCommand(v)} - placeholder="请输入要执行的动作"/> + item['title'] = e.target.value} + placeholder="请输入" + addonAfter={( + + )}/> + {item['type'] === 'jenkins' ? ( + + + item['url'] = e.target.value} + placeholder="例如:https://jenkins.example.com/job/my-job"/> + + + item['user'] = e.target.value} + placeholder="可选"/> + + + item['token'] = e.target.value} + placeholder="可选"/> + + + item['timeout'] = Number(e.target.value || 20)} + placeholder="20"/> + + + item['params_text'] = e.target.value} + placeholder='{"branch":"main","version":"$SPUG_RELEASE"}'/> + + + ) : ( + + item['data'] = cleanCommand(v)} + placeholder="请输入要执行的动作"/> + + )} {!store.isReadOnly && ( + )} @@ -217,7 +295,10 @@ class Ext2Setup2 extends React.Component { diff --git a/spug_web/src/pages/deploy/request/Ext1Form.js b/spug_web/src/pages/deploy/request/Ext1Form.js index 4a2ca4a2a3..0682a90e5c 100644 --- a/spug_web/src/pages/deploy/request/Ext1Form.js +++ b/spug_web/src/pages/deploy/request/Ext1Form.js @@ -10,6 +10,7 @@ import { LoadingOutlined, SyncOutlined } from '@ant-design/icons'; import HostSelector from './HostSelector'; import { http, history, includes } from 'libs'; import store from './store'; +import hostStore from 'pages/host/store'; import lds from 'lodash'; import moment from 'moment'; @@ -32,6 +33,7 @@ export default observer(function () { const [loading, setLoading] = useState(false); const [repositories, setRepositories] = useState([]); const [host_ids, setHostIds] = useState([]); + const [group_ids, setGroupIds] = useState([]); const [plan, setPlan] = useState(store.record.plan); const [fetching, setFetching] = useState(false); const [git_type, setGitType] = useState(); @@ -43,10 +45,27 @@ export default observer(function () { useEffect(() => { const {app_host_ids, host_ids} = store.record; setHostIds(lds.clone(host_ids || app_host_ids)); + hostStore.initial() fetchVersions() // eslint-disable-next-line react-hooks/exhaustive-deps }, []) + function handleChangeGroups(ids) { + setGroupIds(ids) + const selected = new Set(host_ids); + for (let id of ids) { + const counter = hostStore.counter[id]; + if (counter) { + counter.forEach(h_id => { + if (app_host_ids.includes(h_id)) { + selected.add(h_id) + } + }) + } + } + setHostIds([...selected]) + } + function fetchVersions() { setFetching(true); const deploy_id = store.record.deploy_id @@ -70,6 +89,7 @@ export default observer(function () { formData['id'] = store.record.id; formData['deploy_id'] = store.record.deploy_id; formData['host_ids'] = host_ids; + formData['group_ids'] = group_ids; formData['type'] = store.record.type; formData['extra'] = [git_type, extra1, extra2]; if (plan) formData.plan = plan.format('YYYY-MM-DD HH:mm:00'); @@ -232,6 +252,13 @@ export default observer(function () { )} + + + diff --git a/spug_web/src/pages/deploy/request/Ext2Form.js b/spug_web/src/pages/deploy/request/Ext2Form.js index 22726c31ef..1c0cd9b56c 100644 --- a/spug_web/src/pages/deploy/request/Ext2Form.js +++ b/spug_web/src/pages/deploy/request/Ext2Form.js @@ -6,11 +6,12 @@ import React, { useState, useEffect } from 'react'; import { observer } from 'mobx-react'; import { UploadOutlined } from '@ant-design/icons'; -import { Modal, Form, Input, Upload, DatePicker, message, Button } from 'antd'; +import { Modal, Form, Input, Upload, DatePicker, message, Button, Select } from 'antd'; import HostSelector from './HostSelector'; import { http, clsNames, X_TOKEN } from 'libs'; import styles from './index.module.less'; import store from './store'; +import hostStore from 'pages/host/store'; import lds from 'lodash'; export default observer(function () { @@ -20,14 +21,32 @@ export default observer(function () { const [uploading, setUploading] = useState(false); const [fileList, setFileList] = useState([]); const [host_ids, setHostIds] = useState([]); + const [group_ids, setGroupIds] = useState([]); const [plan, setPlan] = useState(store.record.plan); useEffect(() => { const {app_host_ids, host_ids, extra} = store.record; setHostIds(lds.clone(host_ids || app_host_ids)); + hostStore.initial() if (store.record.extra) setFileList([{...extra, uid: '0'}]) }, []) + function handleChangeGroups(ids) { + setGroupIds(ids) + const selected = new Set(host_ids); + for (let id of ids) { + const counter = hostStore.counter[id]; + if (counter) { + counter.forEach(h_id => { + if (app_host_ids.includes(h_id)) { + selected.add(h_id) + } + }) + } + } + setHostIds([...selected]) + } + function handleSubmit() { if (host_ids.length === 0) { return message.error('请至少选择一个要发布的目标主机') @@ -36,6 +55,7 @@ export default observer(function () { const formData = form.getFieldsValue(); formData['id'] = store.record.id; formData['host_ids'] = host_ids; + formData['group_ids'] = group_ids; formData['type'] = store.record.type; formData['deploy_id'] = store.record.deploy_id; if (plan) formData.plan = plan.format('YYYY-MM-DD HH:mm:00'); @@ -103,6 +123,13 @@ export default observer(function () { )} + + + diff --git a/spug_web/src/pages/deploy/request/Rollback.js b/spug_web/src/pages/deploy/request/Rollback.js index b4c59d29de..8df5a03ae2 100644 --- a/spug_web/src/pages/deploy/request/Rollback.js +++ b/spug_web/src/pages/deploy/request/Rollback.js @@ -9,6 +9,7 @@ import { Modal, Form, Input, Select, Button, message } from 'antd'; import HostSelector from './HostSelector'; import { http, includes } from 'libs'; import store from './store'; +import hostStore from 'pages/host/store'; import lds from 'lodash'; import moment from 'moment'; @@ -17,13 +18,31 @@ export default observer(function () { const [visible, setVisible] = useState(false); const [loading, setLoading] = useState(false); const [host_ids, setHostIds] = useState([]); + const [group_ids, setGroupIds] = useState([]); useEffect(() => { const {app_host_ids, host_ids} = store.record; setHostIds(lds.clone(host_ids || app_host_ids)); + hostStore.initial() // eslint-disable-next-line react-hooks/exhaustive-deps }, []) + function handleChangeGroups(ids) { + setGroupIds(ids) + const selected = new Set(host_ids); + for (let id of ids) { + const counter = hostStore.counter[id]; + if (counter) { + counter.forEach(h_id => { + if (app_host_ids.includes(h_id)) { + selected.add(h_id) + } + }) + } + } + setHostIds([...selected]) + } + function handleSubmit() { if (host_ids.length === 0) { return message.error('请至少选择一个要发布的主机') @@ -31,6 +50,7 @@ export default observer(function () { setLoading(true); const formData = form.getFieldsValue(); formData['host_ids'] = host_ids; + formData['group_ids'] = group_ids; http.post('/api/deploy/request/ext1/rollback/', formData) .then(res => { message.success('操作成功'); @@ -74,6 +94,13 @@ export default observer(function () { )} + + + diff --git a/spug_web/src/pages/exec/task/index.js b/spug_web/src/pages/exec/task/index.js index 7f284ee82b..5124219413 100644 --- a/spug_web/src/pages/exec/task/index.js +++ b/spug_web/src/pages/exec/task/index.js @@ -6,7 +6,7 @@ import React, { useState, useEffect } from 'react'; import { observer } from 'mobx-react'; import { PlusOutlined, ThunderboltOutlined, BulbOutlined, QuestionCircleOutlined } from '@ant-design/icons'; -import { Form, Button, Radio, Tooltip } from 'antd'; +import { Form, Button, Radio, Tooltip, Select } from 'antd'; import { ACEditor, AuthDiv, Breadcrumb } from 'components'; import HostSelector from 'pages/host/Selector'; import TemplateSelector from './TemplateSelector'; @@ -15,6 +15,7 @@ import Output from './Output'; import { http, cleanCommand } from 'libs'; import moment from 'moment'; import store from './store'; +import hostStore from 'pages/host/store'; import gStore from 'gStore'; import style from './index.module.less'; @@ -25,6 +26,7 @@ function TaskIndex() { const [template_id, setTemplateId] = useState() const [histories, setHistories] = useState([]) const [parameters, setParameters] = useState([]) + const [group_ids, setGroupIds] = useState([]) const [visible, setVisible] = useState(false) useEffect(() => { @@ -41,6 +43,7 @@ function TaskIndex() { }, [command]) useEffect(() => { + hostStore.initial() gStore.fetchUserSettings() return () => { store.host_ids = [] @@ -55,12 +58,31 @@ function TaskIndex() { return setVisible(true) } setLoading(true) - const formData = {interpreter, template_id, params, host_ids: store.host_ids, command: cleanCommand(command)} + const formData = { + interpreter, + template_id, + params, + host_ids: store.host_ids, + group_ids, + command: cleanCommand(command) + } http.post('/api/exec/do/', formData) .then(store.switchConsole) .finally(() => setLoading(false)) } + function handleChangeGroups(ids) { + setGroupIds(ids) + const selected = new Set(store.host_ids); + for (let id of ids) { + const counter = hostStore.counter[id]; + if (counter) { + counter.forEach(h_id => selected.add(h_id)) + } + } + store.host_ids = [...selected] + } + function handleTemplate(tpl) { if (tpl.host_ids.length > 0) store.host_ids = tpl.host_ids setTemplateId(tpl.id) @@ -89,6 +111,13 @@ function TaskIndex() { store.host_ids = ids}/> + + +