diff --git a/spug_api/apps/repository/__init__.py b/spug_api/apps/repository/__init__.py new file mode 100644 index 0000000..89f622a --- /dev/null +++ b/spug_api/apps/repository/__init__.py @@ -0,0 +1,3 @@ +# Copyright: (c) OpenSpug Organization. https://github.com/openspug/spug +# Copyright: (c) +# Released under the AGPL-3.0 License. diff --git a/spug_api/apps/repository/models.py b/spug_api/apps/repository/models.py new file mode 100644 index 0000000..9cbc8b8 --- /dev/null +++ b/spug_api/apps/repository/models.py @@ -0,0 +1,49 @@ +# Copyright: (c) OpenSpug Organization. https://github.com/openspug/spug +# Copyright: (c) +# Released under the AGPL-3.0 License. +from django.db import models +from libs.mixins import ModelMixin +from apps.app.models import App, Environment, Deploy +from apps.account.models import User +from datetime import datetime +import json + + +class Repository(models.Model, ModelMixin): + STATUS = ( + ('0', '未开始'), + ('1', '构建中'), + ('2', '失败'), + ('5', '成功'), + ) + app = models.ForeignKey(App, on_delete=models.PROTECT) + env = models.ForeignKey(Environment, on_delete=models.PROTECT) + deploy = models.ForeignKey(Deploy, on_delete=models.PROTECT) + version = models.CharField(max_length=50) + spug_version = models.CharField(max_length=50) + remarks = models.CharField(max_length=255, null=True) + extra = models.TextField() + status = models.CharField(max_length=2, choices=STATUS, default='0') + created_at = models.DateTimeField(auto_now_add=True) + created_by = models.ForeignKey(User, on_delete=models.PROTECT) + + @staticmethod + def make_spug_version(deploy_id): + return f'{deploy_id}_{datetime.now().strftime("%Y%m%d%H%M%S")}' + + def to_view(self): + tmp = self.to_dict() + tmp['extra'] = json.loads(self.extra) + tmp['status_alias'] = self.get_status_display() + if hasattr(self, 'app_name'): + tmp['app_name'] = self.app_name + if hasattr(self, 'env_name'): + tmp['env_name'] = self.env_name + if hasattr(self, 'created_by_user'): + tmp['created_by_user'] = self.created_by_user + return tmp + + class Meta: + db_table = 'repositories' + ordering = ('-id',) + diff --git a/spug_api/apps/repository/urls.py b/spug_api/apps/repository/urls.py new file mode 100644 index 0000000..2c66d75 --- /dev/null +++ b/spug_api/apps/repository/urls.py @@ -0,0 +1,10 @@ +# Copyright: (c) OpenSpug Organization. https://github.com/openspug/spug +# Copyright: (c) +# Released under the AGPL-3.0 License. +from django.urls import path + +from .views import * + +urlpatterns = [ + path('', RepositoryView.as_view()), +] diff --git a/spug_api/apps/repository/utils.py b/spug_api/apps/repository/utils.py new file mode 100644 index 0000000..424a5a1 --- /dev/null +++ b/spug_api/apps/repository/utils.py @@ -0,0 +1,145 @@ +# Copyright: (c) OpenSpug Organization. https://github.com/openspug/spug +# Copyright: (c) +# Released under the AGPL-3.0 License. +from django_redis import get_redis_connection +from django.conf import settings +from django.db import close_old_connections +from libs.utils import AttrDict, human_time +from apps.repository.models import Repository +import subprocess +import json +import uuid +import os + +REPOS_DIR = settings.REPOS_DIR + + +class SpugError(Exception): + pass + + +def dispatch(rep: Repository): + rds = get_redis_connection() + rds_key = f'{settings.BUILD_KEY}:{rep.spug_version}' + rep.status = '1' + rep.save() + helper = Helper(rds, rds_key) + try: + api_token = uuid.uuid4().hex + rds.setex(api_token, 60 * 60, f'{rep.app_id},{rep.env_id}') + helper.send_info('local', f'完成\r\n{human_time()} 构建准备... ') + env = AttrDict( + SPUG_APP_NAME=rep.app.name, + SPUG_APP_ID=str(rep.app_id), + SPUG_DEPLOY_ID=str(rep.deploy_id), + SPUG_BUILD_ID=str(rep.id), + SPUG_ENV_ID=str(rep.env_id), + SPUG_ENV_KEY=rep.env.key, + SPUG_VERSION=rep.version, + SPUG_API_TOKEN=api_token, + SPUG_REPOS_DIR=REPOS_DIR, + ) + _build(rep, helper, env) + rep.status = '5' + except Exception as e: + rep.status = '2' + raise e + finally: + helper.local(f'cd {REPOS_DIR} && rm -rf {rep.spug_version}') + close_old_connections() + # save the build log for two weeks + rds.expire(rds_key, 14 * 24 * 60 * 60) + rds.close() + rep.save() + + +def _build(rep: Repository, helper, env): + extend = rep.deploy.extend_obj + extras = json.loads(rep.extra) + git_dir = os.path.join(REPOS_DIR, str(rep.deploy_id)) + build_dir = os.path.join(REPOS_DIR, rep.spug_version) + tar_file = os.path.join(REPOS_DIR, 'build', f'{rep.spug_version}.tar.gz') + env.update(SPUG_DST_DIR=extend.dst_dir) + if extras[0] == 'branch': + tree_ish = extras[2] + env.update(SPUG_GIT_BRANCH=extras[1], SPUG_GIT_COMMIT_ID=extras[2]) + else: + tree_ish = extras[1] + env.update(SPUG_GIT_TAG=extras[1]) + helper.send_info('local', '完成\r\n') + + if extend.hook_pre_server: + helper.send_step('local', 1, f'{human_time()} 检出前任务...\r\n') + helper.local(f'cd {git_dir} && {extend.hook_pre_server}', env) + + helper.send_step('local', 2, f'{human_time()} 执行检出... ') + command = f'cd {git_dir} && git archive --prefix={rep.spug_version}/ {tree_ish} | (cd .. && tar xf -)' + helper.local(command) + helper.send_info('local', '完成\r\n') + + if extend.hook_post_server: + helper.send_step('local', 3, f'{human_time()} 检出后任务...\r\n') + helper.local(f'cd {build_dir} && {extend.hook_post_server}', env) + + helper.send_step('local', 4, f'\r\n{human_time()} 执行打包... ') + filter_rule, exclude, contain = json.loads(extend.filter_rule), '', rep.spug_version + files = helper.parse_filter_rule(filter_rule['data']) + if files: + if filter_rule['type'] == 'exclude': + excludes = [] + for x in files: + if x.startswith('/'): + excludes.append(f'--exclude={rep.spug_version}{x}') + else: + excludes.append(f'--exclude={x}') + exclude = ' '.join(excludes) + else: + contain = ' '.join(f'{rep.spug_version}/{x}' for x in files) + helper.local(f'cd {REPOS_DIR} && tar zcf {tar_file} {exclude} {contain}') + helper.send_step('local', 5, f'完成') + + +class Helper: + def __init__(self, rds, key): + self.rds = rds + self.key = key + self.rds.delete(self.key) + + def parse_filter_rule(self, data: str, sep='\n'): + data, files = data.strip(), [] + if data: + for line in data.split(sep): + line = line.strip() + if line and not line.startswith('#'): + files.append(line) + return files + + def _send(self, message): + print(message) + self.rds.rpush(self.key, json.dumps(message)) + + def send_info(self, key, message): + self._send({'key': key, 'data': message}) + + def send_error(self, key, message, with_break=True): + message = '\r\n' + message + self._send({'key': key, 'status': 'error', 'data': message}) + if with_break: + raise SpugError + + def send_step(self, key, step, data): + self._send({'key': key, 'step': step, 'data': data}) + + def local(self, command, env=None): + if env: + env = dict(env.items()) + env.update(os.environ) + command = 'set -e\n' + command + task = subprocess.Popen(command, env=env, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + while True: + message = task.stdout.readline() + if not message: + break + self.send_info('local', message.decode()) + if task.wait() != 0: + self.send_error('local', f'exit code: {task.returncode}') diff --git a/spug_api/apps/repository/views.py b/spug_api/apps/repository/views.py new file mode 100644 index 0000000..acf5231 --- /dev/null +++ b/spug_api/apps/repository/views.py @@ -0,0 +1,53 @@ +# Copyright: (c) OpenSpug Organization. https://github.com/openspug/spug +# Copyright: (c) +# Released under the AGPL-3.0 License. +from django.views.generic import View +from django.db.models import F +from libs import json_response, JsonParser, Argument +from apps.repository.models import Repository +from apps.repository.utils import dispatch +from apps.app.models import Deploy +from threading import Thread +import json + + +class RepositoryView(View): + def get(self, request): + data = Repository.objects.annotate( + app_name=F('app__name'), + env_name=F('env__name'), + created_by_user=F('created_by__nickname')) + return json_response([x.to_view() for x in data]) + + def post(self, request): + form, error = JsonParser( + Argument('deploy_id', type=int, help='参数错误'), + Argument('version', help='请输入构建版本'), + Argument('extra', type=list, help='参数错误'), + Argument('remarks', required=False) + ).parse(request.body) + if error is None: + deploy = Deploy.objects.filter(pk=form.deploy_id).first() + if not deploy: + return json_response(error='未找到指定发布配置') + form.extra = json.dumps(form.extra) + form.spug_version = Repository.make_spug_version(deploy.id) + rep = Repository.objects.create( + app_id=deploy.app_id, + env_id=deploy.env_id, + created_by=request.user, + **form) + Thread(target=dispatch, args=(rep,)).start() + return json_response(rep.to_view()) + return json_response(error=error) + + def delete(self, request): + form, error = JsonParser( + Argument('id', type=int, help='请指定操作对象') + ).parse(request.GET) + if error is None: + repository = Repository.objects.filter(pk=form.id).first() + if not repository: + return json_response(error='未找到指定构建记录') + + return json_response(error=error) diff --git a/spug_api/consumer/consumers.py b/spug_api/consumer/consumers.py index b4d32fd..470618c 100644 --- a/spug_api/consumer/consumers.py +++ b/spug_api/consumer/consumers.py @@ -2,10 +2,12 @@ # Copyright: (c) # Released under the AGPL-3.0 License. from channels.generic.websocket import WebsocketConsumer +from django.conf import settings from django_redis import get_redis_connection from asgiref.sync import async_to_sync from apps.host.models import Host from threading import Thread +import time import json @@ -34,6 +36,44 @@ class ExecConsumer(WebsocketConsumer): self.send(text_data='pong') +class ComConsumer(WebsocketConsumer): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + token = self.scope['url_route']['kwargs']['token'] + module = self.scope['url_route']['kwargs']['module'] + if module == 'build': + self.key = f'{settings.BUILD_KEY}:{token}' + else: + raise TypeError(f'unknown module for {module}') + self.rds = get_redis_connection() + + def connect(self): + self.accept() + + def disconnect(self, code): + self.rds.close() + + def get_response(self, index): + counter = 0 + while counter < 30: + response = self.rds.lindex(self.key, index) + if response: + return response.decode() + counter += 1 + time.sleep(0.2) + + def receive(self, text_data='', **kwargs): + if text_data.isdigit(): + index = int(text_data) + response = self.get_response(index) + while response: + index += 1 + self.send(text_data=response) + time.sleep(1) + response = self.get_response(index) + self.send(text_data='pong') + + class SSHConsumer(WebsocketConsumer): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) diff --git a/spug_api/consumer/routing.py b/spug_api/consumer/routing.py index 2b9aeff..d869671 100644 --- a/spug_api/consumer/routing.py +++ b/spug_api/consumer/routing.py @@ -10,6 +10,7 @@ ws_router = AuthMiddleware( URLRouter([ path('ws/exec//', ExecConsumer), path('ws/ssh//', SSHConsumer), + path('ws///', ComConsumer), path('ws/notify/', NotifyConsumer), ]) ) diff --git a/spug_api/spug/settings.py b/spug_api/spug/settings.py index 23590a5..2bf952d 100644 --- a/spug_api/spug/settings.py +++ b/spug_api/spug/settings.py @@ -46,6 +46,7 @@ INSTALLED_APPS = [ 'apps.app', 'apps.deploy', 'apps.notify', + 'apps.repository', ] MIDDLEWARE = [ @@ -103,6 +104,7 @@ TEMPLATES = [ SCHEDULE_KEY = 'spug:schedule' MONITOR_KEY = 'spug:monitor' REQUEST_KEY = 'spug:request' +BUILD_KEY = 'spug:build' REPOS_DIR = os.path.join(BASE_DIR, 'repos') # Internationalization @@ -116,7 +118,7 @@ USE_I18N = True USE_L10N = True -USE_TZ = True +USE_TZ = False AUTHENTICATION_EXCLUDES = ( '/account/login/', diff --git a/spug_api/spug/urls.py b/spug_api/spug/urls.py index c5cc0df..0635e8f 100644 --- a/spug_api/spug/urls.py +++ b/spug_api/spug/urls.py @@ -29,6 +29,7 @@ urlpatterns = [ path('config/', include('apps.config.urls')), path('app/', include('apps.app.urls')), path('deploy/', include('apps.deploy.urls')), + path('repository/', include('apps.repository.urls')), path('home/', include('apps.home.urls')), path('notify/', include('apps.notify.urls')), path('file/', include('apps.file.urls')), diff --git a/spug_web/src/pages/deploy/repository/Console.js b/spug_web/src/pages/deploy/repository/Console.js new file mode 100644 index 0000000..ec98171 --- /dev/null +++ b/spug_web/src/pages/deploy/repository/Console.js @@ -0,0 +1,80 @@ +/** + * 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 { observer } from 'mobx-react'; +import { FullscreenOutlined, FullscreenExitOutlined, LoadingOutlined } from '@ant-design/icons'; +import { Modal, Steps } from 'antd'; +import { X_TOKEN, human_time } from 'libs'; +import styles from './index.module.less'; +import store from './store'; + +export default observer(function Console() { + const [fullscreen, setFullscreen] = useState(false); + const [step, setStep] = useState(0); + const [status, setStatus] = useState('process') + + useEffect(() => { + store.outputs = [`${human_time()} 建立连接... `] + let index = 0; + const token = store.record.spug_version; + const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'; + const socket = new WebSocket(`${protocol}//${window.location.host}/api/ws/build/${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 {data, step, status} = JSON.parse(e.data); + if (data !== undefined) store.outputs.push(data); + if (step !== undefined) setStep(step); + if (status !== undefined) setStatus(status); + } + } + return () => { + socket.close(); + store.outputs = [] + } + }, []) + + function handleClose() { + store.fetchRecords(); + store.logVisible = false + } + + function StepItem(props) { + let icon = null; + if (props.step === step && status === 'process') { + icon = + } + return + } + + return ( + 构建控制台, +
setFullscreen(!fullscreen)}> + {fullscreen ? : } +
+ ]} + footer={null} + onCancel={handleClose} + className={styles.console} + maskClosable={false}> + + + + + + + +
{store.outputs}
+
+ ) +}) diff --git a/spug_web/src/pages/deploy/repository/Detail.js b/spug_web/src/pages/deploy/repository/Detail.js new file mode 100644 index 0000000..f537062 --- /dev/null +++ b/spug_web/src/pages/deploy/repository/Detail.js @@ -0,0 +1,61 @@ +import React, { useState, useEffect } from 'react'; +import { observer } from 'mobx-react'; +import { Drawer, Descriptions, Table } from 'antd'; +import { http } from 'libs'; +import store from './store'; +import styles from './index.module.less'; + +export default observer(function (props) { + const [fetching, setFetching] = useState(true); + const [order, setOrder] = useState({}); + + useEffect(() => { + if (store.record.id && props.visible) { + + } + }, [props.visible]) + + const columns = [{ + title: '应用名称', + key: 'name', + }, { + title: '商品主图', + dataIndex: 'pic', + }, { + title: '单价', + dataIndex: 'price', + align: 'right', + }, { + title: '数量', + key: 'number', + align: 'right', + }, { + title: '金额', + key: 'money', + align: 'right', + }] + + const record = store.record; + const [extra1, extra2, extra3] = record.extra || []; + return ( + store.detailVisible = false}> + 基本信息}> + {record.app_name} + {record.env_name} + {record.version} + {extra1 === 'branch' ? ([ + {extra2}, + {extra3}, + ]) : ( + {extra2} + )} + {record.spug_version} + {record.created_at} + {record.remarks} + {record.created_by_user} + + 发布记录} style={{marginTop: 24}}/> + + + ) +}) \ No newline at end of file diff --git a/spug_web/src/pages/deploy/repository/Form.js b/spug_web/src/pages/deploy/repository/Form.js new file mode 100644 index 0000000..8f514d8 --- /dev/null +++ b/spug_web/src/pages/deploy/repository/Form.js @@ -0,0 +1,158 @@ +/** + * 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 { observer } from 'mobx-react'; +import { LoadingOutlined, SyncOutlined } from '@ant-design/icons'; +import { Modal, Form, Input, Select, Button, message } from 'antd'; +import http from 'libs/http'; +import store from './store'; +import lds from 'lodash'; + +export default observer(function () { + const [form] = Form.useForm(); + const [loading, setLoading] = useState(false); + const [fetching, setFetching] = useState(true); + const [git_type, setGitType] = useState(lds.get(store.deploy, 'extra.0', 'branch')); + const [extra1, setExtra1] = useState(lds.get(store.deploy, 'extra.1')); + const [extra2, setExtra2] = useState(lds.get(store.deploy, 'extra.2')); + const [versions, setVersions] = useState({}); + + useEffect(() => { + fetchVersions(); + }, []) + + useEffect(() => { + if (extra1 === undefined) { + const {branches, tags} = versions; + let [extra1, extra2] = [undefined, undefined]; + if (git_type === 'branch') { + if (branches) { + extra1 = _getDefaultBranch(branches); + extra2 = lds.get(branches[extra1], '0.id') + } + } else { + if (tags) { + extra1 = lds.get(Object.keys(tags), 0) + } + } + setExtra1(extra1) + setExtra2(extra2) + } + }, [versions, git_type, extra1]) + + function fetchVersions() { + setFetching(true); + http.get(`/api/app/deploy/${store.deploy.id}/versions/`, {timeout: 120000}) + .then(res => setVersions(res)) + .finally(() => setFetching(false)) + } + + function _getDefaultBranch(branches) { + branches = Object.keys(branches); + let branch = branches[0]; + for (let item of store.records) { + if (item['deploy_id'] === store.record['deploy_id']) { + const b = lds.get(item, 'extra.1'); + if (branches.includes(b)) { + branch = b + } + break + } + } + return branch + } + + function switchType(v) { + setExtra1(undefined); + setGitType(v) + } + + function switchExtra1(v) { + setExtra1(v) + if (git_type === 'branch') { + setExtra2(lds.get(versions.branches[v], '0.id')) + } + } + + function handleSubmit() { + setLoading(true); + const formData = form.getFieldsValue(); + formData['deploy_id'] = store.deploy.id; + formData['extra'] = [git_type, extra1, extra2]; + http.post('/api/repository/', formData) + .then(res => { + message.success('操作成功'); + store.record = res; + store.formVisible = false; + store.fetchRecords() + }, () => setLoading(false)) + } + + const {branches, tags} = versions; + return ( + store.formVisible = false} + confirmLoading={loading} + onOk={handleSubmit}> +
+ + + + + 根据网络情况,首次刷新可能会很慢,请耐心等待。 + clone 失败? + }> + + + + + + + + {fetching ? : + + } + + + {git_type === 'branch' && ( + + + + )} + + + + +
+ ) +}) \ No newline at end of file diff --git a/spug_web/src/pages/deploy/repository/Table.js b/spug_web/src/pages/deploy/repository/Table.js new file mode 100644 index 0000000..341c08c --- /dev/null +++ b/spug_web/src/pages/deploy/repository/Table.js @@ -0,0 +1,70 @@ +/** + * Copyright (c) OpenSpug Organization. https://github.com/openspug/spug + * Copyright (c) + * Released under the AGPL-3.0 License. + */ +import React from 'react'; +import { observer } from 'mobx-react'; +import { Table, Modal, Tag, message } from 'antd'; +import { PlusOutlined } from '@ant-design/icons'; +import { Action, TableCard, AuthButton } from 'components'; +import { http, hasPermission } from 'libs'; +import store from './store'; + +function ComTable() { + function handleDelete(info) { + Modal.confirm({ + title: '删除确认', + content: `确定要删除【${info['name']}】?`, + onOk: () => { + return http.delete('/api/config/environment/', {params: {id: info.id}}) + .then(() => { + message.success('删除成功'); + store.fetchRecords() + }) + } + }) + } + + const statusColorMap = {'0': 'cyan', '1': 'blue', '2': 'red', '5': 'green'}; + return ( + } + onClick={() => store.addVisible = true}>新建 + ]} + pagination={{ + showSizeChanger: true, + showLessItems: true, + hideOnSinglePage: true, + showTotal: total => `共 ${total} 条`, + pageSizeOptions: ['10', '20', '50', '100'] + }}> + + + + + + + {info.status_alias}}/> + {hasPermission('config.env.edit|config.env.del') && ( + ( + + store.showDetail(info)}>详情 + store.showConsole(info)}>日志 + + )}/> + )} + + ) +} + +export default observer(ComTable) diff --git a/spug_web/src/pages/deploy/repository/index.js b/spug_web/src/pages/deploy/repository/index.js new file mode 100644 index 0000000..ee9edb1 --- /dev/null +++ b/spug_web/src/pages/deploy/repository/index.js @@ -0,0 +1,70 @@ +/** + * Copyright (c) OpenSpug Organization. https://github.com/openspug/spug + * Copyright (c) + * Released under the AGPL-3.0 License. + */ +import React, { useEffect } from 'react'; +import { observer } from 'mobx-react'; +import { Select } from 'antd'; +import { SearchForm, AuthDiv, Breadcrumb, AppSelector } from 'components'; +import { includes } from 'libs'; +import ComTable from './Table'; +import ComForm from './Form'; +import Console from './Console'; +import Detail from './Detail'; +import store from './store'; +import envStore from 'pages/config/environment/store'; +import appStore from 'pages/config/app/store'; + +export default observer(function () { + useEffect(() => { + store.fetchRecords(); + if (!appStore.records.length) appStore.fetchRecords() + }, []) + return ( + + + 首页 + 应用发布 + 构建仓库 + + + + + + + + + + + item.extend === '1'} + onCancel={() => store.addVisible = false} + onSelect={store.confirmAdd}/> + + {store.formVisible && } + {store.logVisible && } + + ) +}) diff --git a/spug_web/src/pages/deploy/repository/index.module.less b/spug_web/src/pages/deploy/repository/index.module.less new file mode 100644 index 0000000..83600df --- /dev/null +++ b/spug_web/src/pages/deploy/repository/index.module.less @@ -0,0 +1,36 @@ +.console { + .fullscreen { + position: absolute; + top: 0; + right: 0; + display: block; + width: 56px; + height: 56px; + line-height: 56px; + text-align: center; + cursor: pointer; + color: rgba(0, 0, 0, .45); + margin-right: 56px; + } + + .fullscreen:hover { + color: #000; + } + + .out { + margin-top: 24px; + min-height: 40px; + max-height: 300px; + padding: 10px 15px; + border: 1px solid #d9d9d9; + border-radius: 4px; + background-color: #fafafa; + } +} + +.split { + height: 1px; + background-color: #eee; + margin: 16px 0 24px 0; + clear: both +} \ No newline at end of file diff --git a/spug_web/src/pages/deploy/repository/store.js b/spug_web/src/pages/deploy/repository/store.js new file mode 100644 index 0000000..2e77ce3 --- /dev/null +++ b/spug_web/src/pages/deploy/repository/store.js @@ -0,0 +1,54 @@ +/** + * Copyright (c) OpenSpug Organization. https://github.com/openspug/spug + * Copyright (c) + * Released under the AGPL-3.0 License. + */ +import { observable, computed } from "mobx"; +import http from 'libs/http'; + +class Store { + @observable records = []; + @observable record = {}; + @observable idMap = {}; + @observable outputs = []; + @observable isFetching = false; + @observable formVisible = false; + @observable addVisible = false; + @observable logVisible = false; + @observable detailVisible = false; + + @observable f_app_id; + @observable f_env_id; + + @computed get dataSource() { + let records = this.records; + if (this.f_app_id) records = records.filter(x => x.app_id === this.f_app_id); + if (this.f_env_id) records = records.filter(x => x.env_id === this.f_env_id); + return records + } + + fetchRecords = () => { + this.isFetching = true; + return http.get('/api/repository/') + .then(res => this.records = res) + .finally(() => this.isFetching = false) + }; + + confirmAdd = (deploy) => { + this.deploy = deploy; + this.formVisible = true; + this.addVisible = false; + }; + + showConsole = (info) => { + this.record = info; + this.logVisible = true + }; + + showDetail = (info) => { + this.record = info; + this.detailVisible = true + } +} + +export default new Store() diff --git a/spug_web/src/routes.js b/spug_web/src/routes.js index ed19077..37c51d9 100644 --- a/spug_web/src/routes.js +++ b/spug_web/src/routes.js @@ -23,6 +23,7 @@ import ExecTask from './pages/exec/task'; import ExecTemplate from './pages/exec/template'; import DeployApp from './pages/deploy/app'; +import DeployRepository from './pages/deploy/repository'; import DeployRequest from './pages/deploy/request'; import DoExt1Index from './pages/deploy/do/Ext1Index'; import DoExt2Index from './pages/deploy/do/Ext2Index'; @@ -59,6 +60,7 @@ export default [ { icon: , title: '应用发布', auth: 'deploy.app.view|deploy.request.view', child: [ {title: '应用管理', auth: 'deploy.app.view', path: '/deploy/app', component: DeployApp}, + {title: '构建仓库', auth: 'deploy.repository.view', path: '/deploy/repository', component: DeployRepository}, {title: '发布申请', auth: 'deploy.request.view', path: '/deploy/request', component: DeployRequest}, {path: '/deploy/do/ext1/:id', component: DoExt1Index}, {path: '/deploy/do/ext2/:id', component: DoExt2Index},