diff --git a/db/update20.0.sql b/db/update20.0.sql index 52310426ba..966fcb1edb 100644 --- a/db/update20.0.sql +++ b/db/update20.0.sql @@ -142,6 +142,8 @@ CREATE TABLE `zt_artifactrepo` ( PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; +ALTER TABLE `zt_repo` ADD `lastCommit` DATETIME NULL DEFAULT NULL AFTER `lastSync`; + ALTER TABLE `zt_build` ADD `artifactRepoID` MEDIUMINT(8) UNSIGNED NOT NULL AFTER `bugs`; CREATE TABLE IF NOT EXISTS `zt_session` ( diff --git a/db/zentao.sql b/db/zentao.sql index b997f1e4b1..95ade2ddd7 100755 --- a/db/zentao.sql +++ b/db/zentao.sql @@ -1482,6 +1482,7 @@ CREATE TABLE IF NOT EXISTS `zt_repo` ( `acl` text NULL, `synced` tinyint(1) NOT NULL DEFAULT '0', `lastSync` datetime NULL, + `lastCommit` datetime NULL, `desc` text NULL, `extra` char(30) NOT NULL DEFAULT '', `preMerge` enum('0','1') COLLATE 'utf8_general_ci' NOT NULL DEFAULT '0', diff --git a/lib/scm/gitea.class.php b/lib/scm/gitea.class.php index 18b65e6d18..9105cc5f4e 100644 --- a/lib/scm/gitea.class.php +++ b/lib/scm/gitea.class.php @@ -322,14 +322,9 @@ class Gitea if(strpos($fromRevision, '^') !== false) { $list = execCmd(escapeCmd("$this->client log -2 $toRevision --pretty=format:%H -- $path"), 'array'); - if(isset($list[1])) - { - $fromRevision = $list[1]; - } - else - { - $fromRevision = 'HEAD^'; - } + if(!isset($list[1])) return execCmd(escapeCmd("$this->client show HEAD"), 'array'); + + $fromRevision = $list[1]; } $lines = execCmd(escapeCmd("$this->client diff $fromRevision $toRevision -- $path"), 'array'); return $lines; diff --git a/lib/scm/gitlab.class.php b/lib/scm/gitlab.class.php index 69728af6dc..cb5457fa2d 100644 --- a/lib/scm/gitlab.class.php +++ b/lib/scm/gitlab.class.php @@ -575,7 +575,7 @@ class gitlab $log->time = date('Y-m-d H:i:s', strtotime($commit->created_at)); $commits[$commit->id] = $log; - $files[$commit->id] = $this->getFilesByCommit($log->revision); + if($getFile) $files[$commit->id] = $this->getFilesByCommit($log->revision); return array('commits' => $commits, 'files' => $files); } @@ -659,7 +659,7 @@ class gitlab * @access public * @return array */ - public function getCommitsByPath($path, $fromRevision = '', $toRevision = '', $perPage = 0, $page = 1, $getUrl = false) + public function getCommitsByPath($path, $fromRevision = '', $toRevision = '', $perPage = 0, $page = 1, $getUrl = false, $beginDate = '', $endDate = '') { $path = ltrim($path, DIRECTORY_SEPARATOR); $api = "commits"; @@ -668,17 +668,17 @@ class gitlab $param->path = urldecode($path); $param->ref_name = ($toRevision != 'HEAD' and $toRevision) ? $toRevision : $this->branch; - $fromDate = $this->getCommittedDate($fromRevision); - $toDate = $this->getCommittedDate($toRevision); + $fromDate = $beginDate ? $beginDate : $this->getCommittedDate($fromRevision); + $toDate = $endDate ? $endDate : $this->getCommittedDate($toRevision); $since = ''; $until = ''; - if($fromRevision and $toRevision) + if(($fromRevision && $toRevision) || ($beginDate && $endDate)) { $since = min($fromDate, $toDate); $until = max($fromDate, $toDate); } - elseif($fromRevision) + elseif($fromRevision || $beginDate) { $since = $fromDate; } @@ -949,4 +949,27 @@ class gitlab } return $lists; } + + /** + * 获取特定对象的api。 + * Get api url for target. + * + * @param string $target + * @access public + * @return string + */ + public function getApiUrl(string $target): string + { + if($target == 'project') + { + return str_replace('repository/', '', $this->root). "?private_token={$this->token}"; + } + $params = array(); + $params['private_token'] = $this->token; + $params['page'] = 1; + $params['per_page'] = isset($params['per_page']) ? $params['per_page'] : 100; + + $api = $this->root . $target . '?' . http_build_query($params); + return $api; + } } diff --git a/lib/scm/gitrepo.class.php b/lib/scm/gitrepo.class.php index ad805caf62..1fd46f4fbe 100644 --- a/lib/scm/gitrepo.class.php +++ b/lib/scm/gitrepo.class.php @@ -310,7 +310,9 @@ class GitRepo if(strpos($fromRevision, '^') !== false) { $list = execCmd(escapeCmd("$this->client log -2 $toRevision --pretty=format:%H -- $path"), 'array'); - if(isset($list[1])) $fromRevision = $list[1]; + if(!isset($list[1])) return execCmd(escapeCmd("$this->client show HEAD"), 'array'); + + $fromRevision = $list[1]; } $lines = execCmd(escapeCmd("$this->client diff $fromRevision $toRevision -- $path"), 'array'); return $lines; diff --git a/lib/scm/gogs.class.php b/lib/scm/gogs.class.php index 95c9173b21..88fe4ecd3b 100644 --- a/lib/scm/gogs.class.php +++ b/lib/scm/gogs.class.php @@ -322,7 +322,9 @@ class Gogs if(strpos($fromRevision, '^') !== false) { $list = execCmd(escapeCmd("$this->client log -2 $toRevision --pretty=format:%H -- $path"), 'array'); - if(isset($list[1])) $fromRevision = $list[1]; + if(!isset($list[1])) return execCmd(escapeCmd("$this->client show HEAD"), 'array'); + + $fromRevision = $list[1]; } $lines = execCmd(escapeCmd("$this->client diff $fromRevision $toRevision -- $path"), 'array'); return $lines; diff --git a/lib/scm/subversion.class.php b/lib/scm/subversion.class.php index 5f6e3087c6..b8ebfe26fe 100644 --- a/lib/scm/subversion.class.php +++ b/lib/scm/subversion.class.php @@ -115,8 +115,8 @@ class Subversion ksort($dirs); $tags = array(); - $trimmed = trim($path, '/'); - $prefix = empty($trimmed) ? '/' : '/' . $trimmed . '/'; + $trimed = trim($path, '/'); + $prefix = empty($trimed) ? '/' : '/' . $trimed . '/'; foreach($dirs as $dirNames) { ksort($dirNames); diff --git a/module/account/config/dtable.php b/module/account/config/dtable.php index 6609838678..696c01de3e 100644 --- a/module/account/config/dtable.php +++ b/module/account/config/dtable.php @@ -50,6 +50,7 @@ $config->account->actionList['edit']['icon'] = 'edit'; $config->account->actionList['edit']['text'] = $lang->edit; $config->account->actionList['edit']['hint'] = $lang->edit; $config->account->actionList['edit']['data-toggle'] = 'modal'; +$config->account->actionList['edit']['data-size'] = 'sm'; $config->account->actionList['edit']['showText'] = true; $config->account->actionList['edit']['url'] = array('module' => 'account', 'method' => 'edit', 'params' => 'id={id}'); diff --git a/module/account/control.php b/module/account/control.php index 37e0dbf6db..5a9f60d56c 100644 --- a/module/account/control.php +++ b/module/account/control.php @@ -67,8 +67,8 @@ class account extends control $this->loadModel('action')->create('account', $id, 'created'); - if(isonlybody()) return $this->send(array('result' => 'success', 'message' => $this->lang->saveSuccess, 'locate' => 'reload')); - return $this->send(array('result' => 'success', 'message' => $this->lang->saveSuccess, 'locate' => inlink('browse'), 'closeModal' => true)); + if(isonlybody()) return $this->send(array('result' => 'success', 'message' => $this->lang->saveSuccess, 'load' => true)); + return $this->send(array('result' => 'success', 'message' => $this->lang->saveSuccess, 'callback' => 'loadCurrentPage()', 'closeModal' => true)); } $this->app->loadLang('serverroom'); diff --git a/module/account/css/common.ui.css b/module/account/css/common.ui.css index 9f7f8d06e7..1f67fbe25b 100644 --- a/module/account/css/common.ui.css +++ b/module/account/css/common.ui.css @@ -1 +1,9 @@ -#accountCreateForm .form-row {max-width: 500px;} \ No newline at end of file +#accountCreateForm .form-row {max-width: 500px;} + +#mainNavbar>.container {padding-top: 8px} +#mainNavbar .nav {justify-content: left; position: relative; left: 0px; top: -4px;} +@media (min-width: 1400px){#mainNavbar .nav {left: -12px;}} +#mainNavbar {background: none;} +#mainNavbar .nav-item>a {padding-right: 0.5rem} +#mainNavbar .nav-item>.active {color: unset; font-weight: 700;} +#mainNavbar .nav-item>a.active:after {position: absolute; content: ''; border-bottom: 2px solid #2e7fff; inset: 0 6px 0 15px;} diff --git a/module/account/model.php b/module/account/model.php index e6d53911e6..78ccf07232 100644 --- a/module/account/model.php +++ b/module/account/model.php @@ -53,7 +53,7 @@ class accountModel extends model /* Concatenate the conditions for the query. */ if($param) { - $query = $this->loadModel('search')->getQuery($param); + $query = $this->loadModel('search')->getZinQuery($param); if($query) { $this->session->set('accountQuery', $query->sql); diff --git a/module/account/ui/browse.html.php b/module/account/ui/browse.html.php index 166365cabf..e9262a5a19 100644 --- a/module/account/ui/browse.html.php +++ b/module/account/ui/browse.html.php @@ -12,12 +12,17 @@ declare(strict_types=1); namespace zin; -featureBar(li(searchToggle())); +$queryMenuLink = createLink('account', 'browse', "browseType=bySearch¶m={queryID}"); +featureBar +( + set::queryMenuLinkCallback(fn($key) => str_replace('{queryID}', (string)$key, $queryMenuLink)), + li(searchToggle()) +); /* zin: Define the toolbar on main menu. */ $canCreate = hasPriv('account', 'create'); $createLink = $this->createLink('account', 'create'); -$createItem = array('text' => $lang->account->create, 'url' => $createLink, 'class' => 'primary', 'icon' => 'plus', 'data-toggle' => 'modal'); +$createItem = array('text' => $lang->account->create, 'url' => $createLink, 'class' => 'primary', 'icon' => 'plus', 'data-toggle' => 'modal', 'data-size' => 'sm'); $tableData = initTableData($accountList, $config->account->dtable->fieldList, $this->account); diff --git a/module/artifactrepo/config/dtable.php b/module/artifactrepo/config/dtable.php index 1420c94a94..84bdb126c2 100644 --- a/module/artifactrepo/config/dtable.php +++ b/module/artifactrepo/config/dtable.php @@ -14,6 +14,7 @@ $config->artifactrepo->dtable->fieldList['format']['sortType'] = true; $config->artifactrepo->dtable->fieldList['products']['title'] = $lang->repo->product; $config->artifactrepo->dtable->fieldList['products']['name'] = 'productNames'; $config->artifactrepo->dtable->fieldList['products']['width'] = '300'; +$config->artifactrepo->dtable->fieldList['products']['hint'] = true; $config->artifactrepo->dtable->fieldList['type']['title'] = $lang->artifactrepo->type; $config->artifactrepo->dtable->fieldList['type']['sortType'] = true; @@ -23,6 +24,7 @@ $config->artifactrepo->dtable->fieldList['status']['sortType'] = true; $config->artifactrepo->dtable->fieldList['url']['title'] = $lang->artifactrepo->url; $config->artifactrepo->dtable->fieldList['url']['width'] = '400'; +$config->artifactrepo->dtable->fieldList['url']['hint'] = true; $config->artifactrepo->dtable->fieldList['actions']['type'] = 'actions'; $config->artifactrepo->dtable->fieldList['actions']['menu'] = array('edit', 'delete'); diff --git a/module/artifactrepo/control.php b/module/artifactrepo/control.php index e9ab968832..9f62c2fcab 100644 --- a/module/artifactrepo/control.php +++ b/module/artifactrepo/control.php @@ -23,7 +23,7 @@ class artifactrepo extends control @access public * @return void */ - public function browse($browseType = 'all', $orderBy = 'id_desc', $recTotal = 0, $recPerPage = 24, $pageID = 1) + public function browse($browseType = 'all', $orderBy = 'id_desc', $recTotal = 0, $recPerPage = 25, $pageID = 1) { /* Load pager. */ $this->app->loadClass('pager', true); @@ -38,7 +38,7 @@ class artifactrepo extends control $this->view->recTotal = $recTotal; $this->view->recPerPage = $recPerPage; $this->view->artifactRepos = $artifactRepos; - $this->view->products = $this->loadModel('product')->getPairs('', 0, '', 'all'); + $this->view->products = $this->loadModel('product')->getPairs('all', 0, '', 'all'); $this->view->pageLink = $this->createLink('artifactrepo', 'browse', "browseType={$browseType}&orderBy={$orderBy}&recTotal={$recTotal}&recPerPage={$recPerPage}&pageID={$pageID}"); $this->display(); @@ -57,6 +57,7 @@ class artifactrepo extends control { $repo = form::data($this->config->artifactrepo->form->create) ->join('products', ',') + ->add('editedBy', $this->app->user->account) ->add('createdBy', $this->app->user->account) ->add('createdDate', helper::now()) ->get(); @@ -104,9 +105,14 @@ class artifactrepo extends control $artifactRepo = $this->artifactrepo->getByID($artifactRepoID); + $products = $this->loadModel('product')->getPairs('', 0, '', 'all'); + $linkedProducts = $this->loadModel('product')->getByIdList(explode(',', $artifactRepo->products)); + $linkedProductPairs = array_combine(array_keys($linkedProducts), helper::arrayColumn($linkedProducts, 'name')); + $products = $products + $linkedProductPairs; + $this->view->title = $this->lang->artifactrepo->edit; $this->view->artifactRepo = $artifactRepo; - $this->view->products = $this->loadModel('product')->getPairs('', 0, '', 'all'); + $this->view->products = $products; $this->display(); } @@ -138,7 +144,9 @@ class artifactrepo extends control { $repos = $this->artifactrepo->getServerRepos($serverID); - return print(json_encode($repos)); + if(!$repos['result']) return $this->send(array('result' => 'fail', 'message' => $this->lang->artifactrepo->loseConnect)); + + return print(json_encode($repos['data'])); } public function ajaxUpdateArtifactRepos() diff --git a/module/artifactrepo/js/create.ui.js b/module/artifactrepo/js/create.ui.js index 8ce9d80e57..aeab6790da 100644 --- a/module/artifactrepo/js/create.ui.js +++ b/module/artifactrepo/js/create.ui.js @@ -11,12 +11,19 @@ function getArtifactRepo(event) { const server = $(event.target).val(); const url = $.createLink('artifactrepo', 'ajaxGetArtifactRepos', 'serverID=' + server); + if(!server) return; toggleLoading('#repoName', true); $.get(url, function(response) { repoData = JSON.parse(response); + if(repoData.result !== undefined && repoData.result === 'fail') + { + zui.Modal.alert(repoData.message); + toggleLoading('#repoName', false); + return; + } var repoItems = []; for(i in repoData) { diff --git a/module/artifactrepo/lang/de.php b/module/artifactrepo/lang/de.php index 50a10e3071..136dc249a0 100644 --- a/module/artifactrepo/lang/de.php +++ b/module/artifactrepo/lang/de.php @@ -1,10 +1,14 @@ artifactrepo = new stdclass(); -$lang->artifactrepo->common = 'Browse artifact repo'; +$lang->artifactrepo->common = 'Artifact repo'; +$lang->artifactrepo->browse = 'Repo List'; $lang->artifactrepo->create = 'Add artifact repo'; $lang->artifactrepo->edit = 'Edit artifact repo'; $lang->artifactrepo->delete = 'Del artifact repo'; +$lang->artifactrepo->ajaxGetArtifactRepos = 'Api: Artifact Repo List'; +$lang->artifactrepo->ajaxUpdateArtifactRepos = 'Api: Update Status'; + $lang->artifactrepo->name = 'Name'; $lang->artifactrepo->serverID = 'Server'; $lang->artifactrepo->repoName = 'Artifact Repo'; @@ -13,4 +17,6 @@ $lang->artifactrepo->type = 'Type'; $lang->artifactrepo->status = 'Status'; $lang->artifactrepo->url = 'Repo Address'; -$lang->artifactrepo->confirmDelete = 'Are you sure you want to delete this aritfact repo?'; +$lang->artifactrepo->confirmDelete = 'Are you sure you want to delete this artifact repo?'; +$lang->artifactrepo->deleteError = 'The artifact repo is already associated with a build. Please cancel the association and try again'; +$lang->artifactrepo->loseConnect = 'Artifact repo server not responding'; diff --git a/module/artifactrepo/lang/en.php b/module/artifactrepo/lang/en.php index 6920b10e49..136dc249a0 100644 --- a/module/artifactrepo/lang/en.php +++ b/module/artifactrepo/lang/en.php @@ -1,10 +1,14 @@ artifactrepo = new stdclass(); -$lang->artifactrepo->common = 'Browse artifact repo'; +$lang->artifactrepo->common = 'Artifact repo'; +$lang->artifactrepo->browse = 'Repo List'; $lang->artifactrepo->create = 'Add artifact repo'; $lang->artifactrepo->edit = 'Edit artifact repo'; $lang->artifactrepo->delete = 'Del artifact repo'; +$lang->artifactrepo->ajaxGetArtifactRepos = 'Api: Artifact Repo List'; +$lang->artifactrepo->ajaxUpdateArtifactRepos = 'Api: Update Status'; + $lang->artifactrepo->name = 'Name'; $lang->artifactrepo->serverID = 'Server'; $lang->artifactrepo->repoName = 'Artifact Repo'; @@ -13,5 +17,6 @@ $lang->artifactrepo->type = 'Type'; $lang->artifactrepo->status = 'Status'; $lang->artifactrepo->url = 'Repo Address'; -$lang->artifactrepo->confirmDelete = 'The artifact repo cannot be restored after deletion. Are you sure you want to continue?'; +$lang->artifactrepo->confirmDelete = 'Are you sure you want to delete this artifact repo?'; $lang->artifactrepo->deleteError = 'The artifact repo is already associated with a build. Please cancel the association and try again'; +$lang->artifactrepo->loseConnect = 'Artifact repo server not responding'; diff --git a/module/artifactrepo/lang/fr.php b/module/artifactrepo/lang/fr.php index 50a10e3071..136dc249a0 100644 --- a/module/artifactrepo/lang/fr.php +++ b/module/artifactrepo/lang/fr.php @@ -1,10 +1,14 @@ artifactrepo = new stdclass(); -$lang->artifactrepo->common = 'Browse artifact repo'; +$lang->artifactrepo->common = 'Artifact repo'; +$lang->artifactrepo->browse = 'Repo List'; $lang->artifactrepo->create = 'Add artifact repo'; $lang->artifactrepo->edit = 'Edit artifact repo'; $lang->artifactrepo->delete = 'Del artifact repo'; +$lang->artifactrepo->ajaxGetArtifactRepos = 'Api: Artifact Repo List'; +$lang->artifactrepo->ajaxUpdateArtifactRepos = 'Api: Update Status'; + $lang->artifactrepo->name = 'Name'; $lang->artifactrepo->serverID = 'Server'; $lang->artifactrepo->repoName = 'Artifact Repo'; @@ -13,4 +17,6 @@ $lang->artifactrepo->type = 'Type'; $lang->artifactrepo->status = 'Status'; $lang->artifactrepo->url = 'Repo Address'; -$lang->artifactrepo->confirmDelete = 'Are you sure you want to delete this aritfact repo?'; +$lang->artifactrepo->confirmDelete = 'Are you sure you want to delete this artifact repo?'; +$lang->artifactrepo->deleteError = 'The artifact repo is already associated with a build. Please cancel the association and try again'; +$lang->artifactrepo->loseConnect = 'Artifact repo server not responding'; diff --git a/module/artifactrepo/lang/zh-cn.php b/module/artifactrepo/lang/zh-cn.php index c3cc7e3e02..f77c289b54 100644 --- a/module/artifactrepo/lang/zh-cn.php +++ b/module/artifactrepo/lang/zh-cn.php @@ -1,10 +1,14 @@ artifactrepo = new stdclass(); -$lang->artifactrepo->common = '浏览制品库'; +$lang->artifactrepo->common = '制品库'; +$lang->artifactrepo->browse = '制品库列表'; $lang->artifactrepo->create = '添加制品库'; $lang->artifactrepo->edit = '编辑制品库'; $lang->artifactrepo->delete = '删除制品库'; +$lang->artifactrepo->ajaxGetArtifactRepos = '接口:制品库列表'; +$lang->artifactrepo->ajaxUpdateArtifactRepos = '接口:更新制品库状态'; + $lang->artifactrepo->name = '名称'; $lang->artifactrepo->serverID = '服务器'; $lang->artifactrepo->repoName = '制品库'; @@ -13,5 +17,6 @@ $lang->artifactrepo->type = '类型'; $lang->artifactrepo->status = '状态'; $lang->artifactrepo->url = '库地址'; -$lang->artifactrepo->confirmDelete = '制品库删除后无法还原,你确认要继续么?'; +$lang->artifactrepo->confirmDelete = '您确认要删除该制品库吗?'; $lang->artifactrepo->deleteError = '当前制品库中制品已关联版本,请取消关联后重试。'; +$lang->artifactrepo->loseConnect = '制品库服务器无法连接'; diff --git a/module/artifactrepo/model.php b/module/artifactrepo/model.php index 79041a5b5e..7b52fa0289 100644 --- a/module/artifactrepo/model.php +++ b/module/artifactrepo/model.php @@ -71,12 +71,12 @@ class artifactrepoModel extends model if($server->type == 'nexus') { - $url = $server->url . '/service/rest/v1/repositorySettings'; + $url = $server->url . '/service/rest/v1/repositorySettings'; $auth = "{$server->account}:{$server->password}"; - $response = common::http($url, '', array(CURLOPT_USERPWD => $auth)); - $data = json_decode($response); - return is_array($data) ? $data : array(); + $response = common::http($url, '', array(CURLOPT_USERPWD => $auth), array(), 'data', 'POST', 10, true); + $data = array('result' => $response[1] == 200, 'data' => json_decode($response['body'])); + return $data; } return array(); diff --git a/module/artifactrepo/ui/browse.html.php b/module/artifactrepo/ui/browse.html.php index 2bf93ea6fe..ab18128229 100644 --- a/module/artifactrepo/ui/browse.html.php +++ b/module/artifactrepo/ui/browse.html.php @@ -26,8 +26,9 @@ foreach($artifactRepos as $repo) foreach($productList as $productID) { if(!isset($products[$productID])) continue; - $repo->productNames .= ' ' . zget($products, $productID, $productID); + $repo->productNames .= ',' . zget($products, $productID, $productID); } + $repo->productNames = trim($repo->productNames, ','); } } $artifactRepos = initTableData($artifactRepos, $config->artifactrepo->dtable->fieldList, $this->artifactrepo); diff --git a/module/artifactrepo/ui/create.html.php b/module/artifactrepo/ui/create.html.php index 017267a9f7..6717f8db41 100644 --- a/module/artifactrepo/ui/create.html.php +++ b/module/artifactrepo/ui/create.html.php @@ -13,6 +13,7 @@ namespace zin; formPanel ( set::title($lang->artifactrepo->create), + set::actionsClass('w-2/3'), formGroup ( set::width('2/3'), @@ -50,6 +51,7 @@ formPanel ( set::width('2/3'), set::name('type'), + set::required(true), set::label($lang->artifactrepo->type), set::readonly(true), input @@ -62,6 +64,7 @@ formPanel ( set::width('2/3'), set::name('status'), + set::required(true), set::label($lang->artifactrepo->status), set::readonly(true), ), diff --git a/module/artifactrepo/ui/edit.html.php b/module/artifactrepo/ui/edit.html.php index 112022bd99..d45cc267bd 100644 --- a/module/artifactrepo/ui/edit.html.php +++ b/module/artifactrepo/ui/edit.html.php @@ -13,6 +13,7 @@ namespace zin; formPanel ( set::title($lang->artifactrepo->edit), + set::actionsClass('w-2/3'), formGroup ( set::width('2/3'), diff --git a/module/cne/lang/de.php b/module/cne/lang/de.php new file mode 100644 index 0000000000..9a769aff7a --- /dev/null +++ b/module/cne/lang/de.php @@ -0,0 +1,28 @@ +CNE->InstallSuccess = 'Install Success'; +$lang->CNE->InstallFailure = 'Install Fail'; +$lang->CNE->serverError = 'CNE Server Error'; + +$lang->CNE->statusList = array(); +$lang->CNE->statusList['normal'] = 'Normal'; +$lang->CNE->statusList['abnormal'] = 'Abnormal'; +$lang->CNE->statusList['stopped'] = 'Stopped'; +$lang->CNE->statusList['unknown'] = 'UnKnown'; + +$lang->CNE->statusIcons = array(); +$lang->CNE->statusIcons['normal'] = "check"; +$lang->CNE->statusIcons['abnormal'] = "exclamation-pure"; +$lang->CNE->statusIcons['stopped'] = "pause-pure"; +$lang->CNE->statusIcons['unknown'] = "exclamation-pure"; + +$lang->CNE->errorList = array(); +//$lang->CNE->errorList[400] = '不能包含特殊字符'; +$lang->CNE->errorList[400] = 'Request api fail'; +$lang->CNE->errorList[404] = 'Service no exist'; +$lang->CNE->errorList[40004] = 'Certificate does not match the domain'; +$lang->CNE->errorList[41001] = 'Certificate expired'; +$lang->CNE->errorList[41002] = 'Certificate mismatch'; +$lang->CNE->errorList[41003] = 'Incomplete certificate chain'; +$lang->CNE->errorList[41004] = 'Private key does not match the certificate'; +$lang->CNE->errorList[41005] = 'Certificate parsing failed'; +$lang->CNE->errorList[41006] = 'Key parsing failed'; diff --git a/module/cne/lang/en.php b/module/cne/lang/en.php index 6d371ed48f..9a769aff7a 100644 --- a/module/cne/lang/en.php +++ b/module/cne/lang/en.php @@ -1,28 +1,28 @@ CNE->InstallSuccess = 'Installation Succeeded'; -$lang->CNE->InstallFailure = 'Installation Failed'; +$lang->CNE->InstallSuccess = 'Install Success'; +$lang->CNE->InstallFailure = 'Install Fail'; $lang->CNE->serverError = 'CNE Server Error'; $lang->CNE->statusList = array(); $lang->CNE->statusList['normal'] = 'Normal'; -$lang->CNE->statusList['abnormal'] = 'Anomaly'; +$lang->CNE->statusList['abnormal'] = 'Abnormal'; $lang->CNE->statusList['stopped'] = 'Stopped'; -$lang->CNE->statusList['unknown'] = 'Unknown'; +$lang->CNE->statusList['unknown'] = 'UnKnown'; $lang->CNE->statusIcons = array(); -$lang->CNE->statusIcons['normal'] = ""; -$lang->CNE->statusIcons['abnormal'] = ""; -$lang->CNE->statusIcons['stopped'] = ""; -$lang->CNE->statusIcons['unknown'] = ""; +$lang->CNE->statusIcons['normal'] = "check"; +$lang->CNE->statusIcons['abnormal'] = "exclamation-pure"; +$lang->CNE->statusIcons['stopped'] = "pause-pure"; +$lang->CNE->statusIcons['unknown'] = "exclamation-pure"; $lang->CNE->errorList = array(); -//$lang->CNE->errorList[400] = 'Only number and letter allowed'; -$lang->CNE->errorList[400] = 'Failed to request cluster info'; -$lang->CNE->errorList[404] = 'Service does not exist'; +//$lang->CNE->errorList[400] = '不能包含特殊字符'; +$lang->CNE->errorList[400] = 'Request api fail'; +$lang->CNE->errorList[404] = 'Service no exist'; $lang->CNE->errorList[40004] = 'Certificate does not match the domain'; -$lang->CNE->errorList[41001] = 'Certificate has expired'; +$lang->CNE->errorList[41001] = 'Certificate expired'; $lang->CNE->errorList[41002] = 'Certificate mismatch'; $lang->CNE->errorList[41003] = 'Incomplete certificate chain'; -$lang->CNE->errorList[41004] = 'Certificate and private key do not match'; -$lang->CNE->errorList[41005] = 'Failed parsing certificate'; -$lang->CNE->errorList[41006] = 'Failed parsing key'; +$lang->CNE->errorList[41004] = 'Private key does not match the certificate'; +$lang->CNE->errorList[41005] = 'Certificate parsing failed'; +$lang->CNE->errorList[41006] = 'Key parsing failed'; diff --git a/module/cne/lang/fr.php b/module/cne/lang/fr.php new file mode 100644 index 0000000000..9a769aff7a --- /dev/null +++ b/module/cne/lang/fr.php @@ -0,0 +1,28 @@ +CNE->InstallSuccess = 'Install Success'; +$lang->CNE->InstallFailure = 'Install Fail'; +$lang->CNE->serverError = 'CNE Server Error'; + +$lang->CNE->statusList = array(); +$lang->CNE->statusList['normal'] = 'Normal'; +$lang->CNE->statusList['abnormal'] = 'Abnormal'; +$lang->CNE->statusList['stopped'] = 'Stopped'; +$lang->CNE->statusList['unknown'] = 'UnKnown'; + +$lang->CNE->statusIcons = array(); +$lang->CNE->statusIcons['normal'] = "check"; +$lang->CNE->statusIcons['abnormal'] = "exclamation-pure"; +$lang->CNE->statusIcons['stopped'] = "pause-pure"; +$lang->CNE->statusIcons['unknown'] = "exclamation-pure"; + +$lang->CNE->errorList = array(); +//$lang->CNE->errorList[400] = '不能包含特殊字符'; +$lang->CNE->errorList[400] = 'Request api fail'; +$lang->CNE->errorList[404] = 'Service no exist'; +$lang->CNE->errorList[40004] = 'Certificate does not match the domain'; +$lang->CNE->errorList[41001] = 'Certificate expired'; +$lang->CNE->errorList[41002] = 'Certificate mismatch'; +$lang->CNE->errorList[41003] = 'Incomplete certificate chain'; +$lang->CNE->errorList[41004] = 'Private key does not match the certificate'; +$lang->CNE->errorList[41005] = 'Certificate parsing failed'; +$lang->CNE->errorList[41006] = 'Key parsing failed'; diff --git a/module/cne/lang/vi.php b/module/cne/lang/vi.php new file mode 100644 index 0000000000..9a769aff7a --- /dev/null +++ b/module/cne/lang/vi.php @@ -0,0 +1,28 @@ +CNE->InstallSuccess = 'Install Success'; +$lang->CNE->InstallFailure = 'Install Fail'; +$lang->CNE->serverError = 'CNE Server Error'; + +$lang->CNE->statusList = array(); +$lang->CNE->statusList['normal'] = 'Normal'; +$lang->CNE->statusList['abnormal'] = 'Abnormal'; +$lang->CNE->statusList['stopped'] = 'Stopped'; +$lang->CNE->statusList['unknown'] = 'UnKnown'; + +$lang->CNE->statusIcons = array(); +$lang->CNE->statusIcons['normal'] = "check"; +$lang->CNE->statusIcons['abnormal'] = "exclamation-pure"; +$lang->CNE->statusIcons['stopped'] = "pause-pure"; +$lang->CNE->statusIcons['unknown'] = "exclamation-pure"; + +$lang->CNE->errorList = array(); +//$lang->CNE->errorList[400] = '不能包含特殊字符'; +$lang->CNE->errorList[400] = 'Request api fail'; +$lang->CNE->errorList[404] = 'Service no exist'; +$lang->CNE->errorList[40004] = 'Certificate does not match the domain'; +$lang->CNE->errorList[41001] = 'Certificate expired'; +$lang->CNE->errorList[41002] = 'Certificate mismatch'; +$lang->CNE->errorList[41003] = 'Incomplete certificate chain'; +$lang->CNE->errorList[41004] = 'Private key does not match the certificate'; +$lang->CNE->errorList[41005] = 'Certificate parsing failed'; +$lang->CNE->errorList[41006] = 'Key parsing failed'; diff --git a/module/cne/lang/zh-cn.php b/module/cne/lang/zh-cn.php index 57539e5198..a034968fd7 100644 --- a/module/cne/lang/zh-cn.php +++ b/module/cne/lang/zh-cn.php @@ -10,10 +10,10 @@ $lang->CNE->statusList['stopped'] = '关闭'; $lang->CNE->statusList['unknown'] = '无数据'; $lang->CNE->statusIcons = array(); -$lang->CNE->statusIcons['normal'] = ""; -$lang->CNE->statusIcons['abnormal'] = ""; -$lang->CNE->statusIcons['stopped'] = ""; -$lang->CNE->statusIcons['unknown'] = ""; +$lang->CNE->statusIcons['normal'] = "check"; +$lang->CNE->statusIcons['abnormal'] = "exclamation-pure"; +$lang->CNE->statusIcons['stopped'] = "pause-pure"; +$lang->CNE->statusIcons['unknown'] = "exclamation-pure"; $lang->CNE->errorList = array(); //$lang->CNE->errorList[400] = '不能包含特殊字符'; diff --git a/module/cne/lang/zh-tw.php b/module/cne/lang/zh-tw.php new file mode 100644 index 0000000000..a034968fd7 --- /dev/null +++ b/module/cne/lang/zh-tw.php @@ -0,0 +1,28 @@ +CNE->InstallSuccess = '安装成功'; +$lang->CNE->InstallFailure = '安装失败'; +$lang->CNE->serverError = 'CNE服务器出错'; + +$lang->CNE->statusList = array(); +$lang->CNE->statusList['normal'] = '正常'; +$lang->CNE->statusList['abnormal'] = '异常'; +$lang->CNE->statusList['stopped'] = '关闭'; +$lang->CNE->statusList['unknown'] = '无数据'; + +$lang->CNE->statusIcons = array(); +$lang->CNE->statusIcons['normal'] = "check"; +$lang->CNE->statusIcons['abnormal'] = "exclamation-pure"; +$lang->CNE->statusIcons['stopped'] = "pause-pure"; +$lang->CNE->statusIcons['unknown'] = "exclamation-pure"; + +$lang->CNE->errorList = array(); +//$lang->CNE->errorList[400] = '不能包含特殊字符'; +$lang->CNE->errorList[400] = '请求集群接口失败'; +$lang->CNE->errorList[404] = '服务不存在'; +$lang->CNE->errorList[40004] = '证书与域名不匹配'; +$lang->CNE->errorList[41001] = '证书过期'; +$lang->CNE->errorList[41002] = '证书不匹配'; +$lang->CNE->errorList[41003] = '证书链不完整'; +$lang->CNE->errorList[41004] = '私钥与证书不匹配'; +$lang->CNE->errorList[41005] = '证书解析失败'; +$lang->CNE->errorList[41006] = '密钥解析失败'; diff --git a/module/cne/model.php b/module/cne/model.php index cef2287884..d5ac00d3c7 100644 --- a/module/cne/model.php +++ b/module/cne/model.php @@ -50,7 +50,7 @@ class cneModel extends model if(empty($result) || $result->code != 200 || empty($result->data)) return array(); $instanceList = $result->data; - return array_combine(array_column($instanceList, 'name'), $instanceList); + return array_combine(helper::arrayColumn($instanceList, 'name'), $instanceList); } /** @@ -335,7 +335,7 @@ class cneModel extends model $customDomain = $this->loadModel('setting')->getItem('owner=system&module=common§ion=domain&key=customDomain'); if($customDomain) return $customDomain; - return getenv('APP_DOMAIN') ? getenv('APP_DOMAIN') : $this->config->CNE->app->domain; + return getenv('APP_DOMAIN'); } /** @@ -449,7 +449,7 @@ class cneModel extends model $apiUrl = "/api/cne/statistics/app"; $result = $this->apiPost($apiUrl, $apiData, $this->config->CNE->api->headers); - if(!isset($result->code) || $result->code != 200)return array_combine(array_column($instancesMetrics, 'id'), $instancesMetrics); + if(!isset($result->code) || $result->code != 200)return array_combine(helper::arrayColumn($instancesMetrics, 'id'), $instancesMetrics); foreach($result->data as $k8sMetric) { @@ -468,7 +468,7 @@ class cneModel extends model $instancesMetrics[$k8sMetric->name]->memory->rate = $instancesMetrics[$k8sMetric->name]->memory->limit > 0 ? round($instancesMetrics[$k8sMetric->name]->memory->usage / $instancesMetrics[$k8sMetric->name]->memory->limit * 100, 2) : 0; } - return array_combine(array_column($instancesMetrics, 'id'), $instancesMetrics); + return array_combine(helper::arrayColumn($instancesMetrics, 'id'), $instancesMetrics); } /** @@ -839,22 +839,27 @@ class cneModel extends model { if(empty($mappings)) $mappings = array( array( - "key" => "admin_username", + "key" => "admin_username", "type" => "helm", "path" => "auth.username" ), array( - "key" => "admin_password", - "type" => "helm", - "path" => "auth.password" + "key" => "z_username", + "path" => "z_username", + "type" => "secret" ), array( - "key" => "admin_token", - "type" => "secret", - "path" => "api_token" + "key" => "z_password", + "path" => "z_password", + "type" => "secret" ), + array( + "key" => "api_token", + "path" => "api_token", + "type" => "secret" + ) ); - + $apiParams = new stdclass; $apiParams->cluster = ''; $apiParams->namespace = $instance->spaceData->k8space; @@ -993,7 +998,7 @@ class cneModel extends model if(empty($result) || $result->code != 200 || empty($result->data)) return array(); $dbList = $result->data; - return array_combine(array_column($dbList, 'name'), $dbList); + return array_combine(helper::arrayColumn($dbList, 'name'), $dbList); } /** @@ -1057,7 +1062,7 @@ class cneModel extends model if(empty($result) || $result->code != 200 || empty($result->data)) return array(); $dbList = $result->data; - return array_combine(array_column($dbList, 'name'), $dbList); + return array_combine(helper::arrayColumn($dbList, 'name'), $dbList); } /** @@ -1077,7 +1082,7 @@ class cneModel extends model if(empty($result) || $result->code != 200 || empty($result->data)) return array(); $dbList = $result->data; - return array_combine(array_column($dbList, 'name'), $dbList); + return array_combine(helper::arrayColumn($dbList, 'name'), $dbList); } /** @@ -1144,8 +1149,12 @@ class cneModel extends model public function apiPost($url, $data, $header = array(), $host = '') { $requestUri = ($host ? $host : $this->config->CNE->api->host) . $url; - $result = json_decode(commonModel::http($requestUri, $data, array(CURLOPT_CUSTOMREQUEST => 'POST'), $header, 'json', 20)); - if($result && $result->code == 200) return $result; + $result = json_decode(commonModel::http($requestUri, $data, array(CURLOPT_CUSTOMREQUEST => 'POST'), $header, 'json', 'POST', 20)); + if($result && in_array($result->code, array(200, 201))) + { + $result->code = 200; + return $result; + } if($result) return $this->translateError($result); return $this->cneServerError(); @@ -1313,4 +1322,21 @@ class cneModel extends model $apiUrl = "/api/cne/platform/restore/status"; return $this->apiGet($apiUrl, $apiParams, $this->config->CNE->api->headers); } + + /** + * app资源调度尝试。 + * Try allocate for apps. + * + * @param array $apps + * @access public + * @return object + */ + public function tryAllocate(array $resources): object + { + $apiParams = new stdclass(); + $apiParams->requests = $resources; + + $apiUrl = "/api/cne/system/resource/try-allocate"; + return $this->apiPost($apiUrl, $apiParams, $this->config->CNE->api->headers); + } } diff --git a/module/git/model.php b/module/git/model.php index 2cea1db380..7df4b2771c 100644 --- a/module/git/model.php +++ b/module/git/model.php @@ -69,6 +69,8 @@ class gitModel extends model /* Get repos and load module. */ $this->setRepos(); $this->loadModel('job'); + $this->loadModel('gitlab'); + $this->loadModel('repo'); if(empty($this->repos)) return false; @@ -82,7 +84,11 @@ class gitModel extends model { $this->updateCommit($repo, $commentGroup, true); - if($repo->SCM == 'Gitlab') $this->loadModel('gitlab')->updateCodePath((int)$repo->serviceHost, (int)$repo->serviceProject, (int)$repo->id); + if($repo->SCM == 'Gitlab') + { + $this->gitlab->updateCodePath((int)$repo->serviceHost, (int)$repo->serviceProject, (int)$repo->id); + $this->repo->updateCommitDate((int)$repo->id); + } /* Create compile by tag. */ $jobs = zget($tagGroup, $repoID, array()); @@ -120,6 +126,8 @@ class gitModel extends model */ public function updateCommit($repo, $commentGroup, $printLog = true) { + if($repo->SCM == 'Gitlab') return; + /* Load module and print log. */ $this->loadModel('repo'); if($printLog) $this->printLog("begin repo $repo->id"); @@ -202,7 +210,7 @@ class gitModel extends model if(empty($objectIDs) or !isset($objectTypeMap[$objectType])) continue; $this->post->$objectType = $objectIDs; - $this->repo->link($repo->id, $log->revision, $objectTypeMap[$objectType]); + $this->repo->link($repo->id, $log->revision, $objectTypeMap[$objectType], 'commit'); } } } diff --git a/module/gitea/control.php b/module/gitea/control.php index dbd6c5ddac..d95e35ab6d 100644 --- a/module/gitea/control.php +++ b/module/gitea/control.php @@ -20,6 +20,8 @@ class gitea extends control { parent::__construct($moduleName, $methodName); + if(!commonModel::hasPriv('instance', 'manage')) $this->loadModel('common')->deny('instance', 'manage', false); + /* This is essential when changing tab(menu) from gitea to repo. */ /* Optional: common::setMenuVars('devops', $this->session->repoID); */ if($this->app->rawMethod != 'binduser') $this->loadModel('ci')->setMenu(); @@ -163,8 +165,9 @@ class gitea extends control $changes = common::createChanges($oldGitea, $gitea); $this->loadModel('action')->logHistory($actionID, $changes); - $response['load'] = true; - $response['result'] = 'success'; + $response['load'] = $this->createLink('space', 'browse'); + $response['message'] = zget($this->lang->instance->notices, 'uninstallSuccess'); + $response['result'] = 'success'; return $this->send($response); } @@ -196,7 +199,7 @@ class gitea extends control * @access public * @return void */ - public function bindUser(int $giteaID, string $type = 'all') + public function bindUser($giteaID, $type = 'all') { $zentaoUsers = $this->dao->select('account,email,realname')->from(TABLE_USER)->fetchAll('account'); $userPairs = $this->loadModel('user')->getPairs('noclosed|noletter'); @@ -204,12 +207,12 @@ class gitea extends control if($_POST) { $this->gitea->bindUser($giteaID); - if(dao::isError()) return $this->send(array('result' => 'fail', 'message' => dao::getError())); - return $this->send(array('result' => 'success', 'message' => $this->lang->saveSuccess, 'locate' => $this->server->http_referer)); + if(dao::isError()) return $this->sendError(dao::getError()); + return $this->sendSuccess(array('message' => $this->lang->saveSuccess, 'load' => helper::createLink('space', 'browse'))); } $userList = array(); - $giteaUsers = $this->gitea->apiGetUsers($giteaID); + $giteaUsers = $this->gitea->apiGetUsers($giteaID); $bindedUsers = $this->gitea->getUserAccountIdPairs($giteaID); $matchedResult = $this->gitea->getMatchedUsers($giteaID, $giteaUsers, $zentaoUsers); @@ -245,6 +248,7 @@ class gitea extends control $this->view->recTotal = count($userList); $this->view->userList = $userList; $this->view->userPairs = $userPairs; + $this->view->zentaoUsers = $zentaoUsers; $this->display(); } @@ -263,11 +267,13 @@ class gitea extends control $project = urldecode(base64_decode($project)); $branches = $this->gitea->apiGetBranches($giteaID, $project); - $options = ""; + + $options = array(); + $options[] = array('text' => '', 'value' => '');; foreach($branches as $branch) { - $options .= ""; + $options[] = array('text' => $branch->name, 'value' => $branch->name); } - $this->send($options); + return print(json_encode($options)); } } diff --git a/module/gitea/js/binduser.js b/module/gitea/js/binduser.js new file mode 100644 index 0000000000..014d06eeb1 --- /dev/null +++ b/module/gitea/js/binduser.js @@ -0,0 +1,24 @@ +$(document).ready(function() +{ + $('.gitlab-user-bind').change(function() + { + var user = zentaoUsers[$(this).val()]; + if(user !== undefined) + { + $(this).parent().parent().find('.email').text(user.email) + } + }); + + $(document).on('click', '.zentao-users .chosen-container', function() + { + var $obj = $(this).prev('select'); + var value = $obj.val(); + if($obj.hasClass('filled')) return false; + + $obj.empty(); + $obj.append($('#userList').html()); + $obj.val(value); + $obj.addClass('filled'); + $obj.trigger("chosen:updated"); + }) +}); diff --git a/module/gitea/js/binduser.ui.js b/module/gitea/js/binduser.ui.js index d1537ecfb7..4bfcde782f 100644 --- a/module/gitea/js/binduser.ui.js +++ b/module/gitea/js/binduser.ui.js @@ -10,7 +10,27 @@ window.setUserEmail = function() window.renderGitlabUser = function(result, {row}) { const giteaID = row.data.giteaID; - result.push({html: ``}); + result.push({html: ''}); return result; } + +window.bindUser = function() +{ + const myDTable = $('#table-gitea-binduser').zui('dtable'); + const formData = myDTable.$.getFormData(); + + var bindData = $('#table-gitea-binduser').zui('dtable').$.props.data; + var postData = {}; + postData['giteaUserNames[]'] = []; + for(i in bindData) + { + postData['giteaUserNames[]'].push(bindData[i].giteaID); + postData['zentaoUsers[' + bindData[i].giteaID + ']'] = formData['zentaoUsers[' + bindData[i].giteaID + ']']; + } + + $.ajaxSubmit({ + url: $.createLink('gitea', 'bindUser', 'giteaID=' + giteaID + '&type=' + type), + data: postData + }); +} diff --git a/module/gitea/lang/de.php b/module/gitea/lang/de.php index e73cc56f5a..e7484dabc0 100644 --- a/module/gitea/lang/de.php +++ b/module/gitea/lang/de.php @@ -13,6 +13,7 @@ $lang->gitea->giteaAccount = 'Gitea Account'; $lang->gitea->giteaEmail = 'Email'; $lang->gitea->zentaoAccount = 'Zentao Account'; $lang->gitea->bindingStatus = 'Binding Status'; +$lang->gitea->all = 'All'; $lang->gitea->notBind = 'Not bind'; $lang->gitea->binded = 'Binded'; $lang->gitea->bindDynamic = '%s and Zentao user %s'; diff --git a/module/gitea/lang/en.php b/module/gitea/lang/en.php index 667b7c82b5..046590c667 100644 --- a/module/gitea/lang/en.php +++ b/module/gitea/lang/en.php @@ -13,6 +13,7 @@ $lang->gitea->giteaAccount = 'Gitea Account'; $lang->gitea->giteaEmail = 'Email'; $lang->gitea->zentaoAccount = 'Zentao Account'; $lang->gitea->bindingStatus = 'Binding Status'; +$lang->gitea->all = 'All'; $lang->gitea->notBind = 'Not bind'; $lang->gitea->binded = 'Binded'; $lang->gitea->bindDynamic = '%s and Zentao user %s'; diff --git a/module/gitea/lang/fr.php b/module/gitea/lang/fr.php index e73cc56f5a..e7484dabc0 100644 --- a/module/gitea/lang/fr.php +++ b/module/gitea/lang/fr.php @@ -13,6 +13,7 @@ $lang->gitea->giteaAccount = 'Gitea Account'; $lang->gitea->giteaEmail = 'Email'; $lang->gitea->zentaoAccount = 'Zentao Account'; $lang->gitea->bindingStatus = 'Binding Status'; +$lang->gitea->all = 'All'; $lang->gitea->notBind = 'Not bind'; $lang->gitea->binded = 'Binded'; $lang->gitea->bindDynamic = '%s and Zentao user %s'; diff --git a/module/gitea/lang/zh-cn.php b/module/gitea/lang/zh-cn.php index f160a3e110..144da40f6c 100644 --- a/module/gitea/lang/zh-cn.php +++ b/module/gitea/lang/zh-cn.php @@ -13,6 +13,7 @@ $lang->gitea->giteaAccount = 'Gitea用户'; $lang->gitea->giteaEmail = '邮箱'; $lang->gitea->zentaoAccount = '禅道用户'; $lang->gitea->bindingStatus = '绑定状态'; +$lang->gitea->all = '全部'; $lang->gitea->notBind = '未绑定'; $lang->gitea->binded = '已绑定'; $lang->gitea->bindDynamic = '%s与禅道用户%s'; diff --git a/module/gitea/model.php b/module/gitea/model.php index b2603eedd3..b9a4d8605a 100644 --- a/module/gitea/model.php +++ b/module/gitea/model.php @@ -98,9 +98,9 @@ class giteaModel extends model * * @param int $giteaID * @access public - * @return bool + * @return array */ - public function bindUser(int $giteaID): bool + public function bindUser($giteaID) { $userPairs = $this->loadModel('user')->getPairs('noclosed|noletter'); $users = $this->post->zentaoUsers; @@ -116,7 +116,7 @@ class giteaModel extends model if(count($repeatUsers)) { - dao::$errors[] = sprintf($this->lang->gitea->bindUserError, join(',', $repeatUsers)); + dao::$errors = sprintf($this->lang->gitea->bindUserError, join(',', $repeatUsers)); return false; } @@ -148,8 +148,6 @@ class giteaModel extends model $this->loadModel('action')->create('giteauser', $giteaID, 'bind', '', sprintf($this->lang->gitea->bindDynamic, $giteaNames[$openID], $zentaoUsers[$account]->realname)); } } - - return true; } /** @@ -308,13 +306,13 @@ class giteaModel extends model /** * Get matched gitea users. * - * @param int $giteaID - * @param array $giteaUsers - * @param array $zentaoUsers + * @param int $giteaID + * @param array $giteaUsers + * @param array $zentaoUsers * @access public * @return array */ - public function getMatchedUsers(int $giteaID, array $giteaUsers, array $zentaoUsers): array + public function getMatchedUsers($giteaID, $giteaUsers, $zentaoUsers) { $matches = new stdclass; foreach($giteaUsers as $giteaUser) @@ -331,9 +329,9 @@ class giteaModel extends model $matchedUsers = array(); foreach($giteaUsers as $giteaUser) { - if(isset($bindedUsers[$giteaUser->account])) + if(isset($bindedUsers[$giteaUser->id])) { - $giteaUser->zentaoAccount = $bindedUsers[$giteaUser->account]; + $giteaUser->zentaoAccount = $bindedUsers[$giteaUser->id]; $matchedUsers[$giteaUser->id] = $giteaUser; continue; } @@ -379,6 +377,7 @@ class giteaModel extends model $gitea = $this->getByID($giteaID); $oauth = "oauth2:{$gitea->token}@"; $project->tokenCloneUrl = preg_replace('/(http(s)?:\/\/)/', "\$1$oauth", $project->html_url); + $project->tokenCloneUrl = str_replace(array('https://', 'http://'), strstr($url, ':', true) . '://', $project->tokenCloneUrl); } return $project; diff --git a/module/gitea/ui/binduser.html.php b/module/gitea/ui/binduser.html.php index 39505c6713..9832051775 100644 --- a/module/gitea/ui/binduser.html.php +++ b/module/gitea/ui/binduser.html.php @@ -24,6 +24,8 @@ featureBar toolbar(); jsVar('zentaoUsers', $zentaoUsers); +jsVar('giteaID', $giteaID); +jsVar('type', $type); $config->gitea->dtable->bindUser->fieldList['giteaEmail']['onRenderCell'] = jsRaw('renderGitlabUser'); $config->gitea->dtable->bindUser->fieldList['zentaoUsers']['controlItems'] = $userPairs; form @@ -48,7 +50,7 @@ form array( 'text' => $lang->save, 'btnType' => 'primary', - 'onClick' => jsRaw("() => {\$('#bindForm').trigger('submit')}") + 'onClick' => jsRaw("() => {bindUser()}") ), array( 'text' => $lang->goback, diff --git a/module/gitea/view/binduser.html.php b/module/gitea/view/binduser.html.php index faddecc316..ec2af2764f 100644 --- a/module/gitea/view/binduser.html.php +++ b/module/gitea/view/binduser.html.php @@ -11,60 +11,66 @@ */ ?> +createLink('gitea', 'browse', ""); ?> +
-
-

gitea->bindUser;?>

+
+ ' . $lang->goback, $browseLink, 'self', "data-app='{$app->tab}'", 'btn btn-secondary'); + + $allLink = $this->createLink('gitea', 'binduser', "giteaID={$giteaID}&type=all"); + $bindedLink = $this->createLink('gitea', 'binduser', "giteaID={$giteaID}&type=binded"); + $notBindLink = $this->createLink('gitea', 'binduser', "giteaID={$giteaID}&type=notBind"); + if($type == 'all') + { + echo html::linkButton('' . $lang->gitea->all . "" . count($giteaUsers) . "", $allLink, 'self', "data-app='{$app->tab}'", 'btn btn-info active'); + echo html::linkButton('' . $lang->gitea->notBind, $notBindLink, 'self', "data-app='{$app->tab}'", 'btn btn-info'); + echo html::linkButton('' . $lang->gitea->binded, $bindedLink, 'self', "data-app='{$app->tab}'", 'btn btn-info'); + } + else if($type == 'binded') + { + echo html::linkButton('' . $lang->gitea->all, $allLink, 'self', "data-app='{$app->tab}'", 'btn btn-info'); + echo html::linkButton('' . $lang->gitea->notBind, $notBindLink, 'self', "data-app='{$app->tab}'", 'btn btn-info'); + echo html::linkButton('' . $lang->gitea->binded . "" . count($giteaUsers) . "", $bindedLink, 'self', "data-app='{$app->tab}'", 'btn btn-info active'); + } + else + { + echo html::linkButton('' . $lang->gitea->all, $allLink, 'self', "data-app='{$app->tab}'", 'btn btn-info'); + echo html::linkButton('' . $lang->gitea->notBind . "" . count($giteaUsers) . "", $notBindLink, 'self', "data-app='{$app->tab}'", 'btn btn-info active'); + echo html::linkButton('' . $lang->gitea->binded, $bindedLink, 'self', "data-app='{$app->tab}'", 'btn btn-info'); + } + ?>
- +
- - + + - zentaoAccount)) continue;?> - account]", $giteaUser->realname);?> + id]", $giteaUser->realname);?> - - - - - - - - zentaoAccount)) continue;?> - account]", $giteaUser->realname);?> - - + + - - - @@ -82,4 +88,12 @@ + diff --git a/module/gitlab/control.php b/module/gitlab/control.php index d86f31e4b2..91816b3dae 100644 --- a/module/gitlab/control.php +++ b/module/gitlab/control.php @@ -20,6 +20,15 @@ class gitlab extends control { parent::__construct($moduleName, $methodName); + if(stripos($this->methodName, 'ajax') === false) + { + if(!commonModel::hasPriv('space', 'browse')) $this->loadModel('common')->deny('space', 'browse', false); + + if(!in_array(strtolower(strtolower($this->methodName)), array('browseproject', 'browsegroup', 'browseuser', 'browsebranch', 'browsetag'))) + { + if(!commonModel::hasPriv('instance', 'manage')) $this->loadModel('common')->deny('instance', 'manage', false); + } + } /* This is essential when changing tab(menu) from gitlab to repo. */ /* Optional: common::setMenuVars('devops', $this->session->repoID); */ $this->loadModel('ci')->setMenu(); @@ -195,7 +204,9 @@ class gitlab extends control $this->loadModel('action')->create('gitlabuser', $openID, 'bind', '', sprintf($this->lang->gitlab->bindDynamic, $gitlabNames[$openID], $zentaoUsers[$account]->realname)); } } - return $this->send(array('result' => 'success', 'message' => $this->lang->saveSuccess, 'locate' => $this->createLink('gitlab', 'browse'))); + + if(dao::isError()) return $this->sendError(dao::getError()); + return $this->sendSuccess(array('message' => $this->lang->saveSuccess, 'load' => helper::createLink('space', 'browse'))); } $userList = array(); @@ -263,7 +274,7 @@ class gitlab extends control $changes = common::createChanges($oldGitLab, $gitLab); $this->loadModel('action')->logHistory($actionID, $changes); - $response['load'] = true; + $response['load'] = $this->createLink('space', 'browse'); $response['result'] = 'success'; return $this->send($response); } @@ -618,7 +629,12 @@ class gitlab extends control { $this->gitlab->createUser($gitlabID); - if(dao::isError()) return $this->send(array('result' => 'fail', 'message' => dao::getError())); + if(dao::isError()) + { + $message = dao::getError(); + foreach($message as &$msg) if(is_string($msg)) $msg = zget($this->lang->gitlab->errorResonse, $msg, $msg); + return $this->send(array('result' => 'fail', 'message' => $message)); + } return $this->send(array('result' => 'success', 'message' => $this->lang->saveSuccess, 'locate' => inlink('browseUser', "gitlabID=$gitlabID"))); } @@ -725,7 +741,7 @@ class gitlab extends control if(!$this->app->user->admin) { $openID = $this->gitlab->getUserIDByZentaoAccount($gitlabID, $this->app->user->account); - if(!$openID) return print(js::alert($this->lang->gitlab->mustBindUser) . js::locate($this->createLink('gitlab', 'browse'))); + if(!$openID) return print(js::alert($this->lang->gitlab->mustBindUser) . js::locate($this->createLink('space', 'browse'))); } $this->app->loadClass('pager', true); @@ -745,6 +761,7 @@ class gitlab extends control if(!$project->adminer and isset($project->owner) and $project->owner->id == $openID) $project->adminer = true; $project->isMaintainer = $this->gitlab->checkUserAccess($gitlabID, $project->id, $project, $groupIDList, 'maintainer'); + $project->isDeveloper = $this->gitlab->checkUserAccess($gitlabID, $project->id, $project, $groupIDList, 'developer'); } $gitlab = $this->gitlab->getByID($gitlabID); diff --git a/module/gitlab/js/binduser.js b/module/gitlab/js/binduser.js index a4b6cc07c2..014d06eeb1 100644 --- a/module/gitlab/js/binduser.js +++ b/module/gitlab/js/binduser.js @@ -8,4 +8,17 @@ $(document).ready(function() $(this).parent().parent().find('.email').text(user.email) } }); -}); \ No newline at end of file + + $(document).on('click', '.zentao-users .chosen-container', function() + { + var $obj = $(this).prev('select'); + var value = $obj.val(); + if($obj.hasClass('filled')) return false; + + $obj.empty(); + $obj.append($('#userList').html()); + $obj.val(value); + $obj.addClass('filled'); + $obj.trigger("chosen:updated"); + }) +}); diff --git a/module/gitlab/js/binduser.ui.js b/module/gitlab/js/binduser.ui.js index a650ba4445..0c8d821099 100644 --- a/module/gitlab/js/binduser.ui.js +++ b/module/gitlab/js/binduser.ui.js @@ -14,3 +14,23 @@ window.renderGitlabUser = function(result, {row}) return result; } + +window.bindUser = function() +{ + const myDTable = $('#table-gitlab-binduser').zui('dtable'); + const formData = myDTable.$.getFormData(); + + var bindData = $('#table-gitlab-binduser').zui('dtable').$.props.data; + var postData = {}; + postData['gitlabUserNames[]'] = []; + for(i in bindData) + { + postData['gitlabUserNames[]'].push(bindData[i].gitlabID); + postData['zentaoUsers[' + bindData[i].gitlabID + ']'] = formData['zentaoUsers[' + bindData[i].gitlabID + ']']; + } + + $.ajaxSubmit({ + url: $.createLink('gitlab', 'bindUser', 'gitlabID=' + gitlabID + '&type=' + type), + data: postData + }); +} diff --git a/module/gitlab/lang/de.php b/module/gitlab/lang/de.php index 0b370758e6..23c01afc14 100644 --- a/module/gitlab/lang/de.php +++ b/module/gitlab/lang/de.php @@ -108,7 +108,9 @@ $lang->gitlab->apiError[2] = 'is too short (minimum is 8 characters)'; $lang->gitlab->apiError[3] = "can contain only letters, digits, '_', '-' and '.'. Cannot start with '-', end in '.git' or end in '.atom'"; $lang->gitlab->apiError[4] = 'Branch already exists'; $lang->gitlab->apiError[5] = 'Failed to save group {:path=>["has already been taken"]}'; -$lang->gitlab->apiError[6] = '403 Forbidden'; +$lang->gitlab->apiError[6] = 'Failed to save group {:path=>["已经被使用"]}'; +$lang->gitlab->apiError[7] = '403 Forbidden'; +$lang->gitlab->apiError[8] = 'is invalid'; $lang->gitlab->errorLang[0] = 'You cannot set Internal as its Visibility Level, if it is private in GitLab.'; $lang->gitlab->errorLang[1] = 'You cannot set Public as its Visibility Level, if it is private in GitLab.'; @@ -116,7 +118,12 @@ $lang->gitlab->errorLang[2] = 'Password is too short (minimum is 8 characters)'; $lang->gitlab->errorLang[3] = 'It should contain only letters, digits, underscore, hyphen and period. It should not start with hypen, or end with .git or .atom.'; $lang->gitlab->errorLang[4] = 'Branch already exists.'; $lang->gitlab->errorLang[5] = 'Failed to save group, path has already been taken.'; -$lang->gitlab->errorLang[6] = $lang->gitlab->noAccess; +$lang->gitlab->errorLang[6] = 'Failed to save group, path has already been taken.'; +$lang->gitlab->errorLang[7] = $lang->gitlab->noAccess; +$lang->gitlab->errorLang[8] = "Is invalid"; + +$lang->gitlab->errorResonse['Email has already been taken'] = 'Email has already been taken'; +$lang->gitlab->errorResonse['Username has already been taken'] = 'Username has already been taken'; $lang->gitlab->project = new stdclass; $lang->gitlab->project->id = "Project ID"; diff --git a/module/gitlab/lang/en.php b/module/gitlab/lang/en.php index 0b370758e6..23c01afc14 100644 --- a/module/gitlab/lang/en.php +++ b/module/gitlab/lang/en.php @@ -108,7 +108,9 @@ $lang->gitlab->apiError[2] = 'is too short (minimum is 8 characters)'; $lang->gitlab->apiError[3] = "can contain only letters, digits, '_', '-' and '.'. Cannot start with '-', end in '.git' or end in '.atom'"; $lang->gitlab->apiError[4] = 'Branch already exists'; $lang->gitlab->apiError[5] = 'Failed to save group {:path=>["has already been taken"]}'; -$lang->gitlab->apiError[6] = '403 Forbidden'; +$lang->gitlab->apiError[6] = 'Failed to save group {:path=>["已经被使用"]}'; +$lang->gitlab->apiError[7] = '403 Forbidden'; +$lang->gitlab->apiError[8] = 'is invalid'; $lang->gitlab->errorLang[0] = 'You cannot set Internal as its Visibility Level, if it is private in GitLab.'; $lang->gitlab->errorLang[1] = 'You cannot set Public as its Visibility Level, if it is private in GitLab.'; @@ -116,7 +118,12 @@ $lang->gitlab->errorLang[2] = 'Password is too short (minimum is 8 characters)'; $lang->gitlab->errorLang[3] = 'It should contain only letters, digits, underscore, hyphen and period. It should not start with hypen, or end with .git or .atom.'; $lang->gitlab->errorLang[4] = 'Branch already exists.'; $lang->gitlab->errorLang[5] = 'Failed to save group, path has already been taken.'; -$lang->gitlab->errorLang[6] = $lang->gitlab->noAccess; +$lang->gitlab->errorLang[6] = 'Failed to save group, path has already been taken.'; +$lang->gitlab->errorLang[7] = $lang->gitlab->noAccess; +$lang->gitlab->errorLang[8] = "Is invalid"; + +$lang->gitlab->errorResonse['Email has already been taken'] = 'Email has already been taken'; +$lang->gitlab->errorResonse['Username has already been taken'] = 'Username has already been taken'; $lang->gitlab->project = new stdclass; $lang->gitlab->project->id = "Project ID"; diff --git a/module/gitlab/lang/fr.php b/module/gitlab/lang/fr.php index 9c21823d8a..eb1cbe6849 100644 --- a/module/gitlab/lang/fr.php +++ b/module/gitlab/lang/fr.php @@ -108,7 +108,19 @@ $lang->gitlab->apiError[2] = 'is too short (minimum is 8 characters)'; $lang->gitlab->apiError[3] = "can contain only letters, digits, '_', '-' and '.'. Cannot start with '-', end in '.git' or end in '.atom'"; $lang->gitlab->apiError[4] = 'Branch already exists'; $lang->gitlab->apiError[5] = 'Failed to save group {:path=>["has already been taken"]}'; -$lang->gitlab->apiError[6] = '403 Forbidden'; +$lang->gitlab->apiError[6] = 'Failed to save group {:path=>["已经被使用"]}'; +$lang->gitlab->apiError[7] = '403 Forbidden'; +$lang->gitlab->apiError[8] = 'is invalid'; + +$lang->gitlab->errorLang[0] = 'You cannot set Internal as its Visibility Level, if it is private in GitLab.'; +$lang->gitlab->errorLang[1] = 'You cannot set Public as its Visibility Level, if it is private in GitLab.'; +$lang->gitlab->errorLang[2] = 'Password is too short (minimum is 8 characters)'; +$lang->gitlab->errorLang[3] = 'It should contain only letters, digits, underscore, hyphen and period. It should not start with hypen, or end with .git or .atom.'; +$lang->gitlab->errorLang[4] = 'Branch already exists.'; +$lang->gitlab->errorLang[5] = 'Failed to save group, path has already been taken.'; +$lang->gitlab->errorLang[6] = 'Failed to save group, path has already been taken.'; +$lang->gitlab->errorLang[7] = $lang->gitlab->noAccess; +$lang->gitlab->errorLang[8] = "Is invalid"; $lang->gitlab->errorLang[0] = 'You cannot set Internal as its Visibility Level, if it is private in GitLab.'; $lang->gitlab->errorLang[1] = 'You cannot set Public as its Visibility Level, if it is private in GitLab.'; @@ -118,6 +130,9 @@ $lang->gitlab->errorLang[4] = 'Branch already exists.'; $lang->gitlab->errorLang[5] = 'Failed to save group, path has already been taken.'; $lang->gitlab->errorLang[6] = $lang->gitlab->noAccess; +$lang->gitlab->errorResonse['Email has already been taken'] = 'Email has already been taken'; +$lang->gitlab->errorResonse['Username has already been taken'] = 'Username has already been taken'; + $lang->gitlab->project = new stdclass; $lang->gitlab->project->id = "Project ID"; $lang->gitlab->project->name = "Project name"; diff --git a/module/gitlab/lang/vi.php b/module/gitlab/lang/vi.php index afddbeef4e..95714dcc6e 100644 --- a/module/gitlab/lang/vi.php +++ b/module/gitlab/lang/vi.php @@ -105,7 +105,19 @@ $lang->gitlab->apiError[2] = 'is too short (minimum is 8 characters)'; $lang->gitlab->apiError[3] = "can contain only letters, digits, '_', '-' and '.'. Cannot start with '-', end in '.git' or end in '.atom'"; $lang->gitlab->apiError[4] = 'Branch already exists'; $lang->gitlab->apiError[5] = 'Failed to save group {:path=>["has already been taken"]}'; -$lang->gitlab->apiError[6] = '403 Forbidden'; +$lang->gitlab->apiError[6] = 'Failed to save group {:path=>["已经被使用"]}'; +$lang->gitlab->apiError[7] = '403 Forbidden'; +$lang->gitlab->apiError[8] = 'is invalid'; + +$lang->gitlab->errorLang[0] = 'You cannot set Internal as its Visibility Level, if it is private in GitLab.'; +$lang->gitlab->errorLang[1] = 'You cannot set Public as its Visibility Level, if it is private in GitLab.'; +$lang->gitlab->errorLang[2] = 'Password is too short (minimum is 8 characters)'; +$lang->gitlab->errorLang[3] = 'It should contain only letters, digits, underscore, hyphen and period. It should not start with hypen, or end with .git or .atom.'; +$lang->gitlab->errorLang[4] = 'Branch already exists.'; +$lang->gitlab->errorLang[5] = 'Failed to save group, path has already been taken.'; +$lang->gitlab->errorLang[6] = 'Failed to save group, path has already been taken.'; +$lang->gitlab->errorLang[7] = $lang->gitlab->noAccess; +$lang->gitlab->errorLang[8] = "Is invalid"; $lang->gitlab->errorLang[0] = 'You cannot set Internal as its Visibility Level, if it is private in GitLab.'; $lang->gitlab->errorLang[1] = 'You cannot set Public as its Visibility Level, if it is private in GitLab.'; @@ -115,6 +127,9 @@ $lang->gitlab->errorLang[4] = 'Branch already exists.'; $lang->gitlab->errorLang[5] = 'Failed to save group, path has already been taken.'; $lang->gitlab->errorLang[6] = $lang->gitlab->noAccess; +$lang->gitlab->errorResonse['Email has already been taken'] = 'Email has already been taken'; +$lang->gitlab->errorResonse['Username has already been taken'] = 'Username has already been taken'; + $lang->gitlab->project = new stdclass; $lang->gitlab->project->id = "Project ID"; $lang->gitlab->project->name = "Project name"; diff --git a/module/gitlab/lang/zh-cn.php b/module/gitlab/lang/zh-cn.php index 46096a3737..0c7cf04c62 100644 --- a/module/gitlab/lang/zh-cn.php +++ b/module/gitlab/lang/zh-cn.php @@ -108,7 +108,9 @@ $lang->gitlab->apiError[2] = 'is too short (minimum is 8 characters)'; $lang->gitlab->apiError[3] = "can contain only letters, digits, '_', '-' and '.'. Cannot start with '-', end in '.git' or end in '.atom'"; $lang->gitlab->apiError[4] = 'Branch already exists'; $lang->gitlab->apiError[5] = 'Failed to save group {:path=>["has already been taken"]}'; -$lang->gitlab->apiError[6] = '403 Forbidden'; +$lang->gitlab->apiError[6] = 'Failed to save group {:path=>["已经被使用"]}'; +$lang->gitlab->apiError[7] = '403 Forbidden'; +$lang->gitlab->apiError[8] = 'is invalid'; $lang->gitlab->errorLang[0] = '私有分组的项目,可见性级别不能设为内部。'; $lang->gitlab->errorLang[1] = '私有分组的项目,可见性级别不能设为公开。'; @@ -116,7 +118,12 @@ $lang->gitlab->errorLang[2] = '密码太短(最少8个字符)'; $lang->gitlab->errorLang[3] = "只能包含字母、数字、'.'-'和'.'。不能以'-'开头、以'.git'结尾或以'.atom'结尾。"; $lang->gitlab->errorLang[4] = '分支名已存在。'; $lang->gitlab->errorLang[5] = '保存失败,群组URL路径已经被使用。'; -$lang->gitlab->errorLang[6] = $lang->gitlab->noAccess; +$lang->gitlab->errorLang[6] = '保存失败,群组URL路径已经被使用。'; +$lang->gitlab->errorLang[7] = $lang->gitlab->noAccess; +$lang->gitlab->errorLang[8] = '格式错误'; + +$lang->gitlab->errorResonse['Email has already been taken'] = '邮箱已存在'; +$lang->gitlab->errorResonse['Username has already been taken'] = '用户名已存在'; $lang->gitlab->project = new stdclass; $lang->gitlab->project->id = "项目ID"; diff --git a/module/gitlab/lang/zh-tw.php b/module/gitlab/lang/zh-tw.php index 464a7c9401..122477d26b 100644 --- a/module/gitlab/lang/zh-tw.php +++ b/module/gitlab/lang/zh-tw.php @@ -98,6 +98,9 @@ $lang->gitlab->apiError[2] = 'is too short (minimum is 8 characters)'; $lang->gitlab->apiError[3] = "can contain only letters, digits, '_', '-' and '.'. Cannot start with '-', end in '.git' or end in '.atom'"; $lang->gitlab->apiError[4] = 'Branch already exists'; $lang->gitlab->apiError[5] = 'Failed to save group {:path=>["has already been taken"]}'; +$lang->gitlab->apiError[6] = 'Failed to save group {:path=>["已经被使用"]}'; +$lang->gitlab->apiError[7] = '403 Forbidden'; +$lang->gitlab->apiError[8] = 'is invalid'; $lang->gitlab->errorLang[0] = '私有分組的項目,可見性級別不能設為內部。'; $lang->gitlab->errorLang[1] = '私有分組的項目,可見性級別不能設為公開。'; @@ -105,6 +108,12 @@ $lang->gitlab->errorLang[2] = '密碼太短(最少8個字元)'; $lang->gitlab->errorLang[3] = "只能包含字母、數字、'.'-'和'.'。不能以'-'開頭、以'.git'結尾或以'.atom'結尾。"; $lang->gitlab->errorLang[4] = '分支名已存在。'; $lang->gitlab->errorLang[5] = '保存失敗,群組URL路徑已經被使用。'; +$lang->gitlab->errorLang[6] = '保存失敗,群組URL路徑已經被使用。'; +$lang->gitlab->errorLang[7] = $lang->gitlab->noAccess; +$lang->gitlab->errorLang[8] = '格式错误'; + +$lang->gitlab->errorResonse['Email has already been taken'] = '邮箱已存在'; +$lang->gitlab->errorResonse['Username has already been taken'] = '用户名已存在'; $lang->gitlab->project = new stdclass; $lang->gitlab->project->id = "項目ID"; diff --git a/module/gitlab/model.php b/module/gitlab/model.php index 44dc302e1e..0549fd0a56 100644 --- a/module/gitlab/model.php +++ b/module/gitlab/model.php @@ -400,11 +400,11 @@ class gitlabModel extends model * @access public * @return array */ - public function getCommits($repo, $entry, $revision = 'HEAD', $type = 'dir', $pager = null, $begin = 0, $end = 0) + public function getCommits($repo, $entry, $revision = 'HEAD', $type = 'dir', $pager = null, $begin = '', $end = '') { $scm = $this->app->loadClass('scm'); $scm->setEngine($repo); - $comments = $scm->engine->getCommitsByPath($entry, '', '', isset($pager->recPerPage) ? $pager->recPerPage : 10, isset($pager->pageID) ? $pager->pageID : 1); + $comments = $scm->engine->getCommitsByPath($entry, '', '', isset($pager->recPerPage) ? $pager->recPerPage : 10, isset($pager->pageID) ? $pager->pageID : 1, false, $begin, $end); if(!is_array($comments)) return array(); if(isset($pager->recTotal)) $pager->recTotal = count($comments) < $pager->recPerPage ? $pager->recPerPage * $pager->pageID : $pager->recPerPage * ($pager->pageID + 1); @@ -441,6 +441,21 @@ class gitlabModel extends model return $this->loadModel('pipeline')->update($id); } + /** + * 设置项目信息。 + * Set project data. + * + * @param int $gitlabID + * @param int $projectID + * @param object $project + * @access public + * @return void + */ + public function setProject(int $gitlabID, int $projectID, object $project): void + { + $this->projects[$gitlabID][$projectID] = $project; + } + /** * Send an api get request. * @@ -640,7 +655,7 @@ class gitlabModel extends model */ public function apiGetGroups($gitlabID, $orderBy = 'id_desc', $minRole = '', $keyword = '') { - $apiRoot = $this->getApiRoot($gitlabID); + $apiRoot = $this->getApiRoot($gitlabID, false); $url = sprintf($apiRoot, "/groups"); if($minRole == 'owner') { @@ -1018,7 +1033,7 @@ class gitlabModel extends model * @access public * @return object */ - public function apiGetSingleProject($gitlabID, $projectID, $useUser = false) + public function apiGetSingleProject($gitlabID, $projectID, $useUser = true) { if(isset($this->projects[$gitlabID][$projectID])) return $this->projects[$gitlabID][$projectID]; @@ -1252,12 +1267,13 @@ class gitlabModel extends model * @access public * @return bool */ - public function addPushWebhook($repo) + public function addPushWebhook($repo, $token = '') { $hook = new stdClass; $hook->url = common::getSysURL() . '/api.php/v1/gitlab/webhook?repoID='. $repo->id; $hook->push_events = true; $hook->merge_requests_events = true; + if($token) $hook->token = $token; /* Return an empty array if where is one existing webhook. */ if($this->isWebhookExists($repo, $hook->url)) return array(); @@ -2977,4 +2993,41 @@ class gitlabModel extends model return false; } + + /** + * 判断按钮是否可点击。 + * Adjust the action clickable. + * + * @param object $instance + * @param string $action + * @access public + * @return bool + */ + public function isClickable(object $gitlab, string $action): bool + { + return commonModel::hasPriv('space', 'browse'); + } + + /** + * 判断按钮是否显示在列表页。 + * Judge an action is displayed in browse page. + * + * @param object $sonarqube + * @param string $action + * @access public + * @return bool + */ + public static function isDisplay(object $sonarqube, string $action): bool + { + $action = strtolower($action); + + if(!commonModel::hasPriv('space', 'browse')) return false; + + if(!in_array(strtolower(strtolower($action)), array('browseproject', 'browsegroup', 'browseuser', 'browsebranch', 'browsetag'))) + { + if(!commonModel::hasPriv('instance', 'manage')) return false; + } + + return true; + } } diff --git a/module/gitlab/ui/binduser.html.php b/module/gitlab/ui/binduser.html.php index 0f55a2c74a..f1cbeeccc5 100644 --- a/module/gitlab/ui/binduser.html.php +++ b/module/gitlab/ui/binduser.html.php @@ -24,6 +24,8 @@ featureBar toolbar(); jsVar('zentaoUsers', $zentaoUsers); +jsVar('gitlabID', $gitlabID); +jsVar('type', $type); $config->gitlab->dtable->bindUser->fieldList['gitlabEmail']['onRenderCell'] = jsRaw('renderGitlabUser'); $config->gitlab->dtable->bindUser->fieldList['zentaoUsers']['controlItems'] = $userPairs; form @@ -48,7 +50,7 @@ form array( 'text' => $lang->save, 'btnType' => 'primary', - 'onClick' => jsRaw("() => {\$('#bindForm').trigger('submit')}") + 'onClick' => jsRaw("() => {bindUser()}") ), array( 'text' => $lang->goback, diff --git a/module/gitlab/view/binduser.html.php b/module/gitlab/view/binduser.html.php index 8c89ec2f27..7392eb109d 100644 --- a/module/gitlab/view/binduser.html.php +++ b/module/gitlab/view/binduser.html.php @@ -15,7 +15,7 @@
- ' . $lang->goback, $browseLink, 'self', "data-app='{$app->tab}'", 'btn btn-secondary'); $allLink = $this->createLink('gitlab', 'binduser', "gitlabID={$gitlabID}&type=all"); @@ -55,7 +55,6 @@
- zentaoAccount)) continue;?> id]", $gitlabUser->realname);?> - - - - - - - zentaoAccount)) continue;?> - id]", $gitlabUser->realname);?> - + + - - - - diff --git a/module/gitlab/view/browseproject.html.php b/module/gitlab/view/browseproject.html.php index 9c6cb05416..bd72c168e5 100644 --- a/module/gitlab/view/browseproject.html.php +++ b/module/gitlab/view/browseproject.html.php @@ -22,14 +22,14 @@
- " . $lang->gitlab->project->create, '', "class='btn btn-primary'");?> + " . $lang->gitlab->project->create, '', "class='btn btn-primary'");?>

noData;?> - + createLink('gitlab', 'createProject', "gitlabID=$gitlabID"), " " . $lang->gitlab->project->create, '', "class='btn btn-info'");?>

@@ -60,19 +60,20 @@
diff --git a/module/gogs/control.php b/module/gogs/control.php index dee8b6384f..243bf57af4 100644 --- a/module/gogs/control.php +++ b/module/gogs/control.php @@ -20,6 +20,7 @@ class gogs extends control { parent::__construct($moduleName, $methodName); + if(!commonModel::hasPriv('instance', 'manage')) $this->loadModel('common')->deny('instance', 'manage', false); /* This is essential when changing tab(menu) from gogs to repo. */ /* Optional: common::setMenuVars('devops', $this->session->repoID); */ if($this->app->rawMethod != 'binduser') $this->loadModel('ci')->setMenu(); @@ -163,7 +164,7 @@ class gogs extends control $changes = common::createChanges($oldGogs, $gogs); $this->loadModel('action')->logHistory($actionID, $changes); - $response['load'] = true; + $response['load'] = $this->createLink('space', 'browse'); $response['result'] = 'success'; return $this->send($response); } @@ -195,7 +196,7 @@ class gogs extends control * @access public * @return void */ - public function bindUser(int $gogsID, string $type = 'all') + public function bindUser($gogsID, $type = 'all') { $zentaoUsers = $this->dao->select('account,email,realname')->from(TABLE_USER)->fetchAll('account'); $userPairs = $this->loadModel('user')->getPairs('noclosed|noletter'); @@ -203,8 +204,8 @@ class gogs extends control if($_POST) { $this->gogs->bindUser($gogsID); - if(dao::isError()) return $this->send(array('result' => 'fail', 'message' => dao::getError())); - return $this->send(array('result' => 'success', 'message' => $this->lang->saveSuccess, 'locate' => $this->server->http_referer)); + if(dao::isError()) return $this->sendError(dao::getError()); + return $this->sendSuccess(array('message' => $this->lang->saveSuccess, 'load' => helper::createLink('space', 'browse'))); } $userList = array(); @@ -262,11 +263,13 @@ class gogs extends control $project = urldecode(base64_decode($project)); $branches = $this->gogs->apiGetBranches($gogsID, $project); - $options = ""; + + $options = array(); + $options[] = array('text' => '', 'value' => '');; foreach($branches as $branch) { - $options .= ""; + $options[] = array('text' => $branch->name, 'value' => $branch->name); } - $this->send($options); + return print(json_encode($options)); } } diff --git a/module/gogs/js/binduser.js b/module/gogs/js/binduser.js new file mode 100644 index 0000000000..014d06eeb1 --- /dev/null +++ b/module/gogs/js/binduser.js @@ -0,0 +1,24 @@ +$(document).ready(function() +{ + $('.gitlab-user-bind').change(function() + { + var user = zentaoUsers[$(this).val()]; + if(user !== undefined) + { + $(this).parent().parent().find('.email').text(user.email) + } + }); + + $(document).on('click', '.zentao-users .chosen-container', function() + { + var $obj = $(this).prev('select'); + var value = $obj.val(); + if($obj.hasClass('filled')) return false; + + $obj.empty(); + $obj.append($('#userList').html()); + $obj.val(value); + $obj.addClass('filled'); + $obj.trigger("chosen:updated"); + }) +}); diff --git a/module/gogs/js/binduser.ui.js b/module/gogs/js/binduser.ui.js index ed226f7fd7..02c5dd7afb 100644 --- a/module/gogs/js/binduser.ui.js +++ b/module/gogs/js/binduser.ui.js @@ -14,3 +14,23 @@ window.renderGitlabUser = function(result, {row}) return result; } + +window.bindUser = function() +{ + const myDTable = $('#table-gogs-binduser').zui('dtable'); + const formData = myDTable.$.getFormData(); + + var bindData = $('#table-gogs-binduser').zui('dtable').$.props.data; + var postData = {}; + postData['gogsUserNames[]'] = []; + for(i in bindData) + { + postData['gogsUserNames[]'].push(bindData[i].gogsID); + postData['zentaoUsers[' + bindData[i].gogsID + ']'] = formData['zentaoUsers[' + bindData[i].gogsID + ']']; + } + + $.ajaxSubmit({ + url: $.createLink('gogs', 'bindUser', 'gogsID=' + gogsID + '&type=' + type), + data: postData + }); +} diff --git a/module/gogs/lang/de.php b/module/gogs/lang/de.php index 622508020a..d25fc0e3e3 100644 --- a/module/gogs/lang/de.php +++ b/module/gogs/lang/de.php @@ -13,6 +13,7 @@ $lang->gogs->gogsAccount = 'Gogs Account'; $lang->gogs->gogsEmail = 'Email'; $lang->gogs->zentaoAccount = 'Zentao Account'; $lang->gogs->bindingStatus = 'Binding Status'; +$lang->gogs->all = 'All'; $lang->gogs->notBind = 'Not bind'; $lang->gogs->binded = 'Binded'; $lang->gogs->bindDynamic = '%s and Zentao user %s'; diff --git a/module/gogs/lang/en.php b/module/gogs/lang/en.php index b3b8fdf0bd..91616167a5 100644 --- a/module/gogs/lang/en.php +++ b/module/gogs/lang/en.php @@ -13,6 +13,7 @@ $lang->gogs->gogsAccount = 'Gogs Account'; $lang->gogs->gogsEmail = 'Email'; $lang->gogs->zentaoAccount = 'Zentao Account'; $lang->gogs->bindingStatus = 'Binding Status'; +$lang->gogs->all = 'All'; $lang->gogs->notBind = 'Not bind'; $lang->gogs->binded = 'Binded'; $lang->gogs->bindDynamic = '%s and Zentao user %s'; diff --git a/module/gogs/lang/fr.php b/module/gogs/lang/fr.php index 622508020a..d25fc0e3e3 100644 --- a/module/gogs/lang/fr.php +++ b/module/gogs/lang/fr.php @@ -13,6 +13,7 @@ $lang->gogs->gogsAccount = 'Gogs Account'; $lang->gogs->gogsEmail = 'Email'; $lang->gogs->zentaoAccount = 'Zentao Account'; $lang->gogs->bindingStatus = 'Binding Status'; +$lang->gogs->all = 'All'; $lang->gogs->notBind = 'Not bind'; $lang->gogs->binded = 'Binded'; $lang->gogs->bindDynamic = '%s and Zentao user %s'; diff --git a/module/gogs/lang/zh-cn.php b/module/gogs/lang/zh-cn.php index 5e4f35564f..9a3833e277 100644 --- a/module/gogs/lang/zh-cn.php +++ b/module/gogs/lang/zh-cn.php @@ -13,6 +13,7 @@ $lang->gogs->gogsAccount = 'Gogs用户'; $lang->gogs->gogsEmail = '邮箱'; $lang->gogs->zentaoAccount = '禅道用户'; $lang->gogs->bindingStatus = '绑定状态'; +$lang->gogs->all = '全部'; $lang->gogs->notBind = '未绑定'; $lang->gogs->binded = '已绑定'; $lang->gogs->bindDynamic = '%s与禅道用户%s'; diff --git a/module/gogs/model.php b/module/gogs/model.php index 990693ac3c..f74204187b 100644 --- a/module/gogs/model.php +++ b/module/gogs/model.php @@ -93,9 +93,9 @@ class gogsModel extends model * * @param int $gogsID * @access public - * @return bool + * @return array */ - public function bindUser(int $gogsID): bool + public function bindUser($gogsID) { $userPairs = $this->loadModel('user')->getPairs('noclosed|noletter'); $users = $this->post->zentaoUsers; @@ -143,8 +143,6 @@ class gogsModel extends model $this->loadModel('action')->create('gogsuser', $gogsID, 'bind', '', sprintf($this->lang->gogs->bindDynamic, $gogsNames[$openID], $zentaoUsers[$account]->realname)); } } - - return true; } /** @@ -281,7 +279,7 @@ class gogsModel extends model * @access public * @return array */ - public function getMatchedUsers(int $gogsID, array $gogsUsers, array $zentaoUsers): array + public function getMatchedUsers($gogsID, $gogsUsers, $zentaoUsers) { $matches = new stdclass; foreach($gogsUsers as $gogsUser) @@ -298,9 +296,9 @@ class gogsModel extends model $matchedUsers = array(); foreach($gogsUsers as $gogsUser) { - if(isset($bindedUsers[$gogsUser->account])) + if(isset($bindedUsers[$gogsUser->id])) { - $gogsUser->zentaoAccount = $bindedUsers[$gogsUser->account]; + $gogsUser->zentaoAccount = $bindedUsers[$gogsUser->id]; $matchedUsers[$gogsUser->id] = $gogsUser; continue; } diff --git a/module/gogs/ui/binduser.html.php b/module/gogs/ui/binduser.html.php index e63d922aea..3556164078 100644 --- a/module/gogs/ui/binduser.html.php +++ b/module/gogs/ui/binduser.html.php @@ -24,6 +24,8 @@ featureBar toolbar(); jsVar('zentaoUsers', $zentaoUsers); +jsVar('gogsID', $gogsID); +jsVar('type', $type); $config->gogs->dtable->bindUser->fieldList['gogsEmail']['onRenderCell'] = jsRaw('renderGitlabUser'); $config->gogs->dtable->bindUser->fieldList['zentaoUsers']['controlItems'] = $userPairs; form @@ -48,7 +50,7 @@ form array( 'text' => $lang->save, 'btnType' => 'primary', - 'onClick' => jsRaw("() => {\$('#bindForm').trigger('submit')}") + 'onClick' => jsRaw("() => {bindUser()}") ), array( 'text' => $lang->goback, diff --git a/module/gogs/view/binduser.html.php b/module/gogs/view/binduser.html.php index fe6468104e..150b03a53c 100644 --- a/module/gogs/view/binduser.html.php +++ b/module/gogs/view/binduser.html.php @@ -11,60 +11,66 @@ */ ?> +createLink('gogs', 'browse', ""); ?> +
-
-

gogs->bindUser;?>

+
+ ' . $lang->goback, $browseLink, 'self', "data-app='{$app->tab}'", 'btn btn-secondary'); + + $allLink = $this->createLink('gogs', 'binduser', "gogsID={$gogsID}&type=all"); + $bindedLink = $this->createLink('gogs', 'binduser', "gogsID={$gogsID}&type=binded"); + $notBindLink = $this->createLink('gogs', 'binduser', "gogsID={$gogsID}&type=notBind"); + if($type == 'all') + { + echo html::linkButton('' . $lang->gogs->all . "" . count($gogsUsers) . "", $allLink, 'self', "data-app='{$app->tab}'", 'btn btn-info active'); + echo html::linkButton('' . $lang->gogs->notBind, $notBindLink, 'self', "data-app='{$app->tab}'", 'btn btn-info'); + echo html::linkButton('' . $lang->gogs->binded, $bindedLink, 'self', "data-app='{$app->tab}'", 'btn btn-info'); + } + else if($type == 'binded') + { + echo html::linkButton('' . $lang->gogs->all, $allLink, 'self', "data-app='{$app->tab}'", 'btn btn-info'); + echo html::linkButton('' . $lang->gogs->notBind, $notBindLink, 'self', "data-app='{$app->tab}'", 'btn btn-info'); + echo html::linkButton('' . $lang->gogs->binded . "" . count($gogsUsers) . "", $bindedLink, 'self', "data-app='{$app->tab}'", 'btn btn-info active'); + } + else + { + echo html::linkButton('' . $lang->gogs->all, $allLink, 'self', "data-app='{$app->tab}'", 'btn btn-info'); + echo html::linkButton('' . $lang->gogs->notBind . "" . count($gogsUsers) . "", $notBindLink, 'self', "data-app='{$app->tab}'", 'btn btn-info active'); + echo html::linkButton('' . $lang->gogs->binded, $bindedLink, 'self', "data-app='{$app->tab}'", 'btn btn-info'); + } + ?>
-
gitea->giteaAvatar;?> gitea->giteaAccount;?> gitea->giteaEmail;?>gitea->zentaoAccount;?>gitea->zentaoEmail;?>gitea->zentaoAccount;?> gitea->bindingStatus;?>
avatar, "height=40");?> - realname;?> -
- account;?> +
+ avatar, "height=20 width=20 class='img-circle'");?> + realname . '@' . $giteaUser->account;?> email;?>account]", $userPairs, '', "class='form-control select chosen'" );?>gitea->notBind;?>
avatar, "height=40");?>id]", $giteaUser->zentaoUsers, $giteaUser->zentaoAccount, "class='form-control select chosen gitea-user-bind'" );?> - realname;?> -
- account;?> -
email;?>account]", $userPairs, $giteaUser->zentaoAccount, "class='form-control select chosen'" );?> - zentaoAccount])):?> - zentaoAccount, '');?> - + binded === 1):?> gitea->binded;?> - + binded === 2):?> ' . $lang->gitea->bindedError . '';?> - - gitea->notBind;?> + ' . $lang->gitea->notBind . '';?>
@@ -63,30 +62,13 @@ realname . '@' . $gitlabUser->account;?> email;?>id]", $userPairs, '', "class='form-control select chosen gitlab-user-bind'" );?>' . $lang->gitlab->notBind . '';?>
id]", $gitlabUser->zentaoUsers, $gitlabUser->zentaoAccount, "class='form-control select chosen gitlab-user-bind'" );?> - avatar, "height=20 width=20 class='img-circle'");?> - realname . '@' . $gitlabUser->account;?> - email;?>id]", $userPairs, $gitlabUser->zentaoAccount, "class='form-control select chosen gitlab-user-bind'" );?> - id, $bindedUsers)):?> - zentaoAccount, '');?> - + binded === 1):?> gitlab->binded;?> - + binded === 2):?> ' . $lang->gitlab->bindedError . '';?> - ' . $lang->gitlab->notBind . '';?> @@ -106,4 +88,12 @@ + diff --git a/module/gitlab/view/browsegroup.html.php b/module/gitlab/view/browsegroup.html.php index 7f204659b7..086d427670 100644 --- a/module/gitlab/view/browsegroup.html.php +++ b/module/gitlab/view/browsegroup.html.php @@ -20,14 +20,14 @@
- " . $lang->gitlab->group->create, '', "class='btn btn-primary'");?> + " . $lang->gitlab->group->create, '', "class='btn btn-primary'");?>

noData;?> - + createLink('gitlab', 'createGroup', "gitlabID=$gitlabID"), " " . $lang->gitlab->group->create, '', "class='btn btn-info'");?>

@@ -60,9 +60,9 @@
user->admin or in_array($gitlabGroup->id, $adminGroupIDList)) ? true : false; - common::printLink('gitlab', 'manageGroupMembers', "gitlabID=$gitlabID&groupID=$gitlabGroup->id", " ", '',"title='{$lang->gitlab->group->manageMembers}' class='btn'"); - echo common::buildIconButton('gitlab', 'editGroup', "gitlabID=$gitlabID&groupID=$gitlabGroup->id", '', 'list', 'edit', '', '', false, '', '', 0, $isAdmin); - echo common::buildIconButton('gitlab', 'deleteGroup', "gitlabID=$gitlabID&groupID=$gitlabGroup->id", '', 'list', 'trash', 'hiddenwin', '', false, '', '', 0, $isAdmin); + if($this->gitlab->isDisplay($gitlab, 'manageGroupMembers')) common::printLink('gitlab', 'manageGroupMembers', "gitlabID=$gitlabID&groupID=$gitlabGroup->id", " ", '',"title='{$lang->gitlab->group->manageMembers}' class='btn'"); + if($this->gitlab->isDisplay($gitlab, 'editGroup')) echo common::buildIconButton('gitlab', 'editGroup', "gitlabID=$gitlabID&groupID=$gitlabGroup->id", '', 'list', 'edit', '', '', false, '', '', 0, $isAdmin); + if($this->gitlab->isDisplay($gitlab, 'deleteGroup')) echo common::buildIconButton('gitlab', 'deleteGroup', "gitlabID=$gitlabID&groupID=$gitlabGroup->id", '', 'list', 'trash', 'hiddenwin', '', false, '', '', 0, $isAdmin); ?>
last_activity_at, 0, 10);?> id]) ? '' : 'disabled'; - $adminerClass = $gitlabProject->adminer ? '' : 'disabled'; - $maintainerClass = $gitlabProject->isMaintainer ? '' : 'disabled'; - $defaultBranchClass = $gitlabProject->adminer ? '' : 'disabled'; - echo common::printIcon('gitlab', 'browseBranch', "gitlabID=$gitlabID&projectID=$gitlabProject->id", '', 'list', 'treemap', '', $defaultBranchClass, false, '', $this->lang->gitlab->browseBranch); - echo common::printIcon('gitlab', 'browseTag', "gitlabID=$gitlabID&projectID=$gitlabProject->id", '', 'list', 'tag', '', $defaultBranchClass, false, '', $this->lang->gitlab->browseTag); - echo common::printIcon('gitlab', 'manageBranchPriv', "gitlabID=$gitlabID&projectID=$gitlabProject->id", '', 'list', 'branch-lock', '', $defaultBranchClass . ' ' . $maintainerClass, false, '', $this->lang->gitlab->browseBranchPriv); - echo common::printIcon('gitlab', 'manageTagPriv', "gitlabID=$gitlabID&projectID=$gitlabProject->id", '', 'list', 'tag-lock', '', $defaultBranchClass . ' ' . $maintainerClass, false, '', $this->lang->gitlab->browseTagPriv); - echo common::printIcon('gitlab', 'manageProjectMembers', 'repoID=' . zget($repoPairs, $gitlabProject->id), '', 'list', 'team', '', $hasRepoClass); - echo common::printIcon('gitlab', 'createWebhook', 'repoID=' . zget($repoPairs, $gitlabProject->id), '', 'list', 'change', 'hiddenwin', $hasRepoClass); - echo common::printIcon('gitlab', 'importIssue', "gitlabID=$gitlabID&projectID=$gitlabProject->id", '', 'list', 'link'); - echo common::printIcon('gitlab', 'editProject', "gitlabID=$gitlabID&projectID=$gitlabProject->id", '', 'list', 'edit', '', $adminerClass); - echo common::printIcon('gitlab', 'deleteProject', "gitlabID=$gitlabID&projectID=$gitlabProject->id", '', 'list', 'trash', 'hiddenwin', $adminerClass); + $hasRepoClass = isset($repoPairs[$gitlabProject->id]) ? '' : 'disabled'; + $maintainerClass = $gitlabProject->isMaintainer ? '' : 'disabled'; + $developerClass = $gitlabProject->isDeveloper ? '' : 'disabled'; + + $defaultBranchClass = $gitlabProject->adminer || $gitlabProject->isMaintainer ? '' : 'disabled'; + if($this->gitlab->isDisplay($gitlab, 'browseBranch')) echo common::printIcon('gitlab', 'browseBranch', "gitlabID=$gitlabID&projectID=$gitlabProject->id", '', 'list', 'treemap', '', $developerClass, false, '', $this->lang->gitlab->browseBranch); + if($this->gitlab->isDisplay($gitlab, 'browseTag')) echo common::printIcon('gitlab', 'browseTag', "gitlabID=$gitlabID&projectID=$gitlabProject->id", '', 'list', 'tag', '', $developerClass, false, '', $this->lang->gitlab->browseTag); + if($this->gitlab->isDisplay($gitlab, 'manageBranchPriv')) echo common::printIcon('gitlab', 'manageBranchPriv', "gitlabID=$gitlabID&projectID=$gitlabProject->id", '', 'list', 'branch-lock', '', $defaultBranchClass, false, '', $this->lang->gitlab->browseBranchPriv); + if($this->gitlab->isDisplay($gitlab, 'manageTagPriv')) echo common::printIcon('gitlab', 'manageTagPriv', "gitlabID=$gitlabID&projectID=$gitlabProject->id", '', 'list', 'tag-lock', '', $defaultBranchClass, false, '', $this->lang->gitlab->browseTagPriv); + if($this->gitlab->isDisplay($gitlab, 'manageProjectMembers')) echo common::printIcon('gitlab', 'manageProjectMembers', 'repoID=' . zget($repoPairs, $gitlabProject->id), '', 'list', 'team', '', $hasRepoClass); + if($this->gitlab->isDisplay($gitlab, 'createWebhook')) echo common::printIcon('gitlab', 'createWebhook', 'repoID=' . zget($repoPairs, $gitlabProject->id), '', 'list', 'change', 'hiddenwin', $hasRepoClass); + if($this->gitlab->isDisplay($gitlab, 'importIssue')) echo common::printIcon('gitlab', 'importIssue', "gitlabID=$gitlabID&projectID=$gitlabProject->id", '', 'list', 'link'); + if($this->gitlab->isDisplay($gitlab, 'editProject')) echo common::printIcon('gitlab', 'editProject', "gitlabID=$gitlabID&projectID=$gitlabProject->id", '', 'list', 'edit', '', $defaultBranchClass); + if($this->gitlab->isDisplay($gitlab, 'deleteProject')) echo common::printIcon('gitlab', 'deleteProject', "gitlabID=$gitlabID&projectID=$gitlabProject->id", '', 'list', 'trash', 'hiddenwin', $defaultBranchClass); ?>
+
- - + + - zentaoAccount)) continue;?> - account]", $gogsUser->realname);?> + id]", $gogsUser->realname);?> - - - - - - - - zentaoAccount)) continue;?> - account]", $gogsUser->realname);?> - - + + - - - @@ -82,4 +88,12 @@ + diff --git a/module/host/control.php b/module/host/control.php index f8008285f8..b17fc3d0fb 100644 --- a/module/host/control.php +++ b/module/host/control.php @@ -152,7 +152,7 @@ class host extends control public function delete($id) { $this->dao->update(TABLE_HOST)->set('deleted')->eq(1)->where('id')->eq($id)->exec(); - $this->loadModel('action')->create('host', $id, 'deleted', '', actionModel::CAN_UNDELETED); + $this->loadModel('action')->create('host', $id, 'deleted', '', $extra = ACTIONMODEL::CAN_UNDELETED); if(dao::isError()) { @@ -178,7 +178,7 @@ class host extends control $reason = $this->lang->host->{$reasonKey}; if($_SERVER['REQUEST_METHOD'] == 'POST') { - $postData = fixer::input('post')->get(); + $postData = fixer::input('post')->skipSpecial('reason')->get(); if(empty($postData->reason)) { dao::$errors['reason'][] = sprintf($this->lang->error->notempty, $reason); diff --git a/module/host/css/browseimage.css b/module/host/css/browseimage.css new file mode 100644 index 0000000000..ef7500cb63 --- /dev/null +++ b/module/host/css/browseimage.css @@ -0,0 +1,4 @@ +.c-name,.c-status,.c-progress,.c-osName{width: 130px;} +.main-header-image{border:none;margin-left: 0!important;padding-left:0;} +.body-modal #mainContent{padding-top: 85px; padding-bottom: 50px;} +.dropdown-menu>li>a {margin: 1px 0;} diff --git a/module/host/css/changestatus.ui.css b/module/host/css/changestatus.ui.css new file mode 100644 index 0000000000..640ba0c3d0 --- /dev/null +++ b/module/host/css/changestatus.ui.css @@ -0,0 +1,2 @@ +.form-grid .form-label{width: 1rem;} +.form-grid .form-group{padding-left: 1rem;} diff --git a/module/host/css/common.ui.css b/module/host/css/common.ui.css index 48f4fc2cd8..5cc5bb4db7 100644 --- a/module/host/css/common.ui.css +++ b/module/host/css/common.ui.css @@ -1,2 +1,12 @@ #status {flex-direction: row;} -#hostCreateForm .form-row:last-child {width: 66%;} \ No newline at end of file +#hostCreateForm .form-row:last-child {width: 66%;} + +#mainNavbar>.container {padding-top: 8px} +#mainNavbar .nav {justify-content: left; position: relative; left: 0px; top: -4px;} +@media (min-width: 1400px){#mainNavbar .nav {left: -12px;}} +#mainNavbar {background: none;} +#mainNavbar .nav-item>a {padding-right: 0.5rem} +#mainNavbar .nav-item>.active {color: unset; font-weight: 700;} +#mainNavbar .nav-item>a.active:after {position: absolute; content: ''; border-bottom: 2px solid #2e7fff; inset: 0 6px 0 15px;} +.form-grid .form-label{width: 8rem;} +.form-grid .form-group{padding-left: 8rem;} \ No newline at end of file diff --git a/module/host/js/browseimage.js b/module/host/js/browseimage.js new file mode 100644 index 0000000000..aa7117f964 --- /dev/null +++ b/module/host/js/browseimage.js @@ -0,0 +1,60 @@ +var interval; + +$(function () { + updateProgressInterval(); +}); + +function updateProgressInterval() { + updateProgress(); + interval = setInterval(function () + { + updateProgress(); + }, 3000); +} + +function updateProgress() { + $.get(createLink('zahost', 'ajaxImageDownloadProgress', 'hostID=' + hostID)).done(function (response) + { + var result = JSON.parse(response); + var statusList = result.data; + + var hasInprogress = false; + for (var imageID in statusList) { + if (statusList[imageID].statusCode) { + if (statusList[imageID].statusCode == 'inprogress' || statusList[imageID].statusCode == 'created' || statusList[imageID].statusCode == 'pending') + { + hasInprogress = true; + $('.image-download-' + imageID).addClass('disabled'); + $('.image-cancel-' + imageID).removeClass('disabled'); + } + else if (statusList[imageID].statusCode == 'completed') + { + $('.image-path-' + imageID).text(statusList[imageID].path); + $('.image-path-' + imageID).attr('title', statusList[imageID].path); + $('.image-download-' + imageID).addClass('disabled'); + $('.image-cancel-' + imageID).addClass('disabled'); + $('.image-cancel-' + imageID).attr('href', '#'); + $('.image-progress-' + imageID).text("100%"); + } + else + { + var link = createLink('zahost', 'downloadImage', "hostID="+hostID+"&imageID="+imageID); + $('.image-download-' + imageID).removeClass('disabled'); + $('.image-download-' + imageID).attr('href', link); + $('.image-cancel-' + imageID).addClass('disabled'); + $('.image-cancel-' + imageID).attr('href', '#'); + $('.image-progress-' + imageID).text(''); + } + $('.image-status-' + imageID).text(statusList[imageID].status); + if(statusList[imageID].progress != '' && statusList[imageID].statusCode != 'completed') + { + $('.image-progress-' + imageID).text(statusList[imageID].progress); + } + } + } + if (!hasInprogress) + { + clearInterval(interval) + } + }); +} diff --git a/module/host/js/browseimage.ui.js b/module/host/js/browseimage.ui.js new file mode 100644 index 0000000000..553021097e --- /dev/null +++ b/module/host/js/browseimage.ui.js @@ -0,0 +1,81 @@ +var interval; + +window.createSortLink = function(col) +{ + var sort = col.name + '_asc'; + if(sort == orderBy) sort = col.name + '_desc'; + + return sortLink.replace('{orderBy}', sort); +} + +window.renderCell = function(result, {col, row}) +{ + if(col.name === 'progress') + { + result[0] = {html: ""}; + } + + if(col.name === 'path') + { + result[0] = {html: ""}; + } + + if(col.name === 'status') + { + result[0] = {html: "" + result[0] + ""}; + } + + return result; +}; + +$(function () { + updateProgressInterval(); +}); + +function updateProgressInterval() { + updateProgress(); + interval = setInterval(function () + { + updateProgress(); + }, 3000); +} + +function updateProgress() { + $.get($.createLink('zahost', 'ajaxImageDownloadProgress', 'hostID=' + hostID)).done(function (response) + { + var result = JSON.parse(response); + var statusList = result.data; + + var hasInprogress = false; + for (var imageID in statusList) { + if (statusList[imageID].statusCode) { + if (statusList[imageID].statusCode == 'inprogress' || statusList[imageID].statusCode == 'created' || statusList[imageID].statusCode == 'pending') + { + hasInprogress = true; + } + else if (statusList[imageID].statusCode == 'completed') + { + $('.image-path-' + imageID).text(statusList[imageID].path); + $('.image-path-' + imageID).attr('title', statusList[imageID].path); + $('.image-progress-' + imageID).text("100%"); + } + else + { + $('.image-progress-' + imageID).text(''); + } + + if(statusList[imageID].status != $('.image-status-' + imageID).text()) loadPage(); + + $('.image-status-' + imageID).text(statusList[imageID].status); + if(statusList[imageID].progress != '' && statusList[imageID].statusCode != 'completed') + { + $('.image-progress-' + imageID).text(statusList[imageID].progress); + } + } + } + if (!hasInprogress) + { + clearInterval(interval) + } + }); +} diff --git a/module/host/lang/de.php b/module/host/lang/de.php index 58fd0aa49e..10634d761a 100644 --- a/module/host/lang/de.php +++ b/module/host/lang/de.php @@ -110,6 +110,7 @@ $lang->host->pri = 'Priority'; $lang->host->tags = 'Platform Label'; $lang->host->provider = 'Priority'; $lang->host->bridgeID = 'Virtual Bridge'; + $lang->host->osNameList['linux'] = 'Linux'; $lang->host->osNameList['windows'] = 'Microsoft Windows'; $lang->host->osNameList['solaris'] = 'Solaris'; diff --git a/module/host/lang/en.php b/module/host/lang/en.php index 58fd0aa49e..10634d761a 100644 --- a/module/host/lang/en.php +++ b/module/host/lang/en.php @@ -110,6 +110,7 @@ $lang->host->pri = 'Priority'; $lang->host->tags = 'Platform Label'; $lang->host->provider = 'Priority'; $lang->host->bridgeID = 'Virtual Bridge'; + $lang->host->osNameList['linux'] = 'Linux'; $lang->host->osNameList['windows'] = 'Microsoft Windows'; $lang->host->osNameList['solaris'] = 'Solaris'; diff --git a/module/host/lang/fr.php b/module/host/lang/fr.php index 30216996f3..f995df5764 100644 --- a/module/host/lang/fr.php +++ b/module/host/lang/fr.php @@ -110,6 +110,7 @@ $lang->host->pri = 'Priority'; $lang->host->tags = 'Platform Label'; $lang->host->provider = 'Priority'; $lang->host->bridgeID = 'Virtual Bridge'; + $lang->host->osNameList['linux'] = 'Linux'; $lang->host->osNameList['windows'] = 'Microsoft Windows'; $lang->host->osNameList['solaris'] = 'Solaris'; diff --git a/module/host/model.php b/module/host/model.php index d28d159ef4..5db38a018f 100644 --- a/module/host/model.php +++ b/module/host/model.php @@ -83,7 +83,7 @@ class hostModel extends model $modules = $param ? $this->loadModel('tree')->getAllChildId($param) : '0'; } - $orderBy = str_replace($orderBy, 't1.', ''); + $orderBy = str_replace('t1.', '', $orderBy); $host = $this->dao->select('*,id as hostID')->from(TABLE_HOST) ->where('deleted')->eq('0') ->andWhere('type')->eq('normal') @@ -157,11 +157,13 @@ class hostModel extends model ->setDefault('cpuNumber,cpuCores,diskSize,memory', 0) ->get(); + $hostInfo->admin = intval($hostInfo->admin); + $hostInfo->serverRoom = intval($hostInfo->serverRoom); $this->dao->update(TABLE_HOST)->data($hostInfo) ->batchCheck($this->config->host->create->requiredFields, 'notempty') ->batchCheck('diskSize,memory', 'float'); if(dao::isError()) return false; - + $intFields = explode(',', $this->config->host->create->intFields); foreach($intFields as $field) { @@ -186,7 +188,7 @@ class hostModel extends model $hostInfo->createdBy = $this->app->user->account; $hostInfo->createdDate = helper::now(); $this->dao->insert(TABLE_HOST)->data($hostInfo)->autoCheck()->exec(); - if(!dao::isError()) + if(!dao::isError()) { $hostID = $this->dao->lastInsertID(); $this->loadModel('action')->create('host', $hostID, 'created'); @@ -219,7 +221,7 @@ class hostModel extends model ->batchCheck($this->config->host->create->requiredFields, 'notempty') ->batchCheck('diskSize,memory', 'float'); if(dao::isError()) return false; - + $intFields = explode(',', $this->config->host->create->intFields); foreach($intFields as $field) { diff --git a/module/host/ui/browseimage.html.php b/module/host/ui/browseimage.html.php new file mode 100644 index 0000000000..6a2c8ed322 --- /dev/null +++ b/module/host/ui/browseimage.html.php @@ -0,0 +1,30 @@ + + * @package host + * @link http://www.zentao.net + */ + +namespace zin; + +jsVar('hostID', $hostID); +jsVar('orderBy', $orderBy); +jsVar('sortLink', helper::createLink('host', 'browseImage', "hostID={$hostID}&browseType=$browseType¶m=$param&orderBy={orderBy}&recTotal={$pager->recTotal}&recPerPage={$pager->recPerPage}")); + +$tableData = initTableData($imageList, $config->host->imageDtable->fieldList); + +dtable +( + set::cols(array_values($config->host->imageDtable->fieldList)), + set::data($tableData), + set::sortLink(jsRaw('createSortLink')), + set::onRenderCell(jsRaw('window.renderCell')), + set::footPager(usePager()), +); + +render(); diff --git a/module/host/ui/changestatus.html.php b/module/host/ui/changestatus.html.php index bd9d897912..26f73f7a57 100644 --- a/module/host/ui/changestatus.html.php +++ b/module/host/ui/changestatus.html.php @@ -21,7 +21,9 @@ formPanel formGroup ( set::name('reason'), - set::control('textarea'), + set::label(' '), + set::control('editor'), + set::required(true), ), ), ); diff --git a/module/host/view/browseimage.html.php b/module/host/view/browseimage.html.php new file mode 100644 index 0000000000..9cea536bf9 --- /dev/null +++ b/module/host/view/browseimage.html.php @@ -0,0 +1,63 @@ + + * @package zahost + * @version $Id$ + * @link http://www.zentao.net + */ +?> +getModuleRoot() . 'common/view/header.html.php';?> + + +
' data-module='vmTemplate'>
+
+ recTotal}&recPerPage={$pager->recPerPage}";?> + +
+

+ zahost->image->imageEmpty;?> +

+
+ +
gogs->gogsAvatar;?> gogs->gogsAccount;?> gogs->gogsEmail;?>gogs->zentaoAccount;?>gogs->zentaoEmail;?>gogs->zentaoAccount;?> gogs->bindingStatus;?>
avatar, "height=40");?> - realname;?> -
- account;?> +
+ avatar, "height=20 width=20 class='img-circle'");?> + realname . '@' . $gogsUser->account;?> email;?>account]", $userPairs, '', "class='form-control select chosen'" );?>gogs->notBind;?>
avatar, "height=40");?>id]", $gogsUser->zentaoUsers, $gogsUser->zentaoAccount, "class='form-control select chosen gogs-user-bind'" );?> - realname;?> -
- account;?> -
email;?>account]", $userPairs, $gogsUser->zentaoAccount, "class='form-control select chosen'" );?> - zentaoAccount])):?> - zentaoAccount, '');?> - + binded === 1):?> gogs->binded;?> - + binded === 2):?> ' . $lang->gogs->bindedError . '';?> - - gogs->notBind;?> + ' . $lang->gogs->notBind . '';?>
+ + + + + + + + + + + + + + status == 'completed' ? zget($image, 'path', '') : '';?> + + + + + + + + + +
zahost->image->name);?>zahost->image->os);?>zahost->status;?>zahost->image->path;?>zahost->image->progress;?>actions;?>
name;?>osName;?>'>zahost->image->statusList, $image->status, '');?>'> + createLink('zahost', 'downloadImage', "hostID={$hostID}&imageID={$image->id}"), '', 'hiddenwin', zget($image, 'downloadMisc', ''));?> + createLink('zahost', 'cancelDownload', "id={$image->id}"), '', 'hiddenwin', zget($image, 'cancelMisc', ''));?> +
+ + +
+getModuleRoot() . 'common/view/footer.html.php';?> diff --git a/module/instance/config.php b/module/instance/config.php index 5adea89295..5eb5e9e3b9 100644 --- a/module/instance/config.php +++ b/module/instance/config.php @@ -21,31 +21,31 @@ $config->instance->enableAutoRestore = in_array('auto-rollback', $features); global $lang, $app; $app->loadLang('space'); -$config->instance->actionList['start']['icon'] = 'play'; -$config->instance->actionList['start']['className'] = 'ajax-submit'; -$config->instance->actionList['start']['hint'] = $lang->instance->start; -$config->instance->actionList['start']['text'] = $lang->instance->start; -$config->instance->actionList['start']['url'] = array('module' => 'instance', 'method' => 'ajaxStart', 'params' => 'id={id}'); +$config->instance->actionList['ajaxStart']['icon'] = 'play'; +$config->instance->actionList['ajaxStart']['className'] = 'ajax-submit'; +$config->instance->actionList['ajaxStart']['hint'] = $lang->instance->start; +$config->instance->actionList['ajaxStart']['text'] = $lang->instance->start; +$config->instance->actionList['ajaxStart']['url'] = array('module' => 'instance', 'method' => 'ajaxStart', 'params' => 'id={id}'); -$config->instance->actionList['stop']['icon'] = 'off'; -$config->instance->actionList['stop']['className'] = 'ajax-submit'; -$config->instance->actionList['stop']['hint'] = $lang->instance->stop; -$config->instance->actionList['stop']['text'] = $lang->instance->stop; -$config->instance->actionList['stop']['data-confirm'] = $lang->instance->notices['confirmStop']; -$config->instance->actionList['stop']['url'] = array('module' => 'instance', 'method' => 'ajaxStop', 'params' => 'id={id}'); +$config->instance->actionList['ajaxStop']['icon'] = 'off'; +$config->instance->actionList['ajaxStop']['className'] = 'ajax-submit'; +$config->instance->actionList['ajaxStop']['hint'] = $lang->instance->stop; +$config->instance->actionList['ajaxStop']['text'] = $lang->instance->stop; +$config->instance->actionList['ajaxStop']['data-confirm'] = $lang->instance->notices['confirmStop']; +$config->instance->actionList['ajaxStop']['url'] = array('module' => 'instance', 'method' => 'ajaxStop', 'params' => 'id={id}'); -$config->instance->actionList['uninstall']['icon'] = 'trash'; -$config->instance->actionList['uninstall']['hint'] = $lang->instance->uninstall; -$config->instance->actionList['uninstall']['text'] = $lang->instance->uninstall; -$config->instance->actionList['uninstall']['className'] = 'ajax-submit'; -$config->instance->actionList['uninstall']['data-confirm'] = $lang->instance->notices['confirmUninstall']; -$config->instance->actionList['uninstall']['url'] = array('module' => 'instance', 'method' => 'ajaxUninstall', 'params' => 'id={id}'); +$config->instance->actionList['ajaxUninstall']['icon'] = 'trash'; +$config->instance->actionList['ajaxUninstall']['hint'] = $lang->instance->uninstall; +$config->instance->actionList['ajaxUninstall']['text'] = $lang->instance->uninstall; +$config->instance->actionList['ajaxUninstall']['className'] = 'ajax-submit'; +$config->instance->actionList['ajaxUninstall']['data-confirm'] = $lang->instance->notices['confirmUninstall']; +$config->instance->actionList['ajaxUninstall']['url'] = array('module' => 'instance', 'method' => 'ajaxUninstall', 'params' => 'id={id}&type={type}'); $config->instance->actionList['visit']['icon'] = 'menu-my'; $config->instance->actionList['visit']['hint'] = $lang->instance->visit; $config->instance->actionList['visit']['text'] = $lang->instance->visit; $config->instance->actionList['visit']['target'] = '_blank'; -$config->instance->actionList['visit']['url'] = array('module' => 'instance', 'method' => 'visit', 'params' => 'id={id}'); +$config->instance->actionList['visit']['url'] = array('module' => 'instance', 'method' => 'visit', 'params' => 'id={id}&externalID={externalID}'); $config->instance->actionList['upgrade']['icon'] = 'refresh'; $config->instance->actionList['upgrade']['data-toggle'] = 'modal'; @@ -56,7 +56,7 @@ $config->instance->actionList['upgrade']['url'] = helper::createLink('in $config->instance->actions = new stdclass(); $config->instance->actions->view = array(); -$config->instance->actions->view['mainActions'] = array('visit', 'start', 'stop', 'upgrade'); -$config->instance->actions->view['suffixActions'] = array('uninstall'); +$config->instance->actions->view['mainActions'] = array('visit', 'ajaxStart', 'ajaxStop', 'upgrade'); +$config->instance->actions->view['suffixActions'] = array('ajaxUninstall'); -$config->instance->devopsApps = array(89 => 'gitea', 58 => 'gitlab', 57 => 'gogs', 59 => 'jenkins', 60 => 'sonarqube', 118 => 'nexus'); +$config->instance->devopsApps = array('gitea', 'gitlab', 'jenkins', 'sonarqube', 'nexus'); diff --git a/module/instance/control.php b/module/instance/control.php index 13af1be5a5..a17b9e4718 100644 --- a/module/instance/control.php +++ b/module/instance/control.php @@ -37,8 +37,62 @@ class instance extends control * @access public * @return void */ - public function view($id, $recTotal = 0, $recPerPage = 20, $pageID = 1, $tab ='baseinfo' ) + public function view($id, $type = 'store', $tab ='baseinfo' ) { + if(!commonModel::hasPriv('space', 'browse')) $this->loadModel('common')->deny('space', 'browse', false); + if($type === 'store') + { + $this->storeView($id, $tab); + } + else + { + $instance = $this->loadModel('gitea')->getByID($id); + $instance->status = ''; + $instance->source = 'user'; + $instance->externalID = $instance->id; + $instance->runDuration = 0; + $instance->appName = $instance->type; + $instance->createdAt = $instance->createdDate; + + $instanceMetric = new stdclass(); + $instanceMetric->cpu = 0; + $instanceMetric->memory = 0; + + $this->view->title = $instance->name; + $this->view->instance = $instance; + $this->view->cloudApp = array(); + $this->view->seniorAppList = array(); + $this->view->actions = $this->loadModel('action')->getList($instance->type, $id); + $this->view->defaultAccount = ''; + $this->view->instanceMetric = $instanceMetric; + $this->view->currentResource = ''; + $this->view->customItems = array(); + $this->view->backupList = array(); + $this->view->hasRestoreLog = false; + $this->view->latestBackup = array(); + $this->view->dbList = array(); + $this->view->domain = ''; + } + + $this->view->users = $this->loadModel('user')->getPairs('noletter'); + $this->view->tab = $tab; + $this->view->type = $type; + $this->display(); + } + + /** + * Show instance view. + * + * @param int $id + * @param int $recTotal + * @param int $recPerPage + * @param int $page + * @access public + * @return void + */ + protected function storeView($id, $tab ='baseinfo' ) + { + if(!commonModel::hasPriv('space', 'browse')) $this->loadModel('common')->deny('space', 'browse', false); $this->loadModel('system'); $this->app->loadLang('system'); @@ -53,9 +107,6 @@ class instance extends control $instanceMetric = $instanceMetric[$instance->id]; $this->lang->switcherMenu = $this->instance->getSwitcher($instance); - $this->app->loadClass('pager', true); - $pager = new pager($recTotal, $recPerPage, $pageID); - $backupList = array(); $latestBackup = new stdclass; if($tab == 'backup') $backupList = $this->instance->backupList($instance); @@ -82,13 +133,18 @@ class instance extends control $customItems = $this->cne->getCustomItems($instance); if($instance->status == 'running') $this->instanceZen->saveAuthInfo($instance); + if(in_array($instance->chart, $this->config->instance->devopsApps)) + { + $url = strstr(getWebRoot(true), ':', true) . '://' . $instance->domain; + $pipeline = $this->loadModel('pipeline')->getByUrl($url); + $instance->externalID = !empty($pipeline) ? $pipeline->id : 0; + } $this->view->title = $instance->appName; $this->view->instance = $instance; $this->view->cloudApp = $this->loadModel('store')->getAppInfoByChart($instance->chart, $instance->channel, false); $this->view->seniorAppList = $tab == 'baseinfo' ? $this->instance->seniorAppList($instance, $instance->channel) : array(); $this->view->actions = $this->loadModel('action')->getList('instance', $id); - $this->view->users = $this->loadModel('user')->getPairs('noletter'); $this->view->defaultAccount = $this->cne->getDefaultAccount($instance); $this->view->instanceMetric = $instanceMetric; $this->view->currentResource = $currentResource; @@ -99,9 +155,6 @@ class instance extends control $this->view->dbList = $dbList; $this->view->domain = $this->cne->getDomain($instance); $this->view->tab = $tab; - $this->view->pager = $pager; - - $this->display(); } /** @@ -225,6 +278,7 @@ class instance extends control */ public function setting($id) { + if(!commonModel::hasPriv('instance', 'manage')) $this->loadModel('common')->deny('instance', 'manage', false); $currentResource = new stdclass; $instance = $this->instance->getByID($id); $currentResource = $this->cne->getAppConfig($instance); @@ -302,6 +356,7 @@ class instance extends control */ public function upgrade($id) { + if(!commonModel::hasPriv('instance', 'manage')) $this->loadModel('common')->deny('instance', 'manage', false); $instance = $this->instance->getByID($id); $instance->latestVersion = $this->store->appLatestVersion($instance->appID, $instance->version); @@ -342,6 +397,7 @@ class instance extends control */ public function visit(int $id, int $externalID = 0): void { + if(!commonModel::hasPriv('space', 'browse')) $this->loadModel('common')->deny('space', 'browse', false); if(!$externalID) { $instance = $this->instance->getByID($id); @@ -366,6 +422,8 @@ class instance extends control */ public function createExternalApp(string $type) { + if(!commonModel::hasPriv('instance', 'manage')) $this->loadModel('common')->deny('instance', 'manage', false); + $this->app->loadModuleConfig('sonarqube'); $this->app->loadLang('pipeline'); @@ -388,6 +446,72 @@ class instance extends control return $this->send(array('result' => 'success', 'message' => $this->lang->saveSuccess, 'locate' => $this->createLink('space', 'browse'))); } + /** + * 编辑手工配置外部应用。 + * Edit a external app. + * + * @param int $externalID + * @access public + * @return viod + */ + public function editExternalApp(int $externalID) + { + if(!commonModel::hasPriv('instance', 'manage')) $this->loadModel('common')->deny('instance', 'manage', false); + + $oldApp = $this->loadModel('pipeline')->getByID($externalID); + + if($_POST) + { + $this->pipeline->update($externalID); + $app = $this->pipeline->getByID($externalID); + if(dao::isError()) return $this->send(array('result' => 'fail', 'message' => dao::getError())); + + $this->loadModel('action'); + $actionID = $this->action->create($app->type, $externalID, 'edited'); + $changes = common::createChanges($oldApp, $app); + $this->action->logHistory($actionID, $changes); + return $this->send(array('result' => 'success', 'message' => $this->lang->saveSuccess, 'load' => true, 'closeModal' => true)); + } + + $this->app->loadLang('space'); + $this->app->loadLang('sonarqube'); + + $this->view->app = $oldApp; + $this->display(); + } + + /** + * 删除一个外部应用。 + * Delete a external app. + * + * @param int $externalID + * @access public + * @return viod + */ + public function deleteExternalApp(int $externalID) + { + if(!commonModel::hasPriv('instance', 'manage')) $this->loadModel('common')->deny('instance', 'manage', false); + + $oldApp = $this->loadModel('pipeline')->getByID($externalID); + $actionID = $this->pipeline->delete($externalID, $oldApp->type); + if(!$actionID) + { + $response['result'] = 'fail'; + $response['callback'] = sprintf('zui.Modal.alert("%s");', $this->lang->pipeline->delError); + return $this->send($response); + } + + $app = $this->pipeline->getByID($externalID); + $changes = common::createChanges($oldApp, $app); + $this->loadModel('action')->logHistory($actionID, $changes); + + $response['load'] = true; + $response['message'] = zget($this->lang->instance->notices, 'uninstallSuccess'); + $response['result'] = 'success'; + + return $this->send($response); + } + /** * (Not used at present.) Install app by custom settings. * @@ -406,29 +530,16 @@ class instance extends control * Install app. * * @param int $appID + * @param string $checkResource * @access public * @return void */ - public function install($appID) + public function install(int $appID, string $checkResource = 'true') { + if(!commonModel::hasPriv('instance', 'manage')) $this->loadModel('common')->deny('instance', 'manage', false); $cloudApp = $this->store->getAppInfo($appID); if(empty($cloudApp)) return $this->send(array('result' => 'fail', 'message' => $this->lang->instance->errors->noAppInfo)); - if(empty($this->config->demoAccounts)) - { - $clusterResource = $this->cne->cneMetrics(); - $freeMemory = intval($clusterResource->metrics->memory->allocatable * 0.9); // Remain 10% memory for system. - if($cloudApp->memory > $freeMemory) - { - $cloudApp = $cloudApp; - $gapMemory = helper::formatKB(intval(($cloudApp->memory - $freeMemory))); - $requiredMemory = helper::formatKB(intval($cloudApp->memory)); - $freeMemory = helper::formatKB(intval($freeMemory)); - - return $this->send(array('result' => 'fail', 'message' => sprintf($this->lang->instance->errors->notEnoughMemory, $cloudApp->alias, $requiredMemory, $freeMemory, $gapMemory))); - } - } - $versionList = $this->store->appVersionList($cloudApp->id); $mysqlList = $this->cne->sharedDBList('mysql'); $pgList = $this->cne->sharedDBList('postgresql'); @@ -456,6 +567,16 @@ class instance extends control if(!validater::checkLength($customData->customDomain, 20, 2)) return $this->send(array('result' => 'fail', 'message' => $this->lang->instance->errors->domainLength)); if(!validater::checkREG($customData->customDomain, '/^[a-z\d]+$/')) return $this->send(array('result' => 'fail', 'message' => $this->lang->instance->errors->wrongDomainCharacter)); + if($checkResource == 'true') + { + $resource = new stdclass(); + $resource->cpu = $cloudApp->cpu; + $resource->memory = $cloudApp->memory; + + $result = $this->cne->tryAllocate(array($resource)); + if(!isset($result->code) || $result->code != 200) return $this->send(array('callback' => 'alertResource()')); + } + /* If select the version, replace the latest version of App by selected version. */ if($customData->version) { @@ -505,15 +626,17 @@ class instance extends control */ public function ajaxUninstall($instanceID, $type = '') { - if($type == 'external') + if(!commonModel::hasPriv('instance', 'manage')) $this->loadModel('common')->deny('instance', 'manage', false); + if($type !== 'store') { $instance = $this->loadModel('pipeline')->getByID($instanceID); - if(!$instance) return $this->send(array('result' => 'success', 'message' => $this->lang->instance->notices['success'])); + if(!$instance) return $this->send(array('result' => 'success', 'message' => $this->lang->instance->notices['success'], 'load' => $this->createLink('space', 'browse'))); - return $this->send($this->fetch($instance->type, 'delete', array('id' => $instance->id))); + if($instance->type == 'nexus') return $this->deleteExternalApp($instance->id); + return $this->fetch($instance->type, 'delete', array('id' => $instance->id)); } $instance = $this->instance->getByID($instanceID); - if(!$instance) return $this->send(array('result' => 'success', 'message' => $this->lang->instance->notices['success'])); + if(!$instance) return $this->send(array('result' => 'success', 'message' => $this->lang->instance->notices['success'], 'load' => $this->createLink('space', 'browse'))); $externalApp = $this->loadModel('space')->getExternalAppByApp($instance); if($externalApp) @@ -524,7 +647,7 @@ class instance extends control $success = $this->instance->uninstall($instance); $this->action->create('instance', $instance->id, 'uninstall', '', json_encode(array('result' => $success, 'app' => array('alias' => $instance->appName, 'app_version' => $instance->version)))); - if($success) return $this->send(array('result' => 'success', 'message' => zget($this->lang->instance->notices, 'uninstallSuccess'), 'locate' => $this->createLink('space', 'browse'))); + if($success) return $this->send(array('result' => 'success', 'message' => zget($this->lang->instance->notices, 'uninstallSuccess'), 'load' => $this->createLink('space', 'browse'))); return $this->send(array('result' => 'fail', 'message' => zget($this->lang->instance->notices, 'uninstallFail'))); } @@ -538,6 +661,7 @@ class instance extends control */ public function ajaxStart($instanceID) { + if(!commonModel::hasPriv('instance', 'manage')) $this->loadModel('common')->deny('instance', 'manage', false); $instance = $this->instance->getByID($instanceID); if(!$instance) return $this->send(array('result' => 'fail', 'message' => $this->lang->instance->instanceNotExists)); @@ -558,6 +682,7 @@ class instance extends control */ public function ajaxStop($instanceID) { + if(!commonModel::hasPriv('instance', 'manage')) $this->loadModel('common')->deny('instance', 'manage', false); $instance = $this->instance->getByID($instanceID); if(!$instance) return $this->send(array('result' => 'fail', 'message' => $this->lang->instance->instanceNotExists)); @@ -656,6 +781,7 @@ class instance extends control */ public function ajaxDBAuthUrl() { + if(!commonModel::hasPriv('space', 'browse')) $this->loadModel('common')->deny('space', 'browse', false); $post = fixer::input('post') ->setDefault('namespace', 'default') ->setDefault('instanceID', 0) @@ -689,6 +815,7 @@ class instance extends control */ public function ajaxAdjustMemory($instanceID) { + if(!commonModel::hasPriv('instance', 'manage')) $this->loadModel('common')->deny('instance', 'manage', false); $postData = fixer::input('post')->get(); /* Check free memory size is enough or not. */ diff --git a/module/instance/css/view.ui.css b/module/instance/css/view.ui.css index 449c86ff4e..44dcc24097 100644 --- a/module/instance/css/view.ui.css +++ b/module/instance/css/view.ui.css @@ -5,4 +5,5 @@ .progress-container {word-break: keep-all; white-space: nowrap;} .progress-container i {margin: auto; margin-right: 6px;} .progress-container .progress {margin: auto; width: 100%; height: .7rem; margin-left: 6px; margin-right: 15px;} -.label-dot {display: inline-block; width: 8px; height: 8px; padding: 0; line-height: 20px; text-indent: -9999em; border-radius: 50%;} \ No newline at end of file +.label-dot {display: inline-block; width: 8px; height: 8px; padding: 0; line-height: 20px; text-indent: -9999em; border-radius: 50%;} +#instanceInfoContainer .table>*>tr>* {padding: .5rem .3rem;} \ No newline at end of file diff --git a/module/instance/js/view.ui.js b/module/instance/js/view.ui.js index b2327d1167..99f863e1b9 100644 --- a/module/instance/js/view.ui.js +++ b/module/instance/js/view.ui.js @@ -1,4 +1,4 @@ -window.openAdminer = function() +$('#mainContent').on('click', '.db-management', function() { var dbName = $(this).data('dbname'); var dbType = $(this).data('dbtype'); @@ -22,22 +22,78 @@ window.openAdminer = function() } } ); -} +}); -var reloadTimes = 0; +var refreshTime = 0; +var timer = null; +var currentStatus = instanceStatus; +const postData = new FormData(); +postData.append('idList[]', instanceID); window.afterPageUpdate = function() { + refreshStatus(); +} + +function refreshStatus() +{ + if(new Date().getTime() - refreshTime < 4000) return; + refreshTime = new Date().getTime(); + + $.ajaxSubmit({ + url: $.createLink('instance', 'ajaxStatus'), + method: 'POST', + data:postData, + onComplete: function(res) + { + if(res.result === 'success') + { + $.each(res.data, function(index, instance) + { + if(instance.status === 'running' && ($('#statusTD').data('reload') === true || $('#memoryRate').data('load') === true)) + { + setTimeout(() => {loadCurrentPage();}, 3000); + currentStatus = instance.status; + return; + } + if(currentStatus != instance.status) + { + loadCurrentPage(); + currentStatus = instance.status; + return; + } + }); + } + + timer = setTimeout(() => {refreshStatus()}, 5000); + } + }); +} + +window.onPageUnmount = function() +{ + if(!timer) return; + clearTimeout(timer); +} + +$('.copy-btn').on('click', function() +{ + var copyText = $(this).parent().find('input'); + copyText.show(); + copyText[0].select(); + document.execCommand("Copy"); + copyText.hide(); + + var that = this; + $(that).tooltip + ({ + trigger: 'click', + placement: 'bottom', + title: copied, + tipClass: 'success', + show:true + }); setTimeout(function() { - if(reloadTimes > 20) return; - if($('#statusTD').data('reload') === true || $('#memoryRate').data('load') == true) - { - reloadTimes++; - fetchContent({url: $.createLink('instance', 'view', 'id=' + instanceID), - selector: '#instanceInfoContainer', - id: 'instanceInfoContainer', - target: '#instanceInfoContainer', - }); - } - }, 4000); -} + $(that).tooltip('hide'); + }, 2000) +}) \ No newline at end of file diff --git a/module/instance/lang/de.php b/module/instance/lang/de.php index 3940f83815..31495e683a 100644 --- a/module/instance/lang/de.php +++ b/module/instance/lang/de.php @@ -1,5 +1,12 @@ instance = new stdclass; +$lang->instance->common = 'Applications'; +$lang->instance->manage = 'Manage Applications'; +$lang->instance->view = 'Application Detail'; +$lang->instance->ajaxStatus = 'Get Application Status'; +$lang->instance->ajaxStart = 'Start Application'; +$lang->instance->ajaxStop = 'Stop Application'; +$lang->instance->ajaxUninstall = 'Uninstall Application'; $lang->instance->name = 'name'; $lang->instance->appName = 'Application type'; $lang->instance->version = 'version'; @@ -13,7 +20,7 @@ $lang->instance->dbType = 'Database'; $lang->instance->advanceOption = 'Advanced Options'; $lang->instance->baseInfo = 'Basic Information'; $lang->instance->backupAndRestore = 'Backup'; -$lang->instance->advanced = 'Advanced'; +$lang->instance->advance = 'Advanced'; $lang->instance->enableLDAP = 'Enable LDAP'; $lang->instance->linkLDAP = 'Integrated LDAP'; $lang->instance->enableSMTP = 'Enable SMTP'; @@ -32,8 +39,9 @@ $lang->instance->uninstall = 'delete'; $lang->instance->visit = 'Visit'; $lang->instance->editName = 'Modify Name'; $lang->instance->cpuCore = 'Core'; -$lang->instance->scaleble = 'Application level expansion'; +$lang->instance->scalable = 'Application level expansion'; $lang->instance->change = 'Modify'; +$lang->instance->browseProject = "Project List"; $lang->instance->systemLDAPInactive = 'System LDAP not enabled'; $lang->instance->toSystemLDAP = 'Deactivate'; @@ -50,6 +58,10 @@ $lang->instance->installAt = 'Creation time'; $lang->instance->runDuration = 'Running'; $lang->instance->defaultAccount = 'Default User'; $lang->instance->defaultPassword = 'Default Password'; +$lang->instance->account = 'User'; +$lang->instance->password = 'Password'; +$lang->instance->token = 'Token'; +$lang->instance->copied = 'Copy successful'; $lang->instance->operationLog = 'Operation Record'; $lang->instance->installedService = 'Installed Service'; $lang->instance->runningService = 'Running Service'; @@ -92,7 +104,7 @@ $lang->instance->backup->dbName = 'name'; $lang->instance->backup->dbStatus = 'Status'; $lang->instance->backup->dbSpentSeconds = 'Time taken (seconds)'; $lang->instance->backup->dbSize = 'Size'; -$lang->instance->backup->volume = 'Data volume'; +$lang->instance->backup->volumne = 'Data volume'; $lang->instance->backup->volName = 'name'; $lang->instance->backup->volMountName = 'Mount directory'; $lang->instance->backup->volStatus = 'Status'; @@ -100,7 +112,7 @@ $lang->instance->backup->volSpentSeconds = 'Time taken (seconds)'; $lang->instance->backup->volSize = 'Size'; $lang->instance->backup->lastRestore = 'Last rollback'; $lang->instance->backup->restoreDate = 'Rollback time'; -$lang->instance->backup->lastBackupAt = 'Last backup time'; +$lang->instance->backup->latestBackupAt = 'Last backup time'; $lang->instance->backup->backupBeforeRestore = 'We recommend that you backup before rolling back!'; $lang->instance->backup->enableAutoBackup = 'Enable automatic backup'; $lang->instance->backup->autoBackup = 'Automatic backup'; @@ -163,7 +175,7 @@ $lang->instance->log->date = 'date'; $lang->instance->log->message = 'Content'; $lang->instance->actionList = array(); -$lang->instance->actionList['istall'] = '%s installed'; +$lang->instance->actionList['install'] = '%s installed'; $lang->instance->actionList['uninstall'] = '%s deleted'; $lang->instance->actionList['start'] = 'Started %s'; $lang->instance->actionList['stop'] = '%s closed'; @@ -186,39 +198,41 @@ $lang->instance->actionList['autorestore'] = 'The system has perform $lang->instance->actionList['deleteexpiredbackup'] = 'The system has deleted expired automatic backups'; $lang->instance->sourceList = array(); -$lang->instance->sourceList['cloud'] = 'Chancheng Public Market'; +$lang->instance->sourceList['cloud'] = 'Store'; $lang->instance->sourceList['local'] = 'Local Market'; +$lang->instance->sourceList['user'] = 'User'; $lang->instance->channelList = array(); $lang->instance->channelList['test'] = 'Test version'; $lang->instance->channelList['stable'] = 'stable version'; $lang->instance->statusList = array(); -$lang->instance->statusList['istallationFailure'] = 'Installation failed'; -$lang->instance->statusList['creating'] = 'Creating'; -$lang->instance->statusList['initializing'] = 'initializing'; -$lang->instance->statusList['pulling'] = 'Downloading'; -$lang->instance->statusList['startup'] = 'Starting'; -$lang->instance->statusList['starting'] = 'Starting'; -$lang->instance->statusList['running'] = 'Running'; -$lang->instance->statusList['spending'] = 'Suspending'; -$lang->instance->statusList['suspended'] = 'paused'; -$lang->instance->statusList['istalling'] = 'Installing'; -$lang->instance->statusList['uninstalling'] = 'Removing'; -$lang->instance->statusList['stopping'] = 'Closing'; -$lang->instance->statusList['stopped'] = 'Closed'; -$lang->instance->statusList['destroying'] = 'Destroying'; -$lang->instance->statusList['destroyed'] = 'Destroyed'; -$lang->instance->statusList['abnormal'] = 'Abnormal'; -$lang->instance->statusList['upgrading'] = 'Updating'; -$lang->instance->statusList['unknown'] = 'unknown'; +$lang->instance->statusList['installationFail'] = 'Installation failed'; +$lang->instance->statusList['creating'] = 'Creating'; +$lang->instance->statusList['initializing'] = 'initializing'; +$lang->instance->statusList['pulling'] = 'Downloading'; +$lang->instance->statusList['startup'] = 'Starting'; +$lang->instance->statusList['starting'] = 'Starting'; +$lang->instance->statusList['running'] = 'Running'; +$lang->instance->statusList['suspending'] = 'Suspending'; +$lang->instance->statusList['suspended'] = 'paused'; +$lang->instance->statusList['installing'] = 'Installing'; +$lang->instance->statusList['uninstalling'] = 'Removing'; +$lang->instance->statusList['stopping'] = 'Closing'; +$lang->instance->statusList['stopped'] = 'Closed'; +$lang->instance->statusList['destroying'] = 'Destroying'; +$lang->instance->statusList['destroyed'] = 'Destroyed'; +$lang->instance->statusList['abnormal'] = 'Abnormal'; +$lang->instance->statusList['upgrading'] = 'Updating'; +$lang->instance->statusList['unknown'] = 'unknown'; +$lang->instance->statusList['scheduling'] = 'Scheduling'; $lang->instance->htmlStatusesClass = array(); -$lang->instance->htmlStatusesClass['running'] = 'success'; -$lang->instance->htmlStatusesClass['stopped'] = 'default'; -$lang->instance->htmlStatusesClass['abnormal'] = 'danger'; -$lang->instance->htmlStatusesClass['istallationFailure'] = 'danger'; -$lang->instance->htmlStatusesClass['busy'] = "warning"; +$lang->instance->htmlStatusesClass['running'] = 'success'; +$lang->instance->htmlStatusesClass['stopped'] = 'default'; +$lang->instance->htmlStatusesClass['abnormal'] = 'danger'; +$lang->instance->htmlStatusesClass['installationFail'] = 'danger'; +$lang->instance->htmlStatusesClass['busy'] = "warning"; $lang->instance->memOptions = array(); $lang->instance->memOptions[128 * 1024] = '128MB'; @@ -244,27 +258,27 @@ $lang->instance->notices['confirmStart'] = 'Are you sure to start the appl $lang->instance->notices['confirmStop'] = 'Are you sure to close this application?'; $lang->instance->notices['confirmUninstall'] = 'Are you sure to delete this application?'; $lang->instance->notices['startSuccess'] = 'Successfully started'; -$lang->instance->notices['startFailure'] = 'Start failed'; +$lang->instance->notices['startFail'] = 'Start failed'; $lang->instance->notices['stopSuccess'] = 'Close successfully'; $lang->instance->notices['stopFail'] = 'Close failed'; $lang->instance->notices['uninstallSuccess'] = 'Successfully deleted'; -$lang->instance->notices['uninstallFailure'] = 'Delete failed'; -$lang->instance->notices['iinstallSuccess'] = 'Installation successful'; -$lang->instance->notices['iinstallFailure'] = 'Installation failed'; +$lang->instance->notices['uninstallFail'] = 'Delete failed'; +$lang->instance->notices['installSuccess'] = 'Installation successful'; +$lang->instance->notices['installFail'] = 'Installation failed'; $lang->instance->notices['upgradeSuccess'] = 'Upgrade successful'; -$lang->instance->notices['upgradeFailure'] = 'Upgrade failed'; +$lang->instance->notices['upgradeFail'] = 'Upgrade failed'; $lang->instance->notices['backupSuccess'] = 'Backup task submitted'; -$lang->instance->notices['backupFailure'] = 'Backup failed'; +$lang->instance->notices['backupFail'] = 'Backup failed'; $lang->instance->notices['restoreSuccess'] = 'The rollback task has been submitted'; -$lang->instance->notices['restoreFailure'] = 'Rollback failed'; +$lang->instance->notices['restoreFail'] = 'Rollback failed'; $lang->instance->notices['deleteSuccess'] = 'DeleteSuccess'; $lang->instance->notices['deleteFail'] = 'Delete failed'; $lang->instance->notices['starting'] = 'Starting, please wait...'; $lang->instance->notices['stopping'] = 'Closing, please wait...'; -$lang->instance->notices['istalling'] = 'Installing, please wait...'; +$lang->instance->notices['installing'] = 'Installing, please wait...'; $lang->instance->notices['uninstalling'] = 'Deleting, please wait...'; $lang->instance->notices['upgrading'] = 'Upgrading, please wait...'; -$lang->instance->notices['backup'] = 'Backing up, please wait...'; +$lang->instance->notices['backuping'] = 'Backing up, please wait...'; $lang->instance->notices['restoring'] = 'Rolling back, please wait...'; $lang->instance->notices['deleting'] = 'Deleting, please wait...'; $lang->instance->notices['adjusting'] = 'Adjusting, please wait...'; @@ -288,6 +302,7 @@ $lang->instance->notices['enableSMTPSuccess'] = 'Enable SMTP successfully'; $lang->instance->notices['disableSMTPSuccess'] = 'Disable SMTP successfully'; $lang->instance->notices['confirmCustom'] = 'After modifying the custom configuration, the service will automatically restart to make the configuration effective.'; $lang->instance->notices['required'] = 'cannot be empty'; +$lang->instance->notices['notEnoughResource'] = 'Insufficient platform resources. Do you want to continue installing?'; $lang->instance->nameChangeTo = '%s is modified to %s.'; $lang->instance->versionChangeTo = 'Upgrade %s to %s.'; @@ -296,8 +311,8 @@ $lang->instance->adjustMemorySize = 'It is recommended to adjust the memory to $lang->instance->enableAutoBackup = 'Enable automatic backup'; $lang->instance->disableAutoBackup = 'Turn off automatic backup'; -$lang->instance->InstanceNotExists = 'Service does not exist'; -$lang->instance->capticasTooSmall = 'The number of replicas cannot be less than 1'; +$lang->instance->instanceNotExists = 'Service does not exist'; +$lang->instance->caplicasTooSmall = 'The number of replicas cannot be less than 1'; $lang->instance->empty = 'Currently no service available'; $lang->instance->noComponent = 'No component, click'; $lang->instance->noHigherVersion = 'No higher version found!'; @@ -323,9 +338,9 @@ $lang->instance->errors->wrongRequestData = 'The submitted data is incorrect $lang->instance->errors->noDBList = 'No database or inaccessible'; $lang->instance->errors->notFoundDB = 'The database cannot be found'; $lang->instance->errors->dbNameIsEmpty = 'Database name is empty'; -$lang->instance->errors->faultToAdjustMemory = 'Failed to adjust memory'; +$lang->instance->errors->failToAdjustMemory = 'Failed to adjust memory'; $lang->instance->errors->switchLDAPFailed = 'Modifying LDAP settings failed'; $lang->instance->errors->switchSMTPFailed = 'Modifying SMTP settings failed'; -$lang->instance->errors->updateCustomimFailed = 'Modifying custom configuration failed'; -$lang->instance->errors->FailToSenior = 'Upgrade to Advanced version failed'; +$lang->instance->errors->updateCustomFailed = 'Modifying custom configuration failed'; +$lang->instance->errors->failToSenior = 'Upgrade to Advanced version failed'; $lang->instance->errors->failedToUpdateDomain = 'Failed to update domain name'; diff --git a/module/instance/lang/en.php b/module/instance/lang/en.php index 41a0110086..31495e683a 100644 --- a/module/instance/lang/en.php +++ b/module/instance/lang/en.php @@ -1,5 +1,12 @@ instance = new stdclass; +$lang->instance->common = 'Applications'; +$lang->instance->manage = 'Manage Applications'; +$lang->instance->view = 'Application Detail'; +$lang->instance->ajaxStatus = 'Get Application Status'; +$lang->instance->ajaxStart = 'Start Application'; +$lang->instance->ajaxStop = 'Stop Application'; +$lang->instance->ajaxUninstall = 'Uninstall Application'; $lang->instance->name = 'name'; $lang->instance->appName = 'Application type'; $lang->instance->version = 'version'; @@ -13,7 +20,7 @@ $lang->instance->dbType = 'Database'; $lang->instance->advanceOption = 'Advanced Options'; $lang->instance->baseInfo = 'Basic Information'; $lang->instance->backupAndRestore = 'Backup'; -$lang->instance->advance = 'Advanced'; +$lang->instance->advance = 'Advanced'; $lang->instance->enableLDAP = 'Enable LDAP'; $lang->instance->linkLDAP = 'Integrated LDAP'; $lang->instance->enableSMTP = 'Enable SMTP'; @@ -34,6 +41,7 @@ $lang->instance->editName = 'Modify Name'; $lang->instance->cpuCore = 'Core'; $lang->instance->scalable = 'Application level expansion'; $lang->instance->change = 'Modify'; +$lang->instance->browseProject = "Project List"; $lang->instance->systemLDAPInactive = 'System LDAP not enabled'; $lang->instance->toSystemLDAP = 'Deactivate'; @@ -50,6 +58,10 @@ $lang->instance->installAt = 'Creation time'; $lang->instance->runDuration = 'Running'; $lang->instance->defaultAccount = 'Default User'; $lang->instance->defaultPassword = 'Default Password'; +$lang->instance->account = 'User'; +$lang->instance->password = 'Password'; +$lang->instance->token = 'Token'; +$lang->instance->copied = 'Copy successful'; $lang->instance->operationLog = 'Operation Record'; $lang->instance->installedService = 'Installed Service'; $lang->instance->runningService = 'Running Service'; @@ -92,7 +104,7 @@ $lang->instance->backup->dbName = 'name'; $lang->instance->backup->dbStatus = 'Status'; $lang->instance->backup->dbSpentSeconds = 'Time taken (seconds)'; $lang->instance->backup->dbSize = 'Size'; -$lang->instance->backup->volume = 'Data volume'; +$lang->instance->backup->volumne = 'Data volume'; $lang->instance->backup->volName = 'name'; $lang->instance->backup->volMountName = 'Mount directory'; $lang->instance->backup->volStatus = 'Status'; @@ -100,8 +112,7 @@ $lang->instance->backup->volSpentSeconds = 'Time taken (seconds)'; $lang->instance->backup->volSize = 'Size'; $lang->instance->backup->lastRestore = 'Last rollback'; $lang->instance->backup->restoreDate = 'Rollback time'; -$lang->instance->backup->latestBackupAt = 'Last Backup'; -$lang->instance->backup->lastBackupAt = 'Last backup time'; +$lang->instance->backup->latestBackupAt = 'Last backup time'; $lang->instance->backup->backupBeforeRestore = 'We recommend that you backup before rolling back!'; $lang->instance->backup->enableAutoBackup = 'Enable automatic backup'; $lang->instance->backup->autoBackup = 'Automatic backup'; @@ -164,7 +175,7 @@ $lang->instance->log->date = 'date'; $lang->instance->log->message = 'Content'; $lang->instance->actionList = array(); -$lang->instance->actionList['install'] = '%s installed'; +$lang->instance->actionList['install'] = '%s installed'; $lang->instance->actionList['uninstall'] = '%s deleted'; $lang->instance->actionList['start'] = 'Started %s'; $lang->instance->actionList['stop'] = '%s closed'; @@ -187,8 +198,9 @@ $lang->instance->actionList['autorestore'] = 'The system has perform $lang->instance->actionList['deleteexpiredbackup'] = 'The system has deleted expired automatic backups'; $lang->instance->sourceList = array(); -$lang->instance->sourceList['cloud'] = 'Chancheng Public Market'; +$lang->instance->sourceList['cloud'] = 'Store'; $lang->instance->sourceList['local'] = 'Local Market'; +$lang->instance->sourceList['user'] = 'User'; $lang->instance->channelList = array(); $lang->instance->channelList['test'] = 'Test version'; @@ -213,6 +225,7 @@ $lang->instance->statusList['destroyed'] = 'Destroyed'; $lang->instance->statusList['abnormal'] = 'Abnormal'; $lang->instance->statusList['upgrading'] = 'Updating'; $lang->instance->statusList['unknown'] = 'unknown'; +$lang->instance->statusList['scheduling'] = 'Scheduling'; $lang->instance->htmlStatusesClass = array(); $lang->instance->htmlStatusesClass['running'] = 'success'; @@ -289,6 +302,7 @@ $lang->instance->notices['enableSMTPSuccess'] = 'Enable SMTP successfully'; $lang->instance->notices['disableSMTPSuccess'] = 'Disable SMTP successfully'; $lang->instance->notices['confirmCustom'] = 'After modifying the custom configuration, the service will automatically restart to make the configuration effective.'; $lang->instance->notices['required'] = 'cannot be empty'; +$lang->instance->notices['notEnoughResource'] = 'Insufficient platform resources. Do you want to continue installing?'; $lang->instance->nameChangeTo = '%s is modified to %s.'; $lang->instance->versionChangeTo = 'Upgrade %s to %s.'; diff --git a/module/instance/lang/fr.php b/module/instance/lang/fr.php index ea146d14fb..31495e683a 100644 --- a/module/instance/lang/fr.php +++ b/module/instance/lang/fr.php @@ -1,5 +1,12 @@ instance = new stdclass; +$lang->instance->common = 'Applications'; +$lang->instance->manage = 'Manage Applications'; +$lang->instance->view = 'Application Detail'; +$lang->instance->ajaxStatus = 'Get Application Status'; +$lang->instance->ajaxStart = 'Start Application'; +$lang->instance->ajaxStop = 'Stop Application'; +$lang->instance->ajaxUninstall = 'Uninstall Application'; $lang->instance->name = 'name'; $lang->instance->appName = 'Application type'; $lang->instance->version = 'version'; @@ -13,7 +20,7 @@ $lang->instance->dbType = 'Database'; $lang->instance->advanceOption = 'Advanced Options'; $lang->instance->baseInfo = 'Basic Information'; $lang->instance->backupAndRestore = 'Backup'; -$lang->instance->advanced = 'Advanced'; +$lang->instance->advance = 'Advanced'; $lang->instance->enableLDAP = 'Enable LDAP'; $lang->instance->linkLDAP = 'Integrated LDAP'; $lang->instance->enableSMTP = 'Enable SMTP'; @@ -32,8 +39,9 @@ $lang->instance->uninstall = 'delete'; $lang->instance->visit = 'Visit'; $lang->instance->editName = 'Modify Name'; $lang->instance->cpuCore = 'Core'; -$lang->instance->scaleble = 'Application level expansion'; +$lang->instance->scalable = 'Application level expansion'; $lang->instance->change = 'Modify'; +$lang->instance->browseProject = "Project List"; $lang->instance->systemLDAPInactive = 'System LDAP not enabled'; $lang->instance->toSystemLDAP = 'Deactivate'; @@ -50,6 +58,10 @@ $lang->instance->installAt = 'Creation time'; $lang->instance->runDuration = 'Running'; $lang->instance->defaultAccount = 'Default User'; $lang->instance->defaultPassword = 'Default Password'; +$lang->instance->account = 'User'; +$lang->instance->password = 'Password'; +$lang->instance->token = 'Token'; +$lang->instance->copied = 'Copy successful'; $lang->instance->operationLog = 'Operation Record'; $lang->instance->installedService = 'Installed Service'; $lang->instance->runningService = 'Running Service'; @@ -92,7 +104,7 @@ $lang->instance->backup->dbName = 'name'; $lang->instance->backup->dbStatus = 'Status'; $lang->instance->backup->dbSpentSeconds = 'Time taken (seconds)'; $lang->instance->backup->dbSize = 'Size'; -$lang->instance->backup->volume = 'Data volume'; +$lang->instance->backup->volumne = 'Data volume'; $lang->instance->backup->volName = 'name'; $lang->instance->backup->volMountName = 'Mount directory'; $lang->instance->backup->volStatus = 'Status'; @@ -100,7 +112,7 @@ $lang->instance->backup->volSpentSeconds = 'Time taken (seconds)'; $lang->instance->backup->volSize = 'Size'; $lang->instance->backup->lastRestore = 'Last rollback'; $lang->instance->backup->restoreDate = 'Rollback time'; -$lang->instance->backup->lastBackupAt = 'Last backup time'; +$lang->instance->backup->latestBackupAt = 'Last backup time'; $lang->instance->backup->backupBeforeRestore = 'We recommend that you backup before rolling back!'; $lang->instance->backup->enableAutoBackup = 'Enable automatic backup'; $lang->instance->backup->autoBackup = 'Automatic backup'; @@ -163,7 +175,7 @@ $lang->instance->log->date = 'date'; $lang->instance->log->message = 'Content'; $lang->instance->actionList = array(); -$lang->instance->actionList['istall'] = '%s installed'; +$lang->instance->actionList['install'] = '%s installed'; $lang->instance->actionList['uninstall'] = '%s deleted'; $lang->instance->actionList['start'] = 'Started %s'; $lang->instance->actionList['stop'] = '%s closed'; @@ -186,8 +198,9 @@ $lang->instance->actionList['autorestore'] = 'The system has perform $lang->instance->actionList['deleteexpiredbackup'] = 'The system has deleted expired automatic backups'; $lang->instance->sourceList = array(); -$lang->instance->sourceList['cloud'] = 'Chancheng Public Market'; +$lang->instance->sourceList['cloud'] = 'Store'; $lang->instance->sourceList['local'] = 'Local Market'; +$lang->instance->sourceList['user'] = 'User'; $lang->instance->channelList = array(); $lang->instance->channelList['test'] = 'Test version'; @@ -212,6 +225,7 @@ $lang->instance->statusList['destroyed'] = 'Destroyed'; $lang->instance->statusList['abnormal'] = 'Abnormal'; $lang->instance->statusList['upgrading'] = 'Updating'; $lang->instance->statusList['unknown'] = 'unknown'; +$lang->instance->statusList['scheduling'] = 'Scheduling'; $lang->instance->htmlStatusesClass = array(); $lang->instance->htmlStatusesClass['running'] = 'success'; @@ -288,6 +302,7 @@ $lang->instance->notices['enableSMTPSuccess'] = 'Enable SMTP successfully'; $lang->instance->notices['disableSMTPSuccess'] = 'Disable SMTP successfully'; $lang->instance->notices['confirmCustom'] = 'After modifying the custom configuration, the service will automatically restart to make the configuration effective.'; $lang->instance->notices['required'] = 'cannot be empty'; +$lang->instance->notices['notEnoughResource'] = 'Insufficient platform resources. Do you want to continue installing?'; $lang->instance->nameChangeTo = '%s is modified to %s.'; $lang->instance->versionChangeTo = 'Upgrade %s to %s.'; diff --git a/module/instance/lang/zh-cn.php b/module/instance/lang/zh-cn.php index 9f18c16818..d7e3e81b02 100644 --- a/module/instance/lang/zh-cn.php +++ b/module/instance/lang/zh-cn.php @@ -1,5 +1,12 @@ instance = new stdclass; +$lang->instance->common = '应用'; +$lang->instance->manage = '管理应用'; +$lang->instance->view = '应用详情'; +$lang->instance->ajaxStatus = '获取应用状态'; +$lang->instance->ajaxStart = '启动应用'; +$lang->instance->ajaxStop = '关闭应用'; +$lang->instance->ajaxUninstall = '卸载应用'; $lang->instance->name = '名称'; $lang->instance->appName = '应用类型'; $lang->instance->version = '版本'; @@ -34,6 +41,7 @@ $lang->instance->editName = '修改名称'; $lang->instance->cpuCore = '核'; $lang->instance->scalable = '应用水平扩容'; $lang->instance->change = '修改'; +$lang->instance->browseProject = "项目列表"; $lang->instance->systemLDAPInactive = '未开启系统LDAP'; $lang->instance->toSystemLDAP = '去启用'; @@ -42,7 +50,6 @@ $lang->instance->enableSMTP = '启用SMTP'; $lang->instance->systemSMTPInactive = '未开启系统SMTP'; $lang->instance->toSystemSMTP = '去启用'; - $lang->instance->serviceInfo = '服务信息'; $lang->instance->appTemplate = '应用模板'; $lang->instance->source = '来源'; @@ -51,6 +58,10 @@ $lang->instance->installAt = '创建时间'; $lang->instance->runDuration = '已运行'; $lang->instance->defaultAccount = '默认用户'; $lang->instance->defaultPassword = '默认密码'; +$lang->instance->account = '用户名'; +$lang->instance->password = '密码'; +$lang->instance->token = 'Token'; +$lang->instance->copied = '复制成功'; $lang->instance->operationLog = '操作记录'; $lang->instance->installedService = '已安装服务'; $lang->instance->runningService = '运行中的服务'; @@ -93,7 +104,7 @@ $lang->instance->backup->dbName = '名称'; $lang->instance->backup->dbStatus = '状态'; $lang->instance->backup->dbSpentSeconds = '耗时(秒)'; $lang->instance->backup->dbSize = '大小'; -$lang->instance->backup->volume = '数据卷'; +$lang->instance->backup->volumne = '数据卷'; $lang->instance->backup->volName = '名称'; $lang->instance->backup->volMountName = '挂载目录'; $lang->instance->backup->volStatus = '状态'; @@ -187,8 +198,9 @@ $lang->instance->actionList['autorestore'] = '系统执行了自动 $lang->instance->actionList['deleteexpiredbackup'] = '系统删除了过期的自动备份'; $lang->instance->sourceList = array(); -$lang->instance->sourceList['cloud'] = '渠成公共市场'; +$lang->instance->sourceList['cloud'] = '应用市场'; $lang->instance->sourceList['local'] = '本地市场'; +$lang->instance->sourceList['user'] = '手工配置'; $lang->instance->channelList = array(); $lang->instance->channelList['test'] = '测试版'; @@ -213,6 +225,7 @@ $lang->instance->statusList['destroyed'] = '已销毁'; $lang->instance->statusList['abnormal'] = '异常'; $lang->instance->statusList['upgrading'] = '更新中'; $lang->instance->statusList['unknown'] = '未知'; +$lang->instance->statusList['scheduling'] = '调度中'; $lang->instance->htmlStatusesClass = array(); $lang->instance->htmlStatusesClass['running'] = 'success'; @@ -289,6 +302,7 @@ $lang->instance->notices['enableSMTPSuccess'] = '启用SMTP成功'; $lang->instance->notices['disableSMTPSuccess'] = '禁用SMTP成功'; $lang->instance->notices['confirmCustom'] = '修改自定义配置后服务将自动重启以使配置生效。'; $lang->instance->notices['required'] = '不能为空'; +$lang->instance->notices['notEnoughResource'] = '平台资源不足,要继续安装吗?'; $lang->instance->nameChangeTo = ' %s 修改为 %s 。'; $lang->instance->versionChangeTo = ' %s 升级为 %s 。'; @@ -309,8 +323,7 @@ $lang->instance->appLifeTip = 'demo账号安装的应用有30分钟限 $lang->instance->serialDiff = '查看版本区别'; $lang->instance->descOfSwitchSerial = '您当前使用的是%s,想要体验更多高级功能,可升级至%s。'; $lang->instance->toSeniorAttention = '重要提示'; -$lang->instance->toSeniorTips = "
  • 版本升级后,无法回退到原版本。
  • 企业版、旗舰版自安装后免费试用6个月。
  • 开源版升级到企业版或旗舰版后,试用期最大支持3个用户, - 请检查开源版用户数量。超出限制将不可用。
  • 升级成功后,服务将自动重启。
  • 为避免造成数据丢失,请您在升级前务必做好数据备份。
"; +$lang->instance->toSeniorTips = "
  • 版本升级后,无法回退到原版本。
  • 企业版、旗舰版自安装后免费试用6个月。
  • 开源版升级到企业版或旗舰版后,试用期最大支持3个用户,请检查开源版用户数量。超出限制将不可用。
  • 升级成功后,服务将自动重启。
  • 为避免造成数据丢失,请您在升级前务必做好数据备份。
"; $lang->instance->errors = new stdclass; $lang->instance->errors->domainLength = '域名长度必须介于2-20字符之间'; diff --git a/module/instance/model.php b/module/instance/model.php index fffe6fa6a9..45b0194f1d 100644 --- a/module/instance/model.php +++ b/module/instance/model.php @@ -49,6 +49,20 @@ class InstanceModel extends model return $instance; } + /** + * 根据应用url获取应用信息。 + * Get a application by url. + * + * @param string $url + * @access public + * @return object + */ + public function getByUrl(string $url) + { + $url = str_replace(array('https://', 'http://'), '', trim($url)); + return $this->dao->select('id')->from(TABLE_INSTANCE)->where('domain')->eq($url)->fetch(); + } + /** * Get by id list. * @@ -63,10 +77,10 @@ class InstanceModel extends model ->andWhere('deleted')->eq(0) ->fetchAll('id'); - $spaces = $this->dao->select('*')->from(TABLE_SPACE)->where('deleted')->eq(0)->andWhere('id')->in(array_column($instances, 'space'))->fetchAll('id'); + $spaces = $this->dao->select('*')->from(TABLE_SPACE)->where('deleted')->eq(0)->andWhere('id')->in(helper::arrayColumn($instances, 'space'))->fetchAll('id'); foreach($instances as $instance) $instance->spaceData = zget($spaces, $instance->space, new stdclass); - $solutionIDList = array_column($instances, 'solution'); + $solutionIDList = helper::arrayColumn($instances, 'solution'); $solutions = $this->dao->select('*')->from(TABLE_SOLUTION)->where('id')->in($solutionIDList)->fetchAll('id'); foreach($instances as $instance) $instance->solutionData = zget($solutions, $instance->solution, new stdclass); @@ -103,13 +117,13 @@ class InstanceModel extends model */ public function getByAccount($account = '', $pager = null, $pinned = '', $searchParam = '', $status = 'all') { - $defaultSpace = $this->loadModel('space')->defaultSpace($account ? $account : $this->app->user->account); + // $defaultSpace = $this->loadModel('space')->defaultSpace($account ? $account : $this->app->user->account); $instances = $this->dao->select('instance.*')->from(TABLE_INSTANCE)->alias('instance') ->leftJoin(TABLE_SPACE)->alias('space')->on('space.id=instance.space') ->where('instance.deleted')->eq(0) - ->andWhere('space.id')->eq($defaultSpace->id) - ->beginIF($account)->andWhere('space.owner')->eq($account)->fi() + // ->andWhere('space.id')->eq($defaultSpace->id) + // ->beginIF($account)->andWhere('space.owner')->eq($account)->fi() ->beginIF($pinned)->andWhere('instance.pinned')->eq((int)$pinned)->fi() ->beginIF($searchParam)->andWhere('instance.name')->like("%{$searchParam}%")->fi() ->beginIF($status != 'all')->andWhere('instance.status')->eq($status)->fi() @@ -119,12 +133,12 @@ class InstanceModel extends model $spaces = $this->dao->select('*')->from(TABLE_SPACE) ->where('deleted')->eq(0) - ->andWhere('id')->in(array_column($instances, 'space')) + ->andWhere('id')->in(helper::arrayColumn($instances, 'space')) ->fetchAll('id'); foreach($instances as $instance) $instance->spaceData = zget($spaces, $instance->space, new stdclass); - $solutionIDList = array_column($instances, 'solution'); + $solutionIDList = helper::arrayColumn($instances, 'solution'); $solutions = $this->dao->select('*')->from(TABLE_SOLUTION)->where('id')->in($solutionIDList)->fetchAll('id'); foreach($instances as $instance) $instance->solutionData = zget($solutions, $instance->solution, new stdclass); @@ -143,7 +157,7 @@ class InstanceModel extends model $spaces = $this->dao->select('*')->from(TABLE_SPACE) ->where('deleted')->eq(0) - ->andWhere('id')->in(array_column($instances, 'space')) + ->andWhere('id')->in(helper::arrayColumn($instances, 'space')) ->fetchAll('id'); foreach($instances as $instance) $instance->spaceData = zget($spaces, $instance->space, new stdclass); @@ -330,7 +344,7 @@ class InstanceModel extends model ->andWhere("REPLACE(domain, '.$sysDomain', '')")->like("%.%") ->fetchAll('id'); - $spaces = $this->dao->select('*')->from(TABLE_SPACE)->where('deleted')->eq(0)->andWhere('id')->in(array_column($instanceList, 'space'))->fetchAll('id'); + $spaces = $this->dao->select('*')->from(TABLE_SPACE)->where('deleted')->eq(0)->andWhere('id')->in(helper::arrayColumn($instanceList, 'space'))->fetchAll('id'); foreach($instanceList as $instance) $instance->spaceData = zget($spaces, $instance->space, new stdclass); @@ -575,7 +589,7 @@ class InstanceModel extends model $settingsMap->global->ingress->host = $settingsMap->ingress->host; } - if(!empty($this->config->instance->devopsApps[$instance->appID])) + if(in_array($instance->chart, $this->config->instance->devopsApps)) { $settingsMap->ci = new stdclass(); $settingsMap->ci->enabled = true; @@ -914,7 +928,7 @@ class InstanceModel extends model */ public function createInstance($app, $space, $thirdDomain, $name = '', $k8name = '', $channel = 'stable', $snippets = array()) { - if(empty($k8name)) $k8name = "{$app->chart}-{$this->app->user->account}-" . date('YmdHis'); //name rule: chartName-userAccount-YmdHis; + if(empty($k8name)) $k8name = "{$app->chart}-" . date('YmdHis'); //name rule: chartName-userAccount-YmdHis; $instanceData = new stdclass; $instanceData->appId = $app->id; @@ -1518,8 +1532,8 @@ class InstanceModel extends model $backupList = $result->data; usort($backupList, function($backup1, $backup2){ return $backup1->create_time < $backup2->create_time; }); - $accounts = array_column($backupList, 'creator'); - foreach($backupList as $backup) $accounts = array_merge($accounts, array_column($backup->restores, 'creator')); + $accounts = helper::arrayColumn($backupList, 'creator'); + foreach($backupList as $backup) $accounts = array_merge($accounts, helper::arrayColumn($backup->restores, 'creator')); $accounts = array_unique($accounts); @@ -1652,7 +1666,7 @@ class InstanceModel extends model ->fetchAll(); if(empty($instanceList)) return; - $spaceList = $this->dao->select('*')->from(TABLE_SPACE)->where('id')->in(array_column($instanceList, 'space'))->fetchAll('id'); + $spaceList = $this->dao->select('*')->from(TABLE_SPACE)->where('id')->in(helper::arrayColumn($instanceList, 'space'))->fetchAll('id'); foreach($instanceList as $instance) { @@ -1710,13 +1724,13 @@ class InstanceModel extends model * * @param object $instance * @param object $metrics - * @param string $type 'bar' is progress bar, 'pie' is progress pie. * @static * @access public * @return mixed */ - public static function printCpuUsage($instance, $metrics, $type = 'bar') + public static function printCpuUsage($instance, $metrics) { + if($instance->source === 'user') return array('color' => '', 'tip' => '', 'rate' => '', 'usage' => '', 'limit' => ''); $rate = $instance->status == 'stopped' ? 0 : $metrics->rate; $tip = "{$rate}% = {$metrics->usage} / {$metrics->limit}"; @@ -1726,14 +1740,7 @@ class InstanceModel extends model if(empty($color) && $rate >= 0 && $rate < 90) $color = 'important'; if(empty($color) && $rate >= 80) $color = 'danger'; - if($type == 'array') return array('color' => $color, 'tip' => $tip, 'rate' => $rate . '%', 'usage' => $metrics->usage, 'limit' => $metrics->limit); - - if(strtolower($type) == 'pie') commonModel::printProgressPie($rate, '', $tip); - - $valueType = 'percent'; - if($instance->status == 'stopped') $valueType = ''; - - commonModel::printProgressBar($rate, '', $tip, $valueType); + return array('color' => $color, 'tip' => $tip, 'rate' => $rate . '%', 'usage' => $metrics->usage, 'limit' => $metrics->limit); } /** @@ -1741,13 +1748,13 @@ class InstanceModel extends model * * @param object $instance * @param object $metrics - * @param string $type 'bar' is progress bar, 'pie' is progress pie. * @static * @access public * @return mixed */ - public static function printMemUsage($instance, $metrics, $type = 'bar') + public static function printMemUsage($instance, $metrics) { + if($instance->source === 'user') return array('color' => '', 'tip' => '', 'rate' => '', 'usage' => '', 'limit' => ''); $rate = $instance->status == 'stopped' ? 0 : $metrics->rate; $tip = "{$rate}% = " . helper::formatKB($metrics->usage) . ' / ' . helper::formatKB($metrics->limit); @@ -1757,14 +1764,7 @@ class InstanceModel extends model if(empty($color) && $rate >= 0 && $rate < 90) $color = 'important'; if(empty($color) && $rate >= 80) $color = 'danger'; - if($type == 'array') return array('color' => $color, 'tip' => $tip, 'rate' => $rate . '%', 'usage' => helper::formatKB($metrics->usage), 'limit' => helper::formatKB($metrics->limit)); - - if(strtolower($type) == 'pie') commonModel::printProgressPie($rate, '', $tip); - - $valueType = 'tip'; - if($instance->status == 'stopped') $valueType = ''; - - commonModel::printProgressBar($rate, '', $tip, $valueType); + return array('color' => $color, 'tip' => $tip, 'rate' => $rate . '%', 'usage' => helper::formatKB($metrics->usage), 'limit' => helper::formatKB($metrics->limit)); } /* @@ -2054,14 +2054,38 @@ class InstanceModel extends model public function isClickable(object $instance, string $action): bool { if(!isset($instance->type)) $instance->type = 'store'; - if($action == 'start') return $instance->type != 'external' ? $this->canDo('start', $instance) : false; - if($action == 'stop') return $instance->type != 'external' ? $this->canDo('stop', $instance) : false; - if($action == 'uninstall') return $instance->type != 'external' && $this->canDo('uninstall', $instance); - if($action == 'visit') return $instance->type != 'external' ? ($instance->domain && $this->canDo('visit', $instance)) : true; - if($action == 'upgrade') return !empty($instance->latestVersion); - if($action == 'bindUser') return ($instance->externalID && in_array($instance->appName, array('GitLab', 'Gitea', 'Gogs'))) ? true : false; - if($action == 'edit') return $instance->type != 'external' ? false : true; + if($instance->type !== 'store') + { + if($action === 'edit' || $action === 'visit') return true; + if($action == 'bindUser') return in_array($instance->appName, array('GitLab', 'Gitea', 'Gogs')); + if($action == 'ajaxUninstall') return true; + return false; + } + + if($action == 'ajaxStart') return $this->canDo('start', $instance); + if($action == 'ajaxStop') return $this->canDo('stop', $instance); + if($action == 'ajaxUninstall') return $this->canDo('uninstall', $instance); + if($action == 'visit') return !empty($instance->domain) && $this->canDo('visit', $instance); + if($action == 'upgrade') return !empty($instance->latestVersion) && in_array($instance->status, array('stopped', 'running')); + if($action == 'edit') return false; + if($action == 'bindUser') return in_array($instance->appName, array('GitLab', 'Gitea', 'Gogs')); + + return true; + } + + /** + * 判断按钮是否显示。 + * Adjust the action display. + * + * @param object $instance + * @param string $action + * @access public + * @return bool + */ + public function isDisplay(object $instance, string $action): bool + { + if($action !== 'visit' && !commonModel::hasPriv('instance', 'manage')) return false; return true; } diff --git a/module/instance/ui/editexternalapp.html.php b/module/instance/ui/editexternalapp.html.php new file mode 100644 index 0000000000..778a05799f --- /dev/null +++ b/module/instance/ui/editexternalapp.html.php @@ -0,0 +1,59 @@ + + * @package instance + * @link https://www.zentao.net + */ + +namespace zin; + +formPanel +( + set::id('appCreateForm'), + set::title($lang->edit . $lang->space->appType[$app->type]), + set::submitBtnText($lang->save), + set::actions(array('submit', array('text' => $lang->cancel, 'data-type' => 'submit', 'data-dismiss' => 'modal'))), + formRow + ( + formGroup + ( + set::name('name'), + set::required(true), + set::label($lang->sonarqube->name), + set::value($app->name), + ) + ), + formRow + ( + formGroup + ( + set::name('url'), + set::required(true), + set::label($lang->sonarqube->url), + set::value($app->url), + ) + ), + formRow + ( + formGroup + ( + set::name('account'), + set::label($lang->sonarqube->account), + set::value($app->account), + ) + ), + formRow + ( + formGroup + ( + set::name('password'), + set::label($lang->sonarqube->password), + set::value($app->password), + ) + ), +); diff --git a/module/instance/ui/setting.html.php b/module/instance/ui/setting.html.php index d8b099bb3e..a826630e78 100644 --- a/module/instance/ui/setting.html.php +++ b/module/instance/ui/setting.html.php @@ -24,6 +24,7 @@ formPanel ( set::name('name'), set::width('500px'), + set::required(true), set::label($lang->instance->name), set::value($instance->name), ) @@ -35,6 +36,7 @@ formPanel set::name('memory_kb'), set::width('250px'), set::control('picker'), + set::required(true), set::label($lang->instance->adjustMem), set::value(intval($currentResource->max->memory / 1024)), set::items($this->instance->filterMemOptions($currentResource)), diff --git a/module/instance/ui/view.html.php b/module/instance/ui/view.html.php index b882560a67..ab2cbbbc95 100644 --- a/module/instance/ui/view.html.php +++ b/module/instance/ui/view.html.php @@ -13,13 +13,27 @@ declare(strict_types=1); namespace zin; -jsVar('instanceID', $instance->id); +jsVar('copied', $lang->instance->copied); +jsVar('instanceID', $instance->id); +jsVar('instanceStatus', $instance->status); +jsVar('instanceType', $type); -$setting = usePager('pager'); +$instance->appName = strtolower($instance->appName); $cpuInfo = $this->instance->printCpuUsage($instance, $instanceMetric->cpu, 'array'); $memoryInfo = $this->instance->printMemUsage($instance, $instanceMetric->memory, 'array'); $actions = $this->loadModel('common')->buildOperateMenu($instance); +if($type !== 'store') +{ + $defaultAccount = new stdclass(); + $defaultAccount->username = $instance->account; + $defaultAccount->password = $instance->password; + $defaultAccount->token = $instance->token; + + $lang->instance->defaultAccount = $lang->instance->account; + $lang->instance->defaultPassword = $lang->instance->password; +} + $dbListWg = array(); foreach($dbList as $db) { @@ -39,11 +53,10 @@ foreach($dbList as $db) btn ( $lang->instance->management, - setClass('btn text-primary ghost ' . $disabledClass), + setClass('btn text-primary ghost db-management ' . $disabledClass), setData('dbname', $db->name), setData('dbtype', $db->db_type), setData('id', $instance->id), - on::click('openAdminer'), ) ), ); @@ -55,10 +68,10 @@ detailHeader( ); div ( - setClass('flex flex-normal gap-x-5'), + setClass('flex flex-normal gap-x-5 justify-center'), div ( - setClass('basis-2/3'), + setClass('flex-none w-2/3'), setID('instanceInfoContainer'), detailBody ( @@ -67,26 +80,27 @@ div /* 应用名称信息图标区块 */ section ( + set::title(''), div ( setClass('flex justify-between'), div ( setClass('flex basis-full'), - img(set::src($instance->logo), setStyle(array('width' => '50px', 'height' => '50px'))), + $type === 'store' ? img(set::src($instance->logo), setStyle(array('width' => '50px', 'height' => '50px'))) : null, div ( - setClass('ml-3 flex col gap-y-1 basis-full'), + setClass(($type === 'store' ? 'ml-3' : '') . ' flex col gap-y-1 basis-full'), div ( $instance->name, setClass('text-xl'), - span($cloudApp->app_version, setClass('ml-3 label lighter rounded-full')) + $type === 'store' ? span($instance->appVersion, setClass('ml-3 label lighter rounded-full')) : null ), - div + $type === 'store' ? div ( setClass('flex progress-container'), set::title($cpuInfo['tip']), - icon('cog-outline text-' . $cpuInfo['color']), + icon('cpu text-' . $cpuInfo['color']), $lang->instance->cpuUsage, div ( @@ -99,7 +113,7 @@ div setStyle('width', $cpuInfo['rate']) ) ), - icon('desktop text-' . $memoryInfo['color']), + icon('memory text-' . $memoryInfo['color']), $lang->instance->memUsage, span ( @@ -120,10 +134,10 @@ div setStyle('width', $memoryInfo['rate']) ) ) - ), + ) : null, ), ), - btn + $type !== 'store' ? null : btn ( $lang->instance->setting, setClass('btn ghost'), @@ -144,18 +158,20 @@ div setClass('table w-auto max-w-full bordered mt-4'), h::tr ( - h::th($lang->instance->status), + $type !== 'store' ? null : h::th($lang->instance->status), h::th($lang->instance->source), // h::th($lang->instance->appTemplate), h::th($lang->instance->installBy), h::th($lang->instance->installAt), - h::th($lang->instance->runDuration), - $defaultAccount ? h::th($lang->instance->defaultAccount) : null, - $defaultAccount ? h::th($lang->instance->defaultPassword) : null, + $type !== 'store' ? null : h::th($lang->instance->runDuration), + !empty($defaultAccount->username) ? h::th($lang->instance->defaultAccount) : null, + !empty($defaultAccount->password) ? h::th($lang->instance->defaultPassword) : null, + !empty($defaultAccount->token) ? h::th($lang->instance->token) : null, + (!in_array($instance->appName, array('gitlab', 'sonarqube'))) ? null : h::th($lang->instance->browseProject), ), h::tr ( - h::td + $type !== 'store' ? null : h::td ( setID('statusTD'), setData('reload', in_array($instance->status, array('creating', 'initializing', 'pulling', 'startup', 'starting', 'suspending', 'installing', 'uninstalling', 'stopping', 'destroying', 'upgrading'))), @@ -169,9 +185,28 @@ div // h::td(a(set::href($this->createLink('store', 'appView', "id=$instance->appID")), $instance->appName)), h::td(zget($users, $instance->createdBy, '')), h::td(substr($instance->createdAt, 0, 16)), - h::td(common::printDuration($instance->runDuration)), - $defaultAccount ? h::td($defaultAccount->username) : null, - $defaultAccount ? h::td($defaultAccount->password) : null, + $type !== 'store' ? null : h::td(common::printDuration($instance->runDuration)), + !empty($defaultAccount->username) ? h::td($defaultAccount->username) : null, + !empty($defaultAccount->password) ? h::td + ( + input(set::type('text'), set::value($defaultAccount->password), set::name('password'), setStyle('display', 'none')), + btn(set::className('copy-btn ghost'),set::icon('copy')) + ): null, + !empty($defaultAccount->token) ? h::td + ( + input(set::type('text'), set::value($defaultAccount->token), set::name('token'), setStyle('display', 'none')), + btn(set::className('copy-btn ghost'),set::icon('copy')) + ): null, + (!in_array($instance->appName, array('gitlab', 'sonarqube'))) ? null : h::td + ( + btn + ( + $lang->instance->management, + setClass('btn text-primary ghost'), + set::disabled($instance->type === 'store' && $instance->status != 'running'), + set::url(createLink($instance->appName, 'browseProject', "{$instance->appName}ID={$instance->externalID}")) + ) + ), ) ) ), @@ -205,10 +240,10 @@ div ), div ( - setClass('basis-auto'), + setClass('w-1/3'), history ( - set::commentUrl(createLink('action', 'comment', array('objectType' => 'instance', 'objectID' => $instance->id))), + set::commentUrl(createLink('action', 'comment', array('objectType' => $type === 'store' ? 'instance' : $instance->type, 'objectID' => $instance->id))), ) ) ); diff --git a/module/instance/zen.php b/module/instance/zen.php index 34a209a1c3..674eed1faa 100644 --- a/module/instance/zen.php +++ b/module/instance/zen.php @@ -21,9 +21,9 @@ class instanceZen extends instance */ protected function saveAuthInfo(object $instance): void { - if(empty($this->config->instance->devopsApps[$instance->appID])) return; + if(!in_array($instance->chart, $this->config->instance->devopsApps)) return; - $url = 'https://' . $instance->domain; + $url = strstr(getWebRoot(true), ':', true) . '://' . $instance->domain; $pipeline = $this->loadModel('pipeline')->getByUrl($url); if(!empty($pipeline)) return; @@ -31,19 +31,21 @@ class instanceZen extends instance if(empty($tempMappings)) return; $pipeline = new stdclass(); - $instance->type = $this->config->instance->devopsApps[$instance->appID]; + $instance->type = $instance->chart; $pipeline->type = $instance->type; $pipeline->private = md5(strval(rand(10,113450))); $pipeline->createdBy = 'system'; $pipeline->createdDate = helper::now(); $pipeline->url = $url; $pipeline->name = $this->generatePipelineName($instance); - $pipeline->token = zget($tempMappings, 'admin_token', ''); - $pipeline->account = zget($tempMappings, 'admin_username', ''); - $pipeline->password = zget($tempMappings, 'admin_password', ''); - $pipeline->token = zget($tempMappings, 'admin_token', ''); + $pipeline->token = zget($tempMappings, 'api_token', ''); + $pipeline->account = zget($tempMappings, 'z_username', ''); + $pipeline->password = zget($tempMappings, 'z_password', ''); + if($instance->appID == 60) $pipeline->token = base64_encode($pipeline->token . ':'); + if(empty($pipeline->account)) $pipeline->account = zget($tempMappings, 'admin_username', ''); $this->pipeline->create($pipeline); + if(dao::isError()) dao::getError(); } /** diff --git a/module/job/config/dtable.php b/module/job/config/dtable.php index 40917a6703..d2565036e2 100644 --- a/module/job/config/dtable.php +++ b/module/job/config/dtable.php @@ -78,7 +78,7 @@ $config->job->actionList = array(); $config->job->actionList['compile']['icon'] = 'history'; $config->job->actionList['compile']['text'] = $lang->compile->browse; $config->job->actionList['compile']['hint'] = $lang->compile->browse; -$config->job->actionList['compile']['url'] = helper::createLink('compile', 'browse',"repoID={repo}&jobID={id}"); +$config->job->actionList['compile']['url'] = array('module' => 'compile', 'method' => 'browse', 'params' => "repoID={repo}&jobID={id}"); $config->job->actionList['edit']['icon'] = 'edit'; $config->job->actionList['edit']['text'] = $lang->job->edit; diff --git a/module/job/control.php b/module/job/control.php index f2c43de1f0..fd9cd73eb2 100644 --- a/module/job/control.php +++ b/module/job/control.php @@ -95,7 +95,7 @@ class job extends control $job->buildSpec = urldecode($job->pipeline) . '@' . $job->jenkinsName; $job->engine = zget($this->lang->job->engineList, $job->engine); $job->frame = zget($this->lang->job->frameList, $job->frame); - $job->productName = $products[$job->product]; + $job->productName = zget($products, $job->product, ''); } $this->view->title = $this->lang->ci->job . $this->lang->colon . $this->lang->job->browse; @@ -144,8 +144,7 @@ class job extends control } $this->loadModel('action')->create('job', $jobID, 'created'); - if($this->viewType == 'json') return $this->send(array('result' => 'success', 'message' => $this->lang->saveSuccess, 'id' => $jobID)); - return $this->send(array('result' => 'success', 'message' => $this->lang->saveSuccess, 'locate' => inlink('browse'))); + return $this->send(array('result' => 'success', 'message' => $this->lang->saveSuccess, 'locate' => inlink('browse', "repoID={$this->post->repo}"))); } $this->loadModel('ci'); @@ -260,6 +259,11 @@ class job extends control if($jobProduct and $jobProduct->deleted == 0) $products += array($job->product => $jobProduct->name); } + if($job->frame == 'sonarqube' && $job->sonarqubeServer && $job->projectKey) + { + $this->view->sonarqubeProjectPairs = $this->loadModel('sonarqube')->getProjectPairs($job->sonarqubeServer, $job->projectKey); + } + $this->view->title = $this->lang->ci->job . $this->lang->colon . $this->lang->job->edit; $this->view->repoPairs = $repoPairs; $this->view->gitlabRepos = $gitlabRepos; @@ -316,7 +320,7 @@ class job extends control { $this->app->loadLang('project'); $taskID = $compile->testtask; - $task = $this->loadModel('testtask')->getByID($taskID); + $task = $this->loadModel('testtask')->getById($taskID); $runs = $this->testtask->getRuns($taskID, 0, 'id'); $cases = array(); @@ -380,18 +384,34 @@ class job extends control public function exec($jobID) { $job = $this->job->getByID($jobID); - if(strtolower($job->engine) == 'gitlab' and (!isset($job->reference) or !$job->reference)) return $this->send(array('result' => 'fail', 'message' => $this->lang->job->setReferenceTips, 'locate' => inlink('edit', "id=$jobID"))); + //if(strtolower($job->engine) == 'gitlab' and (!isset($job->reference) or !$job->reference)) return $this->send(array('result' => 'fail', 'message' => $this->lang->job->setReferenceTips, 'locate' => inlink('edit', "id=$jobID"))); $compile = $this->job->exec($jobID); - if(dao::isError()) return $this->send(array('result' => 'fail', 'callback' => sprintf('zui.Modal.alert("%s");', dao::getError()))); + if(dao::isError()) + { + $errors = ''; + foreach(dao::getError() as $error) + { + if(is_array($error)) + { + foreach($error as $val) + { + $errors .= $val . '\n'; + } + } + else + { + $errors .= $error . '\n'; + } + } + return $this->sendError($errors); + } $this->app->loadLang('compile'); $this->loadModel('action')->create('job', $jobID, 'executed'); - $message = sprintf($this->lang->job->sendExec, zget($this->lang->compile->statusList, $compile->status)); - $response['result'] = 'success'; - $response['callback'] = sprintf('zui.Modal.alert("%s");', $message); - return $this->send($response); + $message = sprintf($this->lang->job->sendExec, zget($this->lang->compile->statusList, $compile->status)); + return $this->sendSuccess(array('message' => $message)); } /** diff --git a/module/job/css/create.ui.css b/module/job/css/create.ui.css index 459ac800a7..d3c11ac47d 100644 --- a/module/job/css/create.ui.css +++ b/module/job/css/create.ui.css @@ -1 +1,2 @@ #pipelineDropmenu .icon-angle-right {display: none;} +#pipelineDropmenu .ghost {--tw-ring-color: var(--form-control-border);} \ No newline at end of file diff --git a/module/job/css/edit.ui.css b/module/job/css/edit.ui.css index 459ac800a7..043cb26e3b 100644 --- a/module/job/css/edit.ui.css +++ b/module/job/css/edit.ui.css @@ -1 +1,2 @@ #pipelineDropmenu .icon-angle-right {display: none;} +#pipelineDropmenu .ghost {--tw-ring-color: var(--form-control-border);} diff --git a/module/job/js/common.ui.js b/module/job/js/common.ui.js index d6a5f7cc6c..1aed5b0773 100644 --- a/module/job/js/common.ui.js +++ b/module/job/js/common.ui.js @@ -7,6 +7,7 @@ function addItem(event) window.customCount ++; $(inputGroup).find('input.custom').attr('id', newName); + $(inputGroup).find('input.paramName').val(''); $(inputGroup).find('input[id="' + newName + '"]').next().attr('for', newName); obj.closest('.form-group').append($(inputGroup)); } diff --git a/module/job/js/create.ui.js b/module/job/js/create.ui.js index 7c61af35e9..222449c903 100644 --- a/module/job/js/create.ui.js +++ b/module/job/js/create.ui.js @@ -201,4 +201,10 @@ $(document).ready(function() { $('#jkTask').val($('#pipelineDropmenu button.dropmenu-btn').data('value')); }); + $(document).on('change', 'select.paramValue', function() + { + var paramValue = $(this).val(); + paramValue = paramValue.substr(1).toUpperCase(); + $(this).prevAll('input').val(paramValue); + }); }); diff --git a/module/job/js/edit.ui.js b/module/job/js/edit.ui.js index a0a96d10dc..7055313b46 100644 --- a/module/job/js/edit.ui.js +++ b/module/job/js/edit.ui.js @@ -201,4 +201,10 @@ $(document).ready(function() { $('#jkTask').val($('#pipelineDropmenu button.dropmenu-btn').data('value')); }); + $(document).on('change', 'select.paramValue', function() + { + var paramValue = $(this).val(); + paramValue = paramValue.substr(1).toUpperCase(); + $(this).prevAll('input').val(paramValue); + }); }); diff --git a/module/job/js/view.ui.js b/module/job/js/view.ui.js new file mode 100644 index 0000000000..4ba46c14bf --- /dev/null +++ b/module/job/js/view.ui.js @@ -0,0 +1,10 @@ +$('#jobCases').html(""); +setTimeout(() => { + var height = $('#jobTaskResult').contents().find(".main-table").height(); + $('#jobTaskResult').contents().find("#main").css('min-width', 'auto'); + $('#jobTaskResult').css('background', 'white'); + $('#jobTaskResult').contents().find("#mainMenu").css('display', 'none'); + $('#jobTaskResult').contents().find("a").attr('target', '__blank'); + $('#jobTaskResult').contents().find(".main-table").css('padding-top', '0'); + $('#jobTaskResult').css('height', height + 40 + 'px'); +}, 1000); \ No newline at end of file diff --git a/module/job/lang/de.php b/module/job/lang/de.php index 0d9eb1dccc..04daba4139 100644 --- a/module/job/lang/de.php +++ b/module/job/lang/de.php @@ -17,6 +17,7 @@ $lang->job->browseAction = 'Pipeline List'; $lang->job->id = 'ID'; $lang->job->name = 'Name'; $lang->job->repo = 'Repo'; +$lang->job->branch = 'Branch'; $lang->job->product = $lang->productCommon; $lang->job->svnDir = 'SVN Tag Watch Path'; $lang->job->jenkins = 'Jenkins'; @@ -60,6 +61,7 @@ $lang->job->repoExists = 'This repository has a build task associated with i $lang->job->projectExists = 'This SonarQube Project has a build task associated with it『%s』'; $lang->job->mustUseJenkins = 'SonarQube frame is only used if the build engine is JenKins.'; $lang->job->jobIsDeleted = 'This repository is associated with a build task, please view the data from the recycle bin'; +$lang->job->selectPipeline = 'Please select a pipeline'; $lang->job->buildTypeList['build'] = 'Only Build'; $lang->job->buildTypeList['buildAndDeploy'] = 'Build And Deploy'; diff --git a/module/job/lang/en.php b/module/job/lang/en.php index 2ea68e5e51..a755443c03 100644 --- a/module/job/lang/en.php +++ b/module/job/lang/en.php @@ -61,7 +61,7 @@ $lang->job->repoExists = 'This repository has a build task associated with i $lang->job->projectExists = 'This SonarQube Project has a build task associated with it『%s』'; $lang->job->mustUseJenkins = 'SonarQube frame is only used if the build engine is JenKins.'; $lang->job->jobIsDeleted = 'This repository is associated with a build task, please view the data from the recycle bin'; -$lang->job->selectPipeline = 'Please select assembly line'; +$lang->job->selectPipeline = 'Please select a pipeline'; $lang->job->buildTypeList['build'] = 'Only Build'; $lang->job->buildTypeList['buildAndDeploy'] = 'Build And Deploy'; diff --git a/module/job/lang/fr.php b/module/job/lang/fr.php index d0a1abc2cf..edc3b045d5 100644 --- a/module/job/lang/fr.php +++ b/module/job/lang/fr.php @@ -17,6 +17,7 @@ $lang->job->browseAction = 'Pipeline List'; $lang->job->id = 'ID'; $lang->job->name = 'Nom'; $lang->job->repo = 'Repo'; +$lang->job->branch = 'Branch'; $lang->job->product = $lang->productCommon; $lang->job->svnDir = 'SVN Tag Watch Path'; $lang->job->jenkins = 'Jenkins'; @@ -60,6 +61,7 @@ $lang->job->repoExists = 'This repository has a build task associated with i $lang->job->projectExists = 'This SonarQube Project has a build task associated with it『%s』'; $lang->job->mustUseJenkins = 'SonarQube frame is only used if the build engine is JenKins.'; $lang->job->jobIsDeleted = 'This repository is associated with a build task, please view the data from the recycle bin'; +$lang->job->selectPipeline = 'Please select a pipeline'; $lang->job->buildTypeList['build'] = 'Seulement Build'; $lang->job->buildTypeList['buildAndDeploy'] = 'Build et D閜loiement'; diff --git a/module/job/model.php b/module/job/model.php index cb11d3af08..0245ec517c 100644 --- a/module/job/model.php +++ b/module/job/model.php @@ -174,6 +174,10 @@ class jobModel extends model $job = fixer::input('post') ->setDefault('atDay,projectKey', '') ->setDefault('sonarqubeServer', 0) + ->setIF($this->post->triggerType != 'commit', 'comment', '') + ->setIF($this->post->triggerType != 'schedule', 'atDay', '') + ->setIF($this->post->triggerType != 'schedule', 'atTime', '') + ->setIF($this->post->triggerType != 'tag', 'lastTag', '') ->add('createdBy', $this->app->user->account) ->add('createdDate', helper::now()) ->remove('repoType,reference') @@ -195,7 +199,7 @@ class jobModel extends model $pipeline = $this->loadModel('gitlab')->apiGetPipeline($repo->serviceHost, $repo->serviceProject, ''); if(!is_array($pipeline) or empty($pipeline)) { - dao::$errors['repo'] = $this->lang->job->engineTips->error; + dao::$errors['repo'][] = $this->lang->job->engineTips->error; return false; } } @@ -211,7 +215,7 @@ class jobModel extends model /* SonarQube tool is only used if the engine is JenKins. */ if($job->engine != 'jenkins' and $job->frame == 'sonarqube') { - dao::$errors[]['frame'] = $this->lang->job->mustUseJenkins; + dao::$errors['frame'][] = $this->lang->job->mustUseJenkins; return false; } @@ -221,7 +225,7 @@ class jobModel extends model if(!empty($sonarqubeJob)) { $message = sprintf($this->lang->job->repoExists, $sonarqubeJob[$job->repo]->id . '-' . $sonarqubeJob[$job->repo]->name); - dao::$errors[]['repo'] = $message; + dao::$errors['repo'][] = $message; return false; } } @@ -232,7 +236,7 @@ class jobModel extends model if(!empty($projectList)) { $message = sprintf($this->lang->job->projectExists, $projectList[$job->projectKey]->id); - dao::$errors[]['projectKey'] = $message; + dao::$errors['projectKey'][] = $message; return false; } } @@ -253,13 +257,13 @@ class jobModel extends model if(empty($paramName) and !empty($paramValue)) { - dao::$errors[] = $this->lang->job->inputName; + dao::$errors['paramName'][] = $this->lang->job->inputName; return false; } if(!empty($paramName) and !validater::checkREG($paramName, '/^[A-Za-z_0-9]+$/')) { - dao::$errors[] = $this->lang->job->invalidName; + dao::$errors['paramName'][] = $this->lang->job->invalidName; return false; } @@ -305,7 +309,7 @@ class jobModel extends model ->add('editedDate', helper::now()) ->remove('repoType,reference') ->get(); - $repo = $this->loadModel('repo')->getByID($job->gitlabRepo); + $repo = $this->loadModel('repo')->getByID($job->repo); if($job->engine == 'jenkins') { @@ -321,12 +325,11 @@ class jobModel extends model $pipeline = $this->loadModel('gitlab')->apiGetPipeline($repo->serviceHost, $repo->serviceProject, ''); if(!is_array($pipeline) or empty($pipeline)) { - dao::$errors['gitlabRepo'] = $this->lang->job->engineTips->error; + dao::$errors['gitlabRepo'][] = $this->lang->job->engineTips->error; return false; } } - $job->repo = $job->gitlabRepo; $job->server = (int)zget($repo, 'serviceHost', 0); $job->pipeline = json_encode(array('project' => $project, 'reference' => '')); } @@ -338,7 +341,7 @@ class jobModel extends model /* SonarQube tool is only used if the engine is JenKins. */ if($job->engine != 'jenkins' and $job->frame == 'sonarqube') { - dao::$errors[] = $this->lang->job->mustUseJenkins; + dao::$errors['engine'][] = $this->lang->job->mustUseJenkins; return false; } @@ -348,7 +351,7 @@ class jobModel extends model if(!empty($sonarqubeJob)) { $message = sprintf($this->lang->job->repoExists, $sonarqubeJob[$job->repo]->id . '-' . $sonarqubeJob[$job->repo]->name); - dao::$errors[]['repo'] = $message; + dao::$errors['repo'][] = $message; return false; } } @@ -359,7 +362,7 @@ class jobModel extends model if(!empty($projectList) && $projectList[$job->projectKey] != $id) { $message = sprintf($this->lang->job->projectExists, $projectList[$job->projectKey]); - dao::$errors[]['projectKey'] = $message; + dao::$errors['projectKey'][] = $message; return false; } } @@ -380,13 +383,13 @@ class jobModel extends model if(empty($paramName) and !empty($paramValue)) { - dao::$errors[] = $this->lang->job->inputName; + dao::$errors['paramName'][] = $this->lang->job->inputName; return false; } if(!empty($paramName) and !validater::checkREG($paramName, '/^[A-Za-z_0-9]+$/')) { - dao::$errors[] = $this->lang->job->invalidName; + dao::$errors['paramName'][] = $this->lang->job->invalidName; return false; } @@ -573,7 +576,7 @@ class jobModel extends model $pipeline = json_decode($job->pipeline); $pipelineParams = new stdclass; - $pipelineParams->ref = $pipeline->reference; + $pipelineParams->ref = $pipeline->reference ? $pipeline->reference : 'master'; $customParams = json_decode($job->customParam); $variables = array(); @@ -592,7 +595,11 @@ class jobModel extends model $compile = new stdclass; $pipeline = $this->loadModel('gitlab')->apiCreatePipeline($job->server, $pipeline->project, $pipelineParams); - if(empty($pipeline->id)) $compile->status = 'create_fail'; + if(empty($pipeline->id)) + { + $this->gitlab->apiErrorHandling($pipeline); + $compile->status = 'create_fail'; + } if(!empty($pipeline->id)) { diff --git a/module/job/ui/create.html.php b/module/job/ui/create.html.php index b1c0337804..c511ba8fcc 100644 --- a/module/job/ui/create.html.php +++ b/module/job/ui/create.html.php @@ -29,17 +29,20 @@ formPanel on::click('.add-param', 'addItem'), on::click('.delete-param', 'deleteItem'), on::click('.custom', 'setValueInput'), + set::actionsClass('w-2/3'), formGroup ( set::name('name'), set::label($lang->job->name), set::required(true), + set::width('1/2'), ), formRow ( formGroup ( set::name('engine'), + set::width('1/2'), set::label($lang->job->engine), set::required(true), set::items(array('' => '') + $lang->job->engineList), @@ -61,6 +64,7 @@ formPanel set::required(true), set::name('repo'), set::items($repoPairs), + set::width('1/2'), on::change('changeRepo'), ), formGroup @@ -70,6 +74,7 @@ formPanel set::label($lang->job->branch), set::required(true), set::name('reference'), + set::width('1/2'), set::items(array()), ), ), @@ -77,6 +82,7 @@ formPanel ( set::name('product'), set::label($lang->job->product), + set::width('1/2'), set::items(array()), ), formGroup @@ -84,6 +90,7 @@ formPanel set::name('frame'), set::label($lang->job->frame), set::items(array()), + set::width('1/2'), on::change('changeFrame'), ), formRow @@ -91,6 +98,7 @@ formPanel formGroup ( set::name('triggerType'), + set::width('1/2'), set::label($lang->job->triggerType), set::items($lang->job->triggerTypeList), on::change('changeTriggerType'), @@ -103,6 +111,7 @@ formPanel ( set::id('svnDirBox'), set::name('svnDir[]'), + set::width('1/2'), set::label($lang->job->svnDir), set::control('select'), ), @@ -116,6 +125,7 @@ formPanel set::label($lang->job->sonarqubeServer), set::items(array('' => '') +$sonarqubeServerList), set::value(''), + set::width('1/2'), set::required(true), on::change('changeSonarqubeServer'), ), @@ -128,6 +138,7 @@ formPanel ( set::name('projectKey'), set::label($lang->job->projectKey), + set::width('1/2'), set::items(array()), set::required(true), ), @@ -138,6 +149,7 @@ formPanel formGroup ( set::name('comment'), + set::width('1/2'), set::label($lang->job->comment), set::required(true), ), @@ -164,6 +176,7 @@ formPanel formGroup ( set::label(''), + set::width('1/2'), inputGroup ( $lang->job->atTime, @@ -182,6 +195,7 @@ formPanel ( set::label($lang->job->jkHost), set::required(true), + set::width('1/2'), inputGroup ( picker @@ -200,6 +214,7 @@ formPanel ( setStyle('width', '150px'), set::id('pipelineDropmenu'), + set::popPlacement('top'), set::text($lang->job->selectPipeline), set::url($this->createLink('jenkins', 'ajaxGetJenkinsTasks')), ), @@ -212,13 +227,14 @@ formPanel formGroup ( set::label($lang->job->customParam), + set::width('2/3'), inputGroup ( $lang->job->paramName, input ( setStyle('width', '50%'), - setClass('form-control'), + setClass('form-control paramName'), set::name('paramName[]'), ), $lang->job->paramValue, diff --git a/module/job/ui/edit.html.php b/module/job/ui/edit.html.php index 344fb1f38a..da380c210f 100644 --- a/module/job/ui/edit.html.php +++ b/module/job/ui/edit.html.php @@ -107,12 +107,14 @@ formPanel on::click('.add-param', 'addItem'), on::click('.delete-param', 'deleteItem'), on::click('.custom', 'setValueInput'), + set::actionsClass('w-2/3'), formGroup ( set::name('name'), set::label($lang->job->name), set::value($job->name), set::required(true), + set::width('1/2'), ), formRow ( @@ -136,6 +138,7 @@ formPanel ( set::label($lang->job->repo), set::required(true), + set::width('1/2'), set::name('repo'), set::items($repoPairs), set::value($job->repo), @@ -155,6 +158,7 @@ formPanel formGroup ( set::name('product'), + set::width('1/2'), set::label($lang->job->product), set::items($products), set::value($job->product), @@ -164,6 +168,7 @@ formPanel set::name('frame'), set::label($lang->job->frame), set::items(array()), + set::width('1/2'), on::change('changeFrame'), ), formRow @@ -171,6 +176,7 @@ formPanel formGroup ( set::name('triggerType'), + set::width('1/2'), set::label($lang->job->triggerType), set::items($lang->job->triggerTypeList), set::value($job->triggerType), @@ -183,6 +189,7 @@ formPanel formGroup ( set::name('svnDir[]'), + set::width('1/2'), set::label($lang->job->svnDir), set::control('select'), set::items(!empty($dirs) ? $dirs : array()), @@ -196,6 +203,7 @@ formPanel ( set::name('sonarqubeServer'), set::label($lang->job->sonarqubeServer), + set::width('1/2'), set::items(array('' => '') +$sonarqubeServerList), set::value($job->sonarqubeServer), set::required(true), @@ -205,12 +213,14 @@ formPanel formRow ( set::id('sonarProject'), - setClass('sonarqube hidden'), + setClass('sonarqube', $job->projectKey ? '' : 'hidden'), formGroup ( set::name('projectKey'), + set::width('1/2'), set::label($lang->job->projectKey), - set::items(array()), + set::items(!empty($sonarqubeProjectPairs) ? $sonarqubeProjectPairs : array()), + set::value($job->projectKey), set::required(true), ), ), @@ -222,6 +232,7 @@ formPanel set::name('comment'), set::label($lang->job->comment), set::value($job->comment), + set::width('1/2'), set::required(true), ), h::span @@ -240,6 +251,7 @@ formPanel set::control('checkListInline'), set::items($lang->datepicker->dayNames), set::value($job->atDay), + set::width('1/2'), ), ), formRow @@ -248,6 +260,7 @@ formPanel formGroup ( set::label(''), + set::width('1/2'), inputGroup ( $lang->job->atTime, @@ -267,6 +280,7 @@ formPanel ( set::label($lang->job->jkHost), set::required(true), + set::width('1/2'), inputGroup ( picker @@ -282,7 +296,7 @@ formPanel ( set::name('jkTask'), set::type('hidden'), - set::value($job->rawPipeline), + set::value(zget($job, 'rawPipeline', $job->pipeline)), ), dropmenu ( @@ -301,13 +315,14 @@ formPanel ( set::label($lang->job->customParam), $customParam, + set::width('2/3'), inputGroup ( $lang->job->paramName, input ( setStyle('width', '50%'), - setClass('form-control'), + setClass('form-control paramName'), set::name('paramName[]'), ), $lang->job->paramValue, @@ -361,4 +376,3 @@ formPanel ); render(); - diff --git a/module/job/ui/view.html.php b/module/job/ui/view.html.php index 8498f761a6..d8728d2f18 100644 --- a/module/job/ui/view.html.php +++ b/module/job/ui/view.html.php @@ -17,7 +17,8 @@ detailHeader to::title( entityLabel( set(array('entityID' => $job->id, 'level' => 1, 'text' => $job->name)) - ) + ), + $job->deleted ? span(setClass('label danger'), $lang->product->deleted) : null, ), ); @@ -98,17 +99,12 @@ detailBody item ( set::name($lang->compile->status), - $status + !empty($status) ? $status : '' ), item ( set::name($lang->compile->time), - $this->job->getTriggerConfig($job) - ), - item - ( - set::name($lang->job->triggerType), - $time + !empty($time) ? $time : '' ), item ( @@ -122,11 +118,7 @@ detailBody set::key('job-result'), set::title($lang->compile->result), set::active(true), - tableData - ( - //TODO; - div('等待 testtask/view/unitgroup.html.php 重构完成,直接替换') - ) + div(setID('jobCases'), setData('task', $compile->testtask)) ) : '', $hasLog ? tabPane ( diff --git a/module/ops/css/common.ui.css b/module/ops/css/common.ui.css index e613b6ad7c..7b5a7f97bc 100644 --- a/module/ops/css/common.ui.css +++ b/module/ops/css/common.ui.css @@ -5,4 +5,13 @@ .osOpsList>a {display: block; padding: 2px 10px 2px 5px; overflow: hidden; line-height: 20px; text-overflow: unset; white-space: nowrap; border-radius: 4px; color: #313c52; margin-top: 5px;} .osOpsList>a.active {color: #2e7fff; background-color: #e6f0ff;} .osOpsList>a.active:hover, .osOpsList>a:hover {color: #fff; background-color: #2e7fff;} -.ops-ml-0 {margin-left: 0!important;} \ No newline at end of file +.ops-ml-0 {margin-left: 0!important;} + +#mainNavbar>.container {padding-top: 8px} +#mainNavbar .nav {justify-content: left; position: relative; left: 0px; top: -4px;} +@media (min-width: 1400px){#mainNavbar .nav {left: -12px;}} +#mainNavbar {background: none;} +#mainNavbar .nav-item>a {padding-right: 0.5rem} +#mainNavbar .nav-item>.active {color: unset; font-weight: 700;} +#mainNavbar .nav-item>a.active:after {position: absolute; content: ''; border-bottom: 2px solid #2e7fff; inset: 0 6px 0 15px;} +.panel-form{max-width: inherit;} \ No newline at end of file diff --git a/module/ops/js/common.ui.js b/module/ops/js/common.ui.js index 5c4b05577e..9ebde46c5a 100644 --- a/module/ops/js/common.ui.js +++ b/module/ops/js/common.ui.js @@ -1,10 +1,9 @@ -window.removeItem = function() +$('#opsForm').on('click', '.icon-plus', function(e) +{ + $(this).parent().parent().after(template); +}); + +$('#opsForm').on('click', '.icon-close', function(e) { $(this).parent().parent().remove(); -} - -window.addItem = function(e) -{ - - $(this).parent().parent().after(template); -} \ No newline at end of file +}); \ No newline at end of file diff --git a/module/ops/ui/setting.html.php b/module/ops/ui/setting.html.php index 0a24eb03de..fddee5e22e 100644 --- a/module/ops/ui/setting.html.php +++ b/module/ops/ui/setting.html.php @@ -22,8 +22,8 @@ $template = <<
- - + +
EOT; @@ -58,8 +58,8 @@ foreach($lang->$module->$fieldList as $key => $value) div ( setClass('ops-actions'), - icon('plus', setClass('ml-2'), on::click('addItem')), - icon('close', setClass('ml-2'), on::click('removeItem')), + icon('plus', setClass('ml-2')), + icon('close', setClass('ml-2')), ) ); } @@ -80,10 +80,11 @@ $formRows[] = formRow $hasSideBar = !empty($lang->{$module}->osNameList) && array_key_exists($field, $lang->{$module}->osNameList); $actions = array('submit'); -if(common::hasPriv('custom', 'restore')) $actions[] = array('class' => 'ajax-submit', 'text' => $lang->custom->restore, 'data-confirm' => $lang->custom->confirmRestore, 'url' => $this->createLink('custom', 'restore', "module=$module&field=$fieldList")); +if(common::hasPriv('custom', 'restore')) $actions[] = array('class' => 'ajax-submit', 'text' => $lang->custom->restore, 'data-confirm' => $lang->custom->confirmRestore, 'url' => $this->createLink('custom', 'restore', "module=$module&field=$fieldList&confirm=yes")); formPanel ( setID('opsForm'), + set::size('md'), $hasSideBar ? setClass('ops-ml-0') : null, set::title($lang->$module->common . ' > ' . $lang->$module->$field), set::actions($actions), diff --git a/module/ops/ui/stage.html.php b/module/ops/ui/stage.html.php new file mode 100644 index 0000000000..2af7a2a0b6 --- /dev/null +++ b/module/ops/ui/stage.html.php @@ -0,0 +1,16 @@ + + * @package ops + * @link https://www.zentao.net + */ + +namespace zin; + +include 'setting.html.php'; + diff --git a/module/repo/config.php b/module/repo/config.php index 28e114a12c..3276f20635 100644 --- a/module/repo/config.php +++ b/module/repo/config.php @@ -1,5 +1,6 @@ loadLang('repo'); $config->program = new stdclass(); $config->program->suffix['c'] = "cpp"; @@ -30,6 +31,19 @@ $config->repo->images = '|png|gif|jpg|ico|jpeg|bmp|'; $config->repo->binary = '|pdf|'; $config->repo->synced = ''; +$config->repo->repoSyncLog = new stdclass(); +$config->repo->repoSyncLog->one = 1; +$config->repo->repoSyncLog->done = array('done', '完成'); +$config->repo->repoSyncLog->total = array('Total', '总数'); +$config->repo->repoSyncLog->fatal = array('fatal', '致命'); +$config->repo->repoSyncLog->error = array('error', '错误'); +$config->repo->repoSyncLog->failed = array('failed', '失败'); +$config->repo->repoSyncLog->finish = 'finish'; +$config->repo->repoSyncLog->emptyRepo = array('empty repository', '空仓库'); +$config->repo->repoSyncLog->finishCount = array('Counting objects: 100%'); +$config->repo->repoSyncLog->logFilePrefix = '/log/clone.progress.'; +$config->repo->repoSyncLog->finishCompress = array('Compressing objects: 100%'); + $config->repo->editor = new stdclass(); $config->repo->editor->create = array('id' => 'desc', 'tools' => 'simpleTools'); $config->repo->editor->edit = array('id' => 'desc', 'tools' => 'simpleTools'); diff --git a/module/repo/config/dtable.php b/module/repo/config/dtable.php index 40b9e1c88d..0a1a8b2df1 100644 --- a/module/repo/config/dtable.php +++ b/module/repo/config/dtable.php @@ -15,12 +15,14 @@ $config->repo->dtable->fieldList['product']['title'] = $lang->repo->product; $config->repo->dtable->fieldList['product']['type'] = 'text'; $config->repo->dtable->fieldList['product']['sortType'] = false; $config->repo->dtable->fieldList['product']['width'] = '136'; +$config->repo->dtable->fieldList['product']['hint'] = true; $config->repo->dtable->fieldList['project']['name'] = 'projectNames'; $config->repo->dtable->fieldList['project']['title'] = $lang->repo->projects; $config->repo->dtable->fieldList['project']['type'] = 'text'; $config->repo->dtable->fieldList['project']['sortType'] = false; $config->repo->dtable->fieldList['project']['width'] = '136'; +$config->repo->dtable->fieldList['project']['hint'] = true; $config->repo->dtable->fieldList['scm']['name'] = 'SCM'; $config->repo->dtable->fieldList['scm']['title'] = $lang->repo->type; @@ -32,6 +34,7 @@ $config->repo->dtable->fieldList['scm']['group'] = 1; $config->repo->dtable->fieldList['path']['name'] = 'codePath'; $config->repo->dtable->fieldList['path']['title'] = $lang->repo->path; $config->repo->dtable->fieldList['path']['type'] = 'text'; +$config->repo->dtable->fieldList['path']['hint'] = true; $config->repo->dtable->fieldList['path']['width'] = '260'; $config->repo->dtable->fieldList['path']['group'] = 1; @@ -55,17 +58,17 @@ $config->repo->dtable->fieldList['actions']['list']['edit']['hint'] = $lang->rep $config->repo->dtable->fieldList['actions']['list']['execJob']['icon'] = 'sonarqube'; $config->repo->dtable->fieldList['actions']['list']['execJob']['hint'] = $lang->sonarqube->execJob; -$config->repo->dtable->fieldList['actions']['list']['execJob']['url'] = helper::createLink('sonarqube', 'execJob', "jobID={job}"); -$config->repo->dtable->fieldList['actions']['list']['execJob']['data-toggle'] = 'modal'; +$config->repo->dtable->fieldList['actions']['list']['execJob']['url'] = array('module' => 'sonarqube', 'method' => 'execJob', 'params' => "jobID={job}"); +$config->repo->dtable->fieldList['actions']['list']['execJob']['className'] = 'ajax-submit'; $config->repo->dtable->fieldList['actions']['list']['reportView']['icon'] = 'audit'; $config->repo->dtable->fieldList['actions']['list']['reportView']['hint'] = $lang->sonarqube->reportView; -$config->repo->dtable->fieldList['actions']['list']['reportView']['url'] = helper::createLink('sonarqube', 'reportView', "jobID={job}"); -$config->repo->dtable->fieldList['actions']['list']['reportView']['data-toggle'] = 'modal'; +$config->repo->dtable->fieldList['actions']['list']['reportView']['url'] = array('module' => 'sonarqube', 'method' => 'reportView', 'params' => "jobID={job}"); -$config->repo->dtable->fieldList['actions']['list']['delete']['icon'] = 'trash'; -$config->repo->dtable->fieldList['actions']['list']['delete']['hint'] = $lang->repo->delete; -$config->repo->dtable->fieldList['actions']['list']['delete']['data-toggle'] = 'modal'; +$config->repo->dtable->fieldList['actions']['list']['delete']['icon'] = 'trash'; +$config->repo->dtable->fieldList['actions']['list']['delete']['hint'] = $lang->repo->delete; +$config->repo->dtable->fieldList['actions']['list']['delete']['data-confirm'] = $this->lang->repo->notice->delete; +$config->repo->dtable->fieldList['actions']['list']['delete']['className'] = 'ajax-submit'; $config->repo->repoDtable = new stdclass(); @@ -208,7 +211,7 @@ $config->repo->taskDtable->fieldList['finishedBy']['sortType'] = true; $config->repo->taskDtable->fieldList['finishedBy']['show'] = true; $config->repo->taskDtable->fieldList['finishedBy']['group'] = 4; -$config->repo->taskDtable->fieldList['assignedTo']['type'] = 'assign'; +$config->repo->taskDtable->fieldList['assignedTo']['type'] = 'user'; $config->repo->taskDtable->fieldList['assignedTo']['sortType'] = true; $config->repo->taskDtable->fieldList['assignedTo']['show'] = true; $config->repo->taskDtable->fieldList['assignedTo']['group'] = 3; @@ -237,6 +240,7 @@ $config->repo->reviewDtable->fieldList['fileLocation']['width'] = '300'; $config->repo->reviewDtable->fieldList['revisionA']['name'] = 'revisionA'; $config->repo->reviewDtable->fieldList['revisionA']['width'] = '100'; +$config->repo->reviewDtable->fieldList['revisionA']['hint'] = true; $config->repo->reviewDtable->fieldList['type']['title'] = $lang->repo->type; $config->repo->reviewDtable->fieldList['type']['name'] = 'repoType'; diff --git a/module/repo/config/form.php b/module/repo/config/form.php index abda3991f3..b0667a2e48 100644 --- a/module/repo/config/form.php +++ b/module/repo/config/form.php @@ -5,15 +5,15 @@ $config->repo->form = new stdclass(); $config->repo->form->create = array(); $config->repo->form->create['product'] = array('required' => true, 'type' => 'array'); -$config->repo->form->create['projects'] = array('required' => false, 'type' => 'array', 'default' => array()); +$config->repo->form->create['projects'] = array('required' => false, 'type' => 'array', 'default' => array()); $config->repo->form->create['SCM'] = array('required' => true, 'type' => 'string', 'filter' => 'trim'); -$config->repo->form->create['serviceHost'] = array('required' => true, 'type' => 'int'); -$config->repo->form->create['serviceProject'] = array('required' => false, 'type' => 'string', 'default' => ''); +$config->repo->form->create['serviceHost'] = array('required' => false, 'type' => 'int'); +$config->repo->form->create['serviceProject'] = array('required' => false, 'type' => 'string', 'default' => ''); $config->repo->form->create['name'] = array('required' => true, 'type' => 'string', 'filter' => 'trim'); -$config->repo->form->create['path'] = array('required' => false, 'type' => 'string', 'default' => ''); +$config->repo->form->create['path'] = array('required' => false, 'type' => 'string', 'default' => ''); $config->repo->form->create['encoding'] = array('required' => true, 'type' => 'string'); -$config->repo->form->create['client'] = array('required' => false, 'type' => 'string', 'default' => ''); -$config->repo->form->create['account'] = array('required' => false, 'type' => 'string', 'default' => ''); -$config->repo->form->create['password'] = array('required' => false, 'type' => 'string', 'default' => ''); -$config->repo->form->create['encrypt'] = array('required' => false, 'type' => 'string', 'default' => ''); -$config->repo->form->create['desc'] = array('required' => false, 'type' => 'string', 'default' => ''); +$config->repo->form->create['client'] = array('required' => false, 'type' => 'string', 'default' => ''); +$config->repo->form->create['account'] = array('required' => false, 'type' => 'string', 'default' => ''); +$config->repo->form->create['password'] = array('required' => false, 'type' => 'string', 'default' => ''); +$config->repo->form->create['encrypt'] = array('required' => false, 'type' => 'string', 'default' => ''); +$config->repo->form->create['desc'] = array('required' => false, 'type' => 'string', 'default' => ''); diff --git a/module/repo/control.php b/module/repo/control.php index 40635e9d5e..38ffa4ac37 100644 --- a/module/repo/control.php +++ b/module/repo/control.php @@ -50,11 +50,18 @@ class repo extends control if($tab == 'project') { + $project = $this->loadModel('project')->getByID($objectID); + if($project->model === 'kanban') return print($this->locate($this->createLink('project', 'index', "projectID=$objectID"))); + $this->loadModel('project')->setMenu($objectID); $this->view->projectID = $objectID; } elseif($tab == 'execution') { + $execution = $this->loadModel('execution')->getByID($objectID); + $features = $this->execution->getExecutionFeatures($execution); + if(!$features['devops']) return print($this->locate($this->createLink('execution', 'task', "executionID=$objectID"))); + $this->loadModel('execution')->setMenu($objectID); $this->view->executionID = $objectID; } @@ -63,7 +70,7 @@ class repo extends control $this->repo->setMenu($this->repos, $repoID); } - if(empty($this->repos) and $this->methodName != 'create') return print($this->locate($this->repo->createLink('create', "objectID=$objectID"))); + if(empty($this->repos) and !in_array($this->methodName, array('create', 'setrules'))) return print($this->locate($this->repo->createLink('create', "objectID=$objectID"))); } /** @@ -89,7 +96,7 @@ class repo extends control $recTotal = count($repoList); $pager = new pager($recTotal, $recPerPage, $pageID); $repoList = array_chunk($repoList, $pager->recPerPage); - $repoList = empty($repoList) ? $repoList : $repoList[$pageID - 1]; + $repoList = empty($repoList) ? array() : $repoList[$pageID - 1]; /* Get success jobs of sonarqube.*/ $jobIDList = array(); @@ -99,19 +106,18 @@ class repo extends control } $successJobs = $this->loadModel('compile')->getSuccessJobs($jobIDList); - $products = $this->loadModel('product')->getPairs('', 0, '', 'all'); + $products = $this->loadModel('product')->getPairs('all', 0, '', 'all'); $projects = $this->loadModel('project')->getPairs(); session_start(); $this->config->repo->search['params']['product']['values'] = $products; $this->config->repo->search['params']['projects']['values'] = $projects; - $this->config->repo->search['actionURL'] = $this->createLink('repo', 'maintain', "objectID={$objectID}&orderBy={$orderBy}&recPerPage={$recPerPage}&pageID={$pageID}&type=bySearch¶m=myQueryID"); - $this->config->repo->search['queryID'] = 0; + $this->config->repo->search['actionURL'] = $this->createLink('repo', 'maintain', "objectID={$objectID}&orderBy={$orderBy}&recPerPage={$recPerPage}&pageID={$pageID}&type=bySearch¶m=myQueryID"); + $this->config->repo->search['queryID'] = $param; $this->config->repo->search['onMenuBar'] = 'yes'; $this->loadModel('search')->setSearchParams($this->config->repo->search); - $this->view->title = $this->lang->repo->common . $this->lang->colon . $this->lang->repo->browse; - + $this->view->title = $this->lang->repo->common . $this->lang->colon . $this->lang->repo->browse; $this->view->type = $type; $this->view->orderBy = $orderBy; $this->view->objectID = $objectID; @@ -150,8 +156,8 @@ class repo extends control { /* Add webhook. */ $repo = $this->repo->getByID($repoID); - $this->loadModel('gitlab')->addPushWebhook($repo); - $this->gitlab->updateCodePath($repo->serviceHost, $repo->serviceProject, $repo->id); + $this->loadModel('gitlab')->updateCodePath($repo->serviceHost, $repo->serviceProject, $repo->id); + $this->repo->updateCommitDate($repoID); } $this->loadModel('action')->create('repo', $repoID, 'created'); @@ -161,6 +167,7 @@ class repo extends control return $this->send(array('result' => 'success', 'message' => $this->lang->saveSuccess, 'locate' => $link)); } + $this->commonAction(0, $objectID); $this->repoZen->buildCreateForm($objectID); } @@ -214,7 +221,7 @@ class repo extends control $products = $this->loadModel('product')->getPairs('', 0, '', 'all'); $linkedProducts = $this->loadModel('product')->getByIdList(explode(',', $repo->product)); - $linkedProductPairs = array_combine(array_keys($linkedProducts), array_column($linkedProducts, 'name')); + $linkedProductPairs = array_combine(array_keys($linkedProducts), helper::arrayColumn($linkedProducts, 'name')); $products = $products + $linkedProductPairs; $this->view->title = $this->lang->repo->common . $this->lang->colon . $this->lang->repo->edit; @@ -254,11 +261,12 @@ class repo extends control $jobs = $this->dao->select('*')->from(TABLE_JOB)->where('repo')->eq($repoID)->andWhere('deleted')->eq('0')->fetchAll(); if($jobs) $error .= ($error ? '\n' : '') . $this->lang->repo->error->linkedJob; - if($error) return print(js::alert($error)); + if($error) return $this->send(array('result' => 'fail', 'message' => $error)); - $this->repo->delete(TABLE_REPO, $repoID); + $this->dao->delete()->from(TABLE_REPO)->where('id')->eq($repoID)->exec(); if(dao::isError()) return print(js::error(dao::getError())); - return print(js::reload('parent')); + $this->loadModel('action')->create('repo', $repoID, 'deleted', ''); + return $this->send(array('result' => 'success', 'load' => true)); } /** @@ -386,11 +394,13 @@ class repo extends control */ public function monaco($repoID, $objectID = 0, $entry = '', $revision = 'HEAD', $showBug = 'false', $encoding = '') { + $this->commonAction($repoID, $objectID); + $file = $entry; $repo = $this->repo->getByID($repoID); $entry = $this->repo->decodePath($entry); $entry = urldecode($entry); - $pathInfo = pathinfo($entry); + $pathInfo = helper::mbPathinfo($entry); $this->view->title = $this->lang->repo->common . $this->lang->colon . $this->lang->repo->view; $this->view->dropMenus = $this->repoZen->getBranchAndTagItems($repo, $this->cookie->repoBranch); @@ -437,7 +447,7 @@ class repo extends control $this->commonAction($repoID, $objectID); $repo = $this->repo->getByID($repoID); session_start(); - $this->session->set('storyList', $this->app->getURI(true), 'product'); + $this->session->set('storyList', inlink('view', "repoID=$repoID&objectID=$objectID&entry=$entry&revision=$revision&showBug=$showBug&encoding=$encoding"), 'product'); session_write_close(); if($browser['name'] != 'ie') return print($this->fetch('repo', 'monaco', "repoID=$repoID&objectID=$objectID&entry=$entry&revision=$revision&showBug=$showBug&encoding=$encoding")); @@ -534,7 +544,7 @@ class repo extends control * @access public * @return void */ - public function browse($repoID = 0, $branchID = '', $objectID = 0, $path = '', $revision = 'HEAD', $refresh = 0, $branchOrTag = 'branch', $type = 'dir', $recTotal = 0, $recPerPage = 10, $pageID = 1) + public function browse($repoID = 0, $branchID = '', $objectID = 0, $path = '', $revision = 'HEAD', $refresh = 0, $branchOrTag = 'branch', $type = 'dir', $recTotal = 0, $recPerPage = 20, $pageID = 1) { $repoID = $this->repo->saveState($repoID, $objectID); $originBranchID = $branchID; @@ -561,14 +571,16 @@ class repo extends control } if(!$repo->synced) $this->locate($this->repo->createLink('showSyncCommit', "repoID=$repoID&objectID=$objectID")); + if($repo->SCM == 'Gitlab') list($branchInfo, $tagInfo) = $this->repoZen->getBrowseInfo($repo); + /* Set branch or tag for git. */ $branches = $tags = $branchesAndTags = array(); if(in_array($repo->SCM, $this->config->repo->gitTypeList)) { $scm = $this->app->loadClass('scm'); $scm->setEngine($repo); - $branches = $scm->branch(); - $initTags = $scm->tags(''); + $branches = isset($branchInfo) && $branchInfo !== false ? $branchInfo : $scm->branch(); + $initTags = isset($tagInfo) && $tagInfo !== false ? $tagInfo : $scm->tags(''); foreach($initTags as $tag) $tags[$tag] = $tag; $branchesAndTags = $branches + $tags; @@ -576,7 +588,7 @@ class repo extends control if($branchID) $this->repo->setRepoBranch($branchID); if(!isset($branchesAndTags[$branchID])) { - $branchID = key($branches); + $branchID = (string)key($branches); $this->repo->setRepoBranch($branchID); } } @@ -621,6 +633,8 @@ class repo extends control $item->revision = $repo->SCM != 'Subversion' ? substr($item->revision, 0, 10) : $item->revision; } + if($path == '') $this->repoZen->updateLastCommit($repo, $lastRevision); + /* Get files info. */ $infos = $this->repoZen->getFilesInfo($repo, $path, $branchID, $refresh, $revision, $lastRevision); @@ -943,7 +957,7 @@ class repo extends control $this->view->suffix = $suffix; $this->view->file = $file; $this->view->repoID = $repoID; - $this->view->branchID = $this->cookie->repoBranch; + $this->view->branchID = (string) $this->cookie->repoBranch; $this->view->objectID = $objectID; $this->view->repo = $repo; $this->view->encoding = str_replace('-', '_', $encoding); @@ -1052,6 +1066,7 @@ class repo extends control $latestInDB = $this->repo->getLatestCommit($repoID); $this->view->version = $latestInDB ? (int)$latestInDB->commit : 1; $this->view->repoID = $repoID; + $this->view->repo = $this->repo->getByID($repoID); $this->view->objectID = $objectID; $this->view->branch = $branch; $this->view->browseLink = $this->repo->createLink('browse', "repoID=" . ($this->app->tab == 'devops' ? $repoID : '') . "&branchID=" . helper::safe64Encode(base64_encode($branch)) . "&objectID=$objectID", '', false) . "#app={$this->app->tab}"; @@ -1127,7 +1142,7 @@ class repo extends control $linkedStories = $this->repo->getRelationByCommit($repoID, $revision, 'story'); if($browseType == 'bySearch') { - $allStories = $this->story->getBySearch($product->id, 0, $queryID, 'id', '', 'story', array_keys($linkedStories), $pager); + $allStories = $this->story->getBySearch($product->id, 0, $queryID, $orderBy, '', 'story', array_keys($linkedStories), $pager); } else { @@ -1193,8 +1208,8 @@ class repo extends control $this->config->bug->search['params']['plan']['values'] = $this->loadModel('productplan')->getForProducts(array($product->id => $product->id)); $this->config->bug->search['params']['module']['values'] = $modules; $this->config->bug->search['params']['execution']['values'] = $this->product->getExecutionPairsByProduct($product->id); - $this->config->bug->search['params']['openedBuild']['values'] = $this->loadModel('build')->getBuildPairs(array($product->id), 'all', ''); - $this->config->bug->search['params']['resolvedBuild']['values'] = $this->loadModel('build')->getBuildPairs(array($product->id), 'all', ''); + $this->config->bug->search['params']['openedBuild']['values'] = $this->loadModel('build')->getBuildPairs($product->id, 'all', ''); + $this->config->bug->search['params']['resolvedBuild']['values'] = $this->loadModel('build')->getBuildPairs($product->id, 'all', ''); unset($this->config->bug->search['fields']['product']); if($product->type == 'normal') @@ -1214,11 +1229,11 @@ class repo extends control $linkedBugs = $this->repo->getRelationByCommit($repoID, $revision, 'bug'); if($browseType == 'bySearch') { - $allBugs = $this->bug->getBySearch($product->id, 0, $queryID, 'id_desc', array_keys($linkedBugs), $pager); + $allBugs = $this->bug->getBySearch($product->id, 0, $queryID, $orderBy, array_keys($linkedBugs), $pager); } else { - $allBugs = $this->bug->getActiveBugs($product->id, 0, '0', array_keys($linkedBugs), $pager); + $allBugs = $this->bug->getActiveBugs($product->id, 0, '0', array_keys($linkedBugs), $pager, $orderBy); } foreach($allBugs as $bug) $bug->statusText = $this->processStatus('bug', $bug); @@ -1378,10 +1393,12 @@ class repo extends control $gitlabList = $this->loadModel('gitlab')->getList(); $gitlab = empty($server) ? array_shift($gitlabList) : $this->gitlab->getById($server); - $repoList = $this->repoZen->getGitlabNotExistRepos($gitlab); + $repoList = $gitlab ? $this->repoZen->getGitlabNotExistRepos($gitlab) : array(); $products = $this->loadModel('product')->getPairs('', 0, '', 'all'); + + $this->view->title = $this->lang->repo->common . $this->lang->colon . $this->lang->repo->importAction; $this->view->gitlabPairs = $this->gitlab->getPairs(); $this->view->products = $products; $this->view->projects = $this->product->getProjectPairsByProductIDList(array_keys($products)); @@ -1403,43 +1420,44 @@ class repo extends control set_time_limit(0); $repo = $this->repo->getByID($repoID); if(empty($repo)) return; - if($repo->synced) return print('finish'); + if($repo->synced) return print($this->config->repo->repoSyncLog->finish); if(in_array($repo->SCM, array('Gitea', 'Gogs'))) { - $logFile = realPath($this->app->getTmpRoot() . "/log/clone.progress." . strtolower($repo->SCM) . ".{$repo->name}.log"); + $logFile = realPath($this->app->getTmpRoot() . $this->config->repo->repoSyncLog->logFilePrefix . strtolower($repo->SCM) . ".{$repo->name}.log"); if($logFile) { $content = file($logFile); - $lastLine = $content[count($content) - 1]; - - if(strpos($lastLine, 'done') === false) + foreach($content as $line) { - if(strpos($lastLine, 'empty repository') !== false) + if($this->repo->strposAry($line, $this->config->repo->repoSyncLog->fatal) !== false) return print($line); + if($this->repo->strposAry($line, $this->config->repo->repoSyncLog->failed) !== false) return print($line); + } + + $lastLine = $content[count($content) - 1]; + if($this->repo->strposAry($lastLine, $this->config->repo->repoSyncLog->done) === false) + { + if($this->repo->strposAry($lastLine, $this->config->repo->repoSyncLog->emptyRepo) !== false) { @unlink($logFile); } - elseif(strpos($lastLine, 'Total') !== false) + elseif($this->repo->strposAry($lastLine, $this->config->repo->repoSyncLog->total) !== false) { $logContent = file_get_contents($logFile); - if(strpos($logContent, 'Counting objects: 100%') !== false and strpos($logContent, 'Compressing objects: 100%') !== false) + if($this->repo->strposAry($logContent, $this->config->repo->repoSyncLog->finishCount) !== false and $this->repo->strposAry($logContent, $this->config->repo->repoSyncLog->finishCompress) !== false) { @unlink($logFile); } else { - return print(1); + return print($this->config->repo->repoSyncLog->one); } } else { - return print(1); + return print($this->config->repo->repoSyncLog->one); } } - elseif(strpos($lastLine, 'fatal') !== false) - { - return print('finish'); - } else { @unlink($logFile); @@ -1477,50 +1495,55 @@ class repo extends control } } - $latestInDB = $this->dao->select('t1.*')->from(TABLE_REPOHISTORY)->alias('t1') - ->leftJoin(TABLE_REPOBRANCH)->alias('t2')->on('t1.id=t2.revision') - ->where('t1.repo')->eq($repoID) - ->beginIF($repo->SCM == 'Git' and $this->cookie->repoBranch)->andWhere('t2.branch')->eq($this->cookie->repoBranch)->fi() - ->beginIF($repo->SCM == 'Gitlab' and $this->cookie->repoBranch)->andWhere('t2.branch')->eq($this->cookie->repoBranch)->fi() - ->orderBy('t1.time') - ->limit(1) - ->fetch(); + $logs = array(); + if($repo->SCM != 'Gitlab') + { + $latestInDB = $this->dao->select('t1.*')->from(TABLE_REPOHISTORY)->alias('t1') + ->leftJoin(TABLE_REPOBRANCH)->alias('t2')->on('t1.id=t2.revision') + ->where('t1.repo')->eq($repoID) + ->beginIF($repo->SCM == 'Git' and $this->cookie->repoBranch)->andWhere('t2.branch')->eq($this->cookie->repoBranch)->fi() + ->beginIF($repo->SCM == 'Gitlab' and $this->cookie->repoBranch)->andWhere('t2.branch')->eq($this->cookie->repoBranch)->fi() + ->orderBy('t1.time') + ->limit(1) + ->fetch(); - $version = empty($latestInDB) ? 1 : $latestInDB->commit + 1; - $logs = array(); - $revision = 'HEAD'; - if($version != 1) $revision = in_array($repo->SCM, array('Git', 'Gitea', 'Gogs')) ? $latestInDB->commit : $latestInDB->revision; - if($type == 'batch') - { - $logs = $this->scm->getCommits($revision, $this->config->repo->batchNum, $branchID); - } - else - { - $logs = $this->scm->getCommits($revision, 0, $branchID); + $version = empty($latestInDB) ? 1 : $latestInDB->commit + 1; + $revision = $version == 1 ? 'HEAD' : (in_array($repo->SCM, array('Git', 'Gitea', 'Gogs')) ? $latestInDB->commit : $latestInDB->revision); + if($type == 'batch') + { + $logs = $this->scm->getCommits($revision, $this->config->repo->batchNum, $branchID); + } + else + { + $logs = $this->scm->getCommits($revision, 0, $branchID); + } } $commitCount = $this->repo->saveCommit($repoID, $logs, $version, $branchID); - if(empty($commitCount) && !$repo->synced) + if(empty($commitCount)) { - if(in_array($repo->SCM, $this->config->repo->gitTypeList)) + if(!$repo->synced) { - if($branchID) $this->repo->saveExistCommits4Branch($repo->id, $branchID); + if(in_array($repo->SCM, $this->config->repo->gitTypeList)) + { + if($branchID) $this->repo->saveExistCommits4Branch($repo->id, $branchID); - $branchID = reset($branches); - helper::setcookie("syncBranch", $branchID, 0, $this->config->webRoot, '', $this->config->cookieSecure, true); + $branchID = reset($branches); + setcookie("syncBranch", $branchID, 0, $this->config->webRoot, '', $this->config->cookieSecure, true); - if($branchID) $this->repo->fixCommit($repoID); - } + if($branchID) $this->repo->fixCommit($repoID); + } - if(empty($branchID)) - { - $this->repo->markSynced($repoID); - return print('finish'); + if(empty($branchID)) + { + $this->repo->markSynced($repoID); + return print($this->config->repo->repoSyncLog->finish); + } } } $this->dao->update(TABLE_REPO)->set('commits=commits + ' . $commitCount)->where('id')->eq($repoID)->exec(); - echo $type == 'batch' ? $commitCount : 'finish'; + echo $type == 'batch' ? $commitCount : $this->config->repo->repoSyncLog->finish; } /** @@ -1657,13 +1680,16 @@ class repo extends control $method = 'review'; } + $params = ''; + if($projectID && $method == 'browse') $params = "&branchID=&objectID=$projectID"; + /* Get repo group by type. */ $repoType = $module == 'mr' ? 'git' : ''; $repoGroup = $this->repo->getRepoGroup('project', $projectID, $repoType); $this->view->repoID = $repoID; $this->view->repoGroup = $repoGroup; - $this->view->link = $this->createLink($module, $method, "repoID=%s"); + $this->view->link = $this->createLink($module, $method, "repoID=%s" . $params) . ($projectID ? '#app=project' : ''); $this->display(); } @@ -1687,7 +1713,7 @@ class repo extends control $products = $postData->objectID ? $this->loadModel('product')->getProductPairsByProject($objectID) : $this->loadModel('product')->getPairs(); $linkedProducts = $this->loadModel('product')->getByIdList($postData->products); - $linkedProductPairs = array_combine(array_keys($linkedProducts), array_column($linkedProducts, 'name')); + $linkedProductPairs = array_combine(array_keys($linkedProducts), helper::arrayColumn($linkedProducts, 'name')); $products = $products + $linkedProductPairs; return print (html::select('product[]', $products, $selectedProducts, "class='form-control chosen' multiple")); diff --git a/module/repo/css/ajaxgeteditorcontent.ui.css b/module/repo/css/ajaxgeteditorcontent.ui.css index 5aa4509a63..c31bb680c1 100644 --- a/module/repo/css/ajaxgeteditorcontent.ui.css +++ b/module/repo/css/ajaxgeteditorcontent.ui.css @@ -38,7 +38,7 @@ #related .nav-item {gap: 0;} .nav-tabs>li>a.active {font-weight: 700;color: #313c52!important} #relationTabs .tab-content{overflow: auto;} -.repoCode .view-line-icon {position: absolute; right: 50px; top: 2px; font-weight: bold; cursor: pointer;} +.repoCode .view-line-icon {position: absolute; right: 50px; top: 2px; font-weight: bold; cursor: pointer;background-color: white;padding: 1px 8px} .repoCode .view-line-icon.add-bug {left: 5px;} .repoCode .view-line-icon.add-bug .icon {font-weight: bold;} .repoCode .view-line-icon .bug-count {vertical-align: middle; padding-left: 5px;} diff --git a/module/repo/css/blame.ui.css b/module/repo/css/blame.ui.css index 7a1a2b4fd8..1ef722ebce 100644 --- a/module/repo/css/blame.ui.css +++ b/module/repo/css/blame.ui.css @@ -1 +1,2 @@ #featureBar .nav .btn>.icon {color: #ffffff;} +.dtable-cell-html {overflow: auto;} \ No newline at end of file diff --git a/module/repo/css/browse.ui.css b/module/repo/css/browse.ui.css index 6d95914336..4f57d8aa70 100644 --- a/module/repo/css/browse.ui.css +++ b/module/repo/css/browse.ui.css @@ -1,8 +1,11 @@ .repo-downloadCode{margin: 8px 10px 8px 0;} .repo-select{margin-right: 10px;} +.repo-select>div {display: inline-block;} .repo-comment{width: 100%; display: block; word-break: keep-all; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;} .dtable-cell-html{width: 100%;} .container .sidebar{width: 550px;} #actionBar .dropdown-menu {margin-top: 10px; padding: 10px; display: none;} #actionBar .dropdown-menu.show{display: block;} -#actionBar>.dropdown {position: absolute!important;} \ No newline at end of file +#actionBar>.dropdown {position: absolute!important;} + +#repoDropmenu .icon-angle-right {display: none;} diff --git a/module/repo/css/common.css b/module/repo/css/common.css index b91fe13698..5c62e69f37 100644 --- a/module/repo/css/common.css +++ b/module/repo/css/common.css @@ -251,4 +251,4 @@ h3 {font-size: 16px;} li.selected .doc-title .icon, li.selected .doc-title a {color: #438EFF !important;} .btn-left, .btn-right {display: none;} -.m-repo-ajaxgetrelationinfo{padding-bottom: 0;} +.m-repo-ajaxgetrelationinfo{padding-bottom: 0;} \ No newline at end of file diff --git a/module/repo/css/common.ui.css b/module/repo/css/common.ui.css index 732824a2a0..0c8ef78452 100644 --- a/module/repo/css/common.ui.css +++ b/module/repo/css/common.ui.css @@ -7,11 +7,11 @@ #fileTabs > .nav-tabs {position: absolute; display: flex; padding-left: 25px;} #fileTabs .monaco-close{margin-right: 18px;} #fileTabs .nav-tabs > li > a > span{margin-left: 18px;font-weight: 700;color: #313c52!important;} +#related .btn-right, #related .btn-left {padding-top: 6px;} #log .action-btn {margin-top: -7px;} .repoCode .btn.btn-right, .repoCode .btn.btn-left {margin-right: 0; padding: 6px 6px;} .btn-left, .btn-right {display: none;} #linkObject{display: none;} -#related .btn-right, #related .btn-left {padding-top: 6px;} #monacoTree .tree-item-content span {cursor: pointer;} .directory {background-image:url('theme/default/images/repo/dir.png');} .file {background-image:url('theme/default/images/repo/txt.png');} @@ -50,3 +50,5 @@ #fileTabs .monaco-dropmenu .caret {display: none;} #monacoTabs .gap-x-5 {-moz-column-gap: 0; column-gap: 0;} #reviewBugContainer {position: fixed; right: 0; top: 0px; width: 25%; height: 100%; background: #fff; border-left: 5px solid #F4F5F7; overflow-y: auto; z-index: 9;} +#log .action-btn .btn {padding-left: .25rem; padding-right: .25rem;} +.dropmenu-btn>.text {max-width: 280px;} \ No newline at end of file diff --git a/module/repo/js/ajaxgeteditorcontent.ui.js b/module/repo/js/ajaxgeteditorcontent.ui.js index 7bf34f2b4b..feed9b12f1 100644 --- a/module/repo/js/ajaxgeteditorcontent.ui.js +++ b/module/repo/js/ajaxgeteditorcontent.ui.js @@ -56,11 +56,10 @@ window.getRelation = function(commit) $('#codeContainer').css('height', codeHeight / 5 * 3); var relatedHeight = codeHeight / 5 * 2 - $('#log').height() - 10; $('#related').css('height', relatedHeight); - $('#related').css('height', relatedHeight); setTimeout(() => { var tabsHeight = $('#relationTabs .nav-tabs').height(); if(!tabsHeight) tabsHeight = 32; - $('#relationTabs .tab-content').css('height', relatedHeight - tabsHeight - 5); + $('#relationTabs .tab-content').css('height', relatedHeight - tabsHeight - 8); }, 500); $('#relationTabs ul li').remove(); $('#relationTabs .tab-content .tab-pane').remove(); @@ -209,7 +208,7 @@ function initPage() parent.loadLinkPage(link); }); - $('#relationTabs').on('click', '.unlinks', function() + $('#relationTabs').off('click', '.unlinks').on('click', '.unlinks', function() { var link = $(this).data('link'); $.post(link, function(data) @@ -236,4 +235,4 @@ function initPage() /* Get file commits. */ showCommitInfo(); -} \ No newline at end of file +} diff --git a/module/repo/js/browse.ui.js b/module/repo/js/browse.ui.js index 12059b8f69..cc0b6ed1d4 100644 --- a/module/repo/js/browse.ui.js +++ b/module/repo/js/browse.ui.js @@ -6,7 +6,7 @@ window.renderCell = function(result, {col, row}) if(col.name === 'name') { var iconHtml = ''; - result[0] = {html:iconHtml + '' + row.data.name + ''}; + result[0] = {html:iconHtml + '' + row.data.name + ''}; return result; } @@ -32,7 +32,7 @@ window.renderCommentCell = function(result, {col, row}) { if(col.name === 'revision') { - result[0] = {html:'' + row.data.revision + '', style:{flexDirection:"column"}}; + result[0] = {html:'' + row.data.revision + '', style:{flexDirection:"column"}}; return result; } @@ -67,7 +67,7 @@ $('#repo-select').on('change', function() /** * 当选中两行时禁用其他行。 * Disable checkable attribution when checked rows equal 2. - * + * * @param object changes * @access public * @return void diff --git a/module/repo/js/create.ui.js b/module/repo/js/create.ui.js index 74971a2b44..65a539948a 100644 --- a/module/repo/js/create.ui.js +++ b/module/repo/js/create.ui.js @@ -9,7 +9,7 @@ function onProductChange(event) var projects = $('[name="projects[]"]').val(); var products = $('[name="product[]"]').val(); - $.post($.createLink('repo', 'ajaxProjectsOfProducts'), {products, projects}, function(response) + $.post($.createLink('repo', 'ajaxProjectsOfProducts'), {'products': products.join(','), 'projects': projects.join(',')}, function(response) { var data = JSON.parse(response); var $projects = $('#projects').zui('picker'); diff --git a/module/repo/js/diff.ui.js b/module/repo/js/diff.ui.js index f357ef6f3f..33cb89cacf 100644 --- a/module/repo/js/diff.ui.js +++ b/module/repo/js/diff.ui.js @@ -33,22 +33,26 @@ window.afterPageUpdate = function() /* Select default tree item. */ const currentElement = findItemInTreeItems(tree, fileAsId, 0); - if(currentElement != undefined) $('#' + currentElement.id).parent().addClass('selected'); + expandTree(); + if(currentElement != undefined) setTimeout(() => + { + $('#' + currentElement.id).parent().addClass('selected'); + }, 100); $('.btn-left').on('click', function() {arrowTabs('monacoTabs', 1);}); $('.btn-right').on('click', function() {arrowTabs('monacoTabs', -2);}); - - $('#repoDownloadCode').on('click', function() - { - var url = $(this).data('link'); - var activeFilePath = $('#monacoTabs .nav-item .active').attr('href').substring(5).replace(/-/g, '='); - window.open(url.replace('{path}', activeFilePath)); - return; - }) - }, 200); + }, 300); }; +window.downloadCode = function() +{ + var url = $(this).data('link'); + var activeFilePath = $('#monacoTabs .nav-item .active').attr('href').substring(5).replace(/-/g, '='); + window.open(url.replace('{path}', activeFilePath)); + return; +} + /** * 点击左侧菜单打开详情tab。 * Open new tab when click tree item. diff --git a/module/repo/js/edit.ui.js b/module/repo/js/edit.ui.js index 0b285521a6..5f865d3cda 100644 --- a/module/repo/js/edit.ui.js +++ b/module/repo/js/edit.ui.js @@ -8,7 +8,7 @@ function onProductChange(event) var projects = $('[name="projects[]"]').val(); var products = $('[name="product[]"]').val(); - $.post($.createLink('repo', 'ajaxProjectsOfProducts'), {products, projects}, function(response) + $.post($.createLink('repo', 'ajaxProjectsOfProducts'), {'products': products.join(','), 'projects': projects.join(',')}, function(response) { var data = JSON.parse(response); var $projects = $('#projects').zui('picker'); diff --git a/module/repo/js/import.ui.js b/module/repo/js/import.ui.js index 482b81eb05..b1264da5b9 100644 --- a/module/repo/js/import.ui.js +++ b/module/repo/js/import.ui.js @@ -38,7 +38,7 @@ function loadProductProjects(event) const projects = $currentRow.find('div.picker-box[data-name="projects"]'); const projectIds = $(projects).val(); - $.post($.createLink('repo', 'ajaxProjectsOfProducts'), {products : products, projects: projectIds, number : 1}, function(response) + $.post($.createLink('repo', 'ajaxProjectsOfProducts'), {products : products.join(','), projects: projectIds, number : 1}, function(response) { var items = JSON.parse(response); $(projects).zui('picker').render({items: items}); diff --git a/module/repo/js/linkbug.ui.js b/module/repo/js/linkbug.ui.js index 475ab59c8f..f478e496a2 100644 --- a/module/repo/js/linkbug.ui.js +++ b/module/repo/js/linkbug.ui.js @@ -3,7 +3,7 @@ window.createSortLink = function(col) var sort = col.name + '_asc'; if(sort == orderBy) sort = col.name + '_desc'; - return sortLink.replace('{orderBy}', sort); + return "javascript:loadModal('" + sortLink.replace('{orderBy}', sort) + "', '#table-repo-linkbug')"; } $(document).off('click','.dtable-footer .batch-btn').on('click', '.dtable-footer .batch-btn', function(e) diff --git a/module/repo/js/linkstory.ui.js b/module/repo/js/linkstory.ui.js index 475ab59c8f..8d2c7ce53f 100644 --- a/module/repo/js/linkstory.ui.js +++ b/module/repo/js/linkstory.ui.js @@ -3,7 +3,7 @@ window.createSortLink = function(col) var sort = col.name + '_asc'; if(sort == orderBy) sort = col.name + '_desc'; - return sortLink.replace('{orderBy}', sort); + return "javascript:loadModal('" + sortLink.replace('{orderBy}', sort) + "', '#table-repo-linkstory')"; } $(document).off('click','.dtable-footer .batch-btn').on('click', '.dtable-footer .batch-btn', function(e) @@ -20,4 +20,4 @@ $(document).off('click','.dtable-footer .batch-btn').on('click', '.dtable-footer url: $(this).data('url'), data: postData }); -}); \ No newline at end of file +}); diff --git a/module/repo/js/linktask.ui.js b/module/repo/js/linktask.ui.js index 475ab59c8f..ee7cf6a266 100644 --- a/module/repo/js/linktask.ui.js +++ b/module/repo/js/linktask.ui.js @@ -3,7 +3,7 @@ window.createSortLink = function(col) var sort = col.name + '_asc'; if(sort == orderBy) sort = col.name + '_desc'; - return sortLink.replace('{orderBy}', sort); + return "javascript:loadModal('" + sortLink.replace('{orderBy}', sort) + "', '#table-repo-linktask')"; } $(document).off('click','.dtable-footer .batch-btn').on('click', '.dtable-footer .batch-btn', function(e) diff --git a/module/repo/js/review.ui.js b/module/repo/js/review.ui.js index 32542074ae..d19fdcd87c 100644 --- a/module/repo/js/review.ui.js +++ b/module/repo/js/review.ui.js @@ -2,9 +2,17 @@ window.renderRepobugList = function (result, {col, row, value}) { if(col.name == 'entry') { - result[0] = {html: '' + row.data.lines + '' + row.data.entry + ''}; + result[0] = {html: '' + row.data.lines + '' + row.data.entry + ''}; return result; } return result; } + +window.createSortLink = function(col) +{ + var sort = col.name + '_asc'; + if(sort == orderBy) sort = col.name + '_desc'; + + return sortLink.replace('{orderBy}', sort); +}; \ No newline at end of file diff --git a/module/repo/js/showsynccommit.ui.js b/module/repo/js/showsynccommit.ui.js index 1e031d33be..d204dcbd5a 100644 --- a/module/repo/js/showsynccommit.ui.js +++ b/module/repo/js/showsynccommit.ui.js @@ -3,6 +3,7 @@ function syncComments() $.get(link, function(data) { if(data == 'finish') return loadPage(browseLink); + if(isNaN(Number(data))) return zui.Modal.alert(data); var count = parseInt(data); if(isNaN(count)) count = 0; diff --git a/module/repo/lang/de.php b/module/repo/lang/de.php index e9d3abd583..ed760a2557 100644 --- a/module/repo/lang/de.php +++ b/module/repo/lang/de.php @@ -1,4 +1,6 @@ repo->common = 'Repo'; $lang->repo->codeRepo = 'Code Library'; $lang->repo->browse = 'View'; @@ -166,7 +168,7 @@ $lang->repo->encodingList['gbk'] = 'GBK'; $lang->repo->scmList['Gitlab'] = 'GitLab'; $lang->repo->scmList['Gogs'] = 'Gogs'; -$lang->repo->scmList['Gitea'] = 'Gitea'; +if(!$config->inQuickon) $lang->repo->scmList['Gitea'] = 'Gitea'; $lang->repo->scmList['Git'] = 'Git'; $lang->repo->scmList['Subversion'] = 'SVN'; @@ -233,6 +235,7 @@ $lang->repo->error->differentVersions = 'The criterion and contrast cannot be th $lang->repo->error->needTwoVersion = 'Two branches or tags must be selected.'; $lang->repo->error->emptyVersion = 'Version cannot be empty'; $lang->repo->error->versionError = 'Wrong version format!'; +$lang->repo->error->projectUnique = $lang->repo->serviceProject . " exists. Go to Admin->System->Data->Recycle Bin to restore it, if you are sure it is deleted."; $lang->repo->syncTips = 'You may find the reference about how to set Git sync from here.'; $lang->repo->encodingsTips = "The encodings of comments can be comma separated values, e.g. utf-8."; diff --git a/module/repo/lang/en.php b/module/repo/lang/en.php index e7723fc66f..dfc79f9518 100644 --- a/module/repo/lang/en.php +++ b/module/repo/lang/en.php @@ -1,4 +1,6 @@ repo->common = 'Repo'; $lang->repo->codeRepo = 'Code Library'; $lang->repo->browse = 'View'; @@ -166,7 +168,7 @@ $lang->repo->encodingList['gbk'] = 'GBK'; $lang->repo->scmList['Gitlab'] = 'GitLab'; $lang->repo->scmList['Gogs'] = 'Gogs'; -$lang->repo->scmList['Gitea'] = 'Gitea'; +if(!$config->inQuickon) $lang->repo->scmList['Gitea'] = 'Gitea'; $lang->repo->scmList['Git'] = 'Git'; $lang->repo->scmList['Subversion'] = 'SVN'; @@ -233,6 +235,7 @@ $lang->repo->error->differentVersions = 'The criterion and contrast cannot be th $lang->repo->error->needTwoVersion = 'Two branches or tags must be selected.'; $lang->repo->error->emptyVersion = 'Version cannot be empty'; $lang->repo->error->versionError = 'Wrong version format!'; +$lang->repo->error->projectUnique = $lang->repo->serviceProject . " exists. Go to Admin->System->Data->Recycle Bin to restore it, if you are sure it is deleted."; $lang->repo->syncTips = 'You may find the reference about how to set Git sync from here.'; $lang->repo->encodingsTips = "The encodings of comments can be comma separated values, e.g. utf-8."; diff --git a/module/repo/lang/fr.php b/module/repo/lang/fr.php index b4c1cb5f3b..03a006e139 100644 --- a/module/repo/lang/fr.php +++ b/module/repo/lang/fr.php @@ -1,4 +1,6 @@ repo->common = 'Référentiel'; $lang->repo->codeRepo = 'Référentiel'; $lang->repo->browse = 'Aff'; @@ -166,7 +168,7 @@ $lang->repo->encodingList['gbk'] = 'GBK'; $lang->repo->scmList['Gitlab'] = 'GitLab'; $lang->repo->scmList['Gogs'] = 'Gogs'; -$lang->repo->scmList['Gitea'] = 'Gitea'; +if(!$config->inQuickon) $lang->repo->scmList['Gitea'] = 'Gitea'; $lang->repo->scmList['Git'] = 'Git'; $lang->repo->scmList['Subversion'] = 'Subversion'; @@ -233,6 +235,7 @@ $lang->repo->error->differentVersions = 'The criterion and contrast cannot be th $lang->repo->error->needTwoVersion = 'Two branches or tags must be selected.'; $lang->repo->error->emptyVersion = 'Version cannot be empty'; $lang->repo->error->versionError = 'Wrong version format!'; +$lang->repo->error->projectUnique = $lang->repo->serviceProject . " exists. Go to Admin->System->Data->Recycle Bin to restore it, if you are sure it is deleted."; $lang->repo->syncTips = 'Vous pouvez trouver la référence sur la façon de définir la synchronisation Git à partir de la page se trouvant ici.'; $lang->repo->encodingsTips = "Les encodages des commentaires de validation peuvent être des valeurs séparées par des virgules,ex: utf-8"; diff --git a/module/repo/lang/vi.php b/module/repo/lang/vi.php index e1c273f751..13348ae89d 100644 --- a/module/repo/lang/vi.php +++ b/module/repo/lang/vi.php @@ -230,3 +230,5 @@ $lang->repo->typeList['performance'] = 'Hiệu suất'; $lang->repo->typeList['security'] = 'Bảo mật'; $lang->repo->typeList['redundancy'] = 'Redundancy'; $lang->repo->typeList['logicError'] = 'Logic Error'; + +$lang->repo->featureBar['maintain']['all'] = 'All'; diff --git a/module/repo/lang/zh-cn.php b/module/repo/lang/zh-cn.php index 8da890c74a..787578881f 100644 --- a/module/repo/lang/zh-cn.php +++ b/module/repo/lang/zh-cn.php @@ -1,5 +1,7 @@ repo->common = '代码'; +global $config; + +$lang->repo->common = '代码库'; $lang->repo->codeRepo = '代码库'; $lang->repo->browse = '浏览'; $lang->repo->viewRevision = '查看修订'; @@ -166,7 +168,7 @@ $lang->repo->encodingList['gbk'] = 'GBK'; $lang->repo->scmList['Gitlab'] = 'GitLab'; $lang->repo->scmList['Gogs'] = 'Gogs'; -$lang->repo->scmList['Gitea'] = 'Gitea'; +if(!$config->inQuickon) $lang->repo->scmList['Gitea'] = 'Gitea'; $lang->repo->scmList['Git'] = '本地 Git'; $lang->repo->scmList['Subversion'] = 'Subversion'; @@ -233,6 +235,7 @@ $lang->repo->error->differentVersions = '基准和对比不能一样'; $lang->repo->error->needTwoVersion = '必须选择两个分支/标签'; $lang->repo->error->emptyVersion = '版本不能为空'; $lang->repo->error->versionError = '版本格式错误!'; +$lang->repo->error->projectUnique = $lang->repo->serviceProject . '已经有这条记录了。如果您确定该记录已删除,请到后台-系统-数据-回收站还原。'; $lang->repo->syncTips = '请参照这里,设置代码库定时同步。'; $lang->repo->encodingsTips = "提交日志的编码,可以用逗号连接起来的多个,比如utf-8。"; diff --git a/module/repo/lang/zh-tw.php b/module/repo/lang/zh-tw.php index 59e41603da..ac3a8e75b7 100644 --- a/module/repo/lang/zh-tw.php +++ b/module/repo/lang/zh-tw.php @@ -154,6 +154,7 @@ $lang->repo->placeholder->gitlabHost = '請填寫GitLab訪問地址'; $lang->repo->notice = new stdclass(); $lang->repo->notice->syncing = '正在同步中, 請稍等...'; $lang->repo->notice->syncComplete = '同步完成,正在跳轉...'; +$lang->repo->notice->syncFailed = '同步失敗.'; $lang->repo->notice->syncedCount = '已經同步記錄條數'; $lang->repo->notice->delete = '是否要刪除該版本庫?'; $lang->repo->notice->successDelete = '已經成功刪除版本庫。'; @@ -213,3 +214,5 @@ $lang->repo->typeList['performance'] = '性能'; $lang->repo->typeList['security'] = '安全'; $lang->repo->typeList['redundancy'] = '冗餘'; $lang->repo->typeList['logicError'] = '邏輯錯誤'; + +$lang->repo->featureBar['maintain']['all'] = '全部'; diff --git a/module/repo/model.php b/module/repo/model.php index 76e27d675e..ed8ad16f6b 100644 --- a/module/repo/model.php +++ b/module/repo/model.php @@ -12,9 +12,12 @@ class repoModel extends model { $account = $this->app->user->account; $acl = !empty($repo->acl->acl) ? $repo->acl->acl : 'custom'; + if(empty($repo->acl)) $repo->acl = new stdclass(); + if(empty($repo->acl->users)) $repo->acl->users = array(); + if(empty($repo->acl->groups)) $repo->acl->groups = array(); if(strpos(",{$this->app->company->admins},", ",$account,") !== false || $acl == 'open') return true; - if($acl == 'custom' && empty($repo->acl->groups) && empty($repo->acl->users)) return true; + if($acl == 'custom' && empty(array_filter($repo->acl->groups)) && empty(array_filter($repo->acl->users))) return true; if($acl == 'private') { @@ -137,11 +140,11 @@ class repoModel extends model if($queryID && $queryID != 'myQueryID') { - $query = $this->loadModel('search')->getQuery($queryID); + $query = $this->loadModel('search')->getZinQuery($queryID); if($query) { $this->session->set($queryName, $query->sql); - $this->session->set($queryName . 'Form', $query->form); + $this->session->set('repoForm', $query->form); } else { @@ -200,8 +203,15 @@ class repoModel extends model if($lastSubmitTime) { - $lastRevision = $this->repoTao->getLastRevision($repo->id); - $repo->lastSubmitTime = isset($lastRevision->time) ? $lastRevision->time : ''; + if($repo->lastCommit) + { + $repo->lastSubmitTime = $repo->lastCommit; + } + else + { + $lastRevision = $this->repoTao->getLastRevision($repo->id); + $repo->lastSubmitTime = isset($lastRevision->time) ? $lastRevision->time : ''; + } } if(in_array(strtolower($repo->SCM), $this->config->repo->gitServiceList)) $repo = $this->processGitService($repo, $getCodePath); @@ -249,22 +259,52 @@ class repoModel extends model */ public function create(object $repo, bool $isPipelineServer): int|false { + if($isPipelineServer) + { + $serviceProject = $this->dao->select('*')->from(TABLE_REPO) + ->where('`SCM`')->eq($repo->SCM) + ->andWhere('`serviceHost`')->eq($repo->serviceHost) + ->andWhere('`serviceProject`')->eq($repo->serviceProject) + ->fetch(); + if($serviceProject) + { + dao::$errors['serviceProject'][] = $this->lang->repo->error->projectUnique; + return false; + } + } + $this->dao->insert(TABLE_REPO)->data($repo, 'serviceToken') ->batchCheck($this->config->repo->create->requiredFields, 'notempty') ->batchCheckIF($repo->SCM != 'Gitlab', 'path,client', 'notempty') ->batchCheckIF($isPipelineServer, 'serviceHost,serviceProject', 'notempty') ->batchCheckIF($repo->SCM == 'Subversion', $this->config->repo->svn->requiredFields, 'notempty') - ->check('name', 'unique', "`SCM` = '{$repo->SCM}'") - ->checkIF($isPipelineServer && $repo->serviceProject, 'serviceProject', 'unique', "`SCM` = '{$repo->SCM}' and `serviceHost` = '{$repo->serviceHost}'") - ->checkIF(!$isPipelineServer, 'path', 'unique', "`SCM` = '{$repo->SCM}' and `serviceHost` = '{$repo->serviceHost}'") + ->check('name', 'unique', "`SCM` = " . $this->dao->sqlobj->quote($repo->SCM)) + ->checkIF(!$isPipelineServer, 'path', 'unique', "`SCM` = " . $this->dao->sqlobj->quote($repo->SCM) . " and `serviceHost` = " . $this->dao->sqlobj->quote($repo->serviceHost)) ->autoCheck() ->exec(); if(dao::isError()) return false; + $repoID = $this->dao->lastInsertID(); + $repo = $this->getByID($repoID); + if($repo->SCM == 'Gitlab') + { + $token = time(); + $res = $this->loadModel('gitlab')->addPushWebhook($repo, $token); + if($res === false) + { + $thi->dao->delete()->from(TABLE_REPO)->where('id')->eq($repoID)->exec(); + dao::$errors['webhook'][] = $this->lang->gitlab->failCreateWebhook; + return false; + } + else + { + $this->dao->update(TABLE_REPO)->set('password')->eq($token)->where('id')->eq($repoID)->exec(); + } + } $this->rmClientVersionFile(); - return $this->dao->lastInsertID(); + return $repoID; } /** @@ -292,8 +332,8 @@ class repoModel extends model $this->dao->insert(TABLE_REPO)->data($repo) ->batchCheck($this->config->repo->create->requiredFields, 'notempty') ->check('serviceHost,serviceProject', 'notempty') - ->check('name', 'unique', "`SCM` = '{$repo->SCM}'") - ->check('serviceProject', 'unique', "`SCM` = '{$repo->SCM}' and `serviceHost` = '{$repo->serviceHost}'") + ->check('name', 'unique', "`SCM` = " . $this->dao->sqlobj->quote($repo->serviceHost)) + ->check('serviceProject', 'unique', "`SCM` = " . $this->dao->sqlobj->quote($repo->SCM) . " and `serviceHost` = " . $this->dao->sqlobj->quote($repo->serviceHost)) ->autoCheck() ->exec(); @@ -306,6 +346,7 @@ class repoModel extends model /* Add webhook. */ $repo = $this->getByID($repoID); $this->loadModel('gitlab')->addPushWebhook($repo); + $this->gitlab->updateCodePath($repo->serviceHost, $repo->serviceProject, $repo->id); } $this->loadModel('action')->create('repo', $repoID, 'created'); @@ -360,15 +401,47 @@ class repoModel extends model $data->prefix = ''; } + if($isPipelineServer) + { + $serviceProject = $this->dao->select('*')->from(TABLE_REPO) + ->where('`SCM`')->eq($data->SCM) + ->andWhere('`serviceHost`')->eq($data->serviceHost) + ->andWhere('`serviceProject`')->eq($data->serviceProject) + ->andWhere('id')->ne($id) + ->fetch(); + if($serviceProject) + { + dao::$errors['serviceProject'][] = $this->lang->repo->error->projectUnique; + return false; + } + } + + if(($repo->serviceHost != $data->serviceHost || $repo->serviceProject != $data->serviceProject) && $data->SCM == 'Gitlab') + { + $repo->gitService = $data->serviceHost; + $repo->project = $data->serviceProject; + + $token = time(); + $res = $this->loadModel('gitlab')->addPushWebhook($repo, $token); + if($res === false) + { + dao::$errors['webhook'][] = $this->lang->gitlab->failCreateWebhook; + return false; + } + else + { + $data->password = $token; + } + } + if($data->encrypt == 'base64') $data->password = base64_encode($data->password); $this->dao->update(TABLE_REPO)->data($data, 'serviceToken') ->batchCheck($this->config->repo->edit->requiredFields, 'notempty') ->batchCheckIF($data->SCM != 'Gitlab', 'path,client', 'notempty') ->batchCheckIF($isPipelineServer, 'serviceHost,serviceProject', 'notempty') ->batchCheckIF($data->SCM == 'Subversion', $this->config->repo->svn->requiredFields, 'notempty') - ->check('name', 'unique', "`SCM` = '{$data->SCM}' and `id` <> $id") - ->checkIF($isPipelineServer && $data->serviceProject, 'serviceProject', 'unique', "`SCM` = '{$data->SCM}' and `serviceHost` = '{$data->serviceHost}' and `id` <> $id") - ->checkIF(!$isPipelineServer, 'path', 'unique', "`SCM` = '{$data->SCM}' and `serviceHost` = '{$data->serviceHost}' and `id` <> $id") + ->check('name', 'unique', "`SCM` = " . $this->dao->sqlobj->quote($data->SCM) . " and `id` != $id") + ->checkIF(!$isPipelineServer, 'path', 'unique', "`SCM` = " . $this->dao->sqlobj->quote($data->SCM) . " and `serviceHost` = " . $this->dao->sqlobj->quote($data->serviceHost) . " and `id` != $id") ->autoCheck() ->where('id')->eq($id)->exec(); @@ -378,6 +451,7 @@ class repoModel extends model { $this->loadModel('gitlab')->updateCodePath($data->serviceHost, $data->serviceProject, $repo->id); $data->path = $this->getByID($id)->path; + $this->updateCommitDate($repo->id); } if($repo->path != $data->path) @@ -397,10 +471,11 @@ class repoModel extends model * @param int $repoID * @param string $revision * @param string $type + * @param string $from repo|commit * @access public * @return void */ - public function link($repoID, $revision, $type = 'story') + public function link($repoID, $revision, $type = 'story', $from = 'repo') { $this->loadModel('action'); if($type == 'story') $links = $this->post->stories; @@ -410,17 +485,33 @@ class repoModel extends model $revisionInfo = $this->dao->select('*')->from(TABLE_REPOHISTORY)->where('repo')->eq($repoID)->andWhere('revision')->eq($revision)->fetch(); if(empty($revisionInfo)) { - $this->updateCommit($repoID); - $revisionInfo = $this->dao->select('*')->from(TABLE_REPOHISTORY)->where('repo')->eq($repoID)->andWhere('revision')->eq($revision)->fetch(); + $repo = $this->getByID($repoID); + if($repo->SCM == 'Gitlab') + { + $scm = $this->app->loadClass('scm'); + $scm->setEngine($repo); + $logs = $scm->getCommits($revision, 1); + $this->saveCommit($repoID, $logs, 0); + } + else + { + $this->updateCommit($repoID); + } + } + + $revisionInfo = $this->dao->select('*')->from(TABLE_REPOHISTORY)->where('repo')->eq($repoID)->andWhere('revision')->eq($revision)->fetch(); + if(empty($revisionInfo)) + { + dao::$errors = $this->lang->fail; + return false; } - if(empty($revisionInfo)) return false; $revisionID = $revisionInfo->id; $committer = $this->dao->select('account')->from(TABLE_USER)->where('commiter')->eq($revisionInfo->committer)->fetch('account'); if(empty($committer)) $committer = $revisionInfo->committer; + if($from == 'repo') $committer = $this->app->user->account; foreach($links as $linkID) { - $relation = new stdclass; $relation->AType = 'revision'; $relation->AID = $revisionID; @@ -430,9 +521,9 @@ class repoModel extends model $this->dao->replace(TABLE_RELATION)->data($relation)->exec(); - if($type == 'story') $this->action->create('story', $linkID, 'linked2revision', '', $revisionID, $committer); - if($type == 'bug') $this->action->create('bug', $linkID, 'linked2revision', '', $revisionID, $committer); - if($type == 'task') $this->action->create('task', $linkID, 'linked2revision', '', $revisionID, $committer); + if($type == 'story') $this->action->create('story', $linkID, 'linked2revision', '', $revisionID, $commiter); + if($type == 'bug') $this->action->create('bug', $linkID, 'linked2revision', '', $revisionID, $commiter); + if($type == 'task') $this->action->create('task', $linkID, 'linked2revision', '', $revisionID, $commiter); } } @@ -543,9 +634,16 @@ class repoModel extends model ->fetchAll(); $productIds = $productItems = array(); - foreach($repos as $repo) + if($projectID) { - $productIds = array_merge($productIds, explode(',', $repo->product)); + $productIds = $this->loadModel('product')->getProductIDByProject($projectID, false); + } + else + { + foreach($repos as $repo) + { + $productIds = array_merge($productIds, explode(',', $repo->product)); + } } $products = $this->loadModel('product')->getByIdList(array_filter(array_unique($productIds))); foreach($products as $productID => $product) @@ -576,6 +674,7 @@ class repoModel extends model $repoItem['keys'] = zget(common::convert2Pinyin(array($repo->name)), $repo->name, ''); $repoProducts = explode(',', $repo->product); + $repoProducts = array_filter($repoProducts); foreach($repoProducts as $productID) { if(($type == 'project' or $type == 'execution') and $projectID) @@ -611,7 +710,6 @@ class repoModel extends model if($repo->encrypt == 'base64') $repo->password = base64_decode($repo->password); $repo->codePath = $repo->path; if(in_array(strtolower($repo->SCM), $this->config->repo->gitServiceList)) $repo = $this->processGitService($repo); - $repo->acl = json_decode($repo->acl); if(empty($repo->acl)) $repo->acl = new stdclass(); if(empty($repo->acl->acl)) $repo->acl->acl = 'custom'; @@ -820,6 +918,11 @@ class repoModel extends model return $branches; } + public function getCommitsByRevisions($revisions) + { + return $this->dao->select('id')->from(TABLE_REPOHISTORY)->where('revision')->in($revisions)->fetchPairs('id'); + } + /** * Get commits. * @@ -861,7 +964,7 @@ class repoModel extends model ->where('1=1') ->andWhere('t1.repo')->eq($repo->id) ->beginIF($revisionTime)->andWhere('t2.`time`')->le($revisionTime)->fi() - ->andWhere('left(t2.comment, 12)')->ne('Merge branch') + ->andWhere('left(t2.`comment`, 12)')->ne('Merge branch') ->beginIF($repo->SCM != 'Subversion' and $this->cookie->repoBranch)->andWhere('t3.branch')->eq($this->cookie->repoBranch)->fi() ->beginIF($type == 'dir') ->andWhere('t1.parent', true)->like(rtrim($entry, '/') . "/%") @@ -878,7 +981,7 @@ class repoModel extends model ->leftJoin(TABLE_REPOBRANCH)->alias('t2')->on('t1.id=t2.revision') ->where('t1.repo')->eq($repoID) ->beginIF($revisionTime)->andWhere('t1.`time`')->le($revisionTime)->fi() - ->andWhere('left(t1.comment, 12)')->ne('Merge branch') + ->andWhere('left(t1.`comment`, 12)')->ne('Merge branch') ->beginIF($repo->SCM != 'Subversion' and $this->cookie->repoBranch)->andWhere('t2.branch')->eq($this->cookie->repoBranch)->fi() ->beginIF($entry != '/' and !empty($entry))->andWhere('t1.id')->in($historyIdList)->fi() ->beginIF($begin)->andWhere('t1.time')->ge($begin)->fi() @@ -1070,7 +1173,7 @@ class repoModel extends model $file->repo = $repoID; $this->dao->insert(TABLE_REPOFILES)->data($file)->exec(); - if($file->oldPath and $file->action == 'R') + if($file->action == 'R' && !empty($file->oldPath)) { $file->path = $file->oldPath; $file->parent = dirname($file->path); @@ -1134,7 +1237,7 @@ class repoModel extends model $repoFile->parent = $parentPath == '\\' ? '/' : $parentPath; $repoFile->type = $info['kind']; $repoFile->action = $info['action']; - $repoFile->oldPath = $info['oldPath']; + $repoFile->oldPath = empty($info['oldPath']) ? '' : $info['oldPath']; $this->dao->insert(TABLE_REPOFILES)->data($repoFile)->exec(); if($repoFile->oldPath and $repoFile->action == 'R') @@ -1486,7 +1589,8 @@ class repoModel extends model return false; } - if(strpos($this->post->client, ' ')) + /* Check command injection. */ + if(preg_match('/[ #&;`,\|*?~<>^()\[\]{}$]/', $this->post->client)) { dao::$errors['client'] = $this->lang->repo->error->clientPath; return false; @@ -2245,7 +2349,7 @@ class repoModel extends model * @access public * @return object */ - public function processGitService($repo, $getCodePath = true) + public function processGitService($repo, $getCodePath = false) { $service = $this->loadModel('pipeline')->getByID($repo->serviceHost); if($repo->SCM == 'Gitlab') @@ -2301,7 +2405,40 @@ class repoModel extends model { /* Update code commit history. */ $commentGroup = $this->loadModel('job')->getTriggerGroup('commit', array($repo->id)); - $this->loadModel('git')->updateCommit($repo, $commentGroup, false); + if($repo->SCM == 'Gitlab') + { + $this->loadModel('repo'); + $jobs = zget($commentGroup, $repo->id, array()); + + $accountPairs = array(); + $userList = $this->loadModel('gitlab')->apiGetUsers($repo->gitService); + $acountIDPairs = $this->gitlab->getUserIdAccountPairs($repo->gitService); + foreach($userList as $gitlabUser) $accountPairs[$gitlabUser->realname] = zget($acountIDPairs, $gitlabUser->id, ''); + + foreach($data->commits as $commit) + { + $log = new stdclass(); + $log->revision = $commit->id; + $log->msg = $commit->message; + $log->author = $commit->author->name; + $log->date = date("Y-m-d H:i:s", strtotime($commit->timestamp)); + + $objects = $this->repo->parseComment($log->msg); + $this->repo->saveAction2PMS($objects, $log, $repo->path, $repo->encoding, 'git', $accountPairs); + + foreach($jobs as $job) + { + foreach(explode(',', $job->comment) as $comment) + { + if(strpos($log->msg, $comment) !== false) $this->loadModel('job')->exec($job->id); + } + } + } + } + else + { + $this->loadModel('git')->updateCommit($repo, $commentGroup, false); + } } } @@ -2418,7 +2555,7 @@ class repoModel extends model ->leftJoin(TABLE_REPOHISTORY)->alias('t2')->on('t1.revision=t2.id') ->leftJoin(TABLE_REPOBRANCH)->alias('t3')->on('t2.id=t3.revision') ->where('t1.repo')->eq($repo->id) - ->andWhere('left(t2.comment, 12)')->ne('Merge branch') + ->andWhere('left(t2.`comment`, 12)')->ne('Merge branch') ->beginIF($repo->SCM != 'Subversion' and $branch)->andWhere('t3.branch')->eq($branch)->fi() ->beginIF($repo->SCM == 'Subversion')->andWhere('t1.parent')->eq("$parent")->fi() ->beginIF($repo->SCM != 'Subversion')->andWhere('t1.parent')->like("$parent%")->fi() @@ -2491,6 +2628,7 @@ class repoModel extends model $paths = array(); $files = $scm->engine->tree($path, 0); + if(empty($files)) $files = array(); foreach($files as $file) { $paths[] = $file->path; @@ -2549,7 +2687,8 @@ class repoModel extends model $scm->setEngine($repo); $this->app->loadClass('requests', true); - $files = $scm->engine->tree('', 1, true); + $files = $scm->engine->tree('', 1, true); + $allFiles = array(); foreach($files as $file) { @@ -2578,7 +2717,7 @@ class repoModel extends model ->leftJoin(TABLE_REPOBRANCH)->alias('t3')->on('t2.id=t3.revision') ->where('t1.repo')->eq($repo->id) ->andWhere('t1.type')->eq('file') - ->andWhere('left(t2.comment, 12)')->ne('Merge branch') + ->andWhere('left(t2.`comment`, 12)')->ne('Merge branch') ->beginIF($repo->SCM != 'Subversion' and $branch)->andWhere('t3.branch')->eq($branch)->fi() ->orderBy('t2.`time` asc') ->fetchAll('path'); @@ -2818,8 +2957,8 @@ class repoModel extends model } } $stories = empty($storyIDs) ? array() : $this->loadModel('story')->getByList($storyIDs); - $bugs = empty($bugIDs) ? array() : $this->loadModel('bug')->getByIdList($bugIDs); - $tasks = empty($taskIDs) ? array() : $this->loadModel('task')->getByIdList($taskIDs); + $bugs = empty($bugIDs) ? array() : $this->loadModel('bug')->getByList($bugIDs); + $tasks = empty($taskIDs) ? array() : $this->loadModel('task')->getByList($taskIDs); $titleList = array(); foreach($relationList as $key => $relation) @@ -2987,6 +3126,7 @@ class repoModel extends model public function updateCommit($repoID, $objectID = 0, $branchID = 0) { $repo = $this->getByID($repoID); + if($repo->SCM == 'Gitlab') return; /* Update code commit history. */ $commentGroup = $this->loadModel('job')->getTriggerGroup('commit', array($repoID)); @@ -3052,6 +3192,7 @@ class repoModel extends model { $action = strtolower($action); + if(!commonModel::hasPriv('repo', $action)) return false; if($action == 'execjob') return $repo->exec == ''; if($action == 'reportview') return $repo->report == ''; @@ -3078,7 +3219,7 @@ class repoModel extends model else { $gitlabUser = $this->loadModel('gitlab')->getUserIDByZentaoAccount($gitlabID, $this->app->user->account); - if(!$gitlabUser) $this->send(array('message' => array())); + if(!$gitlabUser) $this->app->control->send(array('message' => array())); $projects = $this->gitlab->apiGetProjects($gitlabID, $filter ? 'false' : 'true'); $groupIDList = array(0 => 0); @@ -3095,4 +3236,47 @@ class repoModel extends model return $projects; } + + /** + * Check str in array. + * + * @param string $str + * @param array $checkAry + * @access public + * @return bool + */ + public function strposAry($str, $checkAry) + { + foreach($checkAry as $check) + { + if(mb_strpos($str, $check) !== false) return true; + } + + return false; + } + + /** + * 更新版本库最后提交时间。 + * Update repo last commited date. + * + * @param int $repoID + * @access public + * @return void + */ + public function updateCommitDate(int $repoID): void + { + $repo = $this->getByID($repoID); + if($repo->SCM == 'Gitlab') + { + $scm = $this->app->loadClass('scm'); + $scm->setEngine($repo); + $commits = $scm->engine->getCommitsByPath('', '', '', 1, 1); + $commit = $commits[0]; + if(!empty($commit->committed_date)) + { + $lastCommitDate = date('Y-m-d H:i:s', strtotime($commit->committed_date)); + $this->dao->update(TABLE_REPO)->set('lastCommit')->eq($lastCommitDate)->where('id')->eq($repoID)->exec(); + } + } + } } diff --git a/module/repo/ui/blame.html.php b/module/repo/ui/blame.html.php index f460619f69..afa3a96148 100644 --- a/module/repo/ui/blame.html.php +++ b/module/repo/ui/blame.html.php @@ -53,7 +53,11 @@ foreach($blames as $key => $blame) $blames[$key]['content'] = htmlSpecialString($blame['content']); } -foreach($blames as $key => $blame) $blames[$key] = (object)$blame; +foreach($blames as $key => $blame) +{ + $blame['content'] = str_replace(' ', '  ', $blame['content']); + $blames[$key] = (object)$blame; +} $blames = initTableData($blames, $config->repo->blameDtable->fieldList, $this->repo); @@ -65,7 +69,7 @@ foreach($lang->repo->encodingList as $key => $val) } $defaultEncode = $lang->repo->encodingList[$encoding]; -featureBar( +\zin\featureBar( backBtn ( setClass('mr-5'), diff --git a/module/repo/ui/browse.html.php b/module/repo/ui/browse.html.php index 0c8a398724..5c45732faa 100644 --- a/module/repo/ui/browse.html.php +++ b/module/repo/ui/browse.html.php @@ -14,7 +14,13 @@ namespace zin; jsVar('copied', $lang->repo->copied); -if($app->tab == 'devops') dropmenu(set::module('repo'), set::tab('repo')); +$module = $app->tab == 'devops' ? 'repo' : $app->tab; +dropmenu +( + set::module($module), + set::tab($module), + set::url(createLink($module, $app->tab == 'devops' ? 'ajaxGetDropMenu' : 'ajaxGetDropMenuData', "objectID=$objectID&module={$app->rawModule}&method={$app->rawMethod}")) +); /* Prepare repo select data. */ $branchMenus = array(); @@ -26,7 +32,7 @@ foreach($branches as $branchName) $base64BranchID = helper::safe64Encode(base64_encode($branchName)); $branchLink = $this->createLink('repo', 'browse', "repoID=$repoID&branchID=$base64BranchID&objectID=$objectID"); - $branchMenus[] = array('text' => $branchName, 'id' => $branchName, 'keys' => zget(common::convert2Pinyin(array($branchName), $branchName), ''), 'url' => $branchLink); + $branchMenus[] = array('text' => $branchName, 'id' => $branchName, 'keys' => zget(common::convert2Pinyin(array($branchName), $branchName), ''), 'url' => $branchLink, 'data-app' => $app->tab); } foreach($tags as $tagName) { @@ -34,7 +40,7 @@ foreach($tags as $tagName) $base64TagID = helper::safe64Encode(base64_encode($tagName)); $tagLink = $this->createLink('repo', 'browse', "repoID=$repoID&branchID=$base64TagID&objectID=$objectID&path=&revision=HEAD&refresh=0&branchOrTag=tag"); - $tagMenus[] = array('text' => $tagName, 'id' => $tagName, 'keys' => zget(common::convert2Pinyin(array($tagName), $tagName), ''), 'url' => $tagLink); + $tagMenus[] = array('text' => $tagName, 'id' => $tagName, 'keys' => zget(common::convert2Pinyin(array($tagName), $tagName), ''), 'url' => $tagLink, 'data-app' => $app->tab); } $tabs = array(array('name' => 'branch', 'text' => $lang->repo->branch), array('name' => 'tag', 'text' => $lang->repo->tag)); @@ -66,27 +72,33 @@ foreach($paths as $index => $pathName) if($fileName) $breadcrumbItems[] = h::span($fileName); /* zin: Define the set::module('repo') feature bar on main menu. */ -featureBar( +\zin\featureBar( formGroup ( set::className('repo-select'), set::required(true), - dropmenu + $app->tab == 'project' ? dropmenu + ( + set::id('repoDropmenu'), + set::text($repo->name), + set::url(createLink('repo', 'ajaxGetDropMenu', "repoID={$repo->id}&module=repo&method=browse&projectID={$objectID}")) + ) : null, + $repo->SCM != 'Subversion' ? dropmenu ( setID('repoBranchDropMenu'), set::objectID($selected), set::text($selected), set::data(array('data' => $menuData, 'tabs' => $tabs)), - ), + ) : null, ), ...$breadcrumbItems ); /* zin: Define the toolbar on main menu. */ $refreshLink = $this->createLink('repo', 'browse', "repoID=$repoID&branchID=" . $base64BranchID . "&objectID=$objectID&path=" . $this->repo->encodePath($path) . "&revision=$revision&refresh=1"); -$refreshItem = array('text' => $lang->refresh, 'url' => $refreshLink, 'class' => 'primary', 'icon' => 'refresh'); +$refreshItem = array('text' => $lang->refresh, 'url' => $refreshLink, 'class' => 'primary', 'icon' => 'refresh', 'data-app' => $app->tab); -$createItem = array('text' => $lang->repo->createAction, 'url' => createLink('repo', 'create', "objectID={$objectID}")); +$createItem = array('text' => $lang->repo->createAction, 'url' => createLink('repo', 'create', "objectID={$objectID}"), 'data-app' => $app->tab); $tableData = initTableData($infos, $config->repo->repoDtable->fieldList, $this->repo); @@ -95,7 +107,7 @@ $downloadWg = div set::id('modal-downloadCode'), set::title($lang->repo->downloadCode), on('click', '', array('capture' => true, 'prevent' => true, 'stop' => true)), - $cloneUrl->svn ? div + !empty($cloneUrl->svn) ? div ( p(set::className('repo-downloadCode'), $lang->repo->cloneUrl), formRow @@ -106,6 +118,7 @@ $downloadWg = div input ( set::type('text'), + set::name('svnUrl'), set::value($cloneUrl->svn), set::readOnly(true), ), @@ -121,7 +134,7 @@ $downloadWg = div ), ) : null, - $cloneUrl->ssh ? div + !empty($cloneUrl->ssh) ? div ( p(set::className('repo-downloadCode'), $lang->repo->sshClone), formRow @@ -132,6 +145,7 @@ $downloadWg = div input ( set::type('text'), + set::name('sshUrl'), set::value($cloneUrl->ssh), set::readOnly(true), ), @@ -148,7 +162,7 @@ $downloadWg = div ), ) : null, - $cloneUrl->http ? div + !empty($cloneUrl->http) ? div ( p(set::className('repo-downloadCode'), $lang->repo->httpClone), formRow @@ -159,6 +173,7 @@ $downloadWg = div input ( set::type('text'), + set::name('httpUrl'), set::value($cloneUrl->http), set::readOnly(true), ), @@ -193,7 +208,7 @@ toolbar set::className('last-sync-time'), $lang->repo->notice->lastSyncTime . $cacheTime ), - item(set($refreshItem)), + $repo->SCM != 'Gitlab' ? item(set($refreshItem)) : null, dropdown ( set::staticMenu(true), @@ -205,10 +220,10 @@ toolbar ), to::items ( - array($downloadWg) + array($downloadWg) ), ), - hasPriv('repo', 'create') && $this->app->tab == 'project' ? item + hasPriv('repo', 'create') && $app->tab == 'project' ? item ( set($createItem + array ( @@ -232,15 +247,16 @@ dtable $encodePath = $this->repo->encodePath($path); $diffLink = $this->repo->createLink('diff', "repoID=$repoID&objectID=$objectID&entry=" . $encodePath . "&oldrevision={oldRevision}&newRevision={newRevision}"); -jsVar('repoID', $repoID); -jsVar('branch', $branchID); -jsVar('menus', $menus); -jsVar('diffLink', $diffLink); +jsVar('appTab', $app->tab); +jsVar('repoID', $repoID); +jsVar('branch', $branchID); +jsVar('diffLink', $diffLink); jsVar('sortLink', helper::createLink('repo', 'browse', "repoID={$repoID}&recTotal={$pager->recTotal}&recPerPage={$pager->recPerPage}&pageID={$pager->pageID}")); /* Disbale check all checkbox of table header */ $config->repo->commentDtable->fieldList['id']['checkbox'] = jsRaw('(rowID) => rowID !== \'HEADER\''); +if($repo->SCM == 'Gitlab') unset($config->repo->commentDtable->fieldList['commit']); $commentsTableData = initTableData($revisions, $config->repo->commentDtable->fieldList, $this->repo); $readAllLink = $this->repo->createLink('log', "repoID=$repoID&objectID=$objectID&entry=" . $encodePath . "&revision=HEAD&type=$logType"); @@ -261,7 +277,7 @@ sidebar set::canRowCheckable(jsRaw('function(rowID){return canRowCheckable(rowID);}')), set::footToolbar($footToolbar), set::footer(array('toolbar', 'flex', 'pager')), - set::footPager(usePager()), + set::footPager(usePager('pager', 'noTotalCount')), set::showToolbarOnChecked(false), ), ); diff --git a/module/repo/ui/create.html.php b/module/repo/ui/create.html.php index bb65e778b2..069a5b6369 100644 --- a/module/repo/ui/create.html.php +++ b/module/repo/ui/create.html.php @@ -10,7 +10,15 @@ declare(strict_types=1); */ namespace zin; -if($app->tab != 'devops') dropmenu(set::module('repo'), set::tab('repo')); +if($this->app->tab != 'devops') +{ + dropmenu + ( + set::module($app->tab), + set::tab($app->tab), + set::url(createLink($app->tab, 'ajaxGetDropMenuData', "objectID=$objectID&module={$app->rawModule}&method={$app->rawMethod}")) + ); +} jsVar('pathGitTip', $lang->repo->example->path->git); jsVar('pathSvnTip', $lang->repo->example->path->svn); @@ -25,7 +33,7 @@ formPanel on::change('#serviceHost', 'onHostChange'), on::change('#serviceProject', 'onProjectChange'), set::title($lang->repo->createAction), - set::back('repo-maintain'), + set::back('GLOBAL'), formRow ( $this->app->tab != 'devops' ? setClass('hidden') : null, @@ -37,7 +45,7 @@ formPanel set::required(true), set::control(array("type" => "picker","multiple" => true)), set::items($products), - set::value(empty($objectID) ? '' : array_keys($products)) + set::value(empty($objectID) ? '' : implode(',', array_keys($products))) ), ), formGroup @@ -47,7 +55,7 @@ formPanel set::label($lang->repo->projects), set::control(array("type" => "picker","multiple" => true)), set::items($projects), - set::value(empty($relatedProjects) ? '' : $relatedProjects) + set::value(empty($relatedProjects) ? '' : implode(',', array_values($relatedProjects))) ), formRow ( diff --git a/module/repo/ui/diff.html.php b/module/repo/ui/diff.html.php index 7971948e06..a5cc6b61f5 100644 --- a/module/repo/ui/diff.html.php +++ b/module/repo/ui/diff.html.php @@ -12,7 +12,13 @@ declare(strict_types=1); namespace zin; -if($app->tab == 'devops') dropmenu(set::module('repo'), set::tab('repo')); +$module = $app->tab == 'devops' ? 'repo' : $app->tab; +dropmenu +( + set::module($module), + set::tab($module), + set::url(createLink($module, $app->tab == 'devops' ? 'ajaxGetDropMenu' : 'ajaxGetDropMenuData', "objectID=$objectID&module={$app->rawModule}&method={$app->rawMethod}")) +); jsVar('repo', $repo); jsVar('repoLang', $lang->repo); @@ -65,11 +71,11 @@ if(strpos($repo->SCM, 'Subversion') === false) $breadcrumbItems[] = input(set::type('hidden'), set::name('oldRevision'), set::value($oldRevision)); $breadcrumbItems[] = input(set::type('hidden'), set::name('newRevision'), set::value($newRevision)); $breadcrumbItems[] = input(set::type('hidden'), set::name('isBranchOrTag'), set::value($isBranchOrTag)); - $breadcrumbItems[] = span($lang->repo->source . ':'); + $breadcrumbItems[] = span($lang->repo->source . ':', setClass('ml-3')); $breadcrumbItems[] = dropmenu ( setID('source'), - set::objectID($selected), + set::objectID($objectID), set::text($oldRevision), set::data(array('data' => $menuData, 'tabs' => $tabs)), ); @@ -78,7 +84,7 @@ if(strpos($repo->SCM, 'Subversion') === false) $breadcrumbItems[] = dropmenu ( setID('target'), - set::objectID($selected), + set::objectID($objectID), set::text($newRevision), set::data(array('data' => $menuData, 'tabs' => $tabs)), ); @@ -123,9 +129,9 @@ else ); } -featureBar +\zin\featureBar ( - backBtn(set::icon('back'), setClass('bg-transparent diff-back-btn'), $lang->goback), + backBtn(set::icon('back'), setClass('bg-transparent diff-back-btn'), set::back('GLOBAL'), $lang->goback), item(set::type('divider')), ...$breadcrumbItems, ); diff --git a/module/repo/ui/diffeditor.html.php b/module/repo/ui/diffeditor.html.php index 9f34c58059..b8f4ad162d 100644 --- a/module/repo/ui/diffeditor.html.php +++ b/module/repo/ui/diffeditor.html.php @@ -27,7 +27,7 @@ jsVar('entry', $entry); jsVar('diffLink', $diffLink); jsVar('urlParams', "repoID=$repoID&objectID=$objectID&entry=%s&oldRevision=$oldRevision&newRevision=$newRevision&showBug=$showBug&encoding=$encoding"); -featureBar(); +\zin\featureBar(); $dropMenus = array(); if(common::hasPriv('repo', 'download')) $dropMenus[] = array('text' => $this->lang->repo->downloadDiff, 'icon' => 'download', 'data-link' => $this->repo->createLink('download', "repoID=$repoID&path={path}&fromRevision=$oldRevision&toRevision=$newRevision&type=path"), 'id' => 'repoDownloadCode'); @@ -60,6 +60,7 @@ div( ( set::arrow(false), set::staticMenu(true), + on::click('#repoDownloadCode', 'downloadCode'), set::className('absolute top-0 right-0 z-10 monaco-dropmenu'), btn ( diff --git a/module/repo/ui/import.html.php b/module/repo/ui/import.html.php index 2508564171..0aa655b3a9 100644 --- a/module/repo/ui/import.html.php +++ b/module/repo/ui/import.html.php @@ -12,7 +12,7 @@ namespace zin; $items = array(); $items[] = array('name' => 'no', 'label' => $lang->user->abbr->id, 'control' => 'static', 'width' => '32px', 'class' => 'no'); -$items[] = array('name' => 'serviceProject', 'hidden' => true); +$items[] = array('name' => 'serviceProject', 'label' => '', 'hidden' => true); $items[] = array('name' => 'name_with_namespace', 'label' => $lang->repo->repo, 'control' => 'static', 'width' => '264px'); $items[] = array('name' => 'name', 'label' => $lang->repo->importName); $items[] = array('name' => 'product', 'label' => $lang->repo->product, 'control' => array('type' => 'picker', 'multiple' => true), 'items' => $products); @@ -25,7 +25,7 @@ foreach($repoList as $repo) $repo->no = $no ++; } -featureBar +\zin\featureBar ( h::a ( diff --git a/module/repo/ui/log.html.php b/module/repo/ui/log.html.php index c8a47dba13..38120acf9f 100644 --- a/module/repo/ui/log.html.php +++ b/module/repo/ui/log.html.php @@ -10,7 +10,13 @@ declare(strict_types=1); */ namespace zin; -if($app->tab == 'devops') dropmenu(set::module('repo'), set::tab('repo')); +$module = $app->tab == 'devops' ? 'repo' : $app->tab; +dropmenu +( + set::module($module), + set::tab($module), + set::url(createLink($module, $app->tab == 'devops' ? 'ajaxGetDropMenu' : 'ajaxGetDropMenuData', "objectID=$objectID&module={$app->rawModule}&method={$app->rawMethod}")) +); $diffLink = $this->repo->createLink('diff', "repoID=$repoID&objectID=$objectID&entry=" . $this->repo->encodePath($entry) . "&oldrevision={oldRevision}&newRevision={newRevision}"); @@ -55,7 +61,7 @@ $logs = initTableData($logs, $config->repo->logDtable->fieldList); $footToolbar['items'][] = array('text' => $lang->repo->diff, 'className' => "btn primary size-sm btn-diff", 'btnType' => 'primary', 'onClick' => jsRaw('window.diffClick')); -featureBar( +\zin\featureBar( backBtn ( setClass('mr-5'), diff --git a/module/repo/ui/maintain.html.php b/module/repo/ui/maintain.html.php index 416ec2df45..7b6259b6b8 100644 --- a/module/repo/ui/maintain.html.php +++ b/module/repo/ui/maintain.html.php @@ -35,8 +35,9 @@ foreach($repoList as $repo) foreach($productList as $productID) { if(!isset($products[$productID])) continue; - $repo->productNames .= ' ' . zget($products, $productID, $productID); + $repo->productNames .= ',' . zget($products, $productID, $productID); } + $repo->productNames = trim($repo->productNames, ','); } $repo->projectNames = ''; @@ -46,20 +47,23 @@ foreach($repoList as $repo) foreach($projectList as $projectID) { if(!isset($projects[$projectID])) continue; - $repo->projectNames .= ' ' . zget($projects, $projectID, $projectID); + $repo->projectNames .= ',' . zget($projects, $projectID, $projectID); } + $repo->projectNames = trim($repo->projectNames, ','); } } $config->repo->dtable->fieldList['name']['link'] = $this->createLink('repo', 'browse', "repoID={id}&branchID=&objectID={$objectID}"); $config->repo->dtable->fieldList['actions']['list']['edit']['url'] = $this->createLink('repo', 'edit', "repoID={id}&objectID={$objectID}"); -$config->repo->dtable->fieldList['actions']['list']['delete']['url'] = $this->createLink('repo', 'delete', "repoID={id}&objectID={$objectID}"); +$config->repo->dtable->fieldList['actions']['list']['delete']['url'] = $this->createLink('repo', 'delete', "repoID={id}&objectID={$objectID}&confirm=yes"); $repos = initTableData($repoList, $config->repo->dtable->fieldList, $this->repo); +$queryMenuLink = createLink('repo', 'maintain', "objectID=$objectID&orderBy=&recTotal={$pager->recTotal}&pageID={$pager->pageID}&type=bySearch¶m={queryID}"); -featureBar +\zin\featureBar ( set::current('all'), + set::queryMenuLinkCallback(fn($key) => str_replace('{queryID}', (string)$key, $queryMenuLink)), li(searchToggle(set::open($type == 'bySearch'))), ); diff --git a/module/repo/ui/monaco.html.php b/module/repo/ui/monaco.html.php index 94f78c1f0b..dcf85c73c5 100644 --- a/module/repo/ui/monaco.html.php +++ b/module/repo/ui/monaco.html.php @@ -12,7 +12,13 @@ declare(strict_types=1); namespace zin; -if($app->tab == 'devops') dropmenu(set::module('repo'), set::tab('repo')); +$module = $app->tab == 'devops' ? 'repo' : $app->tab; +dropmenu +( + set::module($module), + set::tab($module), + set::url(createLink($module, $app->tab == 'devops' ? 'ajaxGetDropMenu' : 'ajaxGetDropMenuData', "objectID=$objectID&module={$app->rawModule}&method={$app->rawMethod}")) +); $tree = $this->repo->getFileTree($repo); @@ -29,7 +35,7 @@ jsVar('openedFiles', array($entry)); jsVar('urlParams', "repoID=$repoID&objectID=$objectID&entry=%s&revision=$revision&showBug=$showBug&encoding=$encoding"); jsVar('currentLink', $this->createLink('repo', 'view', "repoID=$repoID&objectID=$objectID&entry=$file")); -featureBar(); +\zin\featureBar(); $monacoDropMenus = array(); if(common::hasPriv('repo', 'blame')) $monacoDropMenus[] = array('text' => $this->lang->repo->blame, 'icon' => 'blame', 'data-link' => $this->repo->createLink('blame', "repoID=$repoID&objectID=$objectID&entry={path}&revision=$revision&encoding=$encoding"), 'class' => 'repoDropDownMenu'); diff --git a/module/repo/ui/review.html.php b/module/repo/ui/review.html.php index 99f5fc180b..2cd67841ae 100644 --- a/module/repo/ui/review.html.php +++ b/module/repo/ui/review.html.php @@ -10,15 +10,19 @@ declare(strict_types=1); */ namespace zin; +dropmenu(set::tab('repo')); +jsVar('orderBy', $orderBy); +jsVar('sortLink', createLink('repo', 'review', "repoID=$repoID&browseType=$browseType&orderBy={orderBy}&recTotal={$pager->recTotal}&recPerPage={$pager->recPerPage}&pageID={$pager->pageID}")); + foreach($bugs as $bug) { - $bug->revisionA = $repo->SCM != 'Subversion' ? substr(strtr($bug->v2, '*', '-'), 0, 10) : $bug->v2; + $bug->revisionA = $repo->SCM != 'Subversion' ? strtr($bug->v2, '*', '-') : $bug->v2; $lines = explode(',', trim($bug->lines, ',')); - $bug->entry = $repo->name . '/' . $this->repo->decodePath($bug->entry); if(empty($bug->v1)) { - $revision = $repo->SCM != 'Subversion' ? $this->repo->getGitRevisionName($bug->v2, zget($historys, $bug->v2)) : $bug->v2; + $bug->v2 = $repo->SCM != 'Subversion' ? strtr($bug->v2, '*', '-') : $bug->v2; + $revision = $repo->SCM != 'Subversion' ? $this->repo->getGitRevisionName($bug->v2, zget($historys, $bug->v2)) : $bug->v2; $bug->link = $this->repo->createLink('view', "repoID=$repoID&objectID=0&entry={$bug->entry}&revision={$bug->v2}") . "#L$lines[0]"; } else @@ -29,10 +33,12 @@ foreach($bugs as $bug) if($repo->SCM != 'Subversion') $revision .= ' (' . zget($historys, $bug->v1) . ' : ' . zget($historys, $bug->v2) . ')'; $bug->link = $this->repo->createLink('diff', "repoID=$repoID&objectID=0&entry={$bug->entry}&oldRevision={$bug->v1}&newRevision={$bug->v2}") . "#L$lines[0]"; } + + $bug->entry = $repo->name . '/' . $this->repo->decodePath($bug->entry); } $bugs = initTableData($bugs, $config->repo->reviewDtable->fieldList); -featureBar +\zin\featureBar ( set::linkParams("repoID=$repoID&browseType={key}"), ); @@ -42,6 +48,7 @@ dtable set::userMap($users), set::cols($config->repo->reviewDtable->fieldList), set::data($bugs), + set::sortLink(jsRaw('createSortLink')), set::onRenderCell(jsRaw('window.renderRepobugList')), set::footPager(usePager()), ); diff --git a/module/repo/ui/showsynccommit.html.php b/module/repo/ui/showsynccommit.html.php index 33563d4180..7dbe07537f 100644 --- a/module/repo/ui/showsynccommit.html.php +++ b/module/repo/ui/showsynccommit.html.php @@ -12,7 +12,7 @@ declare(strict_types=1); namespace zin; -featureBar(); +\zin\featureBar(); if(empty($branch)) { @@ -40,7 +40,7 @@ div ( h3($lang->repo->notice->syncing), div(setClass('sync-line')), - p($lang->repo->notice->syncedCount, span($version, set::id('commits'))) + $repo->SCM != 'Gitlab' ? p($lang->repo->notice->syncedCount, span($version, set::id('commits'))) : null ) ) ) diff --git a/module/repo/view/ajaxgetrelationinfo.html.php b/module/repo/view/ajaxgetrelationinfo.html.php index a8c2b2da38..fc0b189484 100644 --- a/module/repo/view/ajaxgetrelationinfo.html.php +++ b/module/repo/view/ajaxgetrelationinfo.html.php @@ -21,39 +21,39 @@ js::set('objectID', zget($object, 'id', '')); - + -
+
-
+
- + -
+
-
+
@@ -61,13 +61,13 @@ js::set('objectID', zget($object, 'id', '')); - ' style="font-size: 14px;"> + '> -
+
@@ -94,7 +94,7 @@ $(function() { var link = createLink(objectType, 'view', objectType + 'ID=' + objectID); var app = objectType == 'bug' ? 'qa' : (objectType == 'task' ? 'execution' : 'product'); - parent.parent.openUrl(link, {'app': app}); + parent.parent.$.apps.open(link, app); }); }); diff --git a/module/repo/view/linkbug.html.php b/module/repo/view/linkbug.html.php index d96d67709c..e739cd87d0 100644 --- a/module/repo/view/linkbug.html.php +++ b/module/repo/view/linkbug.html.php @@ -42,7 +42,7 @@ pri;?>>priAB;?> bug->title;?> openedByAB;?> - bug->abbr->assignedTo;?> + bug->assignedToAB;?> bug->status;?> diff --git a/module/repo/view/showsynccommit.html.php b/module/repo/view/showsynccommit.html.php index fb206c7758..a4e7eb70ae 100644 --- a/module/repo/view/showsynccommit.html.php +++ b/module/repo/view/showsynccommit.html.php @@ -39,6 +39,11 @@ $(function(){ $('#caption').text('repo->notice->syncComplete?>'); return self.location = ''; } + if(data == 'error') + { + $('#mainContent .content p').text('repo->notice->syncFailed?>'); + return; + } $('#commits').html(parseInt($('#commits').html()) + parseInt(data)); setTimeout(syncComments, 10); }); diff --git a/module/repo/zen.php b/module/repo/zen.php index 81468313af..49f9a18127 100644 --- a/module/repo/zen.php +++ b/module/repo/zen.php @@ -287,23 +287,19 @@ class repoZen extends repo */ protected function prepareBatchCreate(): array|false { + if(!$this->post->serviceProject) return false; + $this->app->loadLang('testcase'); $data = array(); - foreach($_POST as $key => $vals) + foreach($this->post->serviceProject as $i => $project) { - if(strpos($key, 'serviceProject') === 0) - { - foreach($vals as $i => $project) - { - $products = array_filter($this->post->product[$i]); - if(empty($products)) dao::$errors['product'][] = sprintf($this->lang->testcase->whichLine . $this->lang->error->notempty, $i, $this->lang->repo->product); - if($this->post->name[$i] == '') dao::$errors['name'][] = sprintf($this->lang->testcase->whichLine . $this->lang->error->notempty, $i, $this->lang->repo->name); - if(dao::isError()) continue; + $products = array_filter($this->post->product[$i]); + if(empty($products)) continue; + if($this->post->name[$i] == '') dao::$errors['name_' . ($i -1)][] = sprintf($this->lang->error->notempty, $this->lang->repo->name); + if(dao::isError()) continue; - $data[] = array('serviceProject' => $project, 'product' => implode(',', $this->post->product[$i]), 'name' => $this->post->name[$i], 'projects' => empty($_POST['projects'][$i]) ? '' : implode(',', $this->post->projects[$i])); - } - } + $data[] = array('serviceProject' => $project, 'product' => implode(',', $this->post->product[$i]), 'name' => $this->post->name[$i], 'projects' => empty($_POST['projects'][$i]) ? '' : implode(',', $this->post->projects[$i])); } if(dao::isError()) return false; @@ -363,7 +359,7 @@ class repoZen extends repo else { $infos = $this->scm->ls($path, $revision); - $revisionList = array_column($infos, 'revision', 'revision'); + $revisionList = helper::arrayColumn($infos, 'revision', 'revision'); $comments = $this->repo->getHistory($repo->id, $revisionList); foreach($infos as $info) { @@ -374,11 +370,12 @@ class repoZen extends repo } } } - if($cacheFile) file_put_contents($cacheFile, serialize($infos), LOCK_EX); + if($cacheFile && !empty($infos)) file_put_contents($cacheFile, serialize($infos), LOCK_EX); } else { $infos = unserialize(file_get_contents($cacheFile)); + if(empty($infos)) unlink($cacheFile); } foreach($infos as $info) @@ -427,5 +424,122 @@ class repoZen extends repo return array('branchMenus' => $branchMenus, 'tagMenus' => $tagMenus, 'selected' => $selected); } + + /** + * 更新版本库最后提交时间。 + * Update repo last commited date. + * + * @param object $repo + * @param object $lastRevision + * @access protected + * @return void + */ + protected function updateLastCommit(object $repo, object $lastRevision): void + { + if(empty($lastRevision->committed_date)) return; + $lastCommitDate = date('Y-m-d H:i:s', strtotime($lastRevision->committed_date)); + if(empty($repo->lastCommit) || $lastCommitDate > $repo->lastCommit) $this->dao->update(TABLE_REPO)->set('lastCommit')->eq($lastCommitDate)->where('id')->eq($repo->id)->exec(); + } + + /** + * 获取browse方法项目、分支、tags信息。 + * Get project、branches、tags info for browse method. + * + * @param object $repo + * @access protected + * @return array + */ + protected function getBrowseInfo(object $repo): array + { + if($repo->SCM == 'Gitlab') + { + $scm = $this->app->loadClass('scm'); + $scm->setEngine($repo); + $urls['project']['url'] = $scm->engine->getApiUrl("project"); + $urls['branches']['url'] = $scm->engine->getApiUrl('branches'); + $urls['tags']['url'] = $scm->engine->getApiUrl('tags'); + + $this->app->loadClass('requests', true); + $result = requests::request_multiple($urls); + + if($result['project']->status_code == 200) + { + $project = json_decode($result['project']->body); + $this->loadModel('gitlab')->setProject((int)$repo->gitService, (int)$repo->project, $project); + } + if(!is_null($result['branches']->headers->offsetGet('x-total'))) + { + $branchList = json_decode($result['branches']->body); + $totalPages = $result['branches']->headers->offsetGet('x-total-pages'); + if($totalPages > 1) + { + $requests = array(); + for($page = 2; $page <= $totalPages; $page++) + { + $requests[$page]['url'] = str_replace('page=1', "page={$page}", $urls['branches']['url']); + } + + $reponses = requests::request_multiple($requests, array('timeout' => 10)); + foreach($reponses as $reponse) + { + $data = json_decode($reponse->body); + if(!is_array($data)) continue; + $branchList = array_merge($branchList, $data); + } + } + + $branches = array(); + $default = array(); + if(!empty($branchList) && is_array($branchList)) + { + foreach($branchList as $branch) + { + if(!isset($branch->name)) continue; + if($branch->default) + { + $default[$branch->name] = $branch->name; + } + else + { + $branches[$branch->name] = $branch->name; + } + } + + if(empty($branches) and empty($default)) $branches['master'] = 'master'; + asort($branches); + $branches = $default + $branches; + } + } + if(!is_null($result['tags']->headers->offsetGet('x-total'))) + { + $tagList = json_decode($result['tags']->body); + $totalPages = $result['tags']->headers->offsetGet('x-total-pages'); + if($totalPages > 1) + { + $requests = array(); + for($page = 2; $page <= $totalPages; $page++) + { + $requests[$page]['url'] = str_replace('page=1', "page={$page}", $urls['tags']['url']); + } + + $reponses = requests::request_multiple($requests, array('timeout' => 10)); + foreach($reponses as $reponse) + { + $data = json_decode($reponse->body); + if(!is_array($data)) continue; + $tagList = array_merge($tagList, $data); + } + } + + $tags = array(); + if(!empty($tagList) && is_array($tagList)) + { + foreach($tagList as $tag) $tags[] = $tag->name; + } + } + + return array(isset($branches) ? $branches : false, isset($tags) ? $tags : false); + } + } } diff --git a/module/serverroom/css/common.ui.css b/module/serverroom/css/common.ui.css index 233e6cd0bd..65ac76b2a2 100644 --- a/module/serverroom/css/common.ui.css +++ b/module/serverroom/css/common.ui.css @@ -1 +1,9 @@ -#serverroomCreateForm .form-row {width: 600px;} \ No newline at end of file +#serverroomCreateForm .form-row {width: 600px;} + +#mainNavbar>.container {padding-top: 8px} +#mainNavbar .nav {justify-content: left; position: relative; left: 0px; top: -4px;} +@media (min-width: 1400px){#mainNavbar .nav {left: -12px;}} +#mainNavbar {background: none;} +#mainNavbar .nav-item>a {padding-right: 0.5rem} +#mainNavbar .nav-item>.active {color: unset; font-weight: 700;} +#mainNavbar .nav-item>a.active:after {position: absolute; content: ''; border-bottom: 2px solid #2e7fff; inset: 0 6px 0 15px;} diff --git a/module/serverroom/model.php b/module/serverroom/model.php index 7f3144d363..2cfc1d6229 100644 --- a/module/serverroom/model.php +++ b/module/serverroom/model.php @@ -40,7 +40,7 @@ class serverroomModel extends model { if($param) { - $query = $this->loadModel('search')->getQuery($param); + $query = $this->loadModel('search')->getZinQuery($param); if($query) { $this->session->set('serverroomQuery', $query->sql); diff --git a/module/serverroom/ui/browse.html.php b/module/serverroom/ui/browse.html.php index 75dbb83aea..b415f4fa1d 100644 --- a/module/serverroom/ui/browse.html.php +++ b/module/serverroom/ui/browse.html.php @@ -12,7 +12,12 @@ declare(strict_types=1); namespace zin; -featureBar(li(searchToggle())); +$queryMenuLink = createLink('serverroom', 'browse', "browseType=bySearch¶m={queryID}"); +featureBar +( + set::queryMenuLinkCallback(fn($key) => str_replace('{queryID}', (string)$key, $queryMenuLink)), + li(searchToggle()) +); /* zin: Define the toolbar on main menu. */ $canCreate = hasPriv('serverroom', 'create'); diff --git a/module/solution/config.php b/module/solution/config.php new file mode 100644 index 0000000000..8e88853748 --- /dev/null +++ b/module/solution/config.php @@ -0,0 +1,5 @@ +solution = new stdclass(); + +$config->solution->gitlab = new stdclass(); +$config->solution->gitlab->minCompatibleVersion = '9.0'; diff --git a/module/solution/lang/zh-cn.php b/module/solution/lang/zh-cn.php new file mode 100644 index 0000000000..1220e55343 --- /dev/null +++ b/module/solution/lang/zh-cn.php @@ -0,0 +1,63 @@ +solution->market = new stdclass; +$lang->solution->market->browse = '解决方案市场'; +$lang->solution->market->view = '解决方案详情'; + +$lang->solution->name = '名称'; + +$lang->solution->browse = '已安装'; +$lang->solution->view = '解决方案详情'; +$lang->solution->detail = '查看'; +$lang->solution->progress = '安装进度'; +$lang->solution->install = '安装'; +$lang->solution->cancelInstall = '取消安装'; +$lang->solution->uninstall = '卸载'; +$lang->solution->retryInstall = '重试'; +$lang->solution->nextStep = '下一步'; +$lang->solution->config = '配置'; + +$lang->solution->introduction = '基本介绍'; +$lang->solution->scenes = '适用场景'; +$lang->solution->diagram = '架构图'; +$lang->solution->includedApp = '包含应用'; +$lang->solution->features = '方案亮点'; +$lang->solution->relatedLinks = '相关链接'; +$lang->solution->customers = '典型客户'; +$lang->solution->apps = '安装的应用'; +$lang->solution->externalApps = '外部应用'; +$lang->solution->resources = '资源占用'; + +$lang->solution->editName = '修改名称'; + +$lang->solution->chooseApp = '请选择要安装的应用'; +$lang->solution->noInstalledSolution = '还没有安装解决方案'; +$lang->solution->toInstall = '去安装'; + +$lang->solution->notices = new stdclass; +$lang->solution->notices->fail = '失败'; +$lang->solution->notices->success = '成功'; +$lang->solution->notices->creatingSolution = '正在创建解决方案。'; +$lang->solution->notices->uninstallingSolution = '正在卸载解决方案'; +$lang->solution->notices->installingApp = '正在安装:'; +$lang->solution->notices->installationSuccess = '解决方案安装成功!'; +$lang->solution->notices->cancelInstall = '确定要取消安装吗?'; +$lang->solution->notices->confirmToUninstall = '确定要卸载吗?'; +$lang->solution->notices->confirmReinstall = '确定要重试安装吗?'; + +$lang->solution->errors = new stdclass; +$lang->solution->errors->error = '错误'; +$lang->solution->errors->notFound = '找不到相关数据'; +$lang->solution->errors->failToInstallApp = '安装%s应用失败'; +$lang->solution->errors->timeout = '安装超时'; +$lang->solution->errors->failToUninstallApp = '卸载%s应用失败'; +$lang->solution->errors->hasInstallationError = '安装过程中发生错误'; +$lang->solution->errors->notFoundAppByVersion = '找不到%s版本的%s应用'; +$lang->solution->errors->notEnoughResource = '资源不足, 请增加配置或释放其它资源后重试。'; + +$lang->solution->installationErrors = array(); +$lang->solution->installationErrors['waiting'] = '安装未开始。'; +$lang->solution->installationErrors['uninstalling'] = '安装已取消。'; +$lang->solution->installationErrors['cneError'] = '安装失败。'; +$lang->solution->installationErrors['timeout'] = '安装超时。'; +$lang->solution->installationErrors['notFoundApp'] = '找不到待安装的应用。'; +$lang->solution->installationErrors['notEnoughResource'] = '资源不足, 请增加配置或释放其它资源后重试。'; diff --git a/module/solution/model.php b/module/solution/model.php new file mode 100644 index 0000000000..acf8f8ad02 --- /dev/null +++ b/module/solution/model.php @@ -0,0 +1,519 @@ + + * @package solution + * @version $Id$ + * @link https://www.qucheng.com + */ +class solutionModel extends model +{ + /** + * Get solution by id. + * + * @param int $id + * @access public + * @return object|null + */ + public function getByID($id) + { + $solution = $this->dao->select('*')->from(TABLE_SOLUTION)->where('id')->eq($id)->fetch(); + if(!$solution) return null; + + $instanceIDList = $this->dao->select('id')->from(TABLE_INSTANCE)->where('solution')->eq($id)->fetchAll('id'); + + $solution->instances = array(); + if($instanceIDList) $solution->instances = $this->loadModel('instance')->getByIDList(array_keys($instanceIDList)); + + return $solution; + } + + /** + * Search + * + * @param string $keyword + * @access public + * @return array + */ + public function search($keyword = '') + { + return $this->dao->select('*')->from(TABLE_SOLUTION) + ->where('deleted')->eq(0) + ->beginIF($keyword)->andWhere('name')->like($keyword)->fi() + ->orderBy('createdAt desc')->fetchAll(); + } + + /** + * Update solution name. + * + * @param int $solutionID + * @access public + * @return int + */ + public function updateName($solutionID) + { + $newSolution = fixer::input('post')->trim('name')->get(); + + return $this->dao->update(TABLE_SOLUTION)->data($newSolution)->autoCheck()->where('id')->eq($solutionID)->exec(); + } + + /** + * Create by solution of cloud market. + * + * @param object $cloudSolution + * @access public + * @return object + */ + public function create($cloudSolution, $components) + { + $postedCharts = $this->session->solutionCharts == '' ? fixer::input('post')->get() : $this->session->solutionCharts; + + /* Sort selected apps. */ + $orderedCategories = $components->order; + $selectedApps = array(); + foreach($orderedCategories as $category) + { + $chart = zget($postedCharts, $category); + + $selectedApps[$category] = $this->pickAppFromSchema($components, $category, $chart, $cloudSolution); + if(empty($selectedApps[$category])) unset($selectedApps[$category]); + } + + /* Create solution. */ + $solution = new stdclass; + $solution->name = $cloudSolution->title; + $solution->appID = $cloudSolution->id; + $solution->appName = $cloudSolution->name; + $solution->appVersion = $cloudSolution->app_version; + $solution->version = $cloudSolution->version; + $solution->chart = $cloudSolution->chart; + $solution->cover = $cloudSolution->background_url; + $solution->introduction = $cloudSolution->introduction; + $solution->desc = $cloudSolution->description; + $solution->status = 'waiting'; + $solution->source = 'cloud'; + $solution->components = json_encode($selectedApps); + $solution->createdBy = $this->app->user->account; + $solution->createdAt = date('Y-m-d H:i:s'); + + $channel = $this->app->session->cloudChannel ? $this->app->session->cloudChannel : $this->config->cloud->api->channel; + + $solution->channel = $channel; + + $this->dao->insert(TABLE_SOLUTION)->data($solution)->exec(); + + if(dao::isError()) return null; + + return $this->getByID($this->dao->lastInsertID()); + } + + /** + * Pick App from schema info by category and chart. + * + * @param object $schema + * @param string $category + * @param string $chart + * @param object $cloudSolution + * @access public + * @return object|null + */ + public function pickAppFromSchema($schema, $category, $chart, $cloudSolution) + { + $categoryList = helper::arrayColumn($schema->category, null, 'name'); + $appGroup = zget($categoryList, $category, array()); + + foreach($appGroup->choices as $appInSchema) + { + + if($appInSchema->name != $chart) continue; + + $appInfo = zget($cloudSolution->apps, $chart); + + $appInfo->version = $appInSchema->version; + $appInfo->app_version = $appInSchema->app_version; + $appInfo->status = 'waiting'; + + return $appInfo; + } + + return; + } + + /** + * Install solution. + * + * @param int $solutionID + * @access public + * @return bool + */ + public function install($solutionID) + { + set_time_limit(0); + session_write_close(); + + $solution = $this->getByID($solutionID); + if(!$solution) + { + dao::$errors[] = $this->lang->solution->errors->notFound; + return false; + } + if(in_array($solution->status, array('installing', 'installed', 'uninstalled'))) return false; + $this->saveStatus($solutionID, 'installing'); + + $this->loadModel('cne'); + $this->loadModel('instance'); + $this->loadModel('store'); + $this->loadModel('common'); + $allMappings = array(); + $solutionSchema = $this->loadModel('store')->solutionConfig('id', $solution->appID); + $channel = $this->app->session->cloudChannel ? $this->app->session->cloudChannel : $this->config->cloud->api->channel; + $components = json_decode($solution->components); + foreach($components as $categorty => $componentApp) + { + $solutionStatus = $this->dao->select('status')->from(TABLE_SOLUTION)->where('id')->eq($solutionID)->fetch(); + if($solutionStatus->status !='installing') + { + /* If status is not installing, should abort installation. Becaust installation was canceled or error happened. */ + dao::$errors[] = $this->lang->solution->errors->hasInstallationError; + return false; + } + + $instance = $this->instance->instanceOfSolution($solution, $componentApp->chart); + /* If not install. */ + if(!$instance) + { + $cloudApp = $this->store->getAppInfo($componentApp->id, false, '', $componentApp->version, $channel); + if(!$cloudApp) + { + $this->saveStatus($solutionID, 'notFoundApp'); + dao::$errors[] = sprintf($this->lang->solution->errors->notFoundAppByVersion, $componentApp->version, $componentApp->alias); + return false; + } + /* Must install the defineded version in solution schema. */ + $cloudApp->version = $componentApp->version; + $cloudApp->app_version = $componentApp->app_version; + + if($componentApp->external) + { + $instance = $this->installExternalApp($cloudApp, $componentApp->external); + } + else + { + /* Check enough memory to install app, or not.*/ + if(!$this->instance->enoughMemory($cloudApp)) + { + $this->saveStatus($solutionID, 'notEnoughResource'); + dao::$errors[] = $this->lang->solution->errors->notEnoughResource; + return false; + } + + if(!$this->checkInstallStatus($solutionID)) return false; + $settings = $this->mountSettings($solutionSchema, $componentApp->chart, $components, $allMappings, isset($components->sonarqube)); + $instance = $this->installApp($cloudApp, $settings); + } + + if(!$instance) + { + $this->saveStatus($solutionID, 'cneError'); + dao::$errors[] = sprintf($this->lang->solution->errors->failToInstallApp, $cloudApp->name); + return false; + } + $this->dao->update(TABLE_INSTANCE)->set('solution')->eq($solutionID)->where('id')->eq($instance->id)->exec(); + + $componentApp->status = 'installing'; + $this->dao->update(TABLE_SOLUTION)->set('components')->eq(json_encode($components))->where('id')->eq($solution->id)->exec(); + } + + if($componentApp->external) + { + $tempMappings = $this->getExternalMapping($solutionSchema, $componentApp); + if($tempMappings) $allMappings[$categorty] = $tempMappings; + + $componentApp->status = 'configured'; + $this->dao->update(TABLE_SOLUTION)->set('components')->eq(json_encode($components))->where('id')->eq($solution->id)->exec(); + continue; + } + + /* Wait instanlled app started. */ + $instance = $this->waitInstanceStart($instance, $solutionID); + if($instance) + { + $mappingKeys = zget($solutionSchema->mappings, $instance->chart, ''); + if($mappingKeys) + { + /* Load settings mapping of installed app for next app. */ + $tempMappings = $this->cne->getSettingsMapping($instance, $mappingKeys); + if($tempMappings) $allMappings[$categorty] = $tempMappings; + } + $componentApp->status = 'installed'; + $this->dao->update(TABLE_SOLUTION)->set('components')->eq(json_encode($components))->where('id')->eq($solution->id)->exec(); + } + else + { + $this->saveStatus($solutionID, 'timeout'); + dao::$errors[] = $this->lang->solution->errors->timeout; + return false; + } + } + + $this->saveStatus($solutionID, 'installed'); + return true; + } + + /** + * Save status. + * + * @param int $solutionID + * @param string $status + * @access public + * @return int + */ + public function saveStatus($solutionID, $status) + { + return $this->dao->update(TABLE_SOLUTION)->set('status')->eq($status)->set('updatedDate')->eq(date("Y-m-d H:i:s"))->where('id')->eq($solutionID)->exec(); + } + + /** + * Mount settings for installing app. + * + * @param object $solutionSchema + * @param string $chart + * @param object $components + * @param array $mappings example: ['git' => ['env.GIT_USERNAME' => 'admin', ...], ...] + * @access private + * @return array + */ + private function mountSettings($solutionSchema, $chart, $components, $mappings, $isInstallSonar = true) + { + $settings = array(); + + $appSettings = zget($solutionSchema->settings, $chart, array()); + foreach($appSettings as $item) + { + switch($item->type) + { + case 'static': + if(!$isInstallSonar && $item->key === 'solution.sonarqube.enabled') + break; + $settings[] = array('key' => $item->key, 'value' => $item->value); + break; + case 'choose': + $appInfo = zget($components, $item->target, ''); + if($appInfo) $settings[] = array('key' => $item->key, 'value' => $appInfo->chart); + break; + case 'mappings': + $mappingInfo = zget($mappings, $item->target, ''); + if($mappingInfo) $settings[] = array('key' => $item->key, 'value' => zget($mappingInfo, $item->key, '')); + break; + } + } + + return $settings; + } + + /** + * installApp + * + * @param object $cloudApp + * @param int $settings + * @access private + * @return mixed + */ + private function installApp($cloudApp, $settings) + { + /* Fake parameters for installation. */ + + $customData = new stdclass; + $customData->customName = $cloudApp->alias; + $customData->dbType = null; + $customData->customDomain = $this->loadModel('instance')->randThirdDomain(); + + $dbInfo = new stdclass; + $dbList = $this->loadModel('cne')->sharedDBList(); + if(count($dbList) > 0) + { + $dbInfo = reset($dbList); + + $customData->dbType = 'sharedDB'; + $customData->dbService = $dbInfo->name; // Use first shared database. + } + + return $this->instance->install($cloudApp, $dbInfo, $customData, null, $settings); + } + + /** + * Wait instance started. + * + * @param object $instance + * @access private + * @return object|bool + */ + private function waitInstanceStart($instance, $solutionID) + { + /* Query status of the installed instance. */ + $times = 0; + for($times = 0; $times < 50; $times++) + { + if(!$this->checkInstallStatus($solutionID)) return false; + $this->dao->update(TABLE_SOLUTION)->set('updatedDate')->eq(date("Y-m-d H:i:s"))->where('id')->eq($solutionID)->exec(); + + sleep(12); + $instance = $this->instance->freshStatus($instance); + $this->saveLog(date('Y-m-d H:i:s').' installing ' . $instance->name . ':' . $instance->status . '#' . $instance->solution); // Code for debug. + if($instance->status == 'running') return $instance; + } + + return false; + } + + /** + * Check solution status. + * + * @param int $solutionID + * @access public + * @return void + */ + public function checkInstallStatus($solutionID) + { + $solution = $this->getByID($solutionID); + if($solution->status != 'installing') return false; + return true; + } + + /** + * Uninstall solution and all included instances . + * + * @param int $solutionID + * @access public + * @return void + */ + public function uninstall($solutionID) + { + $this->loadModel('instance'); + /* Firstly change the status to 'unintalling' for abort installing process. */ + $this->dao->update(TABLE_SOLUTION)->set('status')->eq('uninstalling')->where('id')->eq($solutionID)->exec(); + + $solution = $this->getByID($solutionID); + if(empty($solution)) + { + dao::$errors[] = $this->lang->solution->notFound; + return; + } + + foreach($solution->instances as $instance) + { + $success = $this->instance->uninstall($instance); + if(!$success) + { + dao::$errors[] = sprintf($this->lang->solution->errors->failToUninstallApp, $instance->name); + return; + } + } + + $this->dao->update(TABLE_SOLUTION)->set('status')->eq('uninstalled')->set('deleted')->eq(1)->where('id')->eq($solutionID)->exec(); + } + + /** + * Convert schema choices to select options. + * + * @param object $schemaChoices + * @param object $cloudSolution + * @access public + * @return array + */ + public function createSelectOptions($schemaChoices, $cloudSolution) + { + $options = array(); + foreach($schemaChoices as $cloudApp) + { + $appInfo = zget($cloudSolution->apps, $cloudApp->name, array()); + $options[$cloudApp->name] = zget($appInfo, 'alias', $cloudApp->name); + } + + return $options; + } + + /** + * Print CPU usage. + * + * @param object $solution + * @param object $metrics + * @param string $type 'bar' is progress bar, 'pie' is progress pie. + * @static + * @access public + * @return viod + */ + public function printCpuUsage($solution, $type = 'bar') + { + /* Calculate total usage of all instances. */ + $totalRate = 0; + $totalUsage = 0; + $totalLimit = 0; + $instancesMetric = $this->loadModel('cne')->instancesMetrics($solution->instances); + foreach($instancesMetric as $metric) + { + $totalRate += $metric->cpu->rate; + $totalUsage += $metric->cpu->usage; + $totalLimit += $metric->cpu->limit; + } + + $totalRate = round($totalRate / count($solution->instances), 2); + + $tip = "{$totalRate}% = {$totalUsage} / {$totalLimit}"; + + if(strtolower($type) == 'pie') return commonModel::printProgressPie($totalRate, '', $tip); + + return commonModel::printProgressBar($totalRate, '', $tip, 'percent'); + } + + /** + * Print memory usage. + * + * @param object $solution + * @param object $metrics + * @param string $type 'bar' is progress bar, 'pie' is progress pie. + * @static + * @access public + * @return viod + */ + public function printMemUsage($solution, $type = 'bar') + { + /* Calculate total usage of all instances. */ + $totalRate = 0; + $totalUsage = 0; + $totalLimit = 0; + $instancesMetric = $this->loadModel('cne')->instancesMetrics($solution->instances); + foreach($instancesMetric as $metric) + { + $totalRate += $metric->memory->rate; + $totalUsage += $metric->memory->usage; + $totalLimit += $metric->memory->limit; + } + + $totalRate = round($totalRate / count($solution->instances), 2); + + $tip = "{$totalRate}% = {$totalUsage} / {$totalLimit}"; + + if(strtolower($type) == 'pie') return commonModel::printProgressPie($totalRate, '', $tip); + + return commonModel::printProgressBar($totalRate, '', $tip, 'percent'); + } + + /** + * Save message to error log file. + * + * @param string $message + * @access public + * @return void + */ + public function saveLog($message) + { + $errorFile = $this->app->logRoot . 'php.' . date('Ymd') . '.log.php'; + if(!is_file($errorFile)) file_put_contents($errorFile, "\n"); + + file_put_contents($errorFile, $message . "\n", FILE_APPEND); + } +} diff --git a/module/sonarqube/control.php b/module/sonarqube/control.php index 3e0ff91f50..5051ee8f5d 100644 --- a/module/sonarqube/control.php +++ b/module/sonarqube/control.php @@ -20,6 +20,16 @@ class sonarqube extends control { parent::__construct($moduleName, $methodName); + if(stripos($this->methodName, 'ajax') === false) + { + if(!commonModel::hasPriv('space', 'browse')) $this->loadModel('common')->deny('space', 'browse', false); + + if(!in_array(strtolower(strtolower($this->methodName)), array('browseproject', 'reportview', 'browseissue'))) + { + if(!commonModel::hasPriv('instance', 'manage')) $this->loadModel('common')->deny('instance', 'manage', false); + } + } + /* This is essential when changing tab(menu) from gitlab to repo. */ /* Optional: common::setMenuVars('devops', $this->session->repoID); */ $this->loadModel('ci')->setMenu(); @@ -101,16 +111,7 @@ class sonarqube extends control */ public function ajaxGetProjectList($sonarqubeID, $projectKey = '') { - $jobPairs = $this->loadModel('job')->getJobBySonarqubeProject($sonarqubeID, array(), true, true); - $existsProject = array_diff(array_keys($jobPairs), array($projectKey)); - - $projectList = $this->sonarqube->apiGetProjects($sonarqubeID); - - $projectPairs = array(); - foreach($projectList as $project) - { - if(!empty($project) and !in_array($project->key, $existsProject)) $projectPairs[$project->key] = $project->name; - } + $projectPairs = $this->sonarqube->getProjectPairs($sonarqubeID, $projectKey); $options = array(); foreach($projectPairs as $productKey => $projectName) @@ -197,7 +198,7 @@ class sonarqube extends control if($_POST) { - $this->checkToken($sonarqubeID); + $this->checkToken($oldSonarQube, $sonarqubeID); $this->pipeline->update($sonarqubeID); $sonarqube = $this->pipeline->getByID($sonarqubeID); if(dao::isError()) return $this->send(array('result' => 'fail', 'message' => dao::getError())); @@ -239,7 +240,7 @@ class sonarqube extends control $changes = common::createChanges($oldSonarQube, $sonarQube); $this->action->logHistory($actionID, $changes); - $response['load'] = true; + $response['load'] = $this->createLink('space', 'browse'); $response['result'] = 'success'; return $this->send($response); @@ -296,13 +297,24 @@ class sonarqube extends control /* Get success jobs of sonarqube.*/ $projectJobPairs = $this->loadModel('job')->getJobBySonarqubeProject($sonarqubeID, $projectKeyList); $successJobs = $this->loadModel('compile')->getSuccessJobs($projectJobPairs); + $sonarqube = $this->loadModel('pipeline')->getByID($sonarqubeID); + $instance = $this->loadModel('instance')->getByUrl($sonarqube->url); - $this->view->sonarqube = $this->loadModel('pipeline')->getByID($sonarqubeID); + $sonarqube->instanceID = $sonarqube->id; + $sonarqube->type = 'external'; + if(!empty($instance->id)) + { + $sonarqube->instanceID = $instance->id; + $sonarqube->type = 'store'; + } + + $this->view->sonarqube = $sonarqube; $this->view->keyword = urldecode(urldecode($keyword)); $this->view->pager = $pager; $this->view->title = $this->lang->sonarqube->common . $this->lang->colon . $this->lang->sonarqube->browseProject; $this->view->sonarqubeID = $sonarqubeID; $this->view->sonarqubeProjectList = (empty($sonarqubeProjectList) or empty($sonarqubeProjectList[$pageID - 1])) ? array() : $sonarqubeProjectList[$pageID - 1]; + $this->view->projectJobPairs = $projectJobPairs; $this->view->orderBy = $orderBy; $this->view->successJobs = $successJobs; diff --git a/module/sonarqube/model.php b/module/sonarqube/model.php index 68f11d9a29..c6fc28a7e8 100644 --- a/module/sonarqube/model.php +++ b/module/sonarqube/model.php @@ -302,4 +302,71 @@ class sonarqubeModel extends model ->andWhere('projectKey')->eq($projectKey) ->fetch('product'); } + + /** + * 获取项目键值对。 + * Get project pairs. + * + * @param int $sonarqubeID + * @param string $projectKey + * @access public + * @return array + */ + public function getProjectPairs(int $sonarqubeID, string $projectKey = ''): array + { + $jobPairs = $this->loadModel('job')->getJobBySonarqubeProject($sonarqubeID, array(), true, true); + $existsProject = array_diff(array_keys($jobPairs), array($projectKey)); + + $projectList = $this->apiGetProjects($sonarqubeID); + + $projectPairs = array(); + foreach($projectList as $project) + { + if(!empty($project) and !in_array($project->key, $existsProject)) $projectPairs[$project->key] = $project->name; + } + + return $projectPairs; + } + + /** + * 判断按钮是否可点击。 + * Judge an action is clickable or not. + * + * @param object $sonarqube + * @param string $action + * @access public + * @return bool + */ + public static function isClickable(object $sonarqube, string $action): bool + { + $action = strtolower($action); + + if($action == 'execjob') return $sonarqube->exec == ''; + if($action == 'reportview') return $sonarqube->report == ''; + + return true; + } + + /** + * 判断按钮是否显示在列表页。 + * Judge an action is displayed in browse page. + * + * @param object $sonarqube + * @param string $action + * @access public + * @return bool + */ + public static function isDisplay(object $sonarqube, string $action): bool + { + $action = strtolower($action); + + if(!commonModel::hasPriv('space', 'browse')) return false; + + if(!in_array($action, array('browseproject', 'reportview', 'browseissue'))) + { + if(!commonModel::hasPriv('instance', 'manage')) return false; + } + + return true; + } } diff --git a/module/sonarqube/view/browseproject.html.php b/module/sonarqube/view/browseproject.html.php index 071b3fdf6c..49dbc6641f 100644 --- a/module/sonarqube/view/browseproject.html.php +++ b/module/sonarqube/view/browseproject.html.php @@ -13,10 +13,7 @@

noData;?> - - createLink('sonarqube', 'createProject', "sonarqubeID=$sonarqubeID"), " " . $lang->sonarqube->createProject, '', "class='btn btn-info'");?> + + createLink('instance', 'manage', "sonarqubeID=$sonarqubeID"), " " . $lang->sonarqube->createProject, '', "class='btn btn-info'");?>

@@ -43,7 +40,7 @@
- recTotal}&recPerPage={$pager->recPerPage}&pageID={$pager->pageID}";?> + recPerPage}&pageID={$pager->pageID}";?> diff --git a/module/space/config/dtable.php b/module/space/config/dtable.php index d3f9bae7f3..92cdd94972 100644 --- a/module/space/config/dtable.php +++ b/module/space/config/dtable.php @@ -10,21 +10,26 @@ $config->space->dtable = new stdclass(); $config->space->dtable->fieldList['name']['title'] = $lang->instance->name; $config->space->dtable->fieldList['name']['type'] = 'title'; -$config->space->dtable->fieldList['appName']['title'] = $lang->instance->appName; -$config->space->dtable->fieldList['appName']['type'] = 'text'; -$config->space->dtable->fieldList['appName']['width'] = '90'; +$config->space->dtable->fieldList['appName']['title'] = $lang->instance->appName; +$config->space->dtable->fieldList['appName']['type'] = 'text'; +$config->space->dtable->fieldList['appName']['width'] = '90'; +$config->space->dtable->fieldList['appName']['sortType'] = true; -$config->space->dtable->fieldList['status']['name'] = 'status'; -$config->space->dtable->fieldList['status']['title'] = $lang->space->status; -$config->space->dtable->fieldList['status']['type'] = 'category'; -$config->space->dtable->fieldList['status']['map'] = $lang->instance->statusList; -$config->space->dtable->fieldList['status']['group'] = 'status'; -$config->space->dtable->fieldList['status']['width'] = '80'; +if($config->inQuickon) +{ + $config->space->dtable->fieldList['status']['name'] = 'status'; + $config->space->dtable->fieldList['status']['title'] = $lang->space->status; + $config->space->dtable->fieldList['status']['type'] = 'category'; + $config->space->dtable->fieldList['status']['map'] = $lang->instance->statusList; + $config->space->dtable->fieldList['status']['group'] = 'status'; + $config->space->dtable->fieldList['status']['width'] = '80'; + $config->space->dtable->fieldList['status']['sortType'] = true; -$config->space->dtable->fieldList['appVersion']['title'] = $lang->store->appVersion; -$config->space->dtable->fieldList['appVersion']['type'] = 'text'; -$config->space->dtable->fieldList['appVersion']['group'] = 'version'; -$config->space->dtable->fieldList['appVersion']['width'] = '136'; + $config->space->dtable->fieldList['appVersion']['title'] = $lang->store->appVersion; + $config->space->dtable->fieldList['appVersion']['type'] = 'text'; + $config->space->dtable->fieldList['appVersion']['group'] = 'version'; + $config->space->dtable->fieldList['appVersion']['width'] = '136'; +} $config->space->dtable->fieldList['createdBy']['title'] = $lang->space->createdBy; $config->space->dtable->fieldList['createdBy']['type'] = 'user'; @@ -35,24 +40,25 @@ $config->space->dtable->fieldList['createdAt']['type'] = 'datetime'; $config->space->dtable->fieldList['createdAt']['group'] = 'created'; $config->space->dtable->fieldList['actions']['type'] = 'actions'; -$config->space->dtable->fieldList['actions']['menu'] = array('visit', 'start|stop', 'edit', 'bindUser', 'uninstall', 'upgrade'); +$config->space->dtable->fieldList['actions']['menu'] = array('visit', 'ajaxStart|ajaxStop', 'edit', 'bindUser', 'ajaxUninstall', 'upgrade'); +if(!$config->inQuickon) $config->space->dtable->fieldList['actions']['menu'] = array('visit', 'edit', 'bindUser', 'ajaxUninstall'); -$config->space->dtable->fieldList['actions']['list']['start']['icon'] = 'play'; -$config->space->dtable->fieldList['actions']['list']['start']['className'] = 'ajax-submit'; -$config->space->dtable->fieldList['actions']['list']['start']['hint'] = $lang->instance->start; -$config->space->dtable->fieldList['actions']['list']['start']['url'] = helper::createLink('instance', 'ajaxStart', "id={id}"); +$config->space->dtable->fieldList['actions']['list']['ajaxStart']['icon'] = 'play'; +$config->space->dtable->fieldList['actions']['list']['ajaxStart']['className'] = 'ajax-submit'; +$config->space->dtable->fieldList['actions']['list']['ajaxStart']['hint'] = $lang->instance->start; +$config->space->dtable->fieldList['actions']['list']['ajaxStart']['url'] = array('module' => 'instance', 'method' => 'ajaxStart', 'params' => "id={id}"); -$config->space->dtable->fieldList['actions']['list']['stop']['icon'] = 'off'; -$config->space->dtable->fieldList['actions']['list']['stop']['className'] = 'ajax-submit'; -$config->space->dtable->fieldList['actions']['list']['stop']['hint'] = $lang->instance->stop; -$config->space->dtable->fieldList['actions']['list']['stop']['url'] = helper::createLink('instance', 'ajaxStop', "id={id}"); -$config->space->dtable->fieldList['actions']['list']['stop']['data-confirm'] = $lang->instance->notices['confirmStop']; +$config->space->dtable->fieldList['actions']['list']['ajaxStop']['icon'] = 'off'; +$config->space->dtable->fieldList['actions']['list']['ajaxStop']['className'] = 'ajax-submit'; +$config->space->dtable->fieldList['actions']['list']['ajaxStop']['hint'] = $lang->instance->stop; +$config->space->dtable->fieldList['actions']['list']['ajaxStop']['url'] = array('module' => 'instance', 'method' => 'ajaxStop', 'params' => "id={id}"); +$config->space->dtable->fieldList['actions']['list']['ajaxStop']['data-confirm'] = $lang->instance->notices['confirmStop']; -$config->space->dtable->fieldList['actions']['list']['uninstall']['icon'] = 'trash'; -$config->space->dtable->fieldList['actions']['list']['uninstall']['hint'] = $lang->instance->uninstall; -$config->space->dtable->fieldList['actions']['list']['uninstall']['className'] = 'ajax-submit'; -$config->space->dtable->fieldList['actions']['list']['uninstall']['data-confirm'] = $lang->instance->notices['confirmUninstall']; -$config->space->dtable->fieldList['actions']['list']['uninstall']['url'] = array('module' => 'instance', 'method' => 'ajaxUninstall', 'params' => 'id={orgID}&type={type}'); +$config->space->dtable->fieldList['actions']['list']['ajaxUninstall']['icon'] = 'trash'; +$config->space->dtable->fieldList['actions']['list']['ajaxUninstall']['hint'] = $lang->instance->uninstall; +$config->space->dtable->fieldList['actions']['list']['ajaxUninstall']['className'] = 'ajax-submit'; +$config->space->dtable->fieldList['actions']['list']['ajaxUninstall']['data-confirm'] = $lang->instance->notices['confirmUninstall']; +$config->space->dtable->fieldList['actions']['list']['ajaxUninstall']['url'] = array('module' => 'instance', 'method' => 'ajaxUninstall', 'params' => 'id={orgID}&type={type}'); $config->space->dtable->fieldList['actions']['list']['visit']['icon'] = 'menu-my'; $config->space->dtable->fieldList['actions']['list']['visit']['hint'] = $lang->instance->visit; @@ -63,7 +69,7 @@ $config->space->dtable->fieldList['actions']['list']['upgrade']['icon'] = $config->space->dtable->fieldList['actions']['list']['upgrade']['data-toggle'] = 'modal'; $config->space->dtable->fieldList['actions']['list']['upgrade']['data-size'] = 'sm'; $config->space->dtable->fieldList['actions']['list']['upgrade']['hint'] = $lang->space->upgrade; -$config->space->dtable->fieldList['actions']['list']['upgrade']['url'] = helper::createLink('instance', 'upgrade', "id={id}"); +$config->space->dtable->fieldList['actions']['list']['upgrade']['url'] = array('module' => 'instance', 'method' => 'upgrade', 'params' => "id={id}"); $config->space->dtable->fieldList['actions']['list']['edit']['icon'] = 'edit'; $config->space->dtable->fieldList['actions']['list']['edit']['hint'] = $lang->edit; diff --git a/module/space/control.php b/module/space/control.php index dd8ecaa72c..1ff7b05c7d 100644 --- a/module/space/control.php +++ b/module/space/control.php @@ -23,8 +23,9 @@ class space extends control @access public * @return void */ - public function browse($spaceID = null, $browseType = 'all', $orderBy = 'id_desc', $recTotal = 0, $recPerPage = 24, $pageID = 1) + public function browse($spaceID = null, $browseType = 'all', $orderBy = 'id_desc', $recTotal = 0, $recPerPage = 20, $pageID = 1) { + if(!commonModel::hasPriv('space', 'browse')) $this->loadModel('common')->deny('space', 'browse', false); $this->app->loadLang('instance'); $this->loadModel('instance'); $this->loadModel('store'); @@ -45,12 +46,12 @@ class space extends control $search = $conditions->search; } - $instances = $this->space->getSpaceInstances($space->id, $browseType, $search); + $instances = $this->space->getSpaceInstances(0, $browseType, $search); foreach($instances as $instance) { $instance->externalID = 0; $instance->orgID = $instance->id; - $instance->type = 'app'; + $instance->type = 'store'; if(in_array($instance->appName, $this->config->space->zentaoApps)) { @@ -58,8 +59,9 @@ class space extends control if($externalApp) $instance->externalID = $externalApp->id; } } - $pipelines = $this->loadModel('pipeline')->getList('', 'id_desc'); $maxID = 0; + $pipelines = array(); + if($browseType == 'all' || $browseType == 'running') $pipelines = $this->loadModel('pipeline')->getList('', 'id_desc'); if(!empty($instances)) $maxID = max(array_keys($instances)); foreach($pipelines as $key => $pipeline) { @@ -67,7 +69,7 @@ class space extends control $pipeline->createdAt = $pipeline->createdDate; $pipeline->appName = $this->lang->space->appType[$pipeline->type]; - $pipeline->status = ''; + $pipeline->status = 'running'; $pipeline->type = 'external'; $pipeline->externalID = $pipeline->id; $pipeline->orgID = $pipeline->id; @@ -77,7 +79,7 @@ class space extends control /* Data sort. */ list($order, $sort) = explode('_', $orderBy); - $createdColumn = array_column((array)$allInstances, $order == 'id' ? 'createdAt' : $order); + $createdColumn = helper::arrayColumn($allInstances, $order == 'id' ? 'createdAt' : $order); array_multisort($createdColumn, $sort == 'desc' ? SORT_DESC : SORT_ASC, $allInstances); /* Pager. */ @@ -89,11 +91,14 @@ class space extends control $this->view->title = $this->lang->space->common; $this->view->position[] = $this->lang->space->common; $this->view->pager = $pager; + $this->view->orderBy = $orderBy; $this->view->browseType = $browseType; $this->view->spaceType = $spaceType; $this->view->instances = (empty($allInstances) or empty($allInstances[$pageID - 1])) ? array() : $allInstances[$pageID - 1]; $this->view->currentSpace = $space; $this->view->searchName = $search; + $this->view->users = $this->loadModel('user')->getPairs('noclosed,noletter'); + $this->view->sortLink = $this->createLink('space', 'browse', "spaceID=&browseType={$browseType}&orderBy={orderBy}&recTotal={$recTotal}&recPerPage={$recPerPage}"); $this->display(); } @@ -108,7 +113,7 @@ class space extends control */ public function createApplication($appID = 0) { - if(!commonModel::hasPriv('instance', 'create')) $this->loadModel('common')->deny('instance', 'create', false); + if(!commonModel::hasPriv('instance', 'manage')) $this->loadModel('common')->deny('instance', 'manage', false); $this->app->loadLang('sonarqube'); $this->app->loadLang('jenkins'); @@ -119,6 +124,7 @@ class space extends control $defaultApp = ''; foreach($pagedApps->apps as $app) { + if(in_array($app->id, array(29,51,52,53,54,142))) continue; if(!$appID and $app->alias == 'GitLab') $defaultApp = $app->id; $apps[$app->id] = $app->alias; @@ -128,6 +134,7 @@ class space extends control $pgList = $this->cne->sharedDBList('postgresql'); $versionList = $this->store->getVersionPairs($appID); + $this->view->title = $this->lang->space->install; $this->view->apps = $apps; $this->view->appID = $appID; $this->view->defaultApp = $defaultApp; @@ -149,7 +156,12 @@ class space extends control */ public function getStoreAppInfo(int $appID) { - $cloudApp = $this->loadModel('store')->getAppInfo($appID); + if(!commonModel::hasPriv('space', 'browse')) $this->loadModel('common')->deny('space', 'browse', false); + $cloudApp = $this->loadModel('store')->getAppInfo($appID); + $versionPairs = $this->store->getVersionPairs($appID); + $versionItems = array(); + foreach($versionPairs as $k => $v) $versionItems[] = array('text' => $v, 'value' => $k); + $cloudApp->versionList = $versionItems; return print(json_encode($cloudApp)); } diff --git a/module/space/js/browse.ui.js b/module/space/js/browse.ui.js index 49d00e60ce..85622b93cc 100644 --- a/module/space/js/browse.ui.js +++ b/module/space/js/browse.ui.js @@ -14,60 +14,72 @@ window.renderInstanceList = function (result, {col, row, value}) var statusClass = ''; } result[0] = {html: '' + result[0] + ''}; - return result; } else if(col.name === 'name') { if(row.data.type == 'external') { - if(row.data.appName == 'Gitea' || row.data.appName == 'GitLab' || row.data.appName == 'Gogs') result[0] = {html: '' + result[0] + ''}; + result[0] = {html: '' + result[0] + ''}; } else { result[0] = {html: '' + result[0] + ''}; } - return result; + } + else if(col.name === 'createdAt') + { + if(value.includes('0000-00-00')) result[0] = ''; } return result; } -var timer = null; +var refreshTime = 0; +var timer = null; +const postData = new FormData(); +if(idList.length > 0) +{ + idList.forEach(function(id){postData.append('idList[]', id)}); +} window.afterPageUpdate = function() { - if(timer) return; - const postData = new FormData(); - idList.forEach(function(id) - { - postData.append('idList[]', id) - }); - timer = setInterval(function() - { - $.ajaxSubmit({ - url: $.createLink('instance', 'ajaxStatus'), - method: 'POST', - data:postData, - onComplete: function(res) + if(idList.length === 0) return; + refreshStatus(); +} + +function refreshStatus() +{ + if(new Date().getTime() - refreshTime < 4000) return; + refreshTime = new Date().getTime(); + + $.ajaxSubmit({ + url: $.createLink('instance', 'ajaxStatus'), + method: 'POST', + data:postData, + onComplete: function(res) + { + if(res.result === 'success') { - if(res.result != 'success') return; $.each(res.data, function(index, instance) { if(statusMap[instance.id] != instance.status) { - clearInterval(timer); + loadCurrentPage(); statusMap[instance.id] = instance.status; - loadPage(); + return; } }); } - }); - }, 10000); + + timer = setTimeout(() => {refreshStatus()}, 5000); + } + }); } window.onPageUnmount = function() { - if(timer == null) return; - clearInterval(timer); + if(!timer) return; + clearTimeout(timer); } window.bindUser = function(externalID, appName) @@ -77,6 +89,20 @@ window.bindUser = function(externalID, appName) window.editApp = function(externalID, appName) { - $('#editLinkContainer').attr('href', $.createLink(appName.toLowerCase(), 'edit', 'id=' + externalID)); + if(appName == 'Nexus') + { + $('#editLinkContainer').attr('href', $.createLink('instance', 'editExternalApp', 'id=' + externalID)); + } + else + { + $('#editLinkContainer').attr('href', $.createLink(appName.toLowerCase(), 'edit', 'id=' + externalID)); + } $('#editLinkContainer').trigger('click'); } + +window.createSortLink = function(col) +{ + var sort = col.name + '_asc'; + if(sort == orderBy) sort = col.name + '_desc'; + return sortLink.replace('{orderBy}', sort); +} diff --git a/module/space/js/createapplication.ui.js b/module/space/js/createapplication.ui.js index 0ea2fd2251..23847cc9a6 100644 --- a/module/space/js/createapplication.ui.js +++ b/module/space/js/createapplication.ui.js @@ -88,6 +88,7 @@ function onChangeStoreAppType(event) } } + $('#createStoreAppForm').data('appid', storeApp); $('#createStoreAppForm').attr('action', $.createLink('instance', 'install', 'appID=' + storeApp)); var storeAppName = apps[storeApp]; @@ -108,8 +109,19 @@ function onChangeStoreAppType(event) var app = JSON.parse(response); $('#app_version').val(app.app_version); - $('#version').val(app.version); - if((app.dependencies.mysql && mysqlList) || (app.dependencies.postgresql && pgList)) + if(showVersion === true) + { + $('#version').picker({items: app.versionList, name: 'version', required: true}); + setTimeout(() => + { + $('#version').picker('setValue', app.versionList[0].value); + }, 300); + } + else + { + $('#version').val(app.version); + } + if((app.dependencies.mysql && mysqlList) || (app.dependencies.postgresql && pgList && pgList.length > 0)) { $('div.dbType').removeClass('hidden'); $('[name=dbService]').prop('disabled', false); @@ -148,6 +160,19 @@ function onChangeDbType(event) } } +window.alertResource = function() +{ + zui.Modal.confirm({'message': resourceAlert}).then((res) => + { + if(res) + { + var appID = $('#createStoreAppForm').data('appid'); + $('#createStoreAppForm').attr('action', $.createLink('instance', 'install', 'appID=' + appID + '&checkResource=false')); + $('#createStoreAppForm .form-row .toolbar button[type=submit]').trigger('click'); + } + }); +} + $(function() { onChangeStoreAppType(); diff --git a/module/space/lang/de.php b/module/space/lang/de.php new file mode 100644 index 0000000000..fbc3f84197 --- /dev/null +++ b/module/space/lang/de.php @@ -0,0 +1,36 @@ +space->common = 'Applications'; +$lang->space->browse = 'Application List'; +$lang->space->getStoreAppInfo = 'Get application information'; +$lang->space->status = 'Status'; +$lang->space->noApps = 'No service'; +$lang->space->defaultSpace = 'Default space'; +$lang->space->systemSpace = 'System space'; +$lang->space->searchInstance = 'Search Services'; +$lang->space->upgrade = 'Upgrade'; +$lang->space->install = 'Add an application'; +$lang->space->createdBy = 'Creator'; +$lang->space->createdAt = 'Creation time'; +$lang->space->handConfig = 'Manual configuration'; +$lang->space->addType = 'Add method'; +$lang->space->instanceType = 'Instance type'; + +$lang->space->notice = new stdclass; +$lang->space->notice->toInstall = 'Please go to the application market to install'; + +$lang->space->byList = 'List'; +$lang->space->byCard = 'Card'; + +$lang->space->featureBar['browse']['all'] = 'All'; +if($config->inQuickon) $lang->space->featureBar['browse']['running'] = 'Running'; +if($config->inQuickon) $lang->space->featureBar['browse']['stopped'] = 'Stopped'; +if($config->inQuickon) $lang->space->featureBar['browse']['abnormal'] = 'Abnormal'; + +$lang->space->appType['gitlab'] = 'GitLab'; +$lang->space->appType['gitea'] = 'Gitea'; +$lang->space->appType['gogs'] = 'Gogs'; +$lang->space->appType['jenkins'] = 'Jenkins'; +$lang->space->appType['sonarqube'] = 'SonarQube'; +$lang->space->appType['nexus'] = 'Nexus'; diff --git a/module/space/lang/en.php b/module/space/lang/en.php index 0771e0d377..fbc3f84197 100644 --- a/module/space/lang/en.php +++ b/module/space/lang/en.php @@ -1,27 +1,32 @@ space->status = 'Status'; -$lang->space->noApps = 'No service'; -$lang->space->defaultSpace = 'Default space'; -$lang->space->systemSpace = 'System space'; -$lang->space->searchInstance = 'Search Services'; -$lang->space->upgrade = 'Upgrade'; -$lang->space->install = 'Add an application'; -$lang->space->createdBy = 'Creator'; -$lang->space->createdAt = 'Creation time'; -$lang->space->handConfig = 'Manual configuration'; -$lang->space->addType = 'Add method'; -$lang->space->instanceType = 'Instance type'; +global $config; -$lang->space->notice = new stdclass; -$lang->space->notice->toInstall = 'Please install in application store'; +$lang->space->common = 'Applications'; +$lang->space->browse = 'Application List'; +$lang->space->getStoreAppInfo = 'Get application information'; +$lang->space->status = 'Status'; +$lang->space->noApps = 'No service'; +$lang->space->defaultSpace = 'Default space'; +$lang->space->systemSpace = 'System space'; +$lang->space->searchInstance = 'Search Services'; +$lang->space->upgrade = 'Upgrade'; +$lang->space->install = 'Add an application'; +$lang->space->createdBy = 'Creator'; +$lang->space->createdAt = 'Creation time'; +$lang->space->handConfig = 'Manual configuration'; +$lang->space->addType = 'Add method'; +$lang->space->instanceType = 'Instance type'; + +$lang->space->notice = new stdclass; +$lang->space->notice->toInstall = 'Please go to the application market to install'; $lang->space->byList = 'List'; $lang->space->byCard = 'Card'; -$lang->space->featureBar['browse']['all'] = 'All'; -$lang->space->featureBar['browse']['running'] = 'Running'; -$lang->space->featureBar['browse']['stopped'] = 'Stopped'; -$lang->space->featureBar['browse']['abnormal'] = 'Abnormal'; +$lang->space->featureBar['browse']['all'] = 'All'; +if($config->inQuickon) $lang->space->featureBar['browse']['running'] = 'Running'; +if($config->inQuickon) $lang->space->featureBar['browse']['stopped'] = 'Stopped'; +if($config->inQuickon) $lang->space->featureBar['browse']['abnormal'] = 'Abnormal'; $lang->space->appType['gitlab'] = 'GitLab'; $lang->space->appType['gitea'] = 'Gitea'; diff --git a/module/space/lang/fr.php b/module/space/lang/fr.php new file mode 100644 index 0000000000..fbc3f84197 --- /dev/null +++ b/module/space/lang/fr.php @@ -0,0 +1,36 @@ +space->common = 'Applications'; +$lang->space->browse = 'Application List'; +$lang->space->getStoreAppInfo = 'Get application information'; +$lang->space->status = 'Status'; +$lang->space->noApps = 'No service'; +$lang->space->defaultSpace = 'Default space'; +$lang->space->systemSpace = 'System space'; +$lang->space->searchInstance = 'Search Services'; +$lang->space->upgrade = 'Upgrade'; +$lang->space->install = 'Add an application'; +$lang->space->createdBy = 'Creator'; +$lang->space->createdAt = 'Creation time'; +$lang->space->handConfig = 'Manual configuration'; +$lang->space->addType = 'Add method'; +$lang->space->instanceType = 'Instance type'; + +$lang->space->notice = new stdclass; +$lang->space->notice->toInstall = 'Please go to the application market to install'; + +$lang->space->byList = 'List'; +$lang->space->byCard = 'Card'; + +$lang->space->featureBar['browse']['all'] = 'All'; +if($config->inQuickon) $lang->space->featureBar['browse']['running'] = 'Running'; +if($config->inQuickon) $lang->space->featureBar['browse']['stopped'] = 'Stopped'; +if($config->inQuickon) $lang->space->featureBar['browse']['abnormal'] = 'Abnormal'; + +$lang->space->appType['gitlab'] = 'GitLab'; +$lang->space->appType['gitea'] = 'Gitea'; +$lang->space->appType['gogs'] = 'Gogs'; +$lang->space->appType['jenkins'] = 'Jenkins'; +$lang->space->appType['sonarqube'] = 'SonarQube'; +$lang->space->appType['nexus'] = 'Nexus'; diff --git a/module/space/lang/zh-cn.php b/module/space/lang/zh-cn.php index 434e9b9984..9cc4e801b6 100644 --- a/module/space/lang/zh-cn.php +++ b/module/space/lang/zh-cn.php @@ -1,27 +1,32 @@ space->status = '状态'; -$lang->space->noApps = '暂无服务'; -$lang->space->defaultSpace = '默认空间'; -$lang->space->systemSpace = '系统空间'; -$lang->space->searchInstance = '搜索服务'; -$lang->space->upgrade = '升级'; -$lang->space->install = '添加应用'; -$lang->space->createdBy = '创建者'; -$lang->space->createdAt = '创建时间'; -$lang->space->handConfig = '手工配置'; -$lang->space->addType = '添加方式'; -$lang->space->instanceType = '实例类型'; +global $config; -$lang->space->notice = new stdclass; +$lang->space->common = '应用'; +$lang->space->browse = '应用列表'; +$lang->space->getStoreAppInfo = '获取应用信息'; +$lang->space->status = '状态'; +$lang->space->noApps = '暂无服务'; +$lang->space->defaultSpace = '默认空间'; +$lang->space->systemSpace = '系统空间'; +$lang->space->searchInstance = '搜索服务'; +$lang->space->upgrade = '升级'; +$lang->space->install = '添加应用'; +$lang->space->createdBy = '创建者'; +$lang->space->createdAt = '创建时间'; +$lang->space->handConfig = '手工配置'; +$lang->space->addType = '添加方式'; +$lang->space->instanceType = '实例类型'; + +$lang->space->notice = new stdclass; $lang->space->notice->toInstall = '请到应用市场安装'; $lang->space->byList = '列表'; $lang->space->byCard = '卡片'; -$lang->space->featureBar['browse']['all'] = '全部'; -$lang->space->featureBar['browse']['running'] = '运行中'; -$lang->space->featureBar['browse']['stopped'] = '已停止'; -$lang->space->featureBar['browse']['abnormal'] = '异常'; +$lang->space->featureBar['browse']['all'] = '全部'; +if($config->inQuickon) $lang->space->featureBar['browse']['running'] = '运行中'; +if($config->inQuickon) $lang->space->featureBar['browse']['stopped'] = '已关闭'; +if($config->inQuickon) $lang->space->featureBar['browse']['abnormal'] = '异常'; $lang->space->appType['gitlab'] = 'GitLab'; $lang->space->appType['gitea'] = 'Gitea'; diff --git a/module/space/model.php b/module/space/model.php index f1a91b6390..45f88d7cb1 100644 --- a/module/space/model.php +++ b/module/space/model.php @@ -99,7 +99,7 @@ class spaceModel extends model $instances = $this->dao->select('*')->from(TABLE_INSTANCE) ->where('deleted')->eq(0) - ->andWhere('space')->eq($spaceID) + ->beginIF($spaceID)->andWhere('space')->eq($spaceID)->fi() ->beginIF($status !== 'all')->andWhere('status')->eq($status)->fi() ->beginIF(!empty($searchName))->andWhere('name')->like("%{$searchName}%")->fi() ->orderBy('id desc')->page($pager)->fetchAll('id'); @@ -107,7 +107,7 @@ class spaceModel extends model $this->loadModel('store'); foreach($instances as $instance) $instance->latestVersion = $this->store->appLatestVersion($instance->appID, $instance->version); - $solutionIDList = array_column($instances, 'solution'); + $solutionIDList = helper::arrayColumn($instances, 'solution'); $solutions = $this->dao->select('*')->from(TABLE_SOLUTION)->where('id')->in($solutionIDList)->fetchAll('id'); foreach($instances as $instance) $instance->solutionData = zget($solutions, $instance->solution, new stdclass); diff --git a/module/space/ui/browse.html.php b/module/space/ui/browse.html.php index d9129fa3c2..b0355f6e91 100644 --- a/module/space/ui/browse.html.php +++ b/module/space/ui/browse.html.php @@ -10,10 +10,13 @@ declare(strict_types=1); */ namespace zin; -$statusMap = array(); -$canInstall = hasPriv('instance', 'create'); +jsVar('orderBy', $orderBy); +jsVar('sortLink', $sortLink); -foreach($instances as $instance) if(!$instance->externalID) $statusMap[$instance->id] = $instance->status; +$statusMap = array(); +$canInstall = hasPriv('instance', 'mange'); + +foreach($instances as $instance) if('store' === $instance->type) $statusMap[$instance->id] = $instance->status; jsVar('statusMap', $statusMap); jsVar('idList', array_keys($statusMap)); @@ -45,9 +48,11 @@ toolBar dtable ( + set::userMap($users), set::cols($config->space->dtable->fieldList), set::data($instances), set::onRenderCell(jsRaw('window.renderInstanceList')), + set::sortLink(jsRaw('createSortLink')), set::footPager(usePager()), ); diff --git a/module/space/ui/createapplication.html.php b/module/space/ui/createapplication.html.php index 5a9fcb9a87..dda6f82eb5 100644 --- a/module/space/ui/createapplication.html.php +++ b/module/space/ui/createapplication.html.php @@ -10,23 +10,27 @@ declare(strict_types=1); */ namespace zin; -jsVar('gitlabUrlTips', $lang->gitlab->placeholder->url); -jsVar('gitlabTokenTips', $lang->gitlab->placeholder->token); -jsVar('sonarqubeUrlTips', $lang->sonarqube->placeholder->url); -jsVar('sonarqubeAccountTips', $lang->sonarqube->placeholder->account); -jsVar('jenkinsTokenTips', $lang->jenkins->tokenFirst); -jsVar('jenkinsPasswordTips', $lang->jenkins->tips); -jsVar('apps', $apps); -jsVar('mysqlList', $mysqlList); -jsVar('pgList', $pgList); -jsVar('defaultApp', $defaultApp); -jsVar('appID', $appID); -jsVar('externalApps', $config->space->zentaoApps); - $showVersion = getenv('ALLOW_SELECT_VERSION') && (strtolower(getenv('ALLOW_SELECT_VERSION')) == 'true' || strtolower(getenv('ALLOW_SELECT_VERSION')) == '1'); $dbTypeItems = array(); foreach($lang->instance->dbTypes as $type => $db) $dbTypeItems[] = array('text' => $db, 'value' => $type); +$colWidth = isInModal() ? 'full' : '2/3'; + +jsVar('gitlabUrlTips', $lang->gitlab->placeholder->url); +jsVar('gitlabTokenTips', $lang->gitlab->placeholder->token); +jsVar('sonarqubeUrlTips', $lang->sonarqube->placeholder->url); +jsVar('jenkinsTokenTips', $lang->jenkins->tokenFirst); +jsVar('jenkinsPasswordTips', $lang->jenkins->tips); +jsVar('sonarqubeAccountTips', $lang->sonarqube->placeholder->account); +jsVar('apps', $apps); +jsVar('appID', $appID); +jsVar('pgList', $pgList); +jsVar('mysqlList', $mysqlList); +jsVar('defaultApp', $defaultApp); +jsVar('showVersion', $showVersion); +jsVar('externalApps', $config->space->zentaoApps); +jsVar('resourceAlert', $lang->instance->notices['notEnoughResource']); + if($config->inQuickon) { formPanel @@ -36,12 +40,13 @@ if($config->inQuickon) set::title($lang->space->install), $appID ? set::submitBtnText($lang->instance->install) : null, $appID ? set::actions(array('submit', array('text' => $lang->instance->stop, 'data-type' => 'submit', 'data-dismiss' => 'modal'))) : null, + set::actionsClass('w-2/3'), formRow ( setStyle('display', $appID ? 'none' : 'block'), formGroup ( - set::width('2/3'), + set::width($colWidth), set::label($lang->app->common), set::name('storeAppType'), set::items($apps), @@ -71,7 +76,7 @@ if($config->inQuickon) setStyle('display', 'block'), formGroup ( - set::width('2/3'), + set::width($colWidth), set::label($lang->instance->name), set::name('customName'), set::control('input'), @@ -81,7 +86,7 @@ if($config->inQuickon) ), $showVersion ? formGroup ( - set::width('2/3'), + set::width($colWidth), set::label($lang->instance->version), set::name('version'), set::required(true), @@ -89,7 +94,7 @@ if($config->inQuickon) set::items($versionList) ) : formGroup ( - set::width('2/3'), + set::width($colWidth), set::label($lang->instance->version), set::name('app_version'), set::required(true), @@ -104,7 +109,7 @@ if($config->inQuickon) ( formGroup ( - set::width('2/3'), + set::width($colWidth), set::label($lang->instance->domain), set::required(true), inputGroup @@ -146,7 +151,7 @@ if($config->inQuickon) setClass('dbType dbService'), formGroup ( - set::width('2/3'), + set::width($colWidth), set::label($lang->space->instanceType), set::name('dbService'), set::items(array()), @@ -162,9 +167,10 @@ formPanel set::id('createAppForm'), set::title($lang->space->install), set::url($this->createLink('gitlab', 'create')), + set::actionsClass('w-2/3'), formGroup ( - set::width('2/3'), + set::width($colWidth), set::label($lang->app->common), set::name('appType'), set::items($lang->space->appType), @@ -183,14 +189,14 @@ formPanel ) : null, formGroup ( - set::width('2/3'), + set::width($colWidth), set::label($lang->gitlab->name), set::name('name'), set::required(true), ), formGroup ( - set::width('2/3'), + set::width($colWidth), set::label($lang->gitlab->url), set::name('url'), set::required(true), @@ -201,7 +207,7 @@ formPanel setClass('jenkins sonarqube hidden'), formGroup ( - set::width('2/3'), + set::width($colWidth), set::label($lang->user->account), set::name('account'), set::required(true), @@ -212,7 +218,7 @@ formPanel setClass('token'), formGroup ( - set::width('2/3'), + set::width($colWidth), set::label($lang->gitlab->token), set::name('token'), set::placeholder($lang->gitlab->placeholder->token), @@ -224,7 +230,7 @@ formPanel setClass('jenkins sonarqube password hidden'), formGroup ( - set::width('2/3'), + set::width($colWidth), set::label($lang->user->password), set::name('password'), ), diff --git a/module/space/view/browse.html.php b/module/space/view/browse.html.php index 429524a66c..c9f50dd162 100644 --- a/module/space/view/browse.html.php +++ b/module/space/view/browse.html.php @@ -12,7 +12,7 @@ ?> app->getModuleRoot() . '/common/view/header.html.php';?> instance->notices);?> - + - +
sonarqube->projectKey);?> store->releaseDate;?>: publish_time))->format('Y-m-d');?> store->appType;?>:categories, 'alias')), '/');?>categories, 'alias')), '/');?>
diff --git a/module/system/control.php b/module/system/control.php index 986ae43e56..2645ba59e9 100644 --- a/module/system/control.php +++ b/module/system/control.php @@ -29,9 +29,9 @@ class system extends control } else { - if(!is_writable($this->backupPath)) $this->view->error = sprintf($this->lang->system->backup->error->noWritable, $this->backupPath); + if(!is_writable($this->backupPath)) $this->view->error = sprintf($this->lang->backup->error->noWritable, $this->backupPath); } - if(!is_writable($this->app->getTmpRoot())) $this->view->error = sprintf($this->lang->system->backup->error->noWritable, $this->app->getTmpRoot()); + if(!is_writable($this->app->getTmpRoot())) $this->view->error = sprintf($this->lang->backup->error->noWritable, $this->app->getTmpRoot()); $this->loadModel('action'); $this->loadModel('setting'); @@ -447,13 +447,14 @@ class system extends control */ public function editDomain() { + if(!commonModel::hasPriv('system', 'configDomain')) $this->loadModel('common')->deny('system', 'configDomain', false); $this->loadModel('instance'); if($_POST) { session_write_close(); $this->system->saveDomainSettings(); - if(dao::isError()) return $this->send(array('result' => 'fail', 'message' => dao::$errors)); + if(dao::isError()) return $this->send(array('result' => 'fail', 'message' => dao::getError(true))); return $this->send(array('result' => 'success', 'message' => $this->lang->system->notices->updateDomainSuccess, 'locate' => $this->inlink('domainView'))); } diff --git a/module/system/js/dashboard.ui.js b/module/system/js/dashboard.ui.js index 2e0242abaf..31f07fec8e 100644 --- a/module/system/js/dashboard.ui.js +++ b/module/system/js/dashboard.ui.js @@ -50,6 +50,7 @@ window.afterPageUpdate = function() { if(timer) return; const postData = new FormData(); + if(instanceIdList.length === 0) return; instanceIdList.forEach(function(id) { postData.append('idList[]', id) @@ -63,6 +64,7 @@ window.afterPageUpdate = function() onComplete: function(res) { if(res.result != 'success') return; + if(res.data.length == 0) clearInterval(timer); $.each(res.data, function(index, instance) { if($("#instance-status-" + instance.id).data('status') != instance.status) diff --git a/module/system/lang/de.php b/module/system/lang/de.php index 9901ec3342..6d6fb5b25f 100644 --- a/module/system/lang/de.php +++ b/module/system/lang/de.php @@ -1,9 +1,12 @@ system->common = 'Dashboard'; $lang->system->dashboard = 'Dashboard'; $lang->system->systemInfo = 'System information'; $lang->system->dbManagement = 'Database management'; $lang->system->ldapManagement = 'LDAP'; $lang->system->dbList = 'Database list'; +$lang->system->configDomain = 'Config Domain'; +$lang->system->ossView = 'OSS'; $lang->system->dbName = 'Name'; $lang->system->dbStatus = 'Status'; $lang->system->dbType = 'Type'; @@ -42,6 +45,8 @@ $lang->system->LDAP->ldapUsername = 'Username'; $lang->system->LDAP->ldapName = 'Name'; $lang->system->LDAP->host = 'Host'; $lang->system->LDAP->port = 'Port'; +$lang->system->LDAP->account = 'Account'; +$lang->system->LDAP->password = 'Password'; $lang->system->LDAP->ldapRoot = 'Root node'; $lang->system->LDAP->filterUser = 'User filtering'; $lang->system->LDAP->email = 'Mail Fields'; @@ -210,6 +215,11 @@ $lang->system->backup->rollback = 'Rollback'; $lang->system->backup->restart = 'Restart'; $lang->system->backup->delete = 'Delte'; +$lang->system->backup->statusList['pending'] = 'Waiting'; +$lang->system->backup->statusList['inprogress'] = 'In progress'; +$lang->system->backup->statusList['completed'] = 'Compelete'; +$lang->system->backup->statusList['failed'] = 'Fail'; + $lang->system->backup->restoreProgress['doing'] = 'Doing'; $lang->system->backup->restoreProgress['done'] = 'Done'; @@ -217,12 +227,15 @@ $lang->system->backup->typeList['manual'] = 'Manual backup'; $lang->system->backup->typeList['upgrade'] = 'Automatic backup before upgrade'; $lang->system->backup->typeList['restore'] = 'Automatic backup before rollback'; -$lang->system->backup->waitting = 'In progress,please wait...'; +$lang->system->backup->waitting = 'Backup is in progress, please wait...'; +$lang->system->backup->waittingStore = 'Restoring app data, please wait...'; +$lang->system->backup->progress = 'Backup in progress(%d/%d)'; +$lang->system->backup->progressStore = 'Restoring, progress(%d/%d)'; $lang->system->backup->progressSQL = 'In backup,%s has been backed up'; $lang->system->backup->progressAttach = 'There are a total of %s files in the backup, and %s files have already been backed up'; $lang->system->backup->progressCode = 'There are a total of %s files in the backup, and %s files have already been backed up'; $lang->system->backup->confirmDelete = 'Do you want to delte the backup'; -$lang->system->backup->confirmRestore = 'Please confirm whether to roallback?'; +$lang->system->backup->confirmRestore = 'A restart is required during the platform restore process, which will cause all your current operations to be interrupted and cannot be restored. Are you sure you want to continue?'; $lang->system->backup->holdDays = 'Backup has been retained for the last %s days'; $lang->system->backup->copiedFail = 'Files that failed to copy:'; $lang->system->backup->restoreTip = 'The restore function only restores the database.'; @@ -239,7 +252,8 @@ $lang->system->backup->success->upgrade = 'Upgrade successful!'; $lang->system->backup->success->degrade = 'Successfully downgraded!'; $lang->system->backup->error = new stdclass(); - +$lang->system->backup->error->backupFail = "Backup failed!"; +$lang->system->backup->error->restoreFail = "Restore failed!"; $lang->system->backup->error->upgradeFail = "Upgrade failed!"; $lang->system->backup->error->upgradeOvertime = "Upgrade timed out!"; $lang->system->backup->error->degradeFail = "Downgrade failed!"; diff --git a/module/system/lang/en.php b/module/system/lang/en.php index 8f6556d5e1..6d6fb5b25f 100644 --- a/module/system/lang/en.php +++ b/module/system/lang/en.php @@ -1,9 +1,12 @@ system->common = 'Dashboard'; $lang->system->dashboard = 'Dashboard'; $lang->system->systemInfo = 'System information'; $lang->system->dbManagement = 'Database management'; $lang->system->ldapManagement = 'LDAP'; $lang->system->dbList = 'Database list'; +$lang->system->configDomain = 'Config Domain'; +$lang->system->ossView = 'OSS'; $lang->system->dbName = 'Name'; $lang->system->dbStatus = 'Status'; $lang->system->dbType = 'Type'; @@ -42,6 +45,8 @@ $lang->system->LDAP->ldapUsername = 'Username'; $lang->system->LDAP->ldapName = 'Name'; $lang->system->LDAP->host = 'Host'; $lang->system->LDAP->port = 'Port'; +$lang->system->LDAP->account = 'Account'; +$lang->system->LDAP->password = 'Password'; $lang->system->LDAP->ldapRoot = 'Root node'; $lang->system->LDAP->filterUser = 'User filtering'; $lang->system->LDAP->email = 'Mail Fields'; @@ -225,7 +230,7 @@ $lang->system->backup->typeList['restore'] = 'Automatic backup before rollback'; $lang->system->backup->waitting = 'Backup is in progress, please wait...'; $lang->system->backup->waittingStore = 'Restoring app data, please wait...'; $lang->system->backup->progress = 'Backup in progress(%d/%d)'; -$lang->system->backup->progressStore = 'Restore in progress(%d/%d)'; +$lang->system->backup->progressStore = 'Restoring, progress(%d/%d)'; $lang->system->backup->progressSQL = 'In backup,%s has been backed up'; $lang->system->backup->progressAttach = 'There are a total of %s files in the backup, and %s files have already been backed up'; $lang->system->backup->progressCode = 'There are a total of %s files in the backup, and %s files have already been backed up'; diff --git a/module/system/lang/fr.php b/module/system/lang/fr.php index 9901ec3342..6d6fb5b25f 100644 --- a/module/system/lang/fr.php +++ b/module/system/lang/fr.php @@ -1,9 +1,12 @@ system->common = 'Dashboard'; $lang->system->dashboard = 'Dashboard'; $lang->system->systemInfo = 'System information'; $lang->system->dbManagement = 'Database management'; $lang->system->ldapManagement = 'LDAP'; $lang->system->dbList = 'Database list'; +$lang->system->configDomain = 'Config Domain'; +$lang->system->ossView = 'OSS'; $lang->system->dbName = 'Name'; $lang->system->dbStatus = 'Status'; $lang->system->dbType = 'Type'; @@ -42,6 +45,8 @@ $lang->system->LDAP->ldapUsername = 'Username'; $lang->system->LDAP->ldapName = 'Name'; $lang->system->LDAP->host = 'Host'; $lang->system->LDAP->port = 'Port'; +$lang->system->LDAP->account = 'Account'; +$lang->system->LDAP->password = 'Password'; $lang->system->LDAP->ldapRoot = 'Root node'; $lang->system->LDAP->filterUser = 'User filtering'; $lang->system->LDAP->email = 'Mail Fields'; @@ -210,6 +215,11 @@ $lang->system->backup->rollback = 'Rollback'; $lang->system->backup->restart = 'Restart'; $lang->system->backup->delete = 'Delte'; +$lang->system->backup->statusList['pending'] = 'Waiting'; +$lang->system->backup->statusList['inprogress'] = 'In progress'; +$lang->system->backup->statusList['completed'] = 'Compelete'; +$lang->system->backup->statusList['failed'] = 'Fail'; + $lang->system->backup->restoreProgress['doing'] = 'Doing'; $lang->system->backup->restoreProgress['done'] = 'Done'; @@ -217,12 +227,15 @@ $lang->system->backup->typeList['manual'] = 'Manual backup'; $lang->system->backup->typeList['upgrade'] = 'Automatic backup before upgrade'; $lang->system->backup->typeList['restore'] = 'Automatic backup before rollback'; -$lang->system->backup->waitting = 'In progress,please wait...'; +$lang->system->backup->waitting = 'Backup is in progress, please wait...'; +$lang->system->backup->waittingStore = 'Restoring app data, please wait...'; +$lang->system->backup->progress = 'Backup in progress(%d/%d)'; +$lang->system->backup->progressStore = 'Restoring, progress(%d/%d)'; $lang->system->backup->progressSQL = 'In backup,%s has been backed up'; $lang->system->backup->progressAttach = 'There are a total of %s files in the backup, and %s files have already been backed up'; $lang->system->backup->progressCode = 'There are a total of %s files in the backup, and %s files have already been backed up'; $lang->system->backup->confirmDelete = 'Do you want to delte the backup'; -$lang->system->backup->confirmRestore = 'Please confirm whether to roallback?'; +$lang->system->backup->confirmRestore = 'A restart is required during the platform restore process, which will cause all your current operations to be interrupted and cannot be restored. Are you sure you want to continue?'; $lang->system->backup->holdDays = 'Backup has been retained for the last %s days'; $lang->system->backup->copiedFail = 'Files that failed to copy:'; $lang->system->backup->restoreTip = 'The restore function only restores the database.'; @@ -239,7 +252,8 @@ $lang->system->backup->success->upgrade = 'Upgrade successful!'; $lang->system->backup->success->degrade = 'Successfully downgraded!'; $lang->system->backup->error = new stdclass(); - +$lang->system->backup->error->backupFail = "Backup failed!"; +$lang->system->backup->error->restoreFail = "Restore failed!"; $lang->system->backup->error->upgradeFail = "Upgrade failed!"; $lang->system->backup->error->upgradeOvertime = "Upgrade timed out!"; $lang->system->backup->error->degradeFail = "Downgrade failed!"; diff --git a/module/system/lang/zh-cn.php b/module/system/lang/zh-cn.php index 6116c185de..4706daa40c 100644 --- a/module/system/lang/zh-cn.php +++ b/module/system/lang/zh-cn.php @@ -1,9 +1,12 @@ system->dashboard = '仪表盘'; +$lang->system->common = '仪表盘'; +$lang->system->dashboard = 'DevOps平台仪表盘'; $lang->system->systemInfo = '系统信息'; $lang->system->dbManagement = '数据库管理'; $lang->system->ldapManagement = 'LDAP'; $lang->system->dbList = '数据库列表'; +$lang->system->configDomain = '域名管理'; +$lang->system->ossView = '对象存储管理'; $lang->system->dbName = '名称'; $lang->system->dbStatus = '状态'; $lang->system->dbType = '类型'; @@ -249,7 +252,6 @@ $lang->system->backup->success->upgrade = '升级成功!'; $lang->system->backup->success->degrade = '降级成功!'; $lang->system->backup->error = new stdclass(); - $lang->system->backup->error->backupFail = "备份失败!"; $lang->system->backup->error->restoreFail = "还原失败!"; $lang->system->backup->error->upgradeFail = "升级失败!"; diff --git a/module/system/ui/dashboard.html.php b/module/system/ui/dashboard.html.php index be689c1881..0e6a0da362 100644 --- a/module/system/ui/dashboard.html.php +++ b/module/system/ui/dashboard.html.php @@ -19,7 +19,7 @@ $memoryInfo['tip'] = trim(substr($memoryInfo['tip'], strpos($memoryInfo['tip'], jsVar('cpuInfo', $cpuInfo); jsVar('memoryInfo', $memoryInfo); -jsVar('instanceIdList', array_column($instances, 'id')); +jsVar('instanceIdList', helper::arrayColumn($instances, 'id')); /* 资源统计 */ div diff --git a/module/system/ui/editdomain.html.php b/module/system/ui/editdomain.html.php index 5b1d81f395..0cdd31db49 100644 --- a/module/system/ui/editdomain.html.php +++ b/module/system/ui/editdomain.html.php @@ -31,6 +31,7 @@ formPanel ), formGroup ( + set::width('2/3'), set::label($lang->system->domain->newDomain), set::name('customDomain'), set::value(zget($domainSettings, 'customDomain', '')), @@ -57,6 +58,7 @@ formPanel ), formGroup ( + set::width('2/3'), setClass('cert hidden'), set::label($lang->system->certPem), set::name('certPem'), @@ -65,6 +67,7 @@ formPanel ), formGroup ( + set::width('2/3'), setClass('cert hidden'), set::label($lang->system->certKey), set::name('certPem'), diff --git a/module/system/view/index.html.php b/module/system/view/index.html.php index a76bff722e..4630e5a335 100644 --- a/module/system/view/index.html.php +++ b/module/system/view/index.html.php @@ -17,7 +17,7 @@
diff --git a/module/system/view/instancesblock.html.php b/module/system/view/instancesblock.html.php index eb6703a917..872466c7c0 100644 --- a/module/system/view/instancesblock.html.php +++ b/module/system/view/instancesblock.html.php @@ -1,5 +1,5 @@ app->loadLang('instance');?> - +

instance->runningService;?>