diff --git a/config/zentaopms.php b/config/zentaopms.php
index 426fd2e81d..2648a4fccd 100644
--- a/config/zentaopms.php
+++ b/config/zentaopms.php
@@ -478,3 +478,6 @@ $config->featureGroup->other = array('devops', 'kanban');
$config->bi = new stdclass();
$config->bi->pickerHeight = 150;
+
+$config->hasDropmenuApps = array('program', 'project', 'product', 'execution', 'qa', 'admin');
+$config->excludeDropmenuList = array('program-browse', 'product-all', 'product-index', 'execution-all', 'project-browse', 'product-batchedit', 'admin-index', 'product-create', 'project-create', 'execution-create', 'program-create');
\ No newline at end of file
diff --git a/module/common/lang/de.php b/module/common/lang/de.php
index 6d45763189..7f9c6cc5f2 100644
--- a/module/common/lang/de.php
+++ b/module/common/lang/de.php
@@ -498,6 +498,8 @@ $lang->pager->totalCount = "Total: {recTotal} items";
$lang->pager->pageSize = "{recPerPage} per page";
$lang->pager->itemsRange = "From {start} to {end}";
$lang->pager->pageOfTotal = "Page {page} of {totalPage}";
+$lang->pager->totalCountAB = "Total: {recTotal} items";
+$lang->pager->pageSizeAB = "{recPerPage} per page";
$lang->colorPicker = new stdclass();
$lang->colorPicker->errorTip = 'Not a valid color value';
diff --git a/module/common/lang/en.php b/module/common/lang/en.php
index 9136f93919..4a7cf5557e 100644
--- a/module/common/lang/en.php
+++ b/module/common/lang/en.php
@@ -512,6 +512,8 @@ $lang->pager->totalCount = "Total: {recTotal} items";
$lang->pager->pageSize = "{recPerPage} per page";
$lang->pager->itemsRange = "From {start} to {end}";
$lang->pager->pageOfTotal = "Page {page} of {totalPage}";
+$lang->pager->totalCountAB = "Total: {recTotal} items";
+$lang->pager->pageSizeAB = "{recPerPage} per page";
$lang->colorPicker = new stdclass();
$lang->colorPicker->errorTip = 'Not a valid color value';
diff --git a/module/common/lang/fr.php b/module/common/lang/fr.php
index fbeb1d42cc..fa41af2778 100644
--- a/module/common/lang/fr.php
+++ b/module/common/lang/fr.php
@@ -498,6 +498,8 @@ $lang->pager->totalCount = "Total: {recTotal} lignes";
$lang->pager->pageSize = "{recPerPage} par page";
$lang->pager->itemsRange = "De {start} à {end}";
$lang->pager->pageOfTotal = "Page {page} sur {totalPage}";
+$lang->pager->totalCountAB = "Total: {recTotal} lignes";
+$lang->pager->pageSizeAB = "{recPerPage} par page";
$lang->colorPicker = new stdclass();
$lang->colorPicker->errorTip = "Ce n'est pas une valeur de couleur valide";
diff --git a/module/common/lang/zh-cn.php b/module/common/lang/zh-cn.php
index 10652bbb46..9ac35f1916 100644
--- a/module/common/lang/zh-cn.php
+++ b/module/common/lang/zh-cn.php
@@ -512,6 +512,8 @@ $lang->pager->totalCount = '共 {recTotal} 项';
$lang->pager->pageSize = '每页 {recPerPage} 项';
$lang->pager->itemsRange = '第 {start} ~ {end} 项';
$lang->pager->pageOfTotal = '第 {page}/{totalPage} 页';
+$lang->pager->totalCountAB = '共 {recTotal} 项';
+$lang->pager->pageSizeAB = '每页 {recPerPage} 项';
$lang->colorPicker = new stdclass();
$lang->colorPicker->errorTip = '不是有效的颜色值';
diff --git a/module/repo/config.php b/module/repo/config.php
index f80bf85838..c5d768e985 100644
--- a/module/repo/config.php
+++ b/module/repo/config.php
@@ -1,4 +1,6 @@
program = new stdclass();
$config->program->suffix['c'] = "cpp";
$config->program->suffix['cpp'] = "cpp";
@@ -66,8 +68,9 @@ $config->repo->gitlab->apiPath = "%s/api/v4/projects/%s/repository/";
$config->repo->gitea = new stdclass;
$config->repo->gitea->apiPath = "%s/api/v1/repos/%s/";
-$config->repo->gitServiceList = array('gitlab', 'gitea', 'gogs');
-$config->repo->gitTypeList = array('Gitlab', 'Gitea', 'Gogs', 'Git');
+$config->repo->gitServiceList = array('gitlab', 'gitea', 'gogs');
+$config->repo->gitServiceTypeList = array('Gitlab', 'Gitea', 'Gogs');
+$config->repo->gitTypeList = array('Gitlab', 'Gitea', 'Gogs', 'Git');
$config->repo->rules['module']['task'] = 'Task';
$config->repo->rules['module']['bug'] = 'Bug';
@@ -151,3 +154,16 @@ $config->repo->fileExt["vb"] = array('.vb');
$config->repo->fileExt["verilog"] = array('.v', '.vh');
$config->repo->fileExt["xml"] = array('.xml', '.dtd', '.ascx', '.csproj', '.config', '.wxi', '.wxl', '.wxs', '.xaml', '.svg', '.svgz', '.opf', '.xsl');
$config->repo->fileExt["yaml"] = array('.yaml', '.yml');
+
+$config->repo->search['module'] = 'repo';
+$config->repo->search['fields']['name'] = $lang->repo->name;
+$config->repo->search['fields']['id'] = $lang->repo->id;
+$config->repo->search['fields']['product'] = $lang->repo->product;
+$config->repo->search['fields']['projects'] = $lang->repo->projects;
+$config->repo->search['fields']['SCM'] = $lang->repo->SCM;
+$config->repo->search['fields']['desc'] = $lang->repo->desc;
+
+$config->repo->search['params']['name'] = array('operator' => 'include', 'control' => 'input', 'values' => '');
+$config->repo->search['params']['product'] = array('operator' => 'include', 'control' => 'select', 'values' => '');
+$config->repo->search['params']['projects'] = array('operator' => 'include', 'control' => 'select', 'values' => '');
+$config->repo->search['params']['SCM'] = array('operator' => '=', 'control' => 'select', 'values' => $lang->repo->scmList);
diff --git a/module/repo/config/dtable.php b/module/repo/config/dtable.php
new file mode 100644
index 0000000000..07ed3a02e1
--- /dev/null
+++ b/module/repo/config/dtable.php
@@ -0,0 +1,222 @@
+loadLang('sonarqube');
+
+$config->repo->dtable = new stdclass();
+
+$config->repo->dtable->fieldList['name']['name'] = 'name';
+$config->repo->dtable->fieldList['name']['title'] = $lang->repo->name;
+$config->repo->dtable->fieldList['name']['type'] = 'title';
+$config->repo->dtable->fieldList['name']['width'] = '0.2';
+
+$config->repo->dtable->fieldList['product']['name'] = 'productNames';
+$config->repo->dtable->fieldList['product']['title'] = $lang->repo->product;
+$config->repo->dtable->fieldList['product']['type'] = 'text';
+$config->repo->dtable->fieldList['product']['sortType'] = true;
+$config->repo->dtable->fieldList['product']['width'] = '136';
+
+$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'] = true;
+$config->repo->dtable->fieldList['project']['width'] = '136';
+
+$config->repo->dtable->fieldList['scm']['name'] = 'SCM';
+$config->repo->dtable->fieldList['scm']['title'] = $lang->repo->type;
+$config->repo->dtable->fieldList['scm']['type'] = 'scm';
+$config->repo->dtable->fieldList['scm']['map'] = $lang->repo->scmList;
+$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']['width'] = '260';
+$config->repo->dtable->fieldList['path']['group'] = 1;
+
+$config->repo->dtable->fieldList['lastSubmit']['name'] = 'lastSubmitTime';
+$config->repo->dtable->fieldList['lastSubmit']['title'] = $lang->repo->lastSubmitTime;
+$config->repo->dtable->fieldList['lastSubmit']['type'] = 'datetime';
+$config->repo->dtable->fieldList['lastSubmit']['width'] = '128';
+
+$config->repo->dtable->fieldList['job']['name'] = 'job';
+$config->repo->dtable->fieldList['job']['hidden'] = true;
+
+$config->repo->dtable->fieldList['actions']['name'] = 'actions';
+$config->repo->dtable->fieldList['actions']['title'] = $lang->actions;
+$config->repo->dtable->fieldList['actions']['type'] = 'actions';
+$config->repo->dtable->fieldList['actions']['width'] = '132';
+$config->repo->dtable->fieldList['actions']['menu'] = array('execJob', 'reportView', 'edit', 'delete');
+
+$config->repo->dtable->fieldList['actions']['list']['edit']['icon'] = 'edit';
+$config->repo->dtable->fieldList['actions']['list']['edit']['hint'] = $lang->repo->edit;
+
+$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={jobID}");
+$config->repo->dtable->fieldList['actions']['list']['execJob']['data-toggle'] = 'modal';
+
+$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={jobID}");
+$config->repo->dtable->fieldList['actions']['list']['reportView']['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-toggle'] = 'modal';
+
+$config->repo->repoDtable = new stdclass();
+
+$config->repo->repoDtable->fieldList['name']['name'] = 'name';
+$config->repo->repoDtable->fieldList['name']['title'] = $lang->repo->name;
+$config->repo->repoDtable->fieldList['name']['minWidth'] = '160';
+$config->repo->repoDtable->fieldList['name']['sortType'] = false;
+$config->repo->repoDtable->fieldList['name']['type'] = 'shortTitle';
+$config->repo->repoDtable->fieldList['name']['fixed'] = false;
+$config->repo->repoDtable->fieldList['name']['hint'] = true;
+$config->repo->repoDtable->fieldList['name']['checkbox'] = false;
+
+$config->repo->repoDtable->fieldList['revision']['name'] = 'revision';
+$config->repo->repoDtable->fieldList['revision']['title'] = $lang->repo->revisions;
+$config->repo->repoDtable->fieldList['revision']['sortType'] = false;
+$config->repo->repoDtable->fieldList['revision']['width'] = '90';
+$config->repo->repoDtable->fieldList['revision']['hint'] = true;
+
+$config->repo->repoDtable->fieldList['time']['name'] = 'date';
+$config->repo->repoDtable->fieldList['time']['title'] = $lang->repo->time;
+$config->repo->repoDtable->fieldList['time']['sortType'] = false;
+$config->repo->repoDtable->fieldList['time']['type'] = 'date';
+$config->repo->repoDtable->fieldList['time']['hint'] = true;
+
+$config->repo->repoDtable->fieldList['committer']['name'] = 'account';
+$config->repo->repoDtable->fieldList['committer']['title'] = $lang->repo->committer;
+$config->repo->repoDtable->fieldList['committer']['sortType'] = false;
+$config->repo->repoDtable->fieldList['committer']['type'] = 'user';
+$config->repo->repoDtable->fieldList['committer']['hint'] = true;
+
+$config->repo->repoDtable->fieldList['comment']['name'] = 'originalComment';
+$config->repo->repoDtable->fieldList['comment']['title'] = $lang->repo->comment;
+$config->repo->repoDtable->fieldList['comment']['sortType'] = false;
+$config->repo->repoDtable->fieldList['comment']['type'] = 'text';
+$config->repo->repoDtable->fieldList['comment']['hint'] = true;
+
+$config->repo->commentDtable = new stdclass();
+
+$config->repo->commentDtable->fieldList['id']['title'] = '';
+$config->repo->commentDtable->fieldList['id']['name'] = '';
+$config->repo->commentDtable->fieldList['id']['type'] = 'checkID';
+$config->repo->commentDtable->fieldList['id']['fixed'] = false;
+$config->repo->commentDtable->fieldList['id']['sortType'] = false;
+$config->repo->commentDtable->fieldList['id']['checkbox'] = true;
+$config->repo->commentDtable->fieldList['id']['width'] = '25';
+
+$config->repo->commentDtable->fieldList['revision']['name'] = 'revision';
+$config->repo->commentDtable->fieldList['revision']['title'] = $lang->repo->revisions;
+$config->repo->commentDtable->fieldList['revision']['sortType'] = false;
+$config->repo->commentDtable->fieldList['revision']['type'] = 'title';
+$config->repo->commentDtable->fieldList['revision']['fixed'] = false;
+$config->repo->commentDtable->fieldList['revision']['width'] = '100';
+$config->repo->commentDtable->fieldList['revision']['hint'] = true;
+
+$config->repo->commentDtable->fieldList['commit']['name'] = 'commit';
+$config->repo->commentDtable->fieldList['commit']['title'] = $lang->repo->commit;
+$config->repo->commentDtable->fieldList['commit']['sortType'] = false;
+$config->repo->commentDtable->fieldList['commit']['width'] = '40';
+$config->repo->commentDtable->fieldList ['commit']['hint'] = true;
+
+$config->repo->commentDtable->fieldList['time'] = $config->repo->repoDtable->fieldList['time'];
+$config->repo->commentDtable->fieldList['committer'] = $config->repo->repoDtable->fieldList['committer'];
+$config->repo->commentDtable->fieldList['comment'] = $config->repo->repoDtable->fieldList['comment'];
+
+$config->repo->commentDtable->fieldList['comment']['name'] = 'originalComment';
+$config->repo->commentDtable->fieldList['committer']['name'] = 'committer';
+
+$config->repo->logDtable = new stdclass();
+
+$config->repo->logDtable->fieldList['id']['hidden'] = true;
+
+$config->repo->logDtable->fieldList['revision']['type'] = 'revision';
+$config->repo->logDtable->fieldList['revision']['width'] = '100';
+$config->repo->logDtable->fieldList['revision']['checkbox'] = true;
+$config->repo->logDtable->fieldList['revision']['nestedToggle'] = false;
+
+$config->repo->logDtable->fieldList['commit']['type'] = 'number';
+$config->repo->logDtable->fieldList['commit']['width'] = '50';
+$config->repo->logDtable->fieldList['commit']['sortType'] = false;
+
+$config->repo->logDtable->fieldList['date']['name'] = 'time';
+$config->repo->logDtable->fieldList['date']['type'] = 'datetime';
+$config->repo->logDtable->fieldList['date']['sortType'] = false;
+
+$config->repo->logDtable->fieldList['committer']['name'] = 'committer';
+
+$config->repo->logDtable->fieldList['comment']['type'] = 'html';
+$config->repo->logDtable->fieldList['comment']['width'] = '600';
+
+$config->repo->blameDtable = new stdclass();
+
+$config->repo->blameDtable->fieldList['revision']['type'] = 'revision';
+$config->repo->blameDtable->fieldList['revision']['width'] = '120';
+
+$config->repo->blameDtable->fieldList['commit']['type'] = 'number';
+$config->repo->blameDtable->fieldList['commit']['sortType'] = false;
+$config->repo->blameDtable->fieldList['commit']['width'] = '80';
+
+$config->repo->blameDtable->fieldList['committer']['name'] = 'committer';
+$config->repo->blameDtable->fieldList['committer']['width'] = '150';
+
+$config->repo->blameDtable->fieldList['line']['type'] = 'number';
+$config->repo->blameDtable->fieldList['line']['sortType'] = false;
+$config->repo->blameDtable->fieldList['line']['width'] = '80';
+
+$config->repo->blameDtable->fieldList['content']['title'] = $lang->repo->code;
+$config->repo->blameDtable->fieldList['content']['type'] = 'html';
+$config->repo->blameDtable->fieldList['content']['width'] = '700';
+
+$app->loadLang('task');
+$app->loadModuleConfig('task');
+
+$config->repo->taskDtable = new stdclass();
+$config->repo->taskDtable->fieldList = array();
+$config->repo->taskDtable->fieldList['id']['name'] = 'id';
+$config->repo->taskDtable->fieldList['id']['title'] = $lang->idAB;
+$config->repo->taskDtable->fieldList['id']['type'] = 'id';
+$config->repo->taskDtable->fieldList['id']['checkbox'] = true;
+
+$config->repo->taskDtable->fieldList['pri']['title'] = $lang->priAB;
+$config->repo->taskDtable->fieldList['pri']['type'] = 'pri';
+$config->repo->taskDtable->fieldList['pri']['sortType'] = true;
+$config->repo->taskDtable->fieldList['pri']['show'] = true;
+$config->repo->taskDtable->fieldList['pri']['group'] = 1;
+$config->repo->taskDtable->fieldList['pri']['fixed'] = 'left';
+
+$config->repo->taskDtable->fieldList ['name']['flex'] = 1;
+$config->repo->taskDtable->fieldList ['name']['nestedToggle'] = false;
+$config->repo->taskDtable->fieldList ['name']['sortType'] = true;
+$config->repo->taskDtable->fieldList ['name']['required'] = true;
+$config->repo->taskDtable->fieldList['name']['data-size'] = 'lg';
+$config->repo->taskDtable->fieldList['name']['data-toggle'] = 'modal';
+$config->repo->taskDtable->fieldList ['name']['fixed'] = 'left';
+$config->repo->taskDtable->fieldList ['name']['type'] = 'nestedTitle';
+$config->repo->taskDtable->fieldList ['name']['link'] = array('module' => 'task', 'method' => 'view', 'params' => 'taskID={id}');
+$config->repo->taskDtable->fieldList['name']['title'] = $lang->task->name;
+
+$config->repo->taskDtable->fieldList['finishedBy']['title'] = $lang->task->finishedByAB;
+$config->repo->taskDtable->fieldList['finishedBy']['type'] = 'user';
+$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']['sortType'] = true;
+$config->repo->taskDtable->fieldList['assignedTo']['show'] = true;
+$config->repo->taskDtable->fieldList['assignedTo']['group'] = 3;
+$config->repo->taskDtable->fieldList['assignedTo']['currentUser'] = $app->user->account;
+$config->repo->taskDtable->fieldList['assignedTo']['title'] = $lang->task->assignedTo;
+$config->repo->taskDtable->fieldList['assignedTo']['assignLink'] = array('module' => 'task', 'method' => 'assignTo', 'params' => 'executionID={execution}&taskID={id}');
+
+$config->repo->taskDtable->fieldList['status']['title'] = $lang->statusAB;
+$config->repo->taskDtable->fieldList['status']['type'] = 'status';
+$config->repo->taskDtable->fieldList['status']['statusMap'] = $lang->task->statusList;
+$config->repo->taskDtable->fieldList['status']['sortType'] = true;
+$config->repo->taskDtable->fieldList['status']['show'] = true;
+$config->repo->taskDtable->fieldList['status']['group'] = 1;
diff --git a/module/repo/config/form.php b/module/repo/config/form.php
new file mode 100644
index 0000000000..abda3991f3
--- /dev/null
+++ b/module/repo/config/form.php
@@ -0,0 +1,19 @@
+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['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['name'] = array('required' => true, 'type' => 'string', 'filter' => 'trim');
+$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' => '');
diff --git a/module/repo/control.php b/module/repo/control.php
index ebeeb0e68e..0ed42d8ea6 100644
--- a/module/repo/control.php
+++ b/module/repo/control.php
@@ -52,7 +52,7 @@ class repo extends control
{
$this->loadModel('project')->setMenu($objectID);
}
- else if($tab == 'execution')
+ elseif($tab == 'execution')
{
$this->loadModel('execution')->setMenu($objectID);
}
@@ -69,22 +69,21 @@ class repo extends control
*
* @param int $objectID
* @param string $orderBy
- * @param int $recTotal
* @param int $recPerPage
* @param int $pageID
* @access public
* @return void
*/
- public function maintain($objectID = 0, $orderBy = 'id_desc', $recTotal = 0, $recPerPage = 20, $pageID = 1)
+ public function maintain(int $objectID = 0, string $orderBy = 'id_desc', int $recPerPage = 20, int $pageID = 1, string $type = '', int $param = 0)
{
$repoID = $this->repo->saveState(0, $objectID);
if($this->viewType !== 'json') $this->commonAction($repoID, $objectID);
- $repoList = $this->repo->getList(0, '', $orderBy, null, true);
+ $repoList = $this->repo->getList(0, '', $orderBy, null, false, true, $type, $param);
$sonarRepoList = $this->loadModel('job')->getSonarqubeByRepo(array_keys($repoList));
/* Pager. */
- $this->app->loadClass('pager', $static = true);
+ $this->app->loadClass('pager', true);
$recTotal = count($repoList);
$pager = new pager($recTotal, $recPerPage, $pageID);
$repoList = array_chunk($repoList, $pager->recPerPage);
@@ -98,15 +97,25 @@ class repo extends control
}
$successJobs = $this->loadModel('compile')->getSuccessJobs($jobIDList);
+ $products = $this->loadModel('product')->getPairs('', 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['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->position[] = $this->lang->repo->common;
- $this->view->position[] = $this->lang->repo->browse;
$this->view->orderBy = $orderBy;
$this->view->objectID = $objectID;
$this->view->pager = $pager;
$this->view->repoList = $repoList;
- $this->view->products = $this->loadModel('product')->getPairs('', 0, '', 'all');
+ $this->view->products = $products;
+ $this->view->projects = $projects;
$this->view->sonarRepoList = $sonarRepoList;
$this->view->successJobs = $successJobs;
@@ -114,51 +123,42 @@ class repo extends control
}
/**
+ * 创建版本库。
* Create a repo.
*
* @param int $objectID projectID|executionID
* @access public
* @return void
*/
- public function create($objectID = 0)
+ public function create(int $objectID = 0)
{
if($_POST)
{
- $repoID = $this->repo->create();
+ /* Prepare data. */
+ $formData = form::data($this->config->repo->form->create);
+ $isPipelineServer = in_array(strtolower($this->post->SCM), $this->config->repo->gitServiceList) ? true : false;
+ $repo = $this->repoZen->prepareCreate($formData, $isPipelineServer);
+ /* Create a repo. */
+ if($repo) $repoID = $this->repo->create($repo, $isPipelineServer);
if(dao::isError()) return $this->send(array('result' => 'fail', 'message' => dao::getError()));
- $actionID = $this->loadModel('action')->create('repo', $repoID, 'created');
+ if($this->post->SCM == 'Gitlab')
+ {
+ /* Add webhook. */
+ $repo = $this->repo->getByID($repoID);
+ $this->loadModel('gitlab')->addPushWebhook($repo);
+ $this->gitlab->updateCodePath($repo->serviceHost, $repo->serviceProject, $repo->id);
+ }
+
+ $this->loadModel('action')->create('repo', $repoID, 'created');
+
if($this->viewType == 'json') return $this->send(array('result' => 'success', 'message' => $this->lang->saveSuccess, 'id' => $repoID));
$link = $this->repo->createLink('showSyncCommit', "repoID=$repoID&objectID=$objectID", '', false) . '#app=' . $this->app->tab;
return $this->send(array('result' => 'success', 'message' => $this->lang->saveSuccess, 'locate' => $link));
}
- $repoID = $this->repo->saveState(0, $objectID);
- $this->commonAction($repoID, $objectID);
-
- $this->app->loadLang('action');
-
- if($this->app->tab == 'project' or $this->app->tab == 'execution')
- {
- $products = $this->loadModel('product')->getProductPairsByProject($objectID);
- }
- else
- {
- $products = $this->loadModel('product')->getPairs('', 0, '', 'all');
- }
-
- $this->view->title = $this->lang->repo->common . $this->lang->colon . $this->lang->repo->create;
- $this->view->position[] = $this->lang->repo->create;
- $this->view->groups = $this->loadModel('group')->getPairs();
- $this->view->users = $this->loadModel('user')->getPairs('noletter|noempty|nodeleted|noclosed');
- $this->view->products = $products;
- $this->view->projects = $this->loadModel('product')->getProjectPairsByProductIDList(array_keys($products));
- $this->view->relatedProjects = ($this->app->tab == 'project' or $this->app->tab == 'execution') ? array($objectID) : array();
- $this->view->serviceHosts = $this->loadModel('gitlab')->getPairs();
- $this->view->objectID = $objectID;
-
- $this->display();
+ $this->repoZen->buildCreateForm($objectID);
}
/**
@@ -173,13 +173,13 @@ class repo extends control
{
$this->commonAction($repoID, $objectID);
- $repo = $this->repo->getRepoByID($repoID);
+ $repo = $this->repo->getByID($repoID);
if($_POST)
{
$noNeedSync = $this->repo->update($repoID);
if(dao::isError()) return $this->send(array('result' => 'fail', 'message' => dao::getError()));
- $newRepo = $this->repo->getRepoByID($repoID);
+ $newRepo = $this->repo->getByID($repoID);
$actionID = $this->loadModel('action')->create('repo', $repoID, 'edited');
$changes = common::createChanges($repo, $newRepo);
$this->action->logHistory($actionID, $changes);
@@ -210,7 +210,7 @@ class repo extends control
}
$products = $this->loadModel('product')->getPairs('', 0, '', 'all');
- $linkedProducts = $this->loadModel('product')->getByIdList($repo->product);
+ $linkedProducts = $this->loadModel('product')->getByIdList(explode(',', $repo->product));
$linkedProductPairs = array_combine(array_keys($linkedProducts), array_column($linkedProducts, 'name'));
$products = $products + $linkedProductPairs;
@@ -222,10 +222,8 @@ class repo extends control
$this->view->users = $this->loadModel('user')->getPairs('noletter|noempty|nodeleted|noclosed');
$this->view->products = $products;
$this->view->relatedProjects = $this->repo->filterProject(explode(',', $repo->product), explode(',', $repo->projects));
- $this->view->serviceHosts = array('' => '') + $this->loadModel('pipeline')->getPairs($repo->SCM);
+ $this->view->serviceHosts = $this->loadModel('pipeline')->getPairs($repo->SCM);
- $this->view->position[] = html::a(inlink('maintain'), $this->lang->repo->common);
- $this->view->position[] = $this->lang->repo->edit;
$this->display();
}
@@ -276,7 +274,7 @@ class repo extends control
public function ajaxGetDiffEditorContent($repoID, $objectID = 0, $entry = '', $oldRevision = '', $newRevision = '', $showBug = 'false', $encoding = '')
{
$file = $entry;
- $repo = $this->repo->getRepoByID($repoID);
+ $repo = $this->repo->getByID($repoID);
$entry = urldecode($this->repo->decodePath($entry));
$revision = str_replace('*', '-', $oldRevision);
$nRevision = str_replace('*', '-', $newRevision);
@@ -288,7 +286,6 @@ class repo extends control
$this->scm->setEngine($repo);
$info = $this->scm->info($entry, $nRevision);
- $path = $entry ? $info->path : '';
$this->view->title = $this->lang->repo->common . $this->lang->colon . $this->lang->repo->diff;
$this->view->type = 'diff';
@@ -306,7 +303,7 @@ class repo extends control
$this->view->suffix = 'c';
$this->view->blames = array();
$this->view->showEditor = true;
- $this->display('repo', 'ajaxGetEditorContent');
+ $this->display('repo', 'ajaxgeteditorcontent');
}
/**
@@ -324,7 +321,7 @@ class repo extends control
public function ajaxGetEditorContent($repoID, $objectID = 0, $entry = '', $revision = 'HEAD', $showBug = 'false', $encoding = '')
{
$file = $entry;
- $repo = $this->repo->getRepoByID($repoID);
+ $repo = $this->repo->getByID($repoID);
$entry = urldecode($this->repo->decodePath($entry));
$revision = str_replace('*', '-', $revision);
@@ -387,23 +384,25 @@ class repo extends control
public function monaco($repoID, $objectID = 0, $entry = '', $revision = 'HEAD', $showBug = 'false', $encoding = '')
{
$file = $entry;
- $repo = $this->repo->getRepoByID($repoID);
+ $repo = $this->repo->getByID($repoID);
$entry = $this->repo->decodePath($entry);
$entry = urldecode($entry);
$pathInfo = pathinfo($entry);
- $this->view->title = $this->lang->repo->common . $this->lang->colon . $this->lang->repo->view;
- $this->view->type = 'view';
- $this->view->branchID = $this->cookie->repoBranch;
- $this->view->showBug = $showBug;
- $this->view->encoding = $encoding;
- $this->view->repoID = $repoID;
- $this->view->objectID = $objectID;
- $this->view->repo = $repo;
- $this->view->revision = $revision;
- $this->view->file = $file;
- $this->view->entry = $entry;
- $this->view->pathInfo = $pathInfo;
+ $this->view->title = $this->lang->repo->common . $this->lang->colon . $this->lang->repo->view;
+ $this->view->dropMenus = $this->repoZen->getBranchAndTagItems($repo, $this->cookie->repoBranch);
+ $this->view->type = 'view';
+ $this->view->branchID = $this->cookie->repoBranch;
+ $this->view->showBug = $showBug;
+ $this->view->encoding = $encoding;
+ $this->view->repoID = $repoID;
+ $this->view->objectID = $objectID;
+ $this->view->repo = $repo;
+ $this->view->revision = $revision;
+ $this->view->file = $file;
+ $this->view->entry = $entry;
+ $this->view->pathInfo = $pathInfo;
+
$this->display();
}
@@ -424,7 +423,7 @@ class repo extends control
set_time_limit(0);
$browser = helper::getBrowser();
if($this->get->repoPath) $entry = $this->get->repoPath;
- $this->repo->setBackSession('view', $withOtherModule = true);
+ $this->repo->setBackSession('view', true);
if($repoID == 0) $repoID = $this->session->repoID;
if($revision != 'HEAD')
{
@@ -433,6 +432,7 @@ class repo extends control
}
$this->commonAction($repoID, $objectID);
+ $repo = $this->repo->getByID($repoID);
if($browser['name'] != 'ie') return print($this->fetch('repo', 'monaco', "repoID=$repoID&objectID=$objectID&entry=$entry&revision=$revision&showBug=$showBug&encoding=$encoding"));
if($_POST)
@@ -444,7 +444,6 @@ class repo extends control
}
$file = $entry;
- $repo = $this->repo->getRepoByID($repoID);
$entry = $this->repo->decodePath($entry);
$revision = str_replace('*', '-', $revision);
@@ -471,7 +470,7 @@ class repo extends control
$content = helper::convertEncoding($content, $encoding);
}
- $this->app->loadClass('pager', $static = true);
+ $this->app->loadClass('pager', true);
$pager = new pager(0, 10, 1);
$logType = 'file';
@@ -513,8 +512,6 @@ class repo extends control
$this->view->pathInfo = $pathInfo;
$this->view->title = $this->lang->repo->common . $this->lang->colon . $this->lang->repo->view;
- $this->view->position[] = $this->lang->repo->common;
- $this->view->position[] = $this->lang->repo->view;
$this->display();
}
@@ -531,19 +528,18 @@ class repo extends control
* @access public
* @return void
*/
- public function browse($repoID = 0, $branchID = '', $objectID = 0, $path = '', $revision = 'HEAD', $refresh = 0, $branchOrTag = 'branch')
+ public function browse($repoID = 0, $branchID = '', $objectID = 0, $path = '', $revision = 'HEAD', $refresh = 0, $branchOrTag = 'branch', $type = 'dir', $recTotal = 0, $recPerPage = 10, $pageID = 1)
{
$repoID = $this->repo->saveState($repoID, $objectID);
$originBranchID = $branchID;
if($branchID) $branchID = base64_decode(helper::safe64Decode($branchID));
- /* Get path and refresh. */
+ /* Get path. */
if($this->get->repoPath) $path = $this->get->repoPath;
- if(empty($refresh) and $this->cookie->repoRefresh) $refresh = $this->cookie->repoRefresh;
/* Set menu and session. */
$this->commonAction($repoID, $objectID);
- $this->repo->setBackSession('list', $withOtherModule = true);
+ $this->repo->setBackSession('list', true);
session_start();
$this->session->set('revisionList', $this->app->getURI(true));
@@ -551,7 +547,7 @@ class repo extends control
session_write_close();
/* Get repo and synchronous commit. */
- $repo = $this->repo->getRepoByID($repoID);
+ $repo = $this->repo->getByID($repoID);
if($repo->SCM == 'Git' and !is_dir($repo->path))
{
$error = sprintf($this->lang->repo->error->notFound, $repo->name, $repo->path);
@@ -587,8 +583,8 @@ class repo extends control
$path = $this->repo->decodePath($path);
/* Load pager. */
- $this->app->loadClass('pager', $static = true);
- $pager = new pager(0, 10, 1);
+ $this->app->loadClass('pager', true);
+ $pager = new pager($recTotal, $recPerPage, $pageID);
if($_POST)
{
@@ -599,6 +595,7 @@ class repo extends control
}
/* Refresh repo. */
+ if(empty($refresh) and $this->cookie->repoRefresh) $refresh = $this->cookie->repoRefresh;
if($refresh)
{
$this->repo->updateCommit($repoID, $objectID, $originBranchID);
@@ -607,39 +604,36 @@ class repo extends control
}
/* Set logType and revisions. */
- $logType = 'dir';
+ $logType = $type;
$revisions = $this->repo->getCommits($repo, $path, $revision, $logType, $pager);
$lastRevision = current($revisions);
+ $lastRevision = empty($lastRevision) ? new stdclass() : $lastRevision;
+ $pathInfo = '&root=' . $this->repo->encodePath(empty($path) ? '/' : $path);
+ foreach($revisions as $item)
+ {
+ $item->link = $this->repo->createLink('revision', "repoID=$repoID&objectID=$objectID&revision={$item->revision}" . $pathInfo);
+ $item->revision = $repo->SCM != 'Subversion' ? substr($item->revision, 0, 10) : $item->revision;
+ }
/* Get files info. */
- if($repo->SCM == 'Gitlab')
- {
- $cacheFile = $this->repo->getCacheFile($repo->id, $path, $branchID);
- $cacheRefreshTime = isset($lastRevision->time) ? date('Y-m-d H:i', strtotime($lastRevision->time)) : date('Y-m-d H:i');
- if(!$cacheFile or !file_exists($cacheFile) or filemtime($cacheFile) < strtotime($cacheRefreshTime))
- {
- $infos = $this->repo->getFileList($repo, $branchID, $path);
+ $infos = $this->repoZen->getFilesInfo($repo, $path, $branchID, $refresh, $revision, $lastRevision);
- if($cacheFile)
- {
- if(!file_exists($cacheFile . '.lock'))
- {
- touch($cacheFile . '.lock');
- file_put_contents($cacheFile, serialize($infos));
- unlink($cacheFile . '.lock');
- }
- }
+ $base64BranchID = helper::safe64Encode(base64_encode($branchID));
+ foreach($infos as $info)
+ {
+ $infoPath = trim(urldecode($path) . '/' . $info->name, '/');
+ $info->revision = $repo->SCM == 'Subversion' ? $info->revision : substr($info->revision, 0, 10);
+ if($info->kind == 'dir')
+ {
+ $info->link = $this->repo->createLink('browse', "repoID=$repoID&branchID=$base64BranchID&objectID=$objectID&path=" . $this->repo->encodePath($infoPath));
}
else
{
- $infos = unserialize(file_get_contents($cacheFile));
+ $info->link = $this->repo->createLink('view', "repoID=$repoID&objectID=$objectID&entry=" . $this->repo->encodePath($infoPath));
}
}
- else
- {
- $infos = $this->repo->getFileCommits($repo, $branchID, $path);
- }
- if($this->cookie->repoRefresh) setcookie('repoRefresh', 0, 0, $this->config->webRoot, '', $this->config->cookieSecure, true);
+
+ if($this->cookie->repoRefresh) helper::setcookie('repoRefresh', 0, 0, $this->config->webRoot, '', $this->config->cookieSecure, true);
/* Synchronous commit only in root path. */
if(in_array($repo->SCM, $this->config->repo->gitTypeList) and empty($path) and $infos and empty($revisions)) $this->locate($this->repo->createLink('showSyncCommit', "repoID=$repoID&objectID=$objectID&branch=" . helper::safe64Encode(base64_encode($this->cookie->repoBranch))));
@@ -686,15 +680,16 @@ class repo extends control
public function log($repoID = 0, $objectID = 0, $entry = '', $revision = 'HEAD', $type = 'dir', $recTotal = 0, $recPerPage = 50, $pageID = 1)
{
if($this->get->repoPath) $entry = $this->get->repoPath;
- $this->repo->setBackSession('log', $withOtherModule = true);
+ $this->repo->setBackSession('log', true);
if($repoID == 0) $repoID = $this->session->repoID;
- $repo = $this->repo->getRepoByID($repoID);
+ $repo = $this->repo->getByID($repoID);
$file = $entry;
$entry = $this->repo->decodePath($entry);
- $this->app->loadClass('pager', $static = true);
+ $this->app->loadClass('pager', true);
$pager = new pager($recTotal, $recPerPage, $pageID);
+ $pager->recPerPage = $recPerPage;
if($_POST)
{
@@ -739,20 +734,13 @@ class repo extends control
* @access public
* @return void
*/
- public function revision($repoID, $objectID = 0, $revision = '', $root = '', $type = 'dir')
+ public function revision($repoID, $objectID = 0, $revision = '')
{
- $this->repo->setBackSession();
if($repoID == 0) $repoID = $this->session->repoID;
- $repo = $this->repo->getRepoByID($repoID);
+ $repo = $this->repo->getByID($repoID);
- /* Save session. */
- $this->session->set('revisionList', $this->app->getURI(true), 'repo');
-
- $this->commonAction($repoID, $objectID);
$this->scm->setEngine($repo);
$log = $this->scm->log('', $revision, $revision);
- $log[0]->comment = $this->repo->replaceCommentLink($log[0]->comment);
- $log[0]->commit = '';
$history = $this->dao->select('*')->from(TABLE_REPOHISTORY)->where('revision')->eq($log[0]->revision)->andWhere('repo')->eq($repoID)->fetch();
if($history)
@@ -768,8 +756,6 @@ class repo extends control
{
$oldRevision = $this->dao->select('*')->from(TABLE_REPOHISTORY)->where('revision')->lt($history->revision)->andWhere('repo')->eq($repoID)->orderBy('revision_desc')->limit(1)->fetch('revision');
}
-
- $log[0]->commit = $history->commit;
}
if(empty($oldRevision))
@@ -778,55 +764,7 @@ class repo extends control
if($history and in_array($repo->SCM, $this->config->repo->gitTypeList)) $oldRevision = "{$history->revision}^";
}
- $changes = array();
- $viewPriv = common::hasPriv('repo', 'view');
- $diffPriv = common::hasPriv('repo', 'diff');
- foreach($log[0]->change as $path => $change)
- {
- if($repo->prefix) $path = str_replace($repo->prefix, '', $path);
- $encodePath = $this->repo->encodePath($path);
- if($change['kind'] == '' or $change['kind'] == 'file')
- {
- $change['view'] = $viewPriv ? html::a($this->repo->createLink('view', "repoID=$repoID&objectID=$objectID&entry=$encodePath&revision=$revision"), $this->lang->repo->viewA, '', "data-app='{$this->app->tab}'") : '';
- if($change['action'] == 'M') $change['diff'] = $diffPriv ? html::a($this->repo->createLink('diff', "repoID=$repoID&objectID=$objectID&entry=$encodePath&oldRevision=$oldRevision&newRevision=$revision"), $this->lang->repo->diffAB, '', "data-app='{$this->app->tab}'") : '';
- }
- else
- {
- $change['view'] = $viewPriv ? html::a($this->repo->createLink('browse', "repoID=$repoID&branchID=&objectID=$objectID&path=$encodePath&revision=$revision"), $this->lang->repo->browse, '', "data-app='{$this->app->tab}'") : '';
- if($change['action'] == 'M') $change['diff'] = $diffPriv ? html::a($this->repo->createLink('diff', "repoID=$repoID&objectID=$objectID&entry=$encodePath&oldRevision=$oldRevision&newRevision=$revision"), $this->lang->repo->diffAB, '', "data-app='{$this->app->tab}'") : '';
- }
- $changes[$path] = $change;
- }
-
- $root = $this->repo->decodePath($root);
- $parent = '';
- if($type == 'file')
- {
- $parent = $this->dao->select('parent')->from(TABLE_REPOFILES)
- ->where('revision')->eq($history->id)
- ->andWhere('path')->eq('/' . $root)
- ->fetch('parent');
- }
-
- $this->view->title = $this->lang->repo->common;
- $this->view->log = $log[0];
- $this->view->repo = $repo;
- $this->view->path = $root;
- $this->view->type = $type;
- $this->view->changes = $changes;
- $this->view->repoID = $repoID;
- $this->view->branchID = $this->cookie->repoBranch;
- $this->view->objectID = $objectID;
- $this->view->revision = $log[0]->revision;
- $this->view->parentDir = $parent;
- $this->view->oldRevision = $oldRevision;
- $this->view->preAndNext = $this->repo->getPreAndNext($repo, $root, $revision, $type, 'revision');
-
- $this->view->title = $this->lang->repo->common . $this->lang->colon . $this->lang->repo->viewRevision;
- $this->view->position[] = $this->lang->repo->common;
- $this->view->position[] = $this->lang->repo->viewRevision;
-
- $this->display();
+ $this->locate($this->repo->createLink('diff', "repoID=$repoID&objectID=$objectID&entry=&oldrevision=$oldRevision&newRevision={$log[0]->revision}"));
}
/**
@@ -846,7 +784,7 @@ class repo extends control
if($this->get->repoPath) $entry = $this->get->repoPath;
if($repoID == 0) $repoID = $this->session->repoID;
- $repo = $this->repo->getRepoByID($repoID);
+ $repo = $this->repo->getByID($repoID);
$file = $entry;
$entry = $this->repo->decodePath($entry);
@@ -900,7 +838,7 @@ class repo extends control
if($this->get->repoPath) $entry = $this->get->repoPath;
$file = $entry;
- $repo = $this->repo->getRepoByID($repoID);
+ $repo = $this->repo->getByID($repoID);
$entry = $this->repo->decodePath($entry);
if($repo->SCM == 'Git' and !is_dir($repo->path))
@@ -922,7 +860,7 @@ class repo extends control
if($this->post->arrange)
{
$arrange = $this->post->arrange;
- setcookie('arrange', $arrange);
+ helper::setcookie('arrange', $arrange);
}
if($this->post->encoding) $encoding = $this->post->encoding;
if($this->post->isBranchOrTag) $isBranchOrTag = $this->post->isBranchOrTag;
@@ -999,8 +937,6 @@ class repo extends control
$this->view->info = $info;
$this->view->title = $this->lang->repo->common . $this->lang->colon . $this->lang->repo->diff;
- $this->view->position[] = $this->lang->repo->common;
- $this->view->position[] = $this->lang->repo->diff;
$this->display();
}
@@ -1021,7 +957,7 @@ class repo extends control
{
if($this->get->repoPath) $path = $this->get->repoPath;
$entry = $this->repo->decodePath($path);
- $repo = $this->repo->getRepoByID($repoID);
+ $repo = $this->repo->getByID($repoID);
if($isBranchOrTag)
{
@@ -1031,7 +967,15 @@ class repo extends control
$this->commonAction($repoID);
$this->scm->setEngine($repo);
- $content = $type == 'file' ? $this->scm->cat($entry, $fromRevision) : $this->scm->diff($entry, $fromRevision, $toRevision, 'patch', $isBranchOrTag ? 'isBranchOrTag': '');
+
+ if($type === 'file')
+ {
+ $content = $this->scm->cat($entry, $fromRevision);
+ }
+ else
+ {
+ $content = $this->scm->diff($entry, $fromRevision, $toRevision, 'patch', $isBranchOrTag ? 'isBranchOrTag': '');
+ }
$fileName = basename(urldecode($entry));
if($type != 'file') $fileName .= "r$fromRevision--r$toRevision.patch";
@@ -1050,7 +994,7 @@ class repo extends control
if($_POST)
{
$this->loadModel('setting')->setItem('system.repo.rules', json_encode($this->post->rules));
- return $this->send(array('result' => 'success', 'message' => $this->lang->saveSuccess, 'locate' => inlink('setRules')));
+ return $this->send(array('result' => 'success', 'message' => $this->lang->saveSuccess, 'locate' => 'reload'));
}
$repoID = $this->session->repoID;
@@ -1063,7 +1007,6 @@ class repo extends control
if(is_string($this->config->repo->rules)) $this->config->repo->rules = json_decode($this->config->repo->rules, true);
$this->view->title = $this->lang->repo->common . $this->lang->colon . $this->lang->repo->setRules;
- $this->view->position[] = $this->lang->repo->setRules;
$this->display();
}
@@ -1085,7 +1028,6 @@ class repo extends control
if($branch) $branch = base64_decode(helper::safe64Decode($branch));
$this->view->title = $this->lang->repo->common . $this->lang->colon . $this->lang->repo->showSyncCommit;
- $this->view->position[] = $this->lang->repo->showSyncCommit;
$latestInDB = $this->repo->getLatestCommit($repoID);
$this->view->version = $latestInDB ? (int)$latestInDB->commit : 1;
@@ -1110,25 +1052,28 @@ class repo extends control
* @access public
* @return void
*/
- public function linkStory($repoID, $revision, $browseType = '', $param = 0, $orderBy = 'id_desc', $recTotal = 0, $recPerPage = 100, $pageID = 1)
+ public function linkStory($repoID, $revision, $browseType = '', $param = 0, $orderBy = 'id_desc', $recTotal = 0, $recPerPage = 10, $pageID = 1)
{
if(!empty($_POST['stories']))
{
$this->repo->link($repoID, $revision, 'story');
- if(dao::isError()) return print(js::error(dao::getError()));
- return print(js::closeModal('parent', '', "parent.parent[parent.parent.length - 2].getRelation('$revision')"));
+ if(dao::isError()) return $this->send(array('result' => 'fail', 'message' => dao::getError()));
+
+ return $this->send(array('result' => 'success', 'callback' => "$('.tab-content .active iframe')[0].contentWindow.getRelation('$revision')", 'closeModal' => true));
}
$this->loadModel('story');
+ $this->app->loadLang('release');
+ $this->app->loadModuleConfig('release');
$this->app->loadLang('productplan');
- $repo = $this->repo->getRepoByID($repoID);
+ $repo = $this->repo->getByID($repoID);
$product = $this->loadModel('product')->getById($repo->product);
$modules = $this->loadModel('tree')->getOptionMenu($repo->product, 'story');
/* Load pager. */
- $this->app->loadClass('pager', $static = true);
+ $this->app->loadClass('pager', true);
$pager = new pager($recTotal, $recPerPage, $pageID);
/* Build search form. */
@@ -1152,7 +1097,7 @@ class repo extends control
else
{
$this->config->product->search['fields']['branch'] = $this->lang->product->branch;
- $this->config->product->search['params']['branch']['values'] = array('' => '') + $this->loadModel('branch')->getPairs($product->id, 'noempty');
+ $this->config->product->search['params']['branch']['values'] = $this->loadModel('branch')->getPairs($product->id, 'noempty');
}
session_start();
@@ -1166,7 +1111,7 @@ class repo extends control
}
else
{
- $allStories = $this->story->getProductStories($product->id, 0, '0', 'draft,active,changed', 'story', 'id_desc', false, array_keys($linkedStories), $pager);
+ $allStories = $this->story->getProductStories($product->id, 0, '0', 'draft,active,changed', 'story', $orderBy, false, array_keys($linkedStories), $pager);
}
$this->view->modules = $modules;
@@ -1196,26 +1141,29 @@ class repo extends control
* @access public
* @return void
*/
- public function linkBug($repoID, $revision = '', $browseType = '', $param = 0, $orderBy = 'id_desc', $recTotal = 0, $recPerPage = 100, $pageID = 1)
+ public function linkBug($repoID, $revision = '', $browseType = '', $param = 0, $orderBy = 'id_desc', $recTotal = 0, $recPerPage = 10, $pageID = 1)
{
if(!empty($_POST['bugs']))
{
$this->repo->link($repoID, $revision, 'bug');
- if(dao::isError()) return print(js::error(dao::getError()));
- return print(js::closeModal('parent', '', "parent.parent[parent.parent.length - 2].getRelation('$revision')"));
+ if(dao::isError()) return $this->send(array('result' => 'fail', 'message' => dao::getError()));
+
+ return $this->send(array('result' => 'success', 'callback' => "$('.tab-content .active iframe')[0].contentWindow.getRelation('$revision')", 'closeModal' => true));
}
$this->loadModel('bug');
+ $this->app->loadLang('release');
+ $this->app->loadModuleConfig('release');
$this->app->loadLang('productplan');
$queryID = ($browseType == 'bysearch') ? (int)$param : 0;
- $repo = $this->repo->getRepoByID($repoID);
+ $repo = $this->repo->getByID($repoID);
$product = $this->loadModel('product')->getById($repo->product);
$modules = $this->loadModel('tree')->getOptionMenu($product->id, 'bug');
/* Load pager. */
- $this->app->loadClass('pager', $static = true);
+ $this->app->loadClass('pager', true);
$pager = new pager($recTotal, $recPerPage, $pageID);
/* Build search form. */
@@ -1225,8 +1173,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($product->id, $branch = 'all', $params = '');
- $this->config->bug->search['params']['resolvedBuild']['values'] = $this->loadModel('build')->getBuildPairs($product->id, $branch = 'all', $params = '');
+ $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')
@@ -1237,7 +1185,7 @@ class repo extends control
else
{
$this->config->bug->search['fields']['branch'] = $this->lang->product->branch;
- $this->config->bug->search['params']['branch']['values'] = array('' => '') + $this->loadModel('branch')->getPairs($product->id, 'noempty');
+ $this->config->bug->search['params']['branch']['values'] = $this->loadModel('branch')->getPairs($product->id, 'noempty');
}
session_start();
$this->loadModel('search')->setSearchParams($this->config->bug->search);
@@ -1253,6 +1201,8 @@ class repo extends control
$allBugs = $this->bug->getActiveBugs($product->id, 0, '0', array_keys($linkedBugs), $pager);
}
+ foreach($allBugs as $bug) $bug->statusText = $this->processStatus('bug', $bug);
+
$this->view->modules = $modules;
$this->view->users = $this->loadModel('user')->getPairs('noletter');
$this->view->allBugs = $allBugs;
@@ -1280,14 +1230,15 @@ class repo extends control
* @access public
* @return void
*/
- public function linkTask($repoID, $revision = '', $browseType = 'unclosed', $param = 0, $orderBy = 'id_desc', $recTotal = 0, $recPerPage = 100, $pageID = 1)
+ public function linkTask($repoID, $revision = '', $browseType = 'unclosed', $param = 0, $orderBy = 'id_desc', $recTotal = 0, $recPerPage = 10, $pageID = 1)
{
if(!empty($_POST['tasks']))
{
$this->repo->link($repoID, $revision, 'task');
- if(dao::isError()) return print(js::error(dao::getError()));
- return print(js::closeModal('parent', '', "parent.parent[parent.parent.length - 2].getRelation('$revision')"));
+ if(dao::isError()) return $this->send(array('result' => 'fail', 'message' => dao::getError()));
+
+ return $this->send(array('result' => 'success', 'callback' => "$('.tab-content .active iframe')[0].contentWindow.getRelation('$revision')", 'closeModal' => true));
}
$this->loadModel('execution');
@@ -1298,12 +1249,12 @@ class repo extends control
$browseType = strtolower($browseType);
$queryID = ($browseType == 'bysearch') ? (int)$param : 0;
- $repo = $this->repo->getRepoByID($repoID);
+ $repo = $this->repo->getByID($repoID);
$product = $this->loadModel('product')->getById($repo->product);
- $modules = $this->loadModel('tree')->getOptionMenu($product->id, $viewType = 'task');
+ $modules = $this->loadModel('tree')->getOptionMenu($product->id, 'task');
/* Load pager. */
- $this->app->loadClass('pager', $static = true);
+ $this->app->loadClass('pager', true);
$pager = new pager($recTotal, $recPerPage, $pageID);
/* Build search form. */
@@ -1383,47 +1334,39 @@ class repo extends control
}
/**
+ * 导入版本库。
* Import repos.
*
- * @param int $server
+ * @param int $server
* @access public
* @return void
*/
- public function import($server = 0)
+ public function import(int $server = 0)
{
if($this->viewType !== 'json') $this->commonAction();
+
if($_POST)
{
- $this->repo->batchCreate();
+ $repos = $this->repoZen->prepareBatchCreate();
+ if($repos) $this->repo->batchCreate($repos, (int)$_POST['serviceHost']);
if(dao::isError()) return $this->send(array('result' => 'fail', 'message' => dao::getError()));
+
return $this->send(array('result' => 'success', 'message' => $this->lang->saveSuccess, 'locate' => $this->repo->createLink('maintain')));
}
$gitlabList = $this->loadModel('gitlab')->getList();
$gitlab = empty($server) ? array_shift($gitlabList) : $this->gitlab->getById($server);
- $repoList = array();
- if(!empty($gitlab))
- {
- $repoList = $this->gitlab->apiGetProjects($gitlab->id);
- $existRepoList = $this->dao->select('serviceProject,name')->from(TABLE_REPO)
- ->where('SCM')->eq(ucfirst($gitlab->type))
- ->andWhere('serviceHost')->eq($gitlab->id)
- ->fetchPairs();
- foreach($repoList as $key => $repo)
- {
- if(isset($existRepoList[$repo->id])) unset($repoList[$key]);
- }
- }
+ $repoList = $this->repoZen->getGitlabNotExistRepos($gitlab);
$products = $this->loadModel('product')->getPairs('', 0, '', 'all');
$this->view->gitlabPairs = $this->gitlab->getPairs();
- $this->view->products = $products;
- $this->view->projects = $this->product->getProjectPairsByProductIDList(array_keys($products));
- $this->view->gitlab = $gitlab;
- $this->view->repoList = array_values($repoList);
+ $this->view->products = $products;
+ $this->view->projects = $this->product->getProjectPairsByProductIDList(array_keys($products));
+ $this->view->gitlab = $gitlab;
+ $this->view->repoList = array_values($repoList);
$this->display();
}
@@ -1433,12 +1376,12 @@ class repo extends control
* @param int $repoID
* @param string $type
* @access public
- * @return int|void
+ * @return void
*/
public function ajaxSyncCommit($repoID = 0, $type = 'batch')
{
set_time_limit(0);
- $repo = $this->repo->getRepoByID($repoID);
+ $repo = $this->repo->getByID($repoID);
if(empty($repo)) return;
if($repo->synced) return print($this->config->repo->repoSyncLog->finish);
@@ -1510,7 +1453,7 @@ class repo extends control
}
$this->repo->setRepoBranch($branchID);
- setcookie("syncBranch", $branchID, 0, $this->config->webRoot, '', $this->config->cookieSecure, true);
+ helper::setcookie("syncBranch", $branchID, 0, $this->config->webRoot, '', $this->config->cookieSecure, true);
}
}
@@ -1573,7 +1516,7 @@ class repo extends control
public function ajaxSyncBranchCommit($repoID = 0, $branch = '')
{
set_time_limit(0);
- $repo = $this->repo->getRepoByID($repoID);
+ $repo = $this->repo->getByID($repoID);
if(empty($repo)) return;
if(!in_array($repo->SCM, $this->config->repo->gitTypeList)) return print('finish');
if($branch) $branch = base64_decode(helper::safe64Decode($branch));
@@ -1581,7 +1524,7 @@ class repo extends control
$this->scm->setEngine($repo);
$this->repo->setRepoBranch($branch);
- setcookie("syncBranch", $branch, 0, $this->config->webRoot, '', $this->config->cookieSecure, true);
+ helper::setcookie("syncBranch", $branch, 0, $this->config->webRoot, '', $this->config->cookieSecure, true);
$latestInDB = $this->dao->select('t1.*')->from(TABLE_REPOHISTORY)->alias('t1')
->leftJoin(TABLE_REPOBRANCH)->alias('t2')->on('t1.id=t2.revision')
@@ -1602,7 +1545,7 @@ class repo extends control
{
if($branch) $this->repo->saveExistCommits4Branch($repo->id, $branch);
- setcookie("syncBranch", $branch, 0, $this->config->webRoot, '', $this->config->cookieSecure, true);
+ helper::setcookie("syncBranch", $branch, 0, $this->config->webRoot, '', $this->config->cookieSecure, true);
$this->repo->markSynced($repoID);
return print('finish');
}
@@ -1627,14 +1570,14 @@ class repo extends control
public function ajaxSideCommits($repoID, $path, $objectID = 0, $type = 'dir', $recTotal = 0, $recPerPage = 10, $pageID = 1)
{
if($this->get->repoPath) $path = $this->get->repoPath;
- $this->app->loadClass('pager', $static = true);
+ $this->app->loadClass('pager', true);
$pager = new pager($recTotal, $recPerPage, $pageID);
- $repo = $this->repo->getRepoByID($repoID);
+ $repo = $this->repo->getByID($repoID);
$path = $this->repo->decodePath($path);
$revisions = $this->repo->getCommits($repo, $path, 'HEAD', $type, $pager);
- $this->view->repo = $this->repo->getRepoByID($repoID);
+ $this->view->repo = $this->repo->getByID($repoID);
$this->view->revisions = $revisions;
$this->view->pager = $pager;
$this->view->repoID = $repoID;
@@ -1654,7 +1597,7 @@ class repo extends control
*/
public function ajaxGetSVNDirs($repoID, $path = '')
{
- $repo = $this->repo->getRepoByID($repoID);
+ $repo = $this->repo->getByID($repoID);
if($repo->SCM != 'Subversion') return print(json_encode(array()));
$path = $this->repo->decodePath($path);
@@ -1671,6 +1614,7 @@ class repo extends control
$dirs['/'] = $this->repo->encodePath($path);
foreach($tags as $dirPath => $dirName) $dirs[$dirPath] = $this->repo->encodePath($dirPath);
}
+
return print(json_encode($dirs));
}
@@ -1683,10 +1627,10 @@ class repo extends control
* @access public
* @return void
*/
- public function ajaxGetDropMenu($repoID, $module = 'repo', $method = 'browse', $projectID = 0)
+ public function ajaxGetDropMenu(int $repoID, string $module = 'repo', string $method = 'browse', int $projectID = 0)
{
if($module == 'repo' and !in_array($method, array('review', 'diff'))) $method = 'browse';
- if($module == 'mr') $method = 'browse';
+ if($module == 'mr' && $method != 'create') $method = 'browse';
if($module == 'job') $method = 'browse';
if($module == 'compile' and $method == 'logs') $method = 'browse';
if($module == 'bug' and $method == 'view')
@@ -1696,14 +1640,8 @@ class repo extends control
}
/* Get repo group by type. */
- $repoGroup = $this->repo->getRepoGroup($this->app->tab, $projectID);
- if($module == 'mr')
- {
- foreach($repoGroup as $type => $group)
- {
- if(!in_array(strtolower($type), $this->config->repo->gitServiceList)) unset($repoGroup[$type]);
- }
- }
+ $repoType = $module == 'mr' ? 'git' : '';
+ $repoGroup = $this->repo->getRepoGroup('project', $projectID, $repoType);
$this->view->repoID = $repoID;
$this->view->repoGroup = $repoGroup;
@@ -1749,8 +1687,9 @@ class repo extends control
->setDefault('products', array())
->setDefault('projects', array())
->get();
+ $productIds = $postData->products ? explode(',', $postData->products) : array();
+ $projectIds = $postData->projects ? explode(',', $postData->projects) : array();
- $productIds = $postData->products;
if(empty($productIds))
{
$products = $this->loadModel('product')->getPairs('', 0, '', 'all');
@@ -1759,8 +1698,14 @@ class repo extends control
/* Get all projects that can be accessed. */
$accessProjects = $this->loadModel('product')->getProjectPairsByProductIDList($productIds);
- $selectedProjects = array_intersect(array_keys($accessProjects), $postData->projects);
+ $selectedProjects = array_intersect(array_keys($accessProjects), $projectIds);
+ $options = array();
+ foreach($accessProjects as $projectID => $project)
+ {
+ $options[] = array('text' => $project, 'value' => $projectID);
+ }
+ return print(json_encode($options));
$name = isset($postData->number) ? "projects[{$postData->number}][]" : 'projects[]';
return print (html::select($name, $accessProjects, $selectedProjects, "class='form-control chosen' multiple"));
}
@@ -1776,7 +1721,13 @@ class repo extends control
{
$scm = strtolower($scm);
$hosts = $this->loadModel($scm)->getPairs();
- return print(html::select('pipelineHost', $hosts, '', "class='form-control chosen'"));
+
+ $options = array();
+ foreach($hosts as $hostID => $host)
+ {
+ $options[] = array('text' => $host, 'value' => $hostID);
+ }
+ return print(json_encode($options));
}
/**
@@ -1810,7 +1761,13 @@ class repo extends control
$options = "";
foreach($projects as $project) $options .= "";
- return print($options);
+ $options = array();
+ $options[] = array('text' => '', 'value' => '');;
+ foreach($projects as $project)
+ {
+ $options[] = array('text' => $project->full_name, 'value' => $project->full_name);
+ }
+ return print(json_encode($options));
}
/**
@@ -1830,7 +1787,13 @@ class repo extends control
foreach($projects as $project) $options .= "";
}
- return print($options);
+ $options = array();
+ $options[] = array('text' => '', 'value' => '');;
+ foreach($projects as $project)
+ {
+ $options[] = array('text' => $project->full_name, 'value' => $project->full_name);
+ }
+ return print(json_encode($options));
}
/**
@@ -1843,30 +1806,9 @@ class repo extends control
*/
public function ajaxGetGitlabProjects($gitlabID, $projectIdList = '', $filter = '')
{
- $showAll = ($filter == 'ALL' and common::hasPriv('repo', 'create')) ? true : false;
- if($this->app->user->admin or $showAll)
- {
- $projects = $this->loadModel('gitlab')->apiGetProjects($gitlabID, true, 0, 0, false);
- }
- else
- {
- $gitlabUser = $this->loadModel('gitlab')->getUserIDByZentaoAccount($gitlabID, $this->app->user->account);
- if(!$gitlabUser) $this->send(array('message' => array()));
+ $projects = $this->repo->getGitlabProjects($gitlabID, $projectIdList, $filter);
- $projects = $this->gitlab->apiGetProjects($gitlabID, $filter ? 'false' : 'true');
- $groupIDList = array(0 => 0);
- $groups = $this->gitlab->apiGetGroups($gitlabID, 'name_asc', 'developer');
- foreach($groups as $group) $groupIDList[] = $group->id;
- if($filter == 'IS_DEVELOPER')
- {
- foreach($projects as $key => $project)
- {
- if($this->gitlab->checkUserAccess($gitlabID, 0, $project, $groupIDList, 'developer') == false) unset($projects[$key]);
- }
- }
- }
-
- if(!$projects) $this->send(array('message' => array()));
+ if(!$projects) return print('[]');
$projectIdList = $projectIdList ? explode(',', $projectIdList) : null;
$options = "";
foreach($projects as $project)
@@ -1875,7 +1817,14 @@ class repo extends control
$options .= "";
}
- return print($options);
+ $options = array();
+ $options[] = array('text' => '', 'value' => '');;
+ foreach($projects as $project)
+ {
+ if(!empty($projectIdList) and $project and !in_array($project->id, $projectIdList)) continue;
+ $options[] = array('text' => $project->name_with_namespace, 'value' => $project->id);
+ }
+ return print(json_encode($options));
}
/**
@@ -1889,7 +1838,7 @@ class repo extends control
*/
public function ajaxGetBranchDropMenu($repoID, $branchID, $objectID)
{
- $repo = $this->repo->getRepoByID($repoID);
+ $repo = $this->repo->getByID($repoID);
$branches = $this->repo->getBranches($repo);
if($branchID) $branchID = base64_decode($branchID);
@@ -1914,7 +1863,14 @@ class repo extends control
public function ajaxLoadProducts($repoID)
{
$productPairs = $this->repo->getProductsByRepo($repoID);
- echo html::select('product', array('') + $productPairs, key($productPairs), "class='form-control chosen'");
+
+ $options = array();
+ $options[] = array('text' => '', 'value' => '');;
+ foreach($productPairs as $productID => $productName)
+ {
+ $options[] = array('text' => $productName, 'value' => $productID);
+ }
+ return print(json_encode($options));
}
/**
@@ -1960,7 +1916,7 @@ class repo extends control
public function ajaxGetExecutions($productID, $branch = 0)
{
$executions = $this->repo->getExecutionPairs($productID, $branch);
- echo html::select('execution', array('' => '') + $executions, '', 'class="form-control chosen"');
+ echo html::select('execution', $executions, '', 'class="form-control chosen"');
}
/**
@@ -1973,7 +1929,6 @@ class repo extends control
*/
public function downloadCode($repoID = 0, $branch = '')
{
- $repo = $this->repo->getRepoByID($repoID);
$savePath = $this->app->getDataRoot() . 'repo';
if(!is_dir($savePath))
{
@@ -1981,7 +1936,7 @@ class repo extends control
mkdir($savePath, 0777, true);
}
- $repo = $this->repo->getRepoByID($repoID);
+ $repo = $this->repo->getByID($repoID);
$this->scm = $this->app->loadClass('scm');
$this->scm->setEngine($repo);
$url = $this->scm->getDownloadUrl($branch, $savePath);
@@ -2015,7 +1970,7 @@ class repo extends control
public function ajaxGetFileTree($repoID, $branch = '')
{
$branch = base64_decode($branch);
- $repo = $this->repo->getRepoByID($repoID);
+ $repo = $this->repo->getByID($repoID);
$files = $this->repo->getFileTree($repo, $branch);
return print($files);
}
@@ -2074,7 +2029,7 @@ class repo extends control
public function ajaxGetCommitInfo()
{
$line = $this->post->line;
- $repo = $this->repo->getRepoByID($this->post->repoID);
+ $repo = $this->repo->getByID($this->post->repoID);
$entry = $this->repo->decodePath($this->post->entry);
$revision = $this->post->revision;
$returnType = $this->post->returnType ? $this->post->returnType : 'view';
diff --git a/module/repo/css/ajaxgeteditorcontent.ui.css b/module/repo/css/ajaxgeteditorcontent.ui.css
new file mode 100644
index 0000000000..87703d3cda
--- /dev/null
+++ b/module/repo/css/ajaxgeteditorcontent.ui.css
@@ -0,0 +1,39 @@
+.m-repo-ajaxgeteditorcontent {overflow: hidden;}
+#log .tip-circular {background: #2e7fff; border-radius: 10px; padding: 0px 7px;}
+#log {margin-top: 8px; display: none; padding: 5px; justify-content: space-between; height: 25px; line-height: 20px; background: #efefef;}
+#log .history {margin-left: 5px; max-width: 90%;}
+#log .action-btn .btn {border: none; background: none;}
+#related {display: none; margin-top: 2px; background: #fff; height: 0px;}
+
+#relationTabs .nav > li > a {padding: 8px 1px; display: flex; white-space: pre;}
+#relationTabs .nav-tabs > li > a > span {margin-right: 5px;}
+#relationTabs .tab-pane {display: none;}
+#relationTabs {background: #FFFFFF;}
+#relationTabs .tab-pane.active {display: block;}
+#relationTabs .tab-nav-item {max-width: none !important;}
+#relationTabs .tab-nav-item .icon {line-height: inherit;}
+#relationTabs {overflow: hidden; padding-bottom: 10px; position: relative; height: 100%;}
+#relationTabs > .nav-tabs {position: absolute; display: flex;}
+#relationTabs .tab-nav-link .title {max-width: 300px; display: inline-block; white-space: nowrap; width: 100%; overflow: hidden; text-overflow: ellipsis; padding: 0 5px;}
+#relationTabs{width: 100%}
+#relationTabs .nav-tabs > li > a.active > span {background: #E6F0FF;color: #2e7fff;}
+#relationTabs .nav-tabs > li > a > i, #relationTabs .nav-tabs > li > a > span {background: #EDEEF2; font-weight: normal; padding: 1px 5px;}
+#relationTabs .nav-tabs > li > a > span > i {margin-right: 8px;}
+#relationTabs .nav-tabs > li > .unlinks {padding-left: 0;color: #169}
+#relationTabs .nav-tabs > li > .unlinks:hover {text-decoration: underline;color: #C61A1A;}
+#relationTabs .nav-tabs > li > .unlinks > i {background: none;padding: 0;}
+#relationTabs .nav-tabs>.nav-item>a.active:after{border: none;}
+#relationTabs .tab-content{margin-top: 40px;}
+#related .content, #related .btn {background-color: #F4F5F7; border: none;}
+#related .btn.pull-right {margin-right: 0;z-index: 1;}
+#related .btn.pull-left {z-index: 1;}
+#related .nav-tabs > li > a.active:before {background: none; height: 0;}
+#related .table-empty-tip {padding: 35px 10px;}
+.table-empty-tip {padding: 80px 10px;text-align: center;background: #fff;}
+.repoCode .binary a .icon-download {font-size: 50px;}
+.repoCode .binary, .repoCode .image {text-align: center;}
+.repoCode .binary a {margin: 100px 0px; display: block;}
+.repoCode .image {margin-top: 10px;}
+#codeContainer{height: 100%;}
+#related .nav-item {gap: 0;}
+.nav-tabs>li>a.active {font-weight: 700;color: #313c52!important}
\ No newline at end of file
diff --git a/module/repo/css/blame.ui.css b/module/repo/css/blame.ui.css
new file mode 100644
index 0000000000..7a1a2b4fd8
--- /dev/null
+++ b/module/repo/css/blame.ui.css
@@ -0,0 +1 @@
+#featureBar .nav .btn>.icon {color: #ffffff;}
diff --git a/module/repo/css/browse.ui.css b/module/repo/css/browse.ui.css
new file mode 100644
index 0000000000..725c2dd3e5
--- /dev/null
+++ b/module/repo/css/browse.ui.css
@@ -0,0 +1,7 @@
+.repo-downloadCode{margin: 8px 10px 8px 0;}
+.repo-select{margin-right: 10px;}
+.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;}
+#actionBar>.dropdown {position: absolute!important;}
\ No newline at end of file
diff --git a/module/repo/css/common.ui.css b/module/repo/css/common.ui.css
new file mode 100644
index 0000000000..0f826f5c02
--- /dev/null
+++ b/module/repo/css/common.ui.css
@@ -0,0 +1,53 @@
+#fileTabs .nav-tabs {overflow: hidden;position: absolute; height: 40px;background: #efefef;}
+#fileTabs .nav-tabs > li.nav-item > a {z-index: 1; background: none; padding: 1.7rem; cursor: pointer !important; margin-left: -25px; margin-top: 12px;}
+#fileTabs .nav-tabs > li > a::after {content: ''; position: absolute; top: 2px; left: 10px; width: 85%; right: 6px; bottom: 0; background: #f8f8f8; border: none; border-radius: 10px 10px 0 0; transform: perspective(0.5rem) rotateX(2deg); z-index: -1;}
+#fileTabs .nav-tabs > li > a.active span {font-weight: 700; color: #2b80ff!important;}
+#fileTabs .nav-tabs > li > a.active::after {background: #fff;}
+#fileTabs > .nav-tabs {overflow: hidden; padding-bottom: 10px; position: relative; height: 40px; background: #efefef;}
+#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: 9px;}
+#log .action-btn {margin-top: -7px;}
+.repoCode .btn.btn-right, .repoCode .btn.btn-left {margin-right: 0; padding: 6px 6px;}
+#related .btn-right, #related .btn-left {padding-top: 9px;}
+.btn-left, .btn-right {display: none;}
+#linkObject{display: none;}
+#related .btn-right, #related .btn-left {padding-top: 9px;}
+#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');}
+.mini-icon {display: inline-block; height: 16px; width: 16px; background-color: transparent; background-position: 0 0; background-repeat: no-repeat; vertical-align: text-bottom; margin-right: 5px;}
+#monacoTree .tree-item-content {cursor: pointer;}
+.sync-content {margin: 10px}
+.sync-main-content {padding: 20px;background-color: #fff;border-radius: 4px;}
+.sync-line {border-top: 1px solid #e4e4e4; margin: 10px 0;}
+.sync-main-content div {width: 100%;}
+.sync-main-content h3 {font-size: 16px; font-weight: 700;}
+.sync-main-content p {color: #313c52;}
+.sync-main-content .alert > [class*="icon-"] {font-size: 42px;opacity: 0.8;}
+.repo-linkstory-title {background-color: #e6f0ff; color: #2e7fff; padding: 12px 6px 12px;}
+#repoBranchDropMenu .dropmenu-btn {justify-content: space-between; width: 100%;}
+#monacoTabs .nav > li > a {padding: 8px 10px; display: flex; white-space: pre;}
+#monacoTabs .nav-tabs > li > a > span {margin-right: 5px;}
+#monacoTabs .tab-pane {display: none;}
+#monacoTabs {background: #FFFFFF;}
+#monacoTabs .tab-pane.active {display: block;}
+#monacoTabs .tab-nav-item {max-width: none !important;}
+#monacoTabs .tab-nav-item .icon {line-height: inherit;}
+#monacoTabs {overflow: hidden; position: relative; height: 100%;}
+#monacoTabs > .nav-tabs {position: absolute; display: flex; padding-right: 20px;}
+#fileTabs .arrow-left, #fileTabs .arrow-right {line-height: 28px; padding: 6px; width: 26px; background: #efefef; cursor: pointer;}
+#fileTabs .arrow-right {right: 40px; cursor: pointer;}
+#monacoTabs ul {min-width: 100%; padding-left: 25px; z-index: 1;}
+#monacoTabs .tab-content {margin-top: 40px;}
+#monacoTabsContainer {background: #efefef;}
+.monaco-dropmenu{background: #efefef; height: 40px; line-height: 40px;}
+#fileTabs > .btn {background: #efefef; border: none; --tw-ring-shadow: none;}
+#fileTabs > .btn-right {padding-right: 40px;}
+#repoBranchDropMenu > .dropmenu-btn {background: white; --tw-ring-color: none; color: inherit; margin-bottom: 10px; background: white;}
+.repo-select #repoBranchDropMenu > .dropmenu-btn {background: inherit; --tw-ring-color: none; color: inherit; margin-bottom: 0;}
+#repoBranchDropMenu > .dropmenu-btn > .icon-angle-right {display: none;}
+#log .action-btn .caret {display: none;}
+#fileTabs .monaco-dropmenu .caret {display: none;}
+#monacoTabs .gap-x-5 {-moz-column-gap: 0; column-gap: 0;}
\ No newline at end of file
diff --git a/module/repo/css/diff.ui.css b/module/repo/css/diff.ui.css
new file mode 100644
index 0000000000..86b7dbe62f
--- /dev/null
+++ b/module/repo/css/diff.ui.css
@@ -0,0 +1,18 @@
+.diff-back-btn {border: none; --tw-ring-shadow: none}
+.diff-back-btn::after {border: none;}
+.diff-back-btn::before {border: none;}
+.label-info {background-color: #37b2fe}
+.label-info[href]:focus,.label-info[href]:hover {color: #fff; background-color: #049efe}
+.label-info.label-outline {color: #37b2fe; background: 0 0; border: 1px solid #37b2fe}
+.label {padding: .2em .6em .2em; color: #fff;text-align: center; white-space: nowrap; vertical-align: middle; border-radius: .25em;}
+.diff-label {border: none; --tw-ring-shadow: none; margin-left: 5px; margin-right: 10px}
+.label-exchange {background-color: #566F7C; cursor: pointer;}
+
+#fileTabs .tab-pane {display: none;}
+#fileTabs .tab-pane.active {display: block;}
+#fileTabs .nav-item {max-width: none !important;}
+.sidebar .tree{background-color: white;}
+.container .sidebar{width: 240px;}
+.tree li.tree-item.selected{color: #438EFF !important;}
+#monacoTree{overflow: auto;}
+.monaco-dropmenu{background: #efefef;}
\ No newline at end of file
diff --git a/module/repo/css/import.ui.css b/module/repo/css/import.ui.css
new file mode 100644
index 0000000000..16f6273bd7
--- /dev/null
+++ b/module/repo/css/import.ui.css
@@ -0,0 +1,3 @@
+.form-batch-row-actions>button:first-child {display: none;}
+#repoList tr td:last-child, #repoList tr th:last-child {text-align: right;}
+#repoList tr td:first-child {text-align: center;}
diff --git a/module/repo/css/log.ui.css b/module/repo/css/log.ui.css
new file mode 100644
index 0000000000..7a1a2b4fd8
--- /dev/null
+++ b/module/repo/css/log.ui.css
@@ -0,0 +1 @@
+#featureBar .nav .btn>.icon {color: #ffffff;}
diff --git a/module/repo/css/maintain.ui.css b/module/repo/css/maintain.ui.css
new file mode 100644
index 0000000000..699285e12d
--- /dev/null
+++ b/module/repo/css/maintain.ui.css
@@ -0,0 +1 @@
+.btn-active-text {font-weight: 700; color: #1e6aeb; background: #e6eaf1; --tw-ring-color: var(--color-transparent);}
diff --git a/module/repo/css/monaco.ui.css b/module/repo/css/monaco.ui.css
new file mode 100644
index 0000000000..c04f448235
--- /dev/null
+++ b/module/repo/css/monaco.ui.css
@@ -0,0 +1,7 @@
+#fileTabs .tab-pane {display: none;}
+#fileTabs .tab-pane.active {display: block;}
+#fileTabs .nav-item {max-width: none !important;}
+.sidebar .tree{background-color: white;}
+.container .sidebar{width: 240px;}
+.tree li.tree-item.selected{color: #438EFF !important;}
+#monacoTree{overflow: auto;}
diff --git a/module/repo/js/ajaxgeteditorcontent.ui.js b/module/repo/js/ajaxgeteditorcontent.ui.js
new file mode 100644
index 0000000000..578699eb8a
--- /dev/null
+++ b/module/repo/js/ajaxgeteditorcontent.ui.js
@@ -0,0 +1,236 @@
+var blames = null;
+var codeHeight = 0;
+
+window.onMouseDown = function(obj)
+{
+ showBlameAndRelation(obj.target.position.lineNumber);
+}
+
+/**
+ * 显示关联信息弹窗。
+ * Show blame info and relations.
+ *
+ * @param int line
+ * @access public
+ * @return void
+ */
+function showBlameAndRelation(line)
+{
+ if(!blames) return;
+
+ if(pageType == 'diff') line = diffContent.line.new[line -1];
+
+ var blame = blames[line];
+
+ if(!blame) return;
+
+ var p_line = parseInt(line);
+ while(!blame.revision)
+ {
+ p_line--;
+ blame = blames[p_line];
+ }
+ if($('#log').data('line') == line) return;
+
+ var time = blame.time != 'unknown' ? blame.time : '';
+ var user = blame.committer != 'unknown' ? blame.committer : '';
+ var version = blame.revision.toString().substring(0, 10);
+ var content = blameTmpl.replace('%line', line).replace('%time', time).replace('%name', user).replace('%version', version).replace('%comment', blame.message);
+ $('.history').html(content);
+ $('#log').data('line', line);
+ $('#log').css('display', 'flex');
+ getRelation(blame.revision);
+}
+
+/**
+ * 获取关联信息。
+ * Get relation by commit.
+ *
+ * @param string $commit
+ * @access public
+ * @return void
+ */
+window.getRelation = function(commit)
+{
+ $('.table-empty-tip').show();
+ $('#codeContainer').css('height', codeHeight / 5 * 3);
+ var relatedHeight = codeHeight / 5 * 2 - $('#log').height() - 10;
+ $('#related').css('height', relatedHeight);
+ $('#relationTabs ul li').remove();
+ $('#relationTabs .tab-content .tab-pane').remove();
+
+ $.post($.createLink('repo', 'ajaxGetCommitRelation', 'repoID=' + repoID + '&commit=' + commit), function(data)
+ {
+ var titleList = JSON.parse(data).titleList;
+ $.each(titleList, function(i, titleObj)
+ {
+ openTab(titleObj);
+ });
+
+ if(titleList.length > 0)
+ {
+ $('.table-empty-tip').hide();
+ $('#related button').show();
+ $('#relationTabs ul li a').first().trigger('click');
+ }
+ else
+ {
+ if(!canLinkStory && !canLinkBug && !canLinkTask) $('#related button').hide();
+ }
+
+ arrowTabs('relationTabs', 1);
+ });
+
+ globalCommit = commit;
+ var linkStory = $.createLink('repo', 'linkStory', 'repoID=' + repoID + '&commit=' + commit, '', true);
+ var linkBug = $.createLink('repo', 'linkBug', 'repoID=' + repoID + '&commit=' + commit, '', true);
+ var linkTask = $.createLink('repo', 'linkTask', 'repoID=' + repoID + '&commit=' + commit, '', true);
+ $('#linkStory').data('link', linkStory);
+ $('#linkBug').data('link', linkBug);
+ $('#linkTask').data('link', linkTask);
+ $('#related').show();
+}
+
+/**
+ * 打开关联信息tab。
+ * Open tab.
+ *
+ * @param object $titleObj
+ * @access public
+ * @return void
+ */
+function openTab(titleObj)
+{
+ var tabTemplate = `
{title}`;
+
+ var eleId = titleObj.type + '-' + titleObj.id;
+ var prefixIcon = titleObj.type == 'story' ? 'lightbulb' : (titleObj.type == 'task' ? 'check-sign' : 'bug');
+ var unlinkHref = canUnlinkObject ? $.createLink('repo', 'unlink', 'repoID=' + repoID + '&commit=' + globalCommit + '&objectID=' + titleObj.type + '&objectType=' + titleObj.id) : '';
+ var tabHtml = tabTemplate.replace('{title}', titleObj.title)
+ .replace('{unlinkHref}', unlinkHref)
+ .replace('{unlinkTitle}', unlinkTitle)
+ .replace('{href}', '#related-' + eleId)
+ .replace('{prefixIcon}', prefixIcon);
+
+ $('#relationTabs>ul').append(tabHtml);
+
+ var height = getRelationTabHeight();
+
+ $('#relationTabs .tab-content').append("")
+}
+
+/**
+ * 获取关联信息弹窗高度。
+ * Get relation tab height.
+ *
+ * @param object $titleObj
+ * @access public
+ * @return void
+ */
+function getRelationTabHeight()
+{
+ var relatedHeight = $('#related').height();
+ var tabHeight = $('#related #relationTabs>ul').height();
+
+ relatedHeight = parseInt(relatedHeight) ? parseInt(relatedHeight) : 0;
+ tabHeight = parseInt(tabHeight) ? parseInt(tabHeight) : 0;
+
+ return relatedHeight - tabHeight;
+}
+
+/**
+ * 修改比对差异方式。
+ * Update diff editor inline style.
+ *
+ * @param bool display
+ * @access public
+ * @return void
+ */
+window.updateEditorInline = function(display)
+{
+ modifiedEditor.updateOptions({renderSideBySide: display});
+}
+
+/**
+ * 获取提交信息。
+ * Show commit info.
+ *
+ * @access public
+ * @return void
+ */
+function showCommitInfo()
+{
+ var link = $.createLink('repo', 'ajaxGetCommitInfo');
+ var data = {
+ repoID : repoID,
+ entry : encodePath,
+ revision : revision,
+ sourceRevision: sourceRevision,
+ line : 0,
+ returnType : 'json'
+ };
+
+ $.post(link, data, function(res)
+ {
+ res = JSON.parse(res);
+ blames = res.blames;
+ })
+}
+
+/* 初始化数据 */
+$(function()
+{
+ setTimeout(() => {
+ initPage();
+ }, 200);
+});
+
+/**
+ * 初始化页面。
+ * Init page.
+ *
+ * @access public
+ * @return void
+ */
+function initPage()
+{
+ codeHeight = $.cookie.get('codeContainerHeight');
+ $('#codeContainer').css('height', $.cookie.get('codeContainerHeight'));
+
+ $('.btn-left').on('click', function() {arrowTabs('relationTabs', 1);});
+ $('.btn-right').on('click', function() {arrowTabs('relationTabs', -2);});
+
+ $('#linkStory, #linkBug, #linkTask').on('click', function()
+ {
+ var link = $(this).data('link');
+ parent.loadLinkPage(link);
+ });
+
+ $('#relationTabs').on('click', '.unlinks', function()
+ {
+ var link = $(this).data('link');
+ $.post(link, function(data)
+ {
+ data = JSON.parse(data);
+ if(data.result)
+ {
+ getRelation(data.revision);
+ }
+ else
+ {
+ alert(data.message);
+ }
+ })
+ })
+
+ $('#relationTabs').on('onOpen', function(event, tab)
+ {
+ $('#tab-nav-item-' + tab.id).attr('title', tab.title);
+
+ var relatedHeight = codeHeight / 5 * 2 - $('#log').height() - 45;
+ $('#relationTabs iframe').css('height', relatedHeight);
+ });
+
+ /* 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
new file mode 100644
index 0000000000..d0f13cf102
--- /dev/null
+++ b/module/repo/js/browse.ui.js
@@ -0,0 +1,248 @@
+var revisionMap = {};
+var checkedIds = [];
+
+window.renderCell = function(result, {col, row})
+{
+ if(col.name === 'name')
+ {
+ var iconHtml = '';
+ result[0] = {html:iconHtml + '' + row.data.name + ''};
+
+ return result;
+ }
+
+ if(col.name === 'originalComment')
+ {
+ result[0] = {html:''};
+
+ return result;
+ }
+
+ return result;
+};
+
+/**
+ * commit表格渲染跳转链接。
+ * Render jump link of version.
+ *
+ * @access public
+ * @return void
+ */
+window.renderCommentCell = function(result, {col, row})
+{
+ if(col.name === 'revision')
+ {
+ result[0] = {html:'' + row.data.revision + '', style:{flexDirection:"column"}};
+
+ return result;
+ }
+
+ if(col.name === 'originalComment')
+ {
+ result[0] = {html:'', style:{flexDirection:"column"}};
+
+ return result;
+ }
+
+ return result;
+};
+
+/* Open download page when downZip btn click. */
+$('.downloadZip-btn').on('click', function()
+{
+ var link = $.createLink('repo', 'downloadCode', 'repoID=' + repoID + '&branch=' + branch);
+ window.open(link);
+})
+
+/* Refresh page when repo changed. */
+$('#repo-select').on('change', function()
+{
+ var index = $('#repo-select').prop('selectedIndex');
+ if(menus[index - 1].url != undefined)
+ {
+ openUrl(menus[index - 1].url);
+ }
+})
+
+/**
+ * 当选中两行时禁用其他行。
+ * Disable checkable attribution when checked rows equal 2.
+ *
+ * @param object changes
+ * @access public
+ * @return void
+ */
+window.checkedChange = function(changes)
+{
+ checkedIds = getCurrentCheckedIds();
+
+ if(checkedIds.length < 2)
+ {
+ $('.btn-diff').addClass('disabled')
+ }
+ else
+ {
+ $('.btn-diff').removeClass('disabled')
+ }
+}
+
+/**
+ * 跳转比较差异页面。
+ * Redirect to diff page.
+ *
+ * @access public
+ * @return void
+ */
+window.diffClick = function()
+{
+ var checkedIds = getCurrentCheckedIds();
+ var newDiffLink = diffLink.replace('{oldRevision}', revisionMap[checkedIds[1]]);
+ newDiffLink = newDiffLink.replace('{newRevision}', revisionMap[checkedIds[0]]);
+
+ $.cookie.set('sideRepoSelected', checkedIds.join(','))
+
+ openUrl(newDiffLink);
+}
+
+/**
+ * 当选中数量等于2,则禁用其他所有行。
+ * When the selected row equals 2, disable all other rows.
+ *
+ * @param int rowID
+ * @access public
+ * @return bool
+ */
+window.canRowCheckable = function(rowID)
+{
+ const dtable = zui.DTable.query('#repo-comments-table');
+ if(dtable == undefined) return;
+ var data = dtable.$.props.data;
+
+ if(data.length == 0) return true;
+
+ initRevisionMap(data);
+
+ var currentCheckedIds = getCurrentCheckedIds();
+
+ if(currentCheckedIds.length < 2) return true;
+ if(currentCheckedIds.indexOf(rowID) == -1) return 'disabled'
+
+ return true;
+}
+
+/**
+ * 检测revisionMap是否跟当前页面数据一致,不一致重新生成。
+ * Regenerate revisionMap when revisionMap is not in current list.
+ *
+ * @param array
+ * @access public
+ * @return void
+ */
+function initRevisionMap(data)
+{
+ if(revisionMap[data[data.length - 1].id] !== undefined) return;
+
+ revisionMap = {};
+ for (var i = 0; i < data.length; i++) revisionMap[data[i].id] = data[i].revision;
+
+ /* Check rows where id in cookie. */
+ setTimeout(function()
+ {
+ checkColInCurrentPage()
+ }, 100);
+}
+
+/**
+ * 选中当前页码对应的行。
+ * Select the rows to the current page.
+ *
+ * @access public
+ * @return void
+ */
+function checkColInCurrentPage()
+{
+ const dtable = zui.DTable.query('#repo-comments-table');
+ const checkedIds = $.cookie.get('sideRepoSelected') ? $.cookie.get('sideRepoSelected').split(',') : [];
+
+
+ var currentCheckedIds = [];
+ if(revisionMap[checkedIds[0]]) currentCheckedIds.push(checkedIds[0]);
+ if(revisionMap[checkedIds[1]]) currentCheckedIds.push(checkedIds[1]);
+
+ dtable.$.toggleCheckRows(Object.keys(revisionMap), false);
+
+ if(currentCheckedIds.length > 0)
+ {
+ dtable.$.toggleCheckRows(currentCheckedIds, true);
+ }
+ else
+ {
+ dtable.$.toggleCheckRows(Object.keys(revisionMap).slice(0, 2), true);
+ }
+
+ window.checkedChange();
+}
+
+/**
+ * 获取当前页码选中的行。
+ * Get checked rows in current page.
+ *
+ * @access public
+ * @return array
+ */
+function getCurrentCheckedIds()
+{
+ const dtable = zui.DTable.query('#repo-comments-table');
+ if(dtable.$ == undefined) return [];
+
+ var checkedIds = dtable.$.getChecks();
+ var currentCheckedIds = [];
+
+ for (var i = 0; i < checkedIds.length; i++)
+ {
+ if(revisionMap[checkedIds[i]]) currentCheckedIds.push(checkedIds[i]);
+ }
+
+ if(currentCheckedIds.length > 2) currentCheckedIds = currentCheckedIds.slice(0, 2);
+ return currentCheckedIds;
+}
+
+$('.copy-btn').on('click', function()
+{
+ var copyText = $(this).parent().parent().find('input');
+ copyText[0].select();
+ document.execCommand("Copy");
+ copyText[0].selectionStart = copyText[0].selectionEnd;
+ copyText[0].blur();
+
+ $(that).tooltip('show');
+ var that = this;
+ setTimeout(function()
+ {
+ $(that).tooltip('hide');
+ }, 2000)
+})
+
+/* 此方法在页面被替换时执行。 */
+// function onPageUnmount()
+// {
+// /* 删除全局变量。 */
+// delete window.canRowCheckable;
+// delete window.diffClick;
+// delete window.checkedChange;
+// delete window.renderCommentCell;
+// delete window.renderCell;
+// }
+
+window.afterPageUpdate = function()
+{
+ setTimeout(function()
+ {
+ $('.copy-btn').tooltip({
+ trigger: 'click',
+ placement: 'bottom',
+ title: copied,
+ tipClass: 'success',
+ });
+ }, 200);
+};
diff --git a/module/repo/js/common.ui.js b/module/repo/js/common.ui.js
new file mode 100644
index 0000000000..5fea935ff8
--- /dev/null
+++ b/module/repo/js/common.ui.js
@@ -0,0 +1,223 @@
+$(function()
+{
+ $('#log').on('click', '.btn-close', closeRelation);
+});
+
+/**
+ * 切换仓库。
+ * Swtich repo.
+ *
+ * @param int $repoID
+ * @param string $module
+ * @param string $method
+ * @access public
+ * @return void
+ */
+function switchRepo(repoID, module, method)
+{
+ if(typeof(eventKeyCode) == 'undefined') eventKeyCode = 0;
+ if(eventKeyCode > 0 && eventKeyCode != 13) return false;
+
+ /* The project id is a string, use it as the project model. */
+ if(isNaN(repoID))
+ {
+ $.cookie.set('projectMode', repoID, {expires:config.cookieLife, path:config.webRoot});
+ repoID = 0;
+ }
+
+ if(method != 'settings') method ="browse";
+ link = createLink(module, method, 'repoID=' + repoID);
+ location.href=link;
+}
+
+/**
+ * 切换分支。
+ * Switch branch for git.
+ *
+ * @param string $branchID
+ * @access public
+ * @return void
+ */
+function switchBranch(branchID)
+{
+ $.cookie.set('repoBranch', branchID, {expires:config.cookieLife, path:config.webRoot});
+ $.cookie.set('repoRefresh', 1, {expires:config.cookieLife, path:config.webRoot});
+ location.href=location.href;
+}
+
+var distance = 0;
+
+/**
+ * 左右切换关联信息。
+ * Aarrow tabs area.
+ *
+ * @param string domID
+ * @param number shift 1|-1
+ * @param bool hideRightBtn
+ * @access public
+ * @return void
+ */
+function arrowTabs(domID, shift, hideRightBtn)
+{
+ if($('#' + domID).html() == '') return;
+
+ var hasParent = $('#' + domID + ' .btn-left').length;
+ var $leftBtn = hasParent ? $('#' + domID + ' .btn-left') : $('.btn-left');
+ var $rightBtn = hasParent ? $('#' + domID + ' .btn-right') : $('.btn-right');
+
+ $leftBtn.show();
+ $rightBtn.show();
+ if(hideRightBtn) $rightBtn.hide();
+
+ var tabItemWidth = 0;
+ if($('#' + domID + ' > .nav-tabs')[0]) tabItemWidth = $('#' + domID + ' > .nav-tabs')[0].clientWidth;
+ var tabsWidth = $('#' + domID)[0].clientWidth;
+ if($('#' + domID + ' .close-bugs').length) tabsWidth = tabsWidth * 0.7;
+
+ if(tabItemWidth <= tabsWidth)
+ {
+ $leftBtn.hide();
+ $rightBtn.hide();
+ $('#' + domID + ' > .nav-tabs')[0].style.transform = 'translateX(0px)';
+ return;
+ }
+
+ distance += tabsWidth * shift * 0.2;
+ if(distance > 0) distance = 0;
+ if(distance == 0)
+ {
+ $leftBtn.hide();
+ }
+
+ if((tabItemWidth + distance) <= tabsWidth * 0.75)
+ {
+ $rightBtn.hide();
+ return arrowTabs(domID, 1, true);
+ }
+
+ if(domID == 'monacoTabs' && distance < -60) distance = distance + 60;
+
+ $('#' + domID + ' > .nav-tabs')[0].style.transform = 'translateX('+ distance +'px)';
+}
+
+/**
+ * 关闭关联信息tab。
+ * Close commit relations.
+ *
+ * @access public
+ * @return void
+ */
+function closeRelation()
+{
+ $('#relationTabs ul li').remove();
+ $('#relationTabs .tab-content .tab-pane').remove();
+ $('.history').html('');
+ $('#log').data('line', 0);
+ $('#log').hide();
+
+ $('#codeContainer').css('height', codeHeight);
+ $('#related').css('height', 0);
+};
+
+/**
+ * 获取tabs内容高度。
+ * Get tab-content height.
+ *
+ * @access public
+ * @return void
+ */
+function getIframeHeight()
+{
+ if(iframeHeight) return iframeHeight;
+
+ var windowHeight = $(window).height();
+ var headerHeight = parseInt($('#header').height());
+ var mainNavbar = parseInt($('#mainNavbar').height());
+ var featureBar = parseInt($('#featureBar').height());
+ var tabsbar = parseInt($('.nav-tabs').height());
+ var mainMenuHeight = parseInt($('#mainMenu').css('padding-top')) + parseInt($('#mainMenu').css('padding-bottom'));
+ var appTabsHeight = parseInt($('#appTabs').height());
+ var appsBarHeight = parseInt($('#appsBar').height());
+ featureBar = featureBar ? featureBar : 0;
+ appsBarHeight = appsBarHeight ? appsBarHeight : 0;
+ appTabsHeight = appTabsHeight ? appTabsHeight : 0;
+ mainMenuHeight = mainMenuHeight ? mainMenuHeight : 0;
+ mainNavbar = mainNavbar ? mainNavbar : 0;
+ iframeHeight = windowHeight - headerHeight - appsBarHeight - appTabsHeight - mainMenuHeight - mainNavbar - tabsbar - 20 - featureBar;
+
+ return iframeHeight;
+}
+
+/**
+ * 获取左侧边栏高度。
+ * Get sidebar height.
+ *
+ * @access public
+ * @return void
+ */
+function getSidebarHeight()
+{
+ if(sidebarHeight) return sidebarHeight;
+
+ var windowHeight = $(window).height();
+ var headerHeight = parseInt($('#header').height());
+ var mainNavbar = parseInt($('#mainNavbar').height());
+ var featureBar = parseInt($('#featureBar').height());
+ var repoMenu = parseInt($('#repoBranchDropMenu').height());
+ var mainMenuHeight = parseInt($('#mainMenu').css('padding-top')) + parseInt($('#mainMenu').css('padding-bottom'));
+ var appTabsHeight = parseInt($('#appTabs').height());
+ var appsBarHeight = parseInt($('#appsBar').height());
+ repoMenu = repoMenu ? repoMenu : 0;
+ featureBar = featureBar ? featureBar : 0;
+ appsBarHeight = appsBarHeight ? appsBarHeight : 0;
+ appTabsHeight = appTabsHeight ? appTabsHeight : 0;
+ mainMenuHeight = mainMenuHeight ? mainMenuHeight : 0;
+ mainNavbar = mainNavbar ? mainNavbar : 0;
+ sidebarHeight = windowHeight - headerHeight - appsBarHeight - appTabsHeight - mainMenuHeight - mainNavbar - repoMenu - featureBar;
+
+ return sidebarHeight;
+}
+
+
+/**
+ * 查找选中元素所有的父元素及选中元素的id。
+ * Find parent nodes and item id of selected tree item.
+ *
+ * @access public
+ * @return string
+ */
+function findItemInTreeItems(list, key, level) {
+ for (const item of list) {
+ if(level === 0)
+ {
+ parentTree = [item.key];
+ }
+
+ if (item.key === key) return item;
+
+ if (item.children && item.children.length > 0)
+ {
+ const findedItem = findItemInTreeItems(item.children, key);
+ if (findedItem)
+ {
+ parentTree.push(item.key);
+ return findedItem;
+ }
+ }
+ }
+}
+
+/**
+ * 展开树状结构。
+ * Expand tree node.
+ *
+ * @access public
+ * @return void
+ */
+function expandTree()
+{
+ const treeObj = $('#monacoTree').parent().data('zui.Tree');
+ if(treeObj == undefined || treeObj.$ == undefined) return;
+
+ for (const key of parentTree) treeObj.$.expand(key);
+}
\ No newline at end of file
diff --git a/module/repo/js/create.ui.js b/module/repo/js/create.ui.js
new file mode 100644
index 0000000000..a52a9d1f9f
--- /dev/null
+++ b/module/repo/js/create.ui.js
@@ -0,0 +1,107 @@
+$(function()
+{
+ onScmChange();
+});
+
+function onProductChange(event)
+{
+ var projects = $('[name="projects[]"]').val();
+ var products = $('[name="product[]"]').val();
+
+ $.post($.createLink('repo', 'ajaxProjectsOfProducts'), {products, projects}, function(response)
+ {
+ var data = JSON.parse(response);
+ var $projects = $('#projects').zui('picker');
+ $projects.render({items: data});
+ $projects.$.clear();
+ });
+}
+
+function onHostChange()
+{
+ var host = $('[name=serviceHost]').val();
+ var url = $.createLink('repo', 'ajaxGetProjects', "host=" + host);
+ if(host == '') return false;
+
+ toggleLoading('#serviceProject', true);
+ $.get(url, function(response)
+ {
+ var data = JSON.parse(response);
+ var $project = $('#serviceProject').zui('picker');
+ $project.render({items: data});
+ $project.$.clear();
+ toggleLoading('#serviceProject', false);
+ });
+}
+
+function onProjectChange()
+{
+ $option = $('#serviceProject option').eq($('#serviceProject').prop('selectedIndex'));
+ $('#name').val($option.data('name'));
+}
+
+/**
+ * Changed SCM.
+ *
+ * @param string $scm
+ * @access public
+ * @return void
+ */
+function onScmChange()
+{
+ var scm = $('[name=SCM]').val();
+ if(!scm)
+ {
+ for(i in scmList)
+ {
+ scm = i;
+ break;
+ }
+ }
+
+ (scm == 'Git') ? $('.tips-git').removeClass('hidden') : $('.tips-git').addClass('hidden');
+ if(scm == 'Git' || scm == 'Gitea' || scm == 'Gogs')
+ {
+ $('.account-fields').hide();
+ $('#path').attr('placeholder', pathGitTip);
+ $('#client').attr('placeholder', clientGitTip);
+ }
+ else
+ {
+ $('.account-fields').show();
+ $('#path').attr('placeholder', pathSvnTip);
+ $('#client').attr('placeholder', clientSvnTip);
+ }
+
+ if(scm == 'Git' || scm == 'Subversion')
+ {
+ $('.service').toggle(false);
+ $('.hide-service').toggle(true);
+ }
+ else
+ {
+ $('.service').toggle(true);
+ if(scm == 'Gitea' || scm == 'Gogs')
+ {
+ $('.hide-service').each(function()
+ {
+ if(!$(this).hasClass('hide-git')) $(this).toggle(true);
+ });
+ $('.hide-git').toggle(false);
+ }
+ else
+ {
+ $('.hide-service').toggle(false);
+ }
+
+ var url = $.createLink('repo', 'ajaxGetHosts', "scm=" + scm);
+ $.get(url, function(response)
+ {
+ var data = JSON.parse(response);
+ var $hostPicker = $('#serviceHost').zui('picker');
+ $hostPicker.render({items: data});
+ $hostPicker.$.clear();
+ onHostChange();
+ });
+ }
+}
diff --git a/module/repo/js/diff.ui.js b/module/repo/js/diff.ui.js
new file mode 100644
index 0000000000..7b4789d5cf
--- /dev/null
+++ b/module/repo/js/diff.ui.js
@@ -0,0 +1,245 @@
+var iframeHeight = 0;
+var sidebarHeight = 0;
+var tabTemp;
+var diffAppose = $.cookie.get('renderSideBySide') == 'true';
+
+/* Close tab. */
+$('#monacoTabs').on('click', '.monaco-close', function()
+{
+ var eleId = $(this).parent().attr('href');
+ var tabsEle = $(this).parent().parent().parent();
+ var isActive = $(this).parent().hasClass('active');
+
+ $(this).parent().parent().remove();
+ $(eleId).remove();
+ $('#' + eleId.substring(5)).parent().removeClass('selected');
+ if(isActive) tabsEle.children().last().find('a').trigger('click');
+});
+
+window.afterPageUpdate = function()
+{
+ setTimeout(function()
+ {
+ var fileAsId = file.replace(/=/g, '-');
+ /* Resize moaco height. */
+ $('#monacoTree').css('height', getSidebarHeight() - 8 + 'px');
+ /* Init tab template. */
+ if(!tabTemp) tabTemp = $('#monacoTabs ul li').first().clone();
+
+ /* Load default tab content. */
+ var height = getIframeHeight();
+ $('#tab-' + fileAsId).html("")
+
+ /* Select default tree item. */
+ const currentElement = findItemInTreeItems(tree, fileAsId, 0);
+
+ console.log(1111, $('#' + currentElement.id))
+ 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;
+ })
+ }, 300);
+};
+
+/**
+ * 点击左侧菜单打开详情tab。
+ * Open new tab when click tree item.
+ *
+ * @access public
+ * @return void
+ */
+window.treeClick = function(info)
+{
+ if (info.item.items && info.item.items.length > 0) return;
+ $('#' + info.item.id).parent().addClass('selected');
+ openTab(info.item.key, info.item.text);
+ arrowTabs('monacoTabs', -2);
+}
+
+/**
+ * Change code encoding.
+ *
+ * @param string encoding
+ * @access public
+ * @return void
+ */
+function changeEncoding(encoding)
+{
+ $('#encoding').val(encoding);
+ $('#encoding').parents('form').submit();
+}
+
+/**
+ * Html code decode.
+ *
+ * @param string str
+ * @access public
+ * @return string
+ */
+function htmlspecialchars_decode(str){
+ str = str.replace(/&/g, '&');
+ str = str.replace(/</g, '<');
+ str = str.replace(/>/g, '>');
+ str = str.replace(/"/g, "''");
+ str = str.replace(/'/g, "'");
+ return str;
+}
+
+/**
+ * Get diffs by file name.
+ *
+ * @param string fileName
+ * @access public
+ * @return object
+ */
+window.getDiffs = function(fileName)
+{
+ if(fileName.indexOf('./') === 0) fileName = fileName.substring(2);
+
+ var result = {
+ 'code': {'new': '', 'old': ''},
+ 'line': {'new': [], 'old': []}
+ };
+ $.each(diffs, function(i, diff)
+ {
+ if(diff.fileName == fileName)
+ {
+ if(!diff.contents || typeof diff.contents[0].lines != 'object') return result;
+
+ $.each(diff.contents, function(k, content)
+ {
+ var lines = content.lines;
+ $.each(lines, function(l, code)
+ {
+ if(code.type == 'all' || code.type == 'new')
+ {
+ result.code.new += htmlspecialchars_decode(code.line.substring(1)) + "\n";
+ result.line.new.push(parseInt(code.newlc));
+ }
+
+ if(code.type == 'all' || code.type == 'old')
+ {
+ result.code.old += htmlspecialchars_decode(code.line.substring(1)) + "\n";
+ result.line.old.push(parseInt(code.oldlc));
+ }
+ })
+ })
+ return result;
+ }
+ });
+
+ return result;
+}
+
+/**
+ * 打开新tab。
+ * Open new tab.
+ *
+ * @access public
+ * @return void
+ */
+function openTab(entry, name)
+{
+ var eleId = 'tab-' + entry.replace(/=/g, '-');
+ var element = document.getElementById(eleId);
+ if (element)
+ {
+ $("a[href='" + '#' + eleId + "']").trigger('click');
+ return;
+ }
+
+ var newTab = tabTemp.clone();
+ newTab.find('a').attr('href', '#' + eleId);
+ newTab.find('span').text(name);
+ $('#monacoTabs .nav-tabs').append(newTab);
+
+ var height = getIframeHeight();
+ $('#monacoTabs .tab-content').append("")
+
+ setTimeout(() => {
+ $("a[href='" + '#' + eleId + "']").trigger('click');
+ updateEditorInline('#' + eleId);
+ }, 100);
+}
+
+function updateEditorInline(eleId)
+{
+ $.cookie.set('renderSideBySide', diffAppose);
+ if(typeof $(eleId + ' iframe')[0].contentWindow.updateEditorInline == 'function')
+ {
+ $(eleId + ' iframe')[0].contentWindow.updateEditorInline(diffAppose);
+ }
+}
+
+$('#monacoTabs .nav-item a').on('click', function()
+{
+ var eleId = $(this).attr('href');
+ $(eleId + ' iframe')[0].contentWindow.updateEditorInline(diffAppose);
+});
+
+$(document).ready(function()
+{
+ if(diffAppose)
+ {
+ $('.dropdown-menu #inline').show();
+ $('.dropdown-menu #appose').hide();
+ }
+ else
+ {
+ $('.dropdown-menu #appose').show();
+ $('.dropdown-menu #inline').hide();
+ }
+
+ $('.btn-left').on('click', function() {arrowTabs('monacoTabs', 1);});
+ $('.btn-right').on('click', function() {arrowTabs('monacoTabs', -2);});
+});
+
+$('.inline-appose').on('click', function()
+{
+ $('.inline-appose').hide();
+ diffAppose = !diffAppose;
+ if(diffAppose)
+ {
+ $('.dropdown-menu #inline').show();
+ }
+ else
+ {
+ $('.dropdown-menu #appose').show();
+ }
+ var tabID = $('#monacoTabs .nav-item .active').attr('href');
+ updateEditorInline(tabID);
+ return;
+});
+
+$(".label-exchange").on('click', function()
+{
+ var newDiffLink = diffLink.replace('{oldRevision}', newRevision);
+ newDiffLink = newDiffLink.replace('{newRevision}', oldRevision);
+ openUrl(newDiffLink);
+});
+
+/**
+ * 在当前页面用modal加载链接。
+ * Load link object page.
+ *
+ * @param string $link
+ * @access public
+ * @return void
+ */
+window.loadLinkPage = function(link)
+{
+ $('#linkObject').attr('href', link);
+ $('#linkObject').trigger('click');
+}
\ No newline at end of file
diff --git a/module/repo/js/edit.ui.js b/module/repo/js/edit.ui.js
new file mode 100644
index 0000000000..2f8ab2152f
--- /dev/null
+++ b/module/repo/js/edit.ui.js
@@ -0,0 +1,110 @@
+$(function()
+{
+ onScmChange(true);
+});
+
+function onProductChange(event)
+{
+ var projects = $('[name="projects[]"]').val();
+ var products = $('[name="product[]"]').val();
+
+ $.post($.createLink('repo', 'ajaxProjectsOfProducts'), {products, projects}, function(response)
+ {
+ var data = JSON.parse(response);
+ var $projects = $('#projects').zui('picker');
+ $projects.render({items: data});
+ $projects.$.clear();
+ });
+}
+
+function onHostChange()
+{
+ var host = $('[name=serviceHost]').val();
+ var url = $.createLink('repo', 'ajaxGetProjects', "host=" + host);
+ if(host == '') return false;
+
+ toggleLoading('#serviceProject', true);
+ $.get(url, function(response)
+ {
+ var data = JSON.parse(response);
+ var $project = $('#serviceProject').zui('picker');
+ $project.render({items: data});
+ $project.$.clear();
+ toggleLoading('#serviceProject', false);
+ });
+}
+
+function onProjectChange()
+{
+ $option = $('#serviceProject option').eq($('#serviceProject').prop('selectedIndex'));
+ $('#name').val($option.data('name'));
+}
+
+/**
+ * Changed SCM.
+ *
+ * @param bool $isFirstRequest
+ * @access public
+ * @return void
+ */
+function onScmChange(isFirstRequest = false)
+{
+ var scm = $('[name=SCM]').val();
+ if(!scm)
+ {
+ for(i in scmList)
+ {
+ scm = i;
+ break;
+ }
+ }
+
+ (scm == 'Git') ? $('.tips-git').removeClass('hidden') : $('.tips-git').addClass('hidden');
+ if(scm == 'Git' || scm == 'Gitea' || scm == 'Gogs')
+ {
+ $('.account-fields').hide();
+ $('#path').attr('placeholder', pathGitTip);
+ $('#client').attr('placeholder', clientGitTip);
+ }
+ else
+ {
+ $('.account-fields').show();
+ $('#path').attr('placeholder', pathSvnTip);
+ $('#client').attr('placeholder', clientSvnTip);
+ }
+
+ if(scm == 'Git' || scm == 'Subversion')
+ {
+ $('.service').toggle(false);
+ $('.hide-service').toggle(true);
+ }
+ else
+ {
+ $('.service').toggle(true);
+ if(scm == 'Gitea' || scm == 'Gogs')
+ {
+ $('.hide-service').each(function()
+ {
+ if(!$(this).hasClass('hide-git')) $(this).toggle(true);
+ });
+ $('.hide-git').toggle(false);
+ }
+ else
+ {
+ $('.hide-service').toggle(false);
+ }
+
+ if(!isFirstRequset)
+ {
+ var url = $.createLink('repo', 'ajaxGetHosts', "scm=" + scm);
+ $.get(url, function(response)
+ {
+ var data = JSON.parse(response);
+ var $hostPicker = $('#serviceHost').zui('picker');
+ $hostPicker.render({items: data});
+ $hostPicker.$.clear();
+ onHostChange();
+ });
+ }
+ }
+}
diff --git a/module/repo/js/import.ui.js b/module/repo/js/import.ui.js
new file mode 100644
index 0000000000..abbc524d72
--- /dev/null
+++ b/module/repo/js/import.ui.js
@@ -0,0 +1,46 @@
+$(function()
+{
+ checkItem();
+ $('.form-batch-row-actions .form-batch-btn').each(function()
+ {
+ if($(this).data('type') == 'add') $(this).addClass('hidden');
+ });
+});
+
+/**
+ * Check the project.
+ *
+ * @access public
+ * @return void
+ */
+function checkItem()
+{
+ if(!$("#repoList tbody tr").length) $("#submit").prop('disabled', true);
+}
+
+/**
+ * Change server.
+ *
+ * @access public
+ * @return void
+ */
+function selectServer(event)
+{
+ var server = $(event.target).val();
+ console.log(server);
+ if(server) loadPage($.createLink('repo', 'import', 'server=' + server));
+}
+
+function loadProductProjects(event)
+{
+ const $target = $(event.target);
+ const $currentRow = $target.closest('tr');
+ const products = $target.val();
+ const projects = $currentRow.find('.form-batch-input[data-name="projects"]');
+ const projectIds = $(projects).val();
+
+ $.post($.createLink('repo', 'ajaxProjectsOfProducts'), {products : products, projects: projectIds, number : 1}, function(response)
+ {
+ $(projects).html(response);
+ });
+}
diff --git a/module/repo/js/linkbug.ui.js b/module/repo/js/linkbug.ui.js
new file mode 100644
index 0000000000..475ab59c8f
--- /dev/null
+++ b/module/repo/js/linkbug.ui.js
@@ -0,0 +1,23 @@
+window.createSortLink = function(col)
+{
+ var sort = col.name + '_asc';
+ if(sort == orderBy) sort = col.name + '_desc';
+
+ return sortLink.replace('{orderBy}', sort);
+}
+
+$(document).off('click','.dtable-footer .batch-btn').on('click', '.dtable-footer .batch-btn', function(e)
+{
+ const dtable = zui.DTable.query(e.target);
+ const checkedList = dtable.$.getChecks();
+ if(!checkedList.length) return;
+
+ const tabType = $(this).data('type');
+ const postData = [];
+ postData[`${tabType}[]`] = checkedList;
+
+ $.ajaxSubmit({
+ url: $(this).data('url'),
+ data: postData
+ });
+});
\ No newline at end of file
diff --git a/module/repo/js/linkstory.ui.js b/module/repo/js/linkstory.ui.js
new file mode 100644
index 0000000000..475ab59c8f
--- /dev/null
+++ b/module/repo/js/linkstory.ui.js
@@ -0,0 +1,23 @@
+window.createSortLink = function(col)
+{
+ var sort = col.name + '_asc';
+ if(sort == orderBy) sort = col.name + '_desc';
+
+ return sortLink.replace('{orderBy}', sort);
+}
+
+$(document).off('click','.dtable-footer .batch-btn').on('click', '.dtable-footer .batch-btn', function(e)
+{
+ const dtable = zui.DTable.query(e.target);
+ const checkedList = dtable.$.getChecks();
+ if(!checkedList.length) return;
+
+ const tabType = $(this).data('type');
+ const postData = [];
+ postData[`${tabType}[]`] = checkedList;
+
+ $.ajaxSubmit({
+ 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
new file mode 100644
index 0000000000..475ab59c8f
--- /dev/null
+++ b/module/repo/js/linktask.ui.js
@@ -0,0 +1,23 @@
+window.createSortLink = function(col)
+{
+ var sort = col.name + '_asc';
+ if(sort == orderBy) sort = col.name + '_desc';
+
+ return sortLink.replace('{orderBy}', sort);
+}
+
+$(document).off('click','.dtable-footer .batch-btn').on('click', '.dtable-footer .batch-btn', function(e)
+{
+ const dtable = zui.DTable.query(e.target);
+ const checkedList = dtable.$.getChecks();
+ if(!checkedList.length) return;
+
+ const tabType = $(this).data('type');
+ const postData = [];
+ postData[`${tabType}[]`] = checkedList;
+
+ $.ajaxSubmit({
+ url: $(this).data('url'),
+ data: postData
+ });
+});
\ No newline at end of file
diff --git a/module/repo/js/log.ui.js b/module/repo/js/log.ui.js
new file mode 100644
index 0000000000..7c411cf9b3
--- /dev/null
+++ b/module/repo/js/log.ui.js
@@ -0,0 +1,106 @@
+var revisionMap = {};
+var checkedIds = [];
+
+/**
+ * 当选中两行时禁用其他行。
+ * Disable checkable attribution when checked rows equal 2.
+ *
+ * @param object changes
+ * @access public
+ * @return void
+ */
+window.checkedChange = function(changes)
+{
+ checkedIds = getCurrentCheckedIds();
+
+ if(checkedIds.length < 2)
+ {
+ $('.btn-diff').addClass('disabled')
+ }
+ else
+ {
+ $('.btn-diff').removeClass('disabled')
+ }
+}
+
+/**
+ * 当选中数量等于2,则禁用其他所有行。
+ * When the selected row equals 2, disable all other rows.
+ *
+ * @param int rowID
+ * @access public
+ * @return bool
+ */
+window.canRowCheckable = function(rowID)
+{
+ const dtable = zui.DTable.query('#repo-logs-table');
+ if(dtable == undefined) return;
+ var data = dtable.$.props.data;
+
+ if(data.length == 0) return true;
+
+ initRevisionMap(data);
+
+ var currentCheckedIds = getCurrentCheckedIds();
+
+ if(currentCheckedIds.length < 2) return true;
+ if(currentCheckedIds.indexOf(rowID) == -1) return 'disabled'
+
+ return true;
+}
+
+/**
+ * 检测revisionMap是否跟当前页面数据一致,不一致重新生成。
+ * Regenerate revisionMap when revisionMap is not in current list.
+ *
+ * @param array
+ * @access public
+ * @return void
+ */
+function initRevisionMap(data)
+{
+ if(revisionMap[data[data.length - 1].id] !== undefined) return;
+
+ revisionMap = {};
+ for (var i = 0; i < data.length; i++) revisionMap[data[i].id] = data[i].revision;
+}
+
+/**
+ * 跳转比较差异页面。
+ * Redirect to diff page.
+ *
+ * @access public
+ * @return void
+ */
+window.diffClick = function()
+{
+ var checkedIds = getCurrentCheckedIds();
+ var newDiffLink = diffLink.replace('{oldRevision}', revisionMap[checkedIds[1]]);
+ newDiffLink = newDiffLink.replace('{newRevision}', revisionMap[checkedIds[0]]);
+
+ openUrl(newDiffLink);
+}
+
+/**
+ * 获取当前页码选中的行。
+ * Get checked rows in current page.
+ *
+ * @access public
+ * @return array
+ */
+function getCurrentCheckedIds()
+{
+ const dtable = zui.DTable.query('#repo-logs-table');
+ if(dtable.$ == undefined) return [];
+
+ var checkedIds = dtable.$.getChecks();
+ var currentCheckedIds = [];
+
+ for (var i = 0; i < checkedIds.length; i++)
+ {
+ if(revisionMap[checkedIds[i]]) currentCheckedIds.push(checkedIds[i]);
+ }
+
+ if(currentCheckedIds.length > 2) currentCheckedIds = currentCheckedIds.slice(0, 2);
+ return currentCheckedIds;
+}
diff --git a/module/repo/js/monaco.ui.js b/module/repo/js/monaco.ui.js
new file mode 100644
index 0000000000..b96290d3b2
--- /dev/null
+++ b/module/repo/js/monaco.ui.js
@@ -0,0 +1,135 @@
+var iframeHeight = 0;
+var sidebarHeight = 0;
+var tabTemp;
+var parentTree = [];
+
+/* Close tab. */
+$('#monacoTabs').on('click', '.monaco-close', function()
+{
+ var eleId = $(this).parent().attr('href');
+ var tabsEle = $(this).parent().parent().parent();
+ var isActive = $(this).parent().hasClass('active');
+
+ $(this).parent().parent().remove();
+ $(eleId).remove();
+ $('#' + eleId.substring(5)).parent().removeClass('selected');
+ if(isActive) tabsEle.children().last().find('a').trigger('click');
+
+ arrowTabs('monacoTabs', -1);
+});
+
+window.afterPageUpdate = function()
+{
+ setTimeout(function()
+ {
+ var fileAsId = file.replace(/=/g, '-');
+ /* Resize moaco height. */
+ $('#monacoTree').css('height', getSidebarHeight() - 8 + 'px');
+ /* Init tab template. */
+ if(!tabTemp) tabTemp = $('#monacoTabs ul li').first().clone();
+
+ /* Load default tab content. */
+ var height = getIframeHeight();
+ $('#tab-' + fileAsId).html("")
+
+ /* Select default tree item. */
+ const currentElement = findItemInTreeItems(tree, fileAsId, 0);
+ if(currentElement != undefined) $('#' + currentElement.id).parent().addClass('selected');
+
+ if(['Git', 'Gitlab', 'Gogs', 'Gitea'].indexOf(repo.SCM) == -1)
+ {
+ $('#sourceSwapper').hide();
+ }
+ expandTree();
+
+ $('.btn-left').on('click', function() {arrowTabs('monacoTabs', 1);});
+ $('.btn-right').on('click', function() {arrowTabs('monacoTabs', -2);});
+
+ /* Reload current page when click dropmeu. */
+ $('body').on('click', '.dropmenu-tree .dropmenu-item', function()
+ {
+ var branchOrTag = $(this).attr('title');
+ var url = $(this).data('url');
+
+ if(url != 'javascript:;') return;
+
+ if(branchOrTag != $.cookie.get('repoBranch')) $.cookie.set('repoBranch', branchOrTag);
+
+ openUrl(currentLink);
+ })
+
+ $('.repoDropDownMenu').on('click', function()
+ {
+ var url = $(this).data('link');
+ var activeFilePath = $('#monacoTabs .nav-item .active').attr('href').substring(5).replace(/-/g, '=');
+ if(url.indexOf('blame') >=0 && url.indexOf('download') == -1)
+ {
+ openUrl(url.replace('{path}', activeFilePath));
+ }
+ else
+ {
+ window.open(url.replace('{path}', activeFilePath));
+ }
+ return;
+ })
+ }, 200);
+};
+
+/**
+ * 点击左侧菜单打开详情tab。
+ * Open new tab when click tree item.
+ *
+ * @access public
+ * @return void
+ */
+window.treeClick = function(info)
+{
+ if (info.item.items && info.item.items.length > 0) return;
+ $('#' + info.item.id).parent().addClass('selected');
+ openTab(info.item.key, info.item.text);
+ arrowTabs('monacoTabs', -2);
+}
+
+/**
+ * 打开新tab。
+ * Open new tab.
+ *
+ * @access public
+ * @return void
+ */
+function openTab(entry, name)
+{
+ var eleId = 'tab-' + entry.replace(/=/g, '-');
+ var element = document.getElementById(eleId);
+ if (element)
+ {
+ $("a[href='" + '#' + eleId + "']").trigger('click');
+ return;
+ }
+
+ var newTab = tabTemp.clone();
+ newTab.find('a').attr('href', '#' + eleId);
+ newTab.find('span').text(name);
+ $('#monacoTabs .nav-tabs').append(newTab);
+
+ var height = getIframeHeight();
+ $('#monacoTabs .tab-content').append("")
+
+ setTimeout(() => {
+ $("a[href='" + '#' + eleId + "']").trigger('click');
+ }, 100);
+}
+
+/**
+ * 在当前页面用modal加载链接。
+ * Load link object page.
+ *
+ * @param string $link
+ * @access public
+ * @return void
+ */
+window.loadLinkPage = function(link)
+{
+ $('#linkObject').attr('href', link);
+ $('#linkObject').trigger('click');
+}
diff --git a/module/repo/js/showsynccommit.ui.js b/module/repo/js/showsynccommit.ui.js
new file mode 100644
index 0000000000..d635883664
--- /dev/null
+++ b/module/repo/js/showsynccommit.ui.js
@@ -0,0 +1,15 @@
+function syncComments()
+{
+ $.get(link, function(data)
+ {
+ if(data == 'finish') return openUrl(browseLink);
+
+ var count = parseInt(data);
+ if(isNaN(count)) count = 0;
+ $('#commits').html(parseInt($('#commits').html()) + count);
+
+ setTimeout(syncComments, 100);
+ });
+}
+
+setTimeout(syncComments, 500);
\ No newline at end of file
diff --git a/module/repo/lang/de.php b/module/repo/lang/de.php
index 0a7492e835..cdf725b7ee 100644
--- a/module/repo/lang/de.php
+++ b/module/repo/lang/de.php
@@ -28,7 +28,7 @@ $lang->repo->branch = 'Branch';
$lang->repo->tag = 'Tag';
$lang->repo->addWebHook = 'Add Webhook';
$lang->repo->apiGetRepoByUrl = 'API: Get repo by URL';
-$lang->repo->blameTmpl = 'Code for line %line, %name commited at %time, %version %comment';
+$lang->repo->blameTmpl = 'Code for line %line: %name commited at %time, %version %comment';
$lang->repo->notRelated = 'There is currently no related ZenTao object';
$lang->repo->browseAction = 'Browse Repo';
@@ -51,6 +51,7 @@ $lang->repo->import = 'Import';
$lang->repo->importName = 'Name after import';
$lang->repo->importServer = 'Please select a server';
$lang->repo->gitlabList = 'Gitlab Repo';
+$lang->repo->batchCreate = 'Batch create repo';
$lang->repo->submit = 'Submit';
$lang->repo->cancel = 'Cancel';
@@ -101,7 +102,7 @@ $lang->repo->diff = 'Diff';
$lang->repo->diffAB = 'Diff';
$lang->repo->diffAll = 'Diff All';
$lang->repo->viewDiff = 'View diff';
-$lang->repo->allLog = 'All Revisions';
+$lang->repo->allLog = 'All Commits';
$lang->repo->location = 'Location';
$lang->repo->file = 'File';
$lang->repo->action = 'Action';
@@ -120,6 +121,7 @@ $lang->repo->linkBug = 'Link Bug';
$lang->repo->linkTask = 'Link Task';
$lang->repo->unlink = 'Unlink';
$lang->repo->viewBugs = 'View Bugs';
+$lang->repo->lastSubmitTime = 'Final submission time';
$lang->repo->title = 'Title';
$lang->repo->status = 'Status';
@@ -239,3 +241,5 @@ $lang->repo->typeList['performance'] = 'Performance';
$lang->repo->typeList['security'] = 'Security';
$lang->repo->typeList['redundancy'] = 'Redundancy';
$lang->repo->typeList['logicError'] = 'Logic Error';
+
+$lang->repo->featureBar['maintain']['all'] = 'All';
diff --git a/module/repo/lang/en.php b/module/repo/lang/en.php
index f451b1135f..d701516f49 100644
--- a/module/repo/lang/en.php
+++ b/module/repo/lang/en.php
@@ -28,7 +28,7 @@ $lang->repo->branch = 'Branch';
$lang->repo->tag = 'Tag';
$lang->repo->addWebHook = 'Add Webhook';
$lang->repo->apiGetRepoByUrl = 'API: Get repo by URL';
-$lang->repo->blameTmpl = 'Code for line %line, %name commited at %time, %version %comment';
+$lang->repo->blameTmpl = 'Code for line %line: %name commited at %time, %version %comment';
$lang->repo->notRelated = 'There is currently no related ZenTao object';
$lang->repo->browseAction = 'Browse Repo';
@@ -51,6 +51,7 @@ $lang->repo->import = 'Import';
$lang->repo->importName = 'Name after import';
$lang->repo->importServer = 'Please select a server';
$lang->repo->gitlabList = 'Gitlab Repo';
+$lang->repo->batchCreate = 'Batch create repo';
$lang->repo->submit = 'Submit';
$lang->repo->cancel = 'Cancel';
@@ -101,7 +102,7 @@ $lang->repo->diff = 'Diff';
$lang->repo->diffAB = 'Diff';
$lang->repo->diffAll = 'Diff All';
$lang->repo->viewDiff = 'View diff';
-$lang->repo->allLog = 'All Revisions';
+$lang->repo->allLog = 'All Commits';
$lang->repo->location = 'Location';
$lang->repo->file = 'File';
$lang->repo->action = 'Action';
@@ -120,6 +121,7 @@ $lang->repo->linkBug = 'Link Bug';
$lang->repo->linkTask = 'Link Task';
$lang->repo->unlink = 'Unlink';
$lang->repo->viewBugs = 'View Bugs';
+$lang->repo->lastSubmitTime = 'Final submission time';
$lang->repo->title = 'Title';
$lang->repo->status = 'Status';
@@ -239,3 +241,5 @@ $lang->repo->typeList['performance'] = 'Performance';
$lang->repo->typeList['security'] = 'Security';
$lang->repo->typeList['redundancy'] = 'Redundancy';
$lang->repo->typeList['logicError'] = 'Logic Error';
+
+$lang->repo->featureBar['maintain']['all'] = 'All';
diff --git a/module/repo/lang/fr.php b/module/repo/lang/fr.php
index d96d84dd42..211a917a7b 100644
--- a/module/repo/lang/fr.php
+++ b/module/repo/lang/fr.php
@@ -28,7 +28,7 @@ $lang->repo->branch = 'Branch';
$lang->repo->tag = 'Tag';
$lang->repo->addWebHook = 'Add Webhook';
$lang->repo->apiGetRepoByUrl = 'API: Get repo by URL';
-$lang->repo->blameTmpl = 'Code for line %line, %name commited at %time, %version %comment';
+$lang->repo->blameTmpl = 'Code for line %line: %name commited at %time, %version %comment';
$lang->repo->notRelated = 'There is currently no related ZenTao object';
$lang->repo->browseAction = 'Browse Repo';
@@ -51,6 +51,7 @@ $lang->repo->import = 'Import';
$lang->repo->importName = 'Name after import';
$lang->repo->importServer = 'Please select a server';
$lang->repo->gitlabList = 'Gitlab Repo';
+$lang->repo->batchCreate = 'Batch create repo';
$lang->repo->submit = 'Soumettre';
$lang->repo->cancel = 'Annuler';
@@ -101,7 +102,7 @@ $lang->repo->diff = 'Diff';
$lang->repo->diffAB = 'Diff';
$lang->repo->diffAll = 'Toutes Diff';
$lang->repo->viewDiff = 'Voir diff';
-$lang->repo->allLog = 'Toutes Révisions';
+$lang->repo->allLog = 'All Commits';
$lang->repo->location = 'Localisation';
$lang->repo->file = 'Fichier';
$lang->repo->action = 'Action';
@@ -120,6 +121,7 @@ $lang->repo->linkBug = 'Link Bug';
$lang->repo->linkTask = 'Link Task';
$lang->repo->unlink = 'Unlink';
$lang->repo->viewBugs = 'View Bugs';
+$lang->repo->lastSubmitTime = 'Final submission time';
$lang->repo->title = 'Titre';
$lang->repo->status = 'Statut';
@@ -239,3 +241,5 @@ $lang->repo->typeList['performance'] = 'Performance';
$lang->repo->typeList['security'] = 'Securité';
$lang->repo->typeList['redundancy'] = 'Redondance';
$lang->repo->typeList['logicError'] = 'Erreur Logique';
+
+$lang->repo->featureBar['maintain']['all'] = 'All';
diff --git a/module/repo/lang/vi.php b/module/repo/lang/vi.php
index a221f7c36d..13348ae89d 100644
--- a/module/repo/lang/vi.php
+++ b/module/repo/lang/vi.php
@@ -168,8 +168,7 @@ $lang->repo->placeholder->gitlabHost = 'Input url of gitlab';
$lang->repo->notice = new stdclass();
$lang->repo->notice->syncing = 'Đang đồng bộ. Vui lòng đợi ...';
-$lang->repo->notice->syncComplete = 'Đồng bộ hóa hoàn tất, nhảy ...';
-$lang->repo->notice->syncFailed = 'Đồng bộ hóa không thành công.';
+$lang->repo->notice->syncComplete = 'Synchronized. Now redirecting ...';
$lang->repo->notice->syncedCount = 'The number of records synchronized is ';
$lang->repo->notice->delete = 'Bạn có muốn xóa this repo?';
$lang->repo->notice->successDelete = 'Repository is removed.';
@@ -231,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 4e72237835..e0451235ff 100644
--- a/module/repo/lang/zh-cn.php
+++ b/module/repo/lang/zh-cn.php
@@ -28,7 +28,7 @@ $lang->repo->branch = '分支';
$lang->repo->tag = '标签';
$lang->repo->addWebHook = '添加Webhook';
$lang->repo->apiGetRepoByUrl = '接口:通过URL获取代码库';
-$lang->repo->blameTmpl = '第 %line 行代码相关信息, %name 于 %time 提交 %version %comment';
+$lang->repo->blameTmpl = '第 %line 行代码相关信息: %name 于 %time 提交 %version %comment';
$lang->repo->notRelated = '暂时没有关联禅道对象';
$lang->repo->browseAction = '浏览代码库';
@@ -51,6 +51,7 @@ $lang->repo->import = '导入';
$lang->repo->importName = '导入后的名称';
$lang->repo->importServer = '请选择服务器';
$lang->repo->gitlabList = 'Gitlab代码库';
+$lang->repo->batchCreate = '批量添加代码库';
$lang->repo->submit = '提交';
$lang->repo->cancel = '取消';
@@ -101,7 +102,7 @@ $lang->repo->diff = '比较差异';
$lang->repo->diffAB = '比较';
$lang->repo->diffAll = '全部比较';
$lang->repo->viewDiff = '查看差异';
-$lang->repo->allLog = '所有版本';
+$lang->repo->allLog = '所有提交';
$lang->repo->location = '位置';
$lang->repo->file = '文件';
$lang->repo->action = '操作';
@@ -120,6 +121,7 @@ $lang->repo->linkBug = '关联Bug';
$lang->repo->linkTask = '关联任务';
$lang->repo->unlink = '取消关联';
$lang->repo->viewBugs = '查看Bug';
+$lang->repo->lastSubmitTime = '最后提交时间';
$lang->repo->title = '标题';
$lang->repo->status = '状态';
@@ -239,3 +241,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/lang/zh-tw.php b/module/repo/lang/zh-tw.php
index 43a701fba6..ac3a8e75b7 100644
--- a/module/repo/lang/zh-tw.php
+++ b/module/repo/lang/zh-tw.php
@@ -214,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 faa62e157c..6ee6377a1a 100644
--- a/module/repo/model.php
+++ b/module/repo/model.php
@@ -44,7 +44,7 @@ class repoModel extends model
/* Check the privilege. */
if($repoID)
{
- $repo = $this->getRepoByID($repoID);
+ $repo = $this->getByID($repoID);
if(empty($repo))
{
echo(js::alert($this->lang->repo->error->noFound));
@@ -58,10 +58,10 @@ class repoModel extends model
}
if(!in_array(strtolower($repo->SCM), $this->config->repo->gitServiceList)) unset($this->lang->devops->menu->mr);
- if(count($repos) > 1) $this->lang->switcherMenu = $this->getSwitcher($repoID);
+ // if(count($repos) > 1) $this->lang->switcherMenu = $this->getSwitcher($repoID);
}
- common::setMenuVars('devops', $repoID);
+ if(!in_array($this->app->methodName, array('maintain', 'create', 'edit','import'))) common::setMenuVars('devops', $repoID);
if(!session_id()) session_start();
$this->session->set('repoID', $repoID);
session_write_close();
@@ -81,7 +81,7 @@ class repoModel extends model
if(!in_array($currentModule, $this->config->repo->switcherModuleList)) return '';
if($currentModule == 'repo' and !in_array($currentMethod, $this->config->repo->switcherMethodList)) return '';
- $currentRepo = $this->getRepoByID($repoID);
+ $currentRepo = $this->getByID($repoID);
$currentRepoName = $currentRepo->name;
if($this->app->viewType == 'mhtml' and $repoID)
@@ -111,10 +111,38 @@ class repoModel extends model
* @access public
* @return array
*/
- public function getList($projectID = 0, $SCM = '', $orderBy = 'id_desc', $pager = null, $getCodePath = false)
+ public function getList($projectID = 0, $SCM = '', $orderBy = 'id_desc', $pager = null, $getCodePath = false, $lastSubmitTime = false, $type = '', $param = 0)
{
+ $repoQuery = '';
+ if($type == 'bySearch')
+ {
+ $queryID = $param;
+ $queryName = 'repoQuery';
+
+ if($queryID && $queryID != 'myQueryID')
+ {
+ $query = $this->loadModel('search')->getQuery($queryID);
+ if($query)
+ {
+ $this->session->set($queryName, $query->sql);
+ $this->session->set($queryName . 'Form', $query->form);
+ }
+ else
+ {
+ $this->session->set($queryName, ' 1 = 1');
+ }
+ }
+ else
+ {
+ if($this->session->$queryName == false) $this->session->set($queryName, ' 1 = 1');
+ }
+
+ $repoQuery = $this->session->$queryName;
+ }
+
$repos = $this->dao->select('*')->from(TABLE_REPO)
->where('deleted')->eq('0')
+ ->beginIF(!empty($repoQuery))->andWhere($repoQuery)->fi()
->beginIF($SCM)->andWhere('SCM')->eq($SCM)->fi()
->orderBy($orderBy)
->page($pager)
@@ -154,6 +182,12 @@ class repoModel extends model
}
}
+ if($lastSubmitTime)
+ {
+ $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);
}
@@ -189,52 +223,24 @@ class repoModel extends model
}
/**
+ * 创建版本库。
* Create a repo.
*
+ * @param object $repo
+ * @param bool $isPipelineServer
* @access public
- * @return bool
+ * @return int|false
*/
- public function create()
+ public function create(object $repo, bool $isPipelineServer): int|false
{
- if(!$this->checkClient()) return false;
- if(!$this->checkConnection()) return false;
-
- $isPipelineServer = in_array(strtolower($this->post->SCM), $this->config->repo->gitServiceList) ? true : false;
-
- $data = fixer::input('post')
- ->setIf($isPipelineServer, 'password', $this->post->serviceToken)
- ->setIf($this->post->SCM == 'Gitlab', 'path', '')
- ->setIf($this->post->SCM == 'Gitlab', 'client', '')
- ->setIf($this->post->SCM == 'Gitlab', 'extra', $this->post->serviceProject)
- ->setIf($isPipelineServer, 'prefix', '')
- ->setIf($this->post->SCM == 'Git', 'account', '')
- ->setIf($this->post->SCM == 'Git', 'password', '')
- ->skipSpecial('path,client,account,password')
- ->setDefault('product', '')
- ->join('product', ',')
- ->setDefault('projects', '')->join('projects', ',')
- ->get();
-
- $data->acl = empty($data->acl) ? '' : json_encode($data->acl);
- if($data->SCM == 'Subversion')
- {
- $scm = $this->app->loadClass('scm');
- $scm->setEngine($data);
- $info = $scm->info('');
- $infoRoot = urldecode($info->root);
- $data->prefix = empty($infoRoot) ? '' : trim(str_ireplace($infoRoot, '', str_replace('\\', '/', $data->path)), '/');
- if($data->prefix) $data->prefix = '/' . $data->prefix;
- }
-
- if($data->encrypt == 'base64') $data->password = base64_encode($data->password);
- $this->dao->insert(TABLE_REPO)->data($data, $skip = 'serviceToken')
+ $this->dao->insert(TABLE_REPO)->data($repo, 'serviceToken')
->batchCheck($this->config->repo->create->requiredFields, 'notempty')
- ->batchCheckIF($data->SCM != 'Gitlab', 'path,client', 'notempty')
+ ->batchCheckIF($repo->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}'")
- ->checkIF($isPipelineServer, 'serviceProject', 'unique', "`SCM` = '{$data->SCM}' and `serviceHost` = '{$data->serviceHost}'")
- ->checkIF(!$isPipelineServer, 'path', 'unique', "`SCM` = '{$data->SCM}' and `serviceHost` = '{$data->serviceHost}'")
+ ->batchCheckIF($repo->SCM == 'Subversion', $this->config->repo->svn->requiredFields, 'notempty')
+ ->check('name', 'unique', "`SCM` = '{$repo->SCM}'")
+ ->checkIF($isPipelineServer, 'serviceProject', 'unique', "`SCM` = '{$repo->SCM}' and `serviceHost` = '{$repo->serviceHost}'")
+ ->checkIF(!$isPipelineServer, 'path', 'unique', "`SCM` = '{$repo->SCM}' and `serviceHost` = '{$repo->serviceHost}'")
->autoCheck()
->exec();
@@ -242,46 +248,23 @@ class repoModel extends model
$this->rmClientVersionFile();
- $repoID = $this->dao->lastInsertID();
-
- if($this->post->SCM == 'Gitlab')
- {
- /* Add webhook. */
- $repo = $this->getRepoByID($repoID);
- $this->loadModel('gitlab')->addPushWebhook($repo);
- }
-
- return $repoID;
+ return $this->dao->lastInsertID();
}
/**
+ * 批量创建版本库。
* Batch create repos.
*
+ * @param array $repos
* @access public
* @return bool
*/
- public function batchCreate()
+ public function batchCreate(array $repos, int $serviceHost): bool
{
- $repos = fixer::input('post')->get();
-
- $dataAry = array();
- foreach($repos as $key => $val)
- {
- if(strpos($key, 'serviceProject') === 0)
- {
- $i = substr($key, 14);
- if(empty($repos->{'product' . $i})) dao::$errors['product' . $i][] = sprintf($this->lang->error->notempty, $this->lang->repo->product);
- if(empty($repos->{'name' . $i})) dao::$errors['name' . $i][] = sprintf($this->lang->error->notempty, $this->lang->repo->name);
-
- if(!empty($repos->{'product' . $i}) and !empty($repos->{'name' . $i})) $dataAry[] = array('serviceProject' => $repos->{'serviceProject' . $i}, 'product' => implode(',', $repos->{'product' . $i}), 'name' => $repos->{'name' . $i}, 'projects' => empty($repos->projects[$i]) ? '' : implode(',', $repos->projects[$i]));
- }
- }
- if(dao::isError()) return false;
-
- foreach($dataAry as $data)
+ foreach($repos as $data)
{
$repo = new stdclass();
- $repo->serviceHost = $repos->serviceHost;
+ $repo->serviceHost = $serviceHost;
$repo->serviceProject = $data['serviceProject'];
$repo->product = $data['product'];
$repo->name = $data['name'];
@@ -305,11 +288,11 @@ class repoModel extends model
if($repo->SCM == 'Gitlab')
{
/* Add webhook. */
- $repo = $this->getRepoByID($repoID);
+ $repo = $this->getByID($repoID);
$this->loadModel('gitlab')->addPushWebhook($repo);
}
- $actionID = $this->loadModel('action')->create('repo', $repoID, 'created');
+ $this->loadModel('action')->create('repo', $repoID, 'created');
}
return true;
@@ -324,7 +307,7 @@ class repoModel extends model
*/
public function update($id)
{
- $repo = $this->getRepoByID($id);
+ $repo = $this->getByID($id);
if($repo->client != $this->post->client and !$this->checkClient()) return false;
if(!$this->checkConnection()) return false;
@@ -363,7 +346,7 @@ class repoModel extends model
}
if($data->encrypt == 'base64') $data->password = base64_encode($data->password);
- $this->dao->update(TABLE_REPO)->data($data, $skip = 'serviceToken')
+ $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')
@@ -376,7 +359,7 @@ class repoModel extends model
$this->rmClientVersionFile();
- if($data->SCM == 'Gitlab') $data->path = $this->getRepoByID($id)->path;
+ if($data->SCM == 'Gitlab') $data->path = $this->getByID($id)->path;
if($repo->path != $data->path)
{
@@ -524,47 +507,70 @@ class repoModel extends model
/**
* Get repos group by repo type.
*
- * @param string type
+ * @param string $type
* @param int $projectID
+ * @param string $repoType
* @access public
* @return array
*/
- public function getRepoGroup($type, $projectID = 0)
+ public function getRepoGroup(string $type, int $projectID = 0, string $repoType = ''): array
{
- $repoPairs = $this->getRepoPairs($type, $projectID);
+ $repos = $this->dao->select('*')->from(TABLE_REPO)
+ ->where('deleted')->eq(0)
+ ->beginIF($repoType == 'git')->andWhere('SCM')->in($this->config->repo->gitServiceTypeList)->fi()
+ ->fetchAll();
- $repos = array();
- foreach($this->lang->repo->scmList as $scmType => $scm) $repos[$scmType] = array();
-
- foreach($repoPairs as $id => $repo)
+ $productIds = $productItems = array();
+ foreach($repos as $repo)
{
- if(strpos($repo, '[gitlab]') !== false)
+ $productIds = array_merge($productIds, explode(',', $repo->product));
+ }
+ $products = $this->loadModel('product')->getByIdList(array_filter(array_unique($productIds)));
+ foreach($products as $productID => $product)
+ {
+ $productItem = array();
+ $productItem['id'] = $productID;
+ $productItem['type'] = 'product';
+ $productItem['text'] = $product->name;
+ $productItem['items'] = array();
+
+ $productItems[$productID] = $productItem;
+ }
+
+ /* Get project products. */
+ $projectProductIds = ($type == 'project' or $type == 'execution') ? $this->loadModel('product')->getProductIDByProject((int)$projectID, false) : array();
+
+ /* Get repo data for dropmenu. */
+ $repoPairs = array();
+ foreach($repos as $repo)
+ {
+ $repo->acl = json_decode($repo->acl);
+ $scm = $repo->SCM == 'Subversion' ? 'svn' : strtolower($repo->SCM);
+ if($this->checkPriv($repo))
{
- $repo = str_replace('[gitlab]', '', $repo);
- $repos['Gitlab'][$id] = $repo;
- }
- elseif(strpos($repo, '[gogs]') !== false)
- {
- $repo = str_replace('[gogs]', '', $repo);
- $repos['Gogs'][$id] = $repo;
- }
- elseif(strpos($repo, '[gitea]') !== false)
- {
- $repo = str_replace('[gitea]', '', $repo);
- $repos['Gitea'][$id] = $repo;
- }
- elseif(strpos($repo, '[svn]') !== false)
- {
- $repo = str_replace('[svn]', '', $repo);
- $repos['SVN'][$id] = $repo;
- }
- elseif(strpos($repo, '[git]') !== false)
- {
- $repo = str_replace('[git]', '', $repo);
- $repos['Git'][$id] = $repo;
+ $repoItem = array();
+ $repoItem['id'] = $repo->id;
+ $repoItem['text'] = $repo->name;
+ $repoItem['keys'] = zget(common::convert2Pinyin(array($repo->name)), $repo->name, '');
+
+ $repoProducts = explode(',', $repo->product);
+ foreach($repoProducts as $productID)
+ {
+ if(($type == 'project' or $type == 'execution') and $projectID)
+ {
+ if(!in_array($productID, $projectProductIds)) continue;
+ }
+
+ if(strpos(",$repo->product,", ",$productID,") !== false)
+ {
+ if(!isset($repoPairs[$productID])) $repoPairs[$productID] = $productItems[$productID];
+ $repoPairs[$productID]['items'][] = $repoItem;
+ }
+ }
}
}
- return $repos;
+
+ return $repoPairs;
}
/**
@@ -574,8 +580,9 @@ class repoModel extends model
* @access public
* @return object
*/
- public function getRepoByID($repoID)
+ public function getByID($repoID)
{
+ if(empty($repoID)) return new stdclass();
$repo = $this->dao->select('*')->from(TABLE_REPO)->where('id')->eq($repoID)->fetch();
if(!$repo) return false;
@@ -637,7 +644,7 @@ class repoModel extends model
$conditions = array();
foreach($matches as $matched) $conditions[] = "(`client`='$matched->gitlab' and `path`='$matched->project')";
- $conditions = '(' . join(' OR ', $conditions). ')';
+ $conditions = '(' . implode(' OR ', $conditions). ')';
$matchedRepos = $this->dao->select('*')->from(TABLE_REPO)->where('SCM')->eq('Gitlab')
->andWhere($conditions)
@@ -716,7 +723,7 @@ class repoModel extends model
$conditions = array();
foreach($matches as $matched) $conditions[] = "(`client`='$matched->gitlab' and `path`='$matched->project')";
- $conditions = '(' . join(' OR ', $conditions). ')';
+ $conditions = '(' . implode(' OR ', $conditions). ')';
$matchedRepos = $this->dao->select('*')->from(TABLE_REPO)->where('SCM')->eq('Gitlab')
->andWhere($conditions)
@@ -742,7 +749,7 @@ class repoModel extends model
public function getByIdList($idList)
{
$repos = $this->dao->select('*')->from(TABLE_REPO)->where('deleted')->eq(0)->andWhere('id')->in($idList)->fetchAll();
- foreach($repos as $i => $repo)
+ foreach($repos as $repo)
{
if($repo->encrypt == 'base64') $repo->password = base64_decode($repo->password);
$repo->acl = json_decode($repo->acl);
@@ -803,6 +810,7 @@ class repoModel extends model
*/
public function getCommits($repo, $entry, $revision = 'HEAD', $type = 'dir', $pager = null, $begin = 0, $end = 0)
{
+ if(!isset($repo->id)) return array();
if($repo->SCM == 'Gitlab') return $this->loadModel('gitlab')->getCommits($repo, $entry, $revision, $type, $pager, $begin, $end);
$entry = ltrim($entry, '/');
@@ -859,6 +867,7 @@ class repoModel extends model
$repoComment->originalComment = $repoComment->comment;
$repoComment->comment = $this->replaceCommentLink($repoComment->comment);
}
+
return $comments;
}
@@ -886,7 +895,7 @@ class repoModel extends model
->fetch();
if(empty($lastComment)) return null;
- $repo = $this->getRepoByID($repoID);
+ $repo = $this->getByID($repoID);
if($repo->SCM == 'Git' and $lastComment->commit != $count)
{
$this->fixCommit($repo->id);
@@ -984,7 +993,7 @@ class repoModel extends model
*/
public function getProductsByRepo($repoID)
{
- $repo = $this->getRepoByID($repoID);
+ $repo = $this->getByID($repoID);
if(empty($repo)) return array();
return $this->dao->select('id,name')->from(TABLE_PRODUCT)
@@ -1347,7 +1356,7 @@ class repoModel extends model
*/
public function setRepoBranch($branch)
{
- setcookie("repoBranch", $branch, 0, $this->config->webRoot, '', $this->config->cookieSecure);
+ helper::setcookie("repoBranch", $branch, 0, $this->config->webRoot, '', $this->config->cookieSecure);
$_COOKIE['repoBranch'] = $branch;
}
@@ -1428,7 +1437,6 @@ class repoModel extends model
$blk = substr($content, 0, 512);
return (
- false ||
substr_count($blk, "^\r\n")/512 > 0.3 ||
substr_count($blk, "^ -~")/512 > 0.3 ||
substr_count($blk, "\x00") > 0
@@ -1521,7 +1529,7 @@ class repoModel extends model
exec($versionCommand, $versionOutput, $versionResult);
if($versionResult)
{
- $message = sprintf($this->lang->repo->error->output, $versionCommand, $versionResult, join("
", $versionOutput));
+ $message = sprintf($this->lang->repo->error->output, $versionCommand, $versionResult, implode("
", $versionOutput));
dao::$errors['client'] = $this->lang->repo->error->cmd . "
" . nl2br($message);
return false;
}
@@ -1539,7 +1547,7 @@ class repoModel extends model
$command = "$client info --username $account --password $password --non-interactive --trust-server-cert-failures=cn-mismatch --trust-server-cert --no-auth-cache $path 2>&1";
if(version_compare($svnVersion, '1.9', '<')) $command = "$client info --username $account --password $password --non-interactive --trust-server-cert --no-auth-cache $path 2>&1";
}
- else if(stripos($path, 'file://') === 1)
+ elseif(stripos($path, 'file://') === 1)
{
$command = "$client info --non-interactive --no-auth-cache $path 2>&1";
}
@@ -1551,7 +1559,7 @@ class repoModel extends model
exec($command, $output, $result);
if($result)
{
- $message = sprintf($this->lang->repo->error->output, $command, $result, join("
", $output));
+ $message = sprintf($this->lang->repo->error->output, $command, $result, implode("
", $output));
if(stripos($message, 'Expected FS format between') !== false and strpos($message, 'found format') !== false)
{
dao::$errors['client'] = $this->lang->repo->error->clientVersion;
@@ -1614,7 +1622,7 @@ class repoModel extends model
exec($command, $output, $result);
if($result)
{
- dao::$errors['submit'] = $this->lang->repo->error->connect . "
" . sprintf($this->lang->repo->error->output, $command, $result, join("
", $output));
+ dao::$errors['submit'] = $this->lang->repo->error->connect . "
" . sprintf($this->lang->repo->error->output, $command, $result, implode("
", $output));
return false;
}
}
@@ -1631,9 +1639,6 @@ class repoModel extends model
public function replaceCommentLink($comment)
{
$rules = $this->processRules();
- $stories = array();
- $tasks = array();
- $bugs = array();
$storyReg = '/' . $rules['storyReg'] . '/i';
$taskReg = '/' . $rules['taskReg'] . '/i';
$bugReg = '/' . $rules['bugReg'] . '/i';
@@ -1948,7 +1953,7 @@ class repoModel extends model
}
else
{
- $this->task->recordEstimate($taskID);
+ $this->task->recordWorkhour($taskID);
}
$action->action = $scm == 'svn' ? 'svncommited' : 'gitcommited';
@@ -2168,7 +2173,7 @@ class repoModel extends model
{
$record = array();
$record['execution'] = $executionID;
- $record['product'] = isset($products[$taskID]) ? "," . join(',', array_keys($products[$taskID])) . "," : ",0,";
+ $record['product'] = isset($products[$taskID]) ? "," . implode(',', array_keys($products[$taskID])) . "," : ",0,";
$records[$taskID] = $record;
}
return $records;
@@ -2221,7 +2226,8 @@ class repoModel extends model
{
if($getCodePath) $project = $this->loadModel('gitlab')->apiGetSingleProject($repo->serviceHost, $repo->serviceProject);
- $repo->path = $service ? sprintf($this->config->repo->{$service->type}->apiPath, $service->url, $repo->serviceProject) : '';
+ $repo->path = (!$repo->path && $service) ? sprintf($this->config->repo->{$service->type}->apiPath, $service->url, $repo->serviceProject) : $repo->path;
+ $repo->apiPath = sprintf($this->config->repo->{$service->type}->apiPath, $service->url, $repo->serviceProject);
$repo->client = $service ? $service->url : '';
$repo->password = $service ? $service->token : '';
$repo->codePath = isset($project->web_url) ? $project->web_url : $repo->path;
@@ -2382,7 +2388,7 @@ class repoModel extends model
/* Get file commits by repo. */
if($repo->SCM != 'Subversion' and empty($branch)) $branch = $this->cookie->repoBranch;
- $fileCommits = $this->dao->select('t1.id,t1.path,t1.type,t1.action,t1.oldPath,t1.parent,t2.revision,t2.`comment`,t2.committer,t2.time')->from(TABLE_REPOFILES)->alias('t1')
+ $fileCommits = $this->dao->select('t1.id,t1.path,t1.type,t1.action,t1.oldPath,t1.parent,t2.revision,t2.comment,t2.committer,t2.time')->from(TABLE_REPOFILES)->alias('t1')
->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)
@@ -2526,14 +2532,11 @@ class repoModel extends model
}
$infos = array('revision' => $lastRevision, 'files' => $allFiles);
- if($cacheFile)
+ if($cacheFile && !file_exists($cacheFile . '.lock'))
{
- if(!file_exists($cacheFile . '.lock'))
- {
- touch($cacheFile . '.lock');
- file_put_contents($cacheFile, serialize($infos));
- unlink($cacheFile . '.lock');
- }
+ touch($cacheFile . '.lock');
+ file_put_contents($cacheFile, serialize($infos));
+ unlink($cacheFile . '.lock');
}
}
else
@@ -2602,7 +2605,7 @@ class repoModel extends model
{
$files = array();
$id = 0;
- foreach($allFiles as $key => $file)
+ foreach($allFiles as $file)
{
$fileName = explode('/', $file);
$parent = '';
@@ -2612,40 +2615,24 @@ class repoModel extends model
$parentID = $parent == '' ? 0 : $files[$parent]['id'];
$parent .= $parent == '' ? $path : '/' . $path;
- if(!isset($files[$parent])){
+ if(!isset($files[$parent]))
+ {
$id++;
+ $id = str_replace('=', '-', base64_encode(urlencode($parent)));
$files[$parent] = array(
'id' => $id,
'parent' => $parentID,
'name' => $path,
'path' => $parent,
- 'key' => $key,
+ 'key' => $id,
);
}
}
}
sort($files);
- $fileTree = $this->buildTree($files);
- $html = '';
- foreach($fileTree as $file)
- {
- $html .= "- ";
- if(isset($file['children']))
- {
- $html .= " {$file['name']}";
- $html .= $this->getFrontFiles($file['children']);
- }
- else
- {
- $html .= " " . html::a('#filePath' . $file['key'], $file['name'], '', "class='repoFileName' data-path='{$file['path']}' title='{$file['path']}'") . '';
- }
- $html .= '
';
- }
- $html .= '
';
-
- return $html;
+ return $this->buildTree($files);
}
/**
@@ -2733,7 +2720,7 @@ class repoModel extends model
$output = new stdClass();
$scm = $this->app->loadClass('scm');
- $repo = $this->getRepoByID($repoID);
+ $repo = $this->getByID($repoID);
if(!$repo) return $output;
$scm->setEngine($repo);
@@ -2806,8 +2793,8 @@ class repoModel extends model
}
}
$stories = empty($storyIDs) ? array() : $this->loadModel('story')->getByList($storyIDs);
- $bugs = empty($bugIDs) ? array() : $this->loadModel('bug')->getByList($bugIDs);
- $tasks = empty($taskIDs) ? array() : $this->loadModel('task')->getByList($taskIDs);
+ $bugs = empty($bugIDs) ? array() : $this->loadModel('bug')->getByIdList($bugIDs);
+ $tasks = empty($taskIDs) ? array() : $this->loadModel('task')->getByIdList($taskIDs);
$titleList = array();
foreach($relationList as $key => $relation)
@@ -2868,7 +2855,7 @@ class repoModel extends model
public function insertDeleteRecord($repoID)
{
set_time_limit(0);
- $repo = $this->loadModel('repo')->getRepoByID($repoID);
+ $repo = $this->loadModel('repo')->getByID($repoID);
if(empty($repo)) return false;
$scm = $this->app->loadClass('scm');
@@ -2935,7 +2922,7 @@ class repoModel extends model
if($values)
{
$sql = 'INSERT INTO ' . TABLE_REPOFILES . ' (`repo`,`revision`,`path`,`oldPath`,`parent`,`type`,`action`) VALUES ' . trim($values, ',');
- $result = $this->dao->exec($sql);
+ $this->dao->exec($sql);
}
$this->loadModel('setting')->setItem('system.repo.synced', $this->config->repo->synced . ',' . $repoID);
@@ -2974,7 +2961,7 @@ class repoModel extends model
*/
public function updateCommit($repoID, $objectID = 0, $branchID = 0)
{
- $repo = $this->getRepoByID($repoID);
+ $repo = $this->getByID($repoID);
/* Update code commit history. */
$commentGroup = $this->loadModel('job')->getTriggerGroup('commit', array($repoID));
@@ -3027,6 +3014,63 @@ class repoModel extends model
return true;
}
+ /**
+ * 判断按钮是否可点击。
+ * Judge an action is clickable or not.
+ *
+ * @param object $repo
+ * @param string $action
+ * @access public
+ * @return bool
+ */
+ public static function isClickable(object $repo, string $action): bool
+ {
+ $action = strtolower($action);
+
+ if($action == 'execjob') return $repo->exec == '';
+ if($action == 'reportview') return $repo->report == '';
+
+ return true;
+ }
+
+ /**
+ * 获取gitlab项目列表。
+ * Get gitlab projects.
+ *
+ * @param int $gitlabID
+ * @param string $projectIdList
+ * @param string $filter
+ * @access public
+ * @return array
+ */
+ public function getGitlabProjects(int $gitlabID, string $projectIdList = '', string $filter = ''): array
+ {
+ $showAll = ($filter == 'ALL' and common::hasPriv('repo', 'create')) ? true : false;
+ if($this->app->user->admin or $showAll)
+ {
+ $projects = $this->loadModel('gitlab')->apiGetProjects($gitlabID, true, 0, 0, false);
+ }
+ else
+ {
+ $gitlabUser = $this->loadModel('gitlab')->getUserIDByZentaoAccount($gitlabID, $this->app->user->account);
+ if(!$gitlabUser) $this->send(array('message' => array()));
+
+ $projects = $this->gitlab->apiGetProjects($gitlabID, $filter ? 'false' : 'true');
+ $groupIDList = array(0 => 0);
+ $groups = $this->gitlab->apiGetGroups($gitlabID, 'name_asc', 'developer');
+ foreach($groups as $group) $groupIDList[] = $group->id;
+ if($filter == 'IS_DEVELOPER')
+ {
+ foreach($projects as $key => $project)
+ {
+ if(!$this->gitlab->checkUserAccess($gitlabID, 0, $project, $groupIDList, 'developer')) unset($projects[$key]);
+ }
+ }
+ }
+
+ return $projects;
+ }
+
/**
* Check str in array.
*
diff --git a/module/repo/tao.php b/module/repo/tao.php
new file mode 100644
index 0000000000..5a2c84a56b
--- /dev/null
+++ b/module/repo/tao.php
@@ -0,0 +1,28 @@
+
+ * @package repo
+ * @link https://www.zentao.net
+ */
+
+class repoTao extends repoModel
+{
+ /**
+ * 获取最后一次提交信息。
+ * Get last revision.
+ *
+ * @param int $repoID
+ * @access protected
+ * @return object|false
+ */
+ protected function getLastRevision(int $repoID): object|false
+ {
+ return $this->dao->select('*')->from(TABLE_REPOHISTORY)->where('repo')->eq($repoID)->orderBy('time_desc')->fetch();
+ }
+}
+
diff --git a/module/repo/ui/ajaxgetdropmenu.html.php b/module/repo/ui/ajaxgetdropmenu.html.php
new file mode 100644
index 0000000000..0ab3fa2076
--- /dev/null
+++ b/module/repo/ui/ajaxgetdropmenu.html.php
@@ -0,0 +1,26 @@
+
+ * @package repo
+ * @link https://www.zentao.net
+ */
+namespace zin;
+
+$data = array('repo' => array_values($repoGroup));
+
+$tabs = array();
+$tabs[] = array('name' => 'repo', 'text' => $lang->repo->codeRepo);
+
+$json = array();
+$json['data'] = $data;
+$json['tabs'] = $tabs;
+$json['searchHint'] = $lang->searchAB;
+$json['labelMap'] = array('product' => $lang->product->common);
+$json['link'] = array('repo' => sprintf($link, '{id}'));
+$json['itemType'] = 'repo';
+
+renderJson($json);
diff --git a/module/repo/ui/ajaxgeteditorcontent.html.php b/module/repo/ui/ajaxgeteditorcontent.html.php
new file mode 100644
index 0000000000..afa47d8a2d
--- /dev/null
+++ b/module/repo/ui/ajaxgeteditorcontent.html.php
@@ -0,0 +1,159 @@
+
+ * @package repo
+ * @link http://www.zentao.net
+ */
+
+namespace zin;
+
+$canLinkStory = common::hasPriv('repo', 'linkStory');
+$canLinkBug = common::hasPriv('repo', 'linkBug');
+$canLinkTask = common::hasPriv('repo', 'linkTask');
+$canUnlinkObject = common::hasPriv('repo', 'unlink');
+
+jsVar('unlinkTitle', $this->lang->repo->unlink);
+jsVar('canUnlinkObject', $canUnlinkObject);
+jsVar('fileExt', $this->config->repo->fileExt);
+jsVar('file', $pathInfo);
+jsVar('filePath', $entry);
+jsVar('blameTmpl', $lang->repo->blameTmpl);
+jsVar('repoID', $repoID);
+jsVar('showEditor', $showEditor);
+jsVar('canLinkStory', $canLinkStory);
+jsVar('canLinkBug', $canLinkBug);
+jsVar('canLinkTask', $canLinkTask);
+jsVar('objectID', 0);
+jsVar('objectType', 'story');
+jsVar('pageType', $type);
+jsVar('revision', $revision);
+jsVar('sourceRevision', $oldRevision);
+jsVar('encodePath', $this->repo->encodePath($entry));
+if($showEditor) jsVar('codeContent', $content);
+
+$lang = 'php';
+foreach($this->config->repo->fileExt as $langName => $exts)
+{
+ foreach($exts as $ext) if(isset($pathInfo['extension']) && str_contains('.' . $pathInfo['extension'], $ext)) $lang = $langName;
+}
+
+if(strpos($config->repo->images, "|$suffix|") !== false)
+{
+ $wg = div
+ (
+ set::class('image'),
+ img(set::src('data:image/' . $suffix . ';base64,' . $content))
+ );
+}
+elseif($suffix == 'binary')
+{
+ $wg = div
+ (
+ set::class('binary'),
+ a
+ (
+ set::href($this->repo->createLink('download', "repoID=$repoID&path=" . $this->repo->encodePath($entry) . "&fromRevision=$revision")),
+ icon('download'),
+ set::target('_blank'),
+ set::title($this->lang->repo->download),
+ )
+ );
+}
+else
+{
+ $options = array(
+ 'value' => $content,
+ 'language' => $lang,
+ 'readOnly' => true,
+ 'autoIndent' => true,
+ 'contextmenu' => true,
+ 'automaticLayout' => true,
+ 'EditorMinimapOptions' => array('enabled' => false),
+ );
+ if($type == 'diff') $options = array(
+ 'language' => $lang,
+ 'readOnly' => true,
+ 'autoIndent' => true,
+ 'contextmenu' => true,
+ 'automaticLayout' => true,
+ 'renderSideBySide' => false,
+ 'EditorMinimapOptions' => array('enabled' => false),
+ );
+ $wg = monaco
+ (
+ set::id('codeContainer'),
+ set::options($options),
+ set::action($type == 'diff' ? 'diff' : 'create'),
+ $type == 'diff' ? set::diffContent(jsRaw('parent.getDiffs(filePath)')) : null,
+ set::onMouseDown('window.onMouseDown')
+ );
+}
+
+$dropMenus = array();
+if($canLinkStory) $dropMenus[] = array('id' => 'linkStory', 'text' => $this->lang->repo->linkStory, 'icon' => 'lightbulb');
+if($canLinkBug) $dropMenus[] = array('id' => 'linkBug', 'text' => $this->lang->repo->linkBug, 'icon' => 'bug');
+if($canLinkTask) $dropMenus[] = array('id' => 'linkTask', 'text' => $this->lang->repo->linkTask, 'icon' => 'todo');
+
+$logWg = div
+(
+ set::id('log'),
+ div(set::class('history')),
+ div
+ (
+ set::class('action-btn pull-right'),
+ div(set::class('btn btn-close pull-right ghost text-black bg-light bg-opacity-50'), icon('close')),
+ !empty($dropMenus) ? dropdown
+ (
+ set::arrow(false),
+ set::staticMenu(true),
+ btn
+ (
+ setClass('ghost text-black bg-light bg-opacity-50'),
+ set::icon('ellipsis-v rotate-90'),
+ ),
+ set::items
+ (
+ $dropMenus
+ ),
+ ) : '',
+ ),
+);
+
+$relatedWg = div
+(
+ set::id('related'),
+ div(set::class('btn btn-left pull-left'), icon('chevron-left')),
+ div(set::class('btn btn-right pull-right'), icon('chevron-right')),
+ div
+ (
+ set::class('panel-title'),
+ tabs
+ (
+ set::id('relationTabs'),
+ tabPane
+ (
+ set::key('tab1'),
+ set::title(''),
+ tableData()
+ ),
+ ),
+ ),
+ div(set::class('table-empty-tip'), p($this->lang->repo->notRelated))
+);
+
+div
+(
+ set::id('monacoEditor'),
+ set::class('repoCode'),
+ $wg,
+ $logWg,
+ $relatedWg
+);
+
+set::zui(true);
+render('pageBase');
diff --git a/module/repo/ui/blame.html.php b/module/repo/ui/blame.html.php
new file mode 100644
index 0000000000..f460619f69
--- /dev/null
+++ b/module/repo/ui/blame.html.php
@@ -0,0 +1,106 @@
+
+ * @package repo
+ * @link https://www.zentao.net
+ */
+namespace zin;
+
+dropmenu(set::module('repo'), set::tab('repo'), set::objectID($repo->id));
+
+/* Prepare breadcrumb navigation data. */
+$base64BranchID = helper::safe64Encode(base64_encode($branchID));
+$breadcrumbItems = array();
+$breadcrumbItems[] = h::a
+(
+ set::href($this->repo->createLink('browse', "repoID=$repoID&branchID=$base64BranchID&objectID=$objectID")),
+ $repo->name,
+);
+$breadcrumbItems[] = h::span('/', setStyle('margin', '0 5px'));
+
+$paths = explode('/', $entry);
+$fileName = array_pop($paths);
+$postPath = '';
+foreach($paths as $pathName)
+{
+ $postPath .= $pathName . '/';
+ $breadcrumbItems[] = h::a
+ (
+ set::href($this->repo->createLink('browse', "repoID=$repoID&branchID=$base64BranchID&objectID=$objectID&path=" . $this->repo->encodePath($postPath))),
+ trim($pathName, '/'),
+ );
+ $breadcrumbItems[] = h::span('/', setStyle('margin', '0 5px'));
+}
+if($fileName) $breadcrumbItems[] = h::span($fileName);
+
+$breadcrumbItems[] = h::span
+(
+ setClass('ml-2 label secondary'),
+ html($revisionName),
+);
+
+foreach($blames as $key => $blame)
+{
+ if(isset($blame['lines']))
+ {
+ if($repo->SCM != 'Subversion') $blames[$key]['revision'] = substr($blame['revision'], 0, 10);
+ }
+
+ $blames[$key]['content'] = htmlSpecialString($blame['content']);
+}
+
+foreach($blames as $key => $blame) $blames[$key] = (object)$blame;
+
+$blames = initTableData($blames, $config->repo->blameDtable->fieldList, $this->repo);
+
+$encodePath = $this->repo->encodePath($entry);
+$encodes = array();
+foreach($lang->repo->encodingList as $key => $val)
+{
+ $encodes[] = array('text' => $val, 'url' => inlink('blame', "repoID=$repoID&objectID=$objectID&entry=$encodePath&revision=$revision&encoding=$key"));
+}
+$defaultEncode = $lang->repo->encodingList[$encoding];
+
+featureBar(
+ backBtn
+ (
+ setClass('mr-5'),
+ set::icon('back'),
+ set::type('secondary'),
+ set::back('GLOBAL'),
+ $lang->goback
+ ),
+ ...$breadcrumbItems
+);
+
+panel
+(
+ set::title($fileName),
+ to::headingActions(
+ btnGroup
+ (
+ btn($defaultEncode),
+ dropdown
+ (
+ btn(setClass('btn dropdown-toggle'),
+ setStyle(array('padding' => '6px', 'border-radius' => '0 2px 2px 0'))),
+ set::items($encodes),
+ set::arrow(true),
+ set::flip(true),
+ set::placement('bottom-end'),
+ ),
+ ),
+ ),
+ dtable
+ (
+ set::cols($config->repo->blameDtable->fieldList),
+ set::data($blames),
+ ),
+);
+
+render();
+
diff --git a/module/repo/ui/browse.html.php b/module/repo/ui/browse.html.php
new file mode 100644
index 0000000000..724873da44
--- /dev/null
+++ b/module/repo/ui/browse.html.php
@@ -0,0 +1,260 @@
+
+ * @package repo
+ * @link http://www.zentao.net
+ */
+
+namespace zin;
+
+jsVar('copied', $lang->repo->copied);
+
+dropmenu(set::module('repo'), set::tab('repo'));
+
+/* Prepare repo select data. */
+$branchMenus = array();
+$tagMenus = array();
+$selected = '';
+foreach($branches as $branchName)
+{
+ $selected = ($branchName == $branchID and $branchOrTag == 'branch') ? $branchName : $selected;
+ $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);
+}
+foreach($tags as $tagName)
+{
+ $selected = ($tagName == $branchID and $branchOrTag == 'tag') ? $tagName : $selected;
+ $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);
+}
+
+$tabs = array(array('name' => 'branch', 'text' => $lang->repo->branch), array('name' => 'tag', 'text' => $lang->repo->tag));
+$menuData = array('branch' => $branchMenus, 'tag' => $tagMenus);
+
+/* Prepare breadcrumb navigation data. */
+$base64BranchID = helper::safe64Encode(base64_encode($branchID));
+$breadcrumbItems = array();
+$breadcrumbItems[] = h::a
+(
+ set::href($this->repo->createLink('browse', "repoID=$repoID&branchID=$base64BranchID&objectID=$objectID")),
+ $repo->name,
+);
+$breadcrumbItems[] = h::span('/', setStyle('margin', '0 5px'));
+
+$paths = explode('/', $path);
+$fileName = array_pop($paths);
+$postPath = '';
+foreach($paths as $index => $pathName)
+{
+ $postPath .= $pathName . '/';
+ $breadcrumbItems[] = h::a
+ (
+ set::href($this->repo->createLink('browse', "repoID=$repoID&branchID=$base64BranchID&objectID=$objectID&path=" . $this->repo->encodePath($postPath))),
+ trim($pathName, '/'),
+ );
+ $breadcrumbItems[] = h::span('/', setStyle('margin', '0 5px'));
+}
+if($fileName) $breadcrumbItems[] = h::span($fileName);
+
+/* zin: Define the set::module('repo') feature bar on main menu. */
+featureBar(
+ formGroup
+ (
+ set::class('repo-select'),
+ set::required(true),
+ dropmenu
+ (
+ setID('repoBranchDropMenu'),
+ set::objectID($selected),
+ set::text($selected),
+ set::data(array('data' => $menuData, 'tabs' => $tabs)),
+ ),
+ ),
+ ...$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');
+
+$tableData = initTableData($infos, $config->repo->repoDtable->fieldList, $this->repo);
+
+$downloadWg = div
+(
+ set::id('modal-downloadCode'),
+ set::title($lang->repo->downloadCode),
+ on('click', '', array('capture' => true, 'prevent' => true, 'stop' => true)),
+ !empty($cloneUrl->svn) ? div
+ (
+ p(set::class('repo-downloadCode'), $lang->repo->cloneUrl),
+ formRow
+ (
+ formGroup
+ (
+ set::width('450px'),
+ input
+ (
+ set::type('text'),
+ set::name('svnUrl'),
+ set::value($cloneUrl->svn),
+ set::readOnly(true),
+ ),
+ ),
+ formGroup
+ (
+ set::width('50px'),
+ btn
+ (
+ set::icon('copy'),
+ )
+ )
+ ),
+ ) : null,
+
+ !empty($cloneUrl->ssh) ? div
+ (
+ p(set::class('repo-downloadCode'), $lang->repo->sshClone),
+ formRow
+ (
+ formGroup
+ (
+ set::width('450px'),
+ input
+ (
+ set::type('text'),
+ set::name('sshUrl'),
+ set::value($cloneUrl->ssh),
+ set::readOnly(true),
+ ),
+ ),
+ formGroup
+ (
+ set::width('50px'),
+ btn
+ (
+ set::class('copy-btn'),
+ set::icon('copy'),
+ )
+ )
+ ),
+ ) : null,
+
+ !empty($cloneUrl->http) ? div
+ (
+ p(set::class('repo-downloadCode'), $lang->repo->httpClone),
+ formRow
+ (
+ formGroup
+ (
+ set::width('450px'),
+ input
+ (
+ set::type('text'),
+ set::name('httpUrl'),
+ set::value($cloneUrl->http),
+ set::readOnly(true),
+ ),
+ ),
+ formGroup
+ (
+ set::width('50px'),
+ btn
+ (
+ set::class('copy-btn'),
+ set::icon('copy'),
+ )
+ )
+ ),
+ ) : null,
+
+ div
+ (
+ setStyle(array('margin-top' => '20px')),
+ btn
+ (
+ set::icon('down-circle'),
+ set::class('downloadZip-btn'),
+ set::text($lang->repo->downloadZip),
+ )
+ ),
+);
+
+toolbar
+(
+ span(
+ set::class('last-sync-time'),
+ $lang->repo->notice->lastSyncTime . $cacheTime
+ ),
+ item(set($refreshItem)),
+ dropdown
+ (
+ set::staticMenu(true),
+ btn
+ (
+ setClass('primary download-btn'),
+ set::icon('download'),
+ $lang->repo->download
+ ),
+ to::items
+ (
+ array($downloadWg)
+ ),
+ )
+);
+
+dtable
+(
+ set::cols($config->repo->repoDtable->fieldList),
+ set::data($tableData),
+ set::onRenderCell(jsRaw('window.renderCell')),
+ set::canRowCheckable(jsRaw('function(rowID){return false;}')),
+ set::footPager(),
+);
+
+/* zin: Define the sidebar in main content. */
+$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('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\'');
+
+$commentsTableData = initTableData($revisions, $config->repo->commentDtable->fieldList, $this->repo);
+
+$readAllLink = $this->repo->createLink('log', "repoID=$repoID&objectID=$objectID&entry=" . $encodePath . "&revision=HEAD&type=$logType");
+
+$footToolbar['items'][] = array('text' => $lang->repo->diff, 'class' => "btn primary size-sm btn-diff", 'btnType' => 'primary', 'onClick' => jsRaw('window.diffClick'));
+$footToolbar['items'][] = array('text' => $lang->repo->allLog, 'url' => $readAllLink);
+
+sidebar
+(
+ set::side('right'),
+ dtable
+ (
+ set::id('repo-comments-table'),
+ set::cols($config->repo->commentDtable->fieldList),
+ set::data($commentsTableData),
+ set::onRenderCell(jsRaw('window.renderCommentCell')),
+ set::onCheckChange(jsRaw('window.checkedChange')),
+ set::canRowCheckable(jsRaw('function(rowID){return canRowCheckable(rowID);}')),
+ set::footToolbar($footToolbar),
+ set::footer(array('toolbar', 'flex', 'pager')),
+ set::footPager(usePager()),
+ set::showToolbarOnChecked(false),
+ ),
+);
+
+render();
diff --git a/module/repo/ui/create.html.php b/module/repo/ui/create.html.php
index 9d8744aacd..e3317f14a0 100644
--- a/module/repo/ui/create.html.php
+++ b/module/repo/ui/create.html.php
@@ -1,6 +1,206 @@
+ * @package repo
+ * @link https://www.zentao.net
+ */
namespace zin;
-div('hello world');
+jsVar('pathGitTip', $lang->repo->example->path->git);
+jsVar('pathSvnTip', $lang->repo->example->path->svn);
+jsVar('clientGitTip', $lang->repo->example->client->git);
+jsVar('clientSvnTip', $lang->repo->example->client->svn);
+jsVar('scmList', $lang->repo->scmList);
-render('page');
+formPanel
+(
+ on::change('#product', 'onProductChange'),
+ on::change('#SCM', 'onScmChange'),
+ on::change('#serviceHost', 'onHostChange'),
+ on::change('#serviceProject', 'onProjectChange'),
+ set::title($lang->repo->createAction),
+ set::back('repo-maintain'),
+ formGroup
+ (
+ set::name("product[]"),
+ set::label($lang->story->product),
+ set::required(true),
+ set::control(array("type" => "picker","multiple" => true)),
+ set::items($products),
+ set::value(empty($objectID) ? '' : array_keys($products))
+ ),
+ formGroup
+ (
+ set::name("projects[]"),
+ set::label($lang->repo->projects),
+ set::control(array("type" => "picker","multiple" => true)),
+ set::items($projects),
+ set::value($relatedProjects)
+ ),
+ formRow
+ (
+ formGroup
+ (
+ set::width('1/2'),
+ set::id('SCM'),
+ set::name('SCM'),
+ set::label($lang->product->typeAB),
+ set::required(true),
+ set::value('Gitlab'),
+ set::control('picker'),
+ set::items($lang->repo->scmList)
+ ),
+ h::span
+ (
+ setClass('tips-git leading-8 ml-2'),
+ html($lang->repo->syncTips),
+ ),
+ ),
+ formRow
+ (
+ setClass('service hide'),
+ formGroup
+ (
+ set::width('1/2'),
+ set::name("serviceHost"),
+ set::label($lang->repo->serviceHost),
+ set::required(true),
+ set::value(""),
+ set::control("picker"),
+ set::items($serviceHosts)
+ ),
+ ),
+ formRow
+ (
+ setClass('service hide'),
+ formGroup
+ (
+ set::width('1/2'),
+ set::name("serviceProject"),
+ set::label($lang->repo->serviceProject),
+ set::required(true),
+ set::control("picker"),
+ ),
+ ),
+ formGroup
+ (
+ set::width('1/2'),
+ set::name("name"),
+ set::label($lang->user->name),
+ set::required(true),
+ set::control("text")
+ ),
+ formRow
+ (
+ setClass('hide-service hide-git'),
+ formGroup
+ (
+ set::name("path"),
+ set::label($lang->repo->path),
+ set::required(true),
+ set::control("text"),
+ set::placeholder($lang->repo->example->path->git),
+ ),
+ ),
+ formGroup
+ (
+ set::width('1/2'),
+ set::name("encoding"),
+ set::label($lang->repo->encoding),
+ set::required(true),
+ set::value("utf-8"),
+ set::control("text"),
+ set::placeholder($lang->repo->encodingsTips),
+ ),
+ formRow
+ (
+ setClass('hide-service'),
+ formGroup
+ (
+ set::name("client"),
+ set::label($lang->repo->client),
+ set::required(true),
+ set::control("text")
+ ),
+ ),
+ formRow
+ (
+ setClass('account-fields hide-service'),
+ formGroup
+ (
+ set::name("account"),
+ set::label($lang->user->account),
+ set::control("text")
+ ),
+ ),
+ formRow
+ (
+ setClass('account-fields hide-service'),
+ formGroup
+ (
+ set::label($lang->user->password),
+ inputGroup
+ (
+ control(set(array
+ (
+ 'name' => "password",
+ 'id' => "password",
+ 'value' => "",
+ 'type' => "password"
+ ))),
+ control(set(array
+ (
+ 'name' => "encrypt",
+ 'id' => "encrypt",
+ 'value' => "base64",
+ 'type' => "picker",
+ 'items' => $lang->repo->encryptList
+ )))
+ )
+ )
+ ),
+ formGroup
+ (
+ set::label($lang->repo->acl),
+ inputGroup
+ (
+ $lang->repo->group,
+ width('full'),
+ control(set(array
+ (
+ 'name' => "acl[groups][]",
+ 'id' => "aclgroups",
+ 'value' => NULL,
+ 'type' => "picker",
+ 'items' => $groups,
+ 'multiple' => true
+ )))
+ ),
+ inputGroup
+ (
+ $lang->repo->user,
+ control(set(array
+ (
+ 'name' => "acl[users][]",
+ 'id' => "aclusers",
+ 'value' => NULL,
+ 'type' => "picker",
+ 'items' => $users,
+ 'multiple' => true
+ ))),
+ setClass('mt-2')
+ )
+ ),
+ formGroup
+ (
+ set::name("desc"),
+ set::label($lang->story->spec),
+ set::control("editor")
+ )
+);
+
+render();
diff --git a/module/repo/ui/diff.html.php b/module/repo/ui/diff.html.php
new file mode 100644
index 0000000000..11261ab64f
--- /dev/null
+++ b/module/repo/ui/diff.html.php
@@ -0,0 +1,76 @@
+
+ * @package repo
+ * @link http://www.zentao.net
+ */
+
+namespace zin;
+
+dropmenu(set::module('repo'), set::tab('repo'));
+
+$browser = helper::getBrowser();
+jsVar('browser', $browser['name']);
+jsVar('edition', $config->edition);
+
+$backURI = $this->session->repoView ? $this->session->repoView : $this->session->repoList;
+
+$breadcrumbItems = array();
+$breadcrumbItems[] = h::a
+(
+ set::href($this->repo->createLink('browse', "repoID=$repoID&branchID=&objectID=$objectID"), $repo->name, '', "data-app='{$app->tab}'"),
+ $repo->name,
+);
+
+$breadcrumbItems[] = h::span('/', setStyle('margin', '0 5px'));
+
+$paths = explode('/', $entry);
+$fileName = array_pop($paths);
+$postPath = '';
+$base64BranchID = helper::safe64Encode(base64_encode($branchID));
+
+foreach($paths as $pathName)
+{
+ $postPath .= $pathName . '/';
+ $breadcrumbItems[] = h::a
+ (
+ set::href($this->repo->createLink('browse', "repoID=$repoID&branchID=$base64BranchID&objectID=$objectID&path=" . $this->repo->encodePath($postPath))),
+ trim($pathName, '/'),
+ );
+
+ $breadcrumbItems[] = h::span('/', setStyle('margin', '0 5px'));
+}
+
+if($fileName) $breadcrumbItems[] = h::span($fileName);
+
+if(strpos($repo->SCM, 'Subversion') === false)
+{
+ $oldRevision = $oldRevision == '^' ? "$newRevision" : $oldRevision;
+ $revisionWg = h::span(setClass('label label-info diff-label'), substr($oldRevision, 0, 10) . " : " . substr($newRevision, 0, 10) . ' (' . zget($historys, $oldRevision, '') . ' : ' . zget($historys, $newRevision, ''));
+}
+else
+{
+ $oldRevision = $oldRevision == '^' ? $newRevision - 1 : $oldRevision;
+ $revisionWg = h::span(setClass('label label-info'), $oldRevision . ':' . $newRevision);
+
+}
+
+$breadcrumbItems[] = $revisionWg;
+$breadcrumbItems[] = span(setClass('label label-exchange'), icon('exchange'));
+
+featureBar
+(
+ backBtn(set::icon('back'), $backURI ? set::url($backURI) : null, setClass('bg-transparent diff-back-btn'), $lang->goback),
+ item(set::type('divider')),
+ ...$breadcrumbItems,
+);
+
+if($diffs) include 'diffeditor.html.php';
+
+jsVar('oldRevision', $oldRevision);
+jsVar('newRevision', $newRevision);
diff --git a/module/repo/ui/diffeditor.html.php b/module/repo/ui/diffeditor.html.php
new file mode 100644
index 0000000000..70432e5042
--- /dev/null
+++ b/module/repo/ui/diffeditor.html.php
@@ -0,0 +1,95 @@
+
+ * @package repo
+ * @link http://www.zentao.net
+ */
+
+namespace zin;
+
+$entry = count($diffs) ? $diffs[0]->fileName : '';
+$currentEntry = $this->repo->encodePath($entry);
+$fileInfo = $entry ? pathinfo($entry) : array();
+$showBug = isset($showBug) ? $showBug : true;
+$objectID = isset($objectID) ? $objectID : 0;
+$tree = $this->repo->getFileTree($repo, '', $diffs);
+$diffLink = $this->repo->createLink('diff', "repoID=$repoID&objectID=$objectID&entry=" . $file . "&oldrevision={oldRevision}&newRevision={newRevision}");
+
+jsVar('diffs', $diffs);
+jsVar('tree', $tree);
+jsVar('file', $currentEntry);
+jsVar('entry', $entry);
+jsVar('diffLink', $diffLink);
+jsVar('urlParams', "repoID=$repoID&objectID=$objectID&entry=%s&oldRevision=$oldRevision&newRevision=$newRevision&showBug=$showBug&encoding=$encoding");
+
+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');
+
+$dropMenus[] = array('text' => $this->lang->repo->viewDiffList['inline'], 'icon' => 'snap-house', 'id' => 'inline', 'class' => 'inline-appose');
+$dropMenus[] = array('text' => $this->lang->repo->viewDiffList['appose'], 'icon' => 'col-archive', 'id' => 'appose', 'class' => 'inline-appose');
+div(
+ set::id('fileTabs'),
+ tabs
+ (
+ set::id('monacoTabs'),
+ set::class('relative'),
+ div(setStyle(array('position' => 'absolute', 'width' => '100%', 'height' => '35px', 'background' => '#efefef', 'top' => '0px'))),
+ tabPane
+ (
+ set::title($fileInfo['basename']),
+ set::active(true),
+ set::key('tab-' . str_replace('=', '-', $currentEntry)),
+ to::suffix
+ (
+ icon
+ (
+ 'close',
+ set::class('monaco-close'),
+ )
+ ),
+ div(set::id('tab-' . $currentEntry)),
+ ),
+ dropdown
+ (
+ set::arrow(false),
+ set::staticMenu(true),
+ set::class('absolute top-0 right-0 z-10 monaco-dropmenu'),
+ btn
+ (
+ setClass('ghost text-black pull-right'),
+ set::icon('ellipsis-v rotate-90'),
+ ),
+ set::items
+ (
+ $dropMenus
+ ),
+ ),
+ div(set::class('absolute top-0 left-0 z-20 arrow-left btn-left'), icon('chevron-left')),
+ div(set::class('absolute top-0 right-0 z-20 arrow-right btn-right'), icon('chevron-right')),
+ )
+);
+
+sidebar
+(
+ set::side('left'),
+ tree
+ (
+ set::id('monacoTree'),
+ set::items($tree),
+ set::canSplit(false),
+ set::collapsedIcon('folder'),
+ set::expandedIcon('folder-open'),
+ set::normalIcon('file-text-alt'),
+ set::activeKey($entry),
+ set::onClickItem(jsRaw('window.treeClick')),
+ )
+);
+
+a(set::class('iframe'), setData('size', '1000px'), setData('toggle', 'modal'), set::id('linkObject'));
\ No newline at end of file
diff --git a/module/repo/ui/edit.html.php b/module/repo/ui/edit.html.php
new file mode 100644
index 0000000000..498d0835c3
--- /dev/null
+++ b/module/repo/ui/edit.html.php
@@ -0,0 +1,212 @@
+
+ * @package repo
+ * @link https://www.zentao.net
+ */
+namespace zin;
+
+jsVar('pathGitTip', $lang->repo->example->path->git);
+jsVar('pathSvnTip', $lang->repo->example->path->svn);
+jsVar('clientGitTip', $lang->repo->example->client->git);
+jsVar('clientSvnTip', $lang->repo->example->client->svn);
+jsVar('scmList', $lang->repo->scmList);
+
+formPanel
+(
+ on::change('#product', 'onProductChange'),
+ on::change('#SCM', 'onScmChange'),
+ on::change('#serviceHost', 'onHostChange'),
+ on::change('#serviceProject', 'onProjectChange'),
+ set::title($lang->repo->edit),
+ set::back('repo-maintain'),
+ formGroup
+ (
+ set::name("product[]"),
+ set::label($lang->story->product),
+ set::required(true),
+ set::control(array("type" => "picker","multiple" => true)),
+ set::items($products),
+ set::value($repo->product)
+ ),
+ formGroup
+ (
+ set::name("projects[]"),
+ set::label($lang->repo->projects),
+ set::control(array("type" => "picker","multiple" => true)),
+ set::items($relatedProjects),
+ set::value($repo->projects)
+ ),
+ formRow
+ (
+ formGroup
+ (
+ set::width('1/2'),
+ set::name("SCM"),
+ set::label($lang->product->typeAB),
+ set::required(true),
+ set::value($repo->SCM),
+ set::control("picker"),
+ set::items($lang->repo->scmList)
+ ),
+ h::span
+ (
+ setClass('tips-git leading-8 ml-2'),
+ html($lang->repo->syncTips),
+ ),
+ ),
+ formRow
+ (
+ setClass('service hide'),
+ formGroup
+ (
+ set::width('1/2'),
+ set::name("serviceHost"),
+ set::label($lang->repo->serviceHost),
+ set::required(true),
+ set::value(isset($repo->gitService) ? $repo->gitService : ''),
+ set::control("picker"),
+ set::items($serviceHosts)
+ ),
+ ),
+ formRow
+ (
+ setClass('service hide'),
+ formGroup
+ (
+ set::width('1/2'),
+ set::name("serviceProject"),
+ set::label($lang->repo->serviceProject),
+ set::required(true),
+ set::control("picker"),
+ set::items($projects),
+ set::value(isset($repo->project) ? $repo->project : ''),
+ ),
+ ),
+ formGroup
+ (
+ set::width('1/2'),
+ set::name("name"),
+ set::label($lang->user->name),
+ set::required(true),
+ set::control("text"),
+ set::value($repo->name),
+ ),
+ formRow
+ (
+ setClass('hide-service hide-git'),
+ formGroup
+ (
+ set::name("path"),
+ set::label($lang->repo->path),
+ set::required(true),
+ set::control("text"),
+ set::placeholder($lang->repo->example->path->git),
+ set::value($repo->path),
+ ),
+ ),
+ formGroup
+ (
+ set::width('1/2'),
+ set::name("encoding"),
+ set::label($lang->repo->encoding),
+ set::required(true),
+ set::value($repo->encoding),
+ set::control("text"),
+ set::placeholder($lang->repo->encodingsTips),
+ ),
+ formRow
+ (
+ setClass('hide-service'),
+ formGroup
+ (
+ set::name("client"),
+ set::label($lang->repo->client),
+ set::required(true),
+ set::control("text"),
+ set::value($repo->client),
+ ),
+ ),
+ formRow
+ (
+ setClass('account-fields hide-service'),
+ formGroup
+ (
+ set::name("account"),
+ set::label($lang->user->account),
+ set::control("text"),
+ set::value($repo->account),
+ ),
+ ),
+ formRow
+ (
+ setClass('account-fields hide-service'),
+ formGroup
+ (
+ set::label($lang->user->password),
+ inputGroup
+ (
+ control(set(array
+ (
+ 'name' => "password",
+ 'id' => "password",
+ 'value' => $repo->password,
+ 'type' => "password"
+ ))),
+ control(set(array
+ (
+ 'name' => "encrypt",
+ 'id' => "encrypt",
+ 'value' => $repo->encrypt,
+ 'type' => "picker",
+ 'items' => $lang->repo->encryptList
+ )))
+ )
+ )
+ ),
+ formGroup
+ (
+ set::label($lang->repo->acl),
+ inputGroup
+ (
+ $lang->repo->group,
+ width('full'),
+ control(set(array
+ (
+ 'name' => "acl[groups][]",
+ 'id' => "aclgroups",
+ 'value' => empty($repo->acl->groups) ? '' : implode(',', $repo->acl->groups),
+ 'type' => "picker",
+ 'items' => $groups,
+ 'multiple' => true
+ )))
+ ),
+ inputGroup
+ (
+ $lang->repo->user,
+ control(set(array
+ (
+ 'name' => "acl[users][]",
+ 'id' => "aclusers",
+ 'value' => empty($repo->acl->users) ? '' : implode(',', $repo->acl->users),
+ 'type' => "picker",
+ 'items' => $users,
+ 'multiple' => true
+ ))),
+ setClass('mt-2')
+ )
+ ),
+ formGroup
+ (
+ set::name("desc"),
+ set::label($lang->story->spec),
+ set::control("editor"),
+ set::value($repo->desc),
+ )
+);
+
+render();
diff --git a/module/repo/ui/import.html.php b/module/repo/ui/import.html.php
new file mode 100644
index 0000000000..b2c4ccf785
--- /dev/null
+++ b/module/repo/ui/import.html.php
@@ -0,0 +1,61 @@
+
+ * @package repo
+ * @link https://www.zentao.net
+ */
+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' => '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);
+$items[] = array('name' => 'projects', 'label' => $lang->repo->projects, 'control' => array('type' => 'picker', 'multiple' => true), 'items' => $projects);
+
+$no = 1;
+foreach($repoList as $repo)
+{
+ $repo->serviceProject = $repo->id;
+ $repo->no = $no ++;
+}
+
+featureBar
+(
+ h::a
+ (
+ setClass('form-title'),
+ set::href($this->createLink('repo', 'import')),
+ $lang->repo->batchCreate,
+ ),
+ picker
+ (
+ setClass('ml-3'),
+ width('200px'),
+ on::change('selectServer'),
+ set::name('servers'),
+ set::id('servers'),
+ set::placeholder($lang->repo->importServer),
+ set::items(array('' => '') + $gitlabPairs),
+ set::value($gitlab->id)
+ ),
+);
+
+formBatchPanel
+(
+ set::id('repoList'),
+ set::back('repo-maintain'),
+ set::mode(count($repoList) == 0 ? 'edit' : 'add'),
+ set::addRowIcon('false'),
+ set::items($items),
+ set::data($repoList),
+ set::maxRows(count($repoList)),
+ on::change('[data-name="product"]', 'loadProductProjects'),
+);
+
+render();
diff --git a/module/repo/ui/linkbug.html.php b/module/repo/ui/linkbug.html.php
new file mode 100644
index 0000000000..c952a9a03c
--- /dev/null
+++ b/module/repo/ui/linkbug.html.php
@@ -0,0 +1,60 @@
+
+ * @package repo
+ * @link https://www.zentao.net
+ */
+namespace zin;
+
+jsVar('orderBy', $orderBy);
+jsVar('sortLink', createLink('repo', 'linkBug', "repoID=$repoID&revision=$revision&browseType=$browseType¶m=$param&orderBy={orderBy}&recTotal={$pager->recTotal}&recPerPage={$pager->recPerPage}&pageID={$pager->pageID}"));
+
+detailHeader
+(
+ to::prefix(''),
+ to::title
+ (
+ $lang->productplan->linkBug,
+ )
+);
+
+$footToolbar = array('items' => array
+(
+ array('text' => $lang->productplan->linkBug, 'className' => 'batch-btn ajax-btn', 'data-url' => helper::createLink('repo', 'linkBug', "repoID=$repoID&revision=$revision&browseType=$browseType¶m=$param&orderBy=$orderBy"))
+), 'btnProps' => array('size' => 'sm', 'btnType' => 'secondary', 'data-type' => 'bugs'));
+
+div(setID('searchFormPanel'), set('data-module', 'bug'), searchToggle(set::open(true), set::module('bug')));
+
+div
+(
+ set('class', 'repo-linkstory-title'),
+ icon('unlink'),
+ span
+ (
+ set('class', 'font-semibold ml-2'),
+ $lang->productplan->unlinkedBugs . "({$pager->recTotal})"
+ )
+);
+$cols = array();
+foreach($config->release->dtable->defaultFields['linkBug'] as $field) $cols[$field] = zget($config->bug->dtable->fieldList, $field, array());
+$cols['title']['data-size'] = 'lg';
+$cols['title']['data-toggle'] = 'modal';
+
+$allBugs = initTableData($allBugs, $cols);
+$data = array_values($allBugs);
+dtable
+(
+ set::userMap($users),
+ set::data($data),
+ set::cols($cols),
+ set::checkable(true),
+ set::footToolbar($footToolbar),
+ set::sortLink(jsRaw('createSortLink')),
+ set::footPager(usePager()),
+);
+
+render();
diff --git a/module/repo/ui/linkstory.html.php b/module/repo/ui/linkstory.html.php
new file mode 100644
index 0000000000..b8d14e3b15
--- /dev/null
+++ b/module/repo/ui/linkstory.html.php
@@ -0,0 +1,63 @@
+
+ * @package repo
+ * @link https://www.zentao.net
+ */
+namespace zin;
+
+jsVar('orderBy', $orderBy);
+jsVar('sortLink', createLink('repo', 'linkStory', "repoID=$repoID&revision=$revision&browseType=$browseType¶m=$param&orderBy={orderBy}&recTotal={$pager->recTotal}&recPerPage={$pager->recPerPage}&pageID={$pager->pageID}"));
+
+detailHeader
+(
+ to::prefix(''),
+ to::title
+ (
+ $lang->repo->linkStory,
+ )
+);
+
+$footToolbar = array('items' => array
+(
+ array('text' => $lang->repo->linkStory, 'className' => 'batch-btn ajax-btn', 'data-url' => helper::createLink('repo', 'linkStory', "repoID=$repoID&revision=$revision&browseType=$browseType¶m=$param&orderBy=$orderBy"))
+), 'btnProps' => array('size' => 'sm', 'btnType' => 'secondary', 'data-type' => 'stories'));
+
+div(setID('searchFormPanel'), set('data-module', 'story'), searchToggle(set::open(true), set::module('story')));
+
+div
+(
+ set('class', 'repo-linkstory-title'),
+ icon('unlink'),
+ span
+ (
+ set('class', 'font-semibold ml-2'),
+ $lang->productplan->unlinkedStories . "({$pager->recTotal})"
+ )
+);
+
+$cols = array();
+foreach($config->release->dtable->defaultFields['linkStory'] as $field) $cols[$field] = zget($config->release->dtable->story->fieldList, $field, array());
+$cols = array_map(function($col){$col['show'] = true; return $col;}, $cols);
+$cols['title']['link'] = $this->createLink('story', 'view', "storyID={id}");
+$cols['title']['nestedToggle'] = false;
+$cols['title']['data-size'] = 'lg';
+
+$allStories = initTableData($allStories, $cols);
+$data = array_values($allStories);
+dtable
+(
+ set::userMap($users),
+ set::data($data),
+ set::cols($cols),
+ set::checkable(true),
+ set::footToolbar($footToolbar),
+ set::sortLink(jsRaw('createSortLink')),
+ set::footPager(usePager()),
+);
+
+render();
diff --git a/module/repo/ui/linktask.html.php b/module/repo/ui/linktask.html.php
new file mode 100644
index 0000000000..6547c4a687
--- /dev/null
+++ b/module/repo/ui/linktask.html.php
@@ -0,0 +1,55 @@
+
+ * @package repo
+ * @link https://www.zentao.net
+ */
+namespace zin;
+
+jsVar('orderBy', $orderBy);
+jsVar('sortLink', createLink('repo', 'linkTask', "repoID=$repoID&revision=$revision&browseType=$browseType¶m=$param&orderBy={orderBy}&recTotal={$pager->recTotal}&recPerPage={$pager->recPerPage}&pageID={$pager->pageID}"));
+
+detailHeader
+(
+ to::prefix(''),
+ to::title
+ (
+ $lang->repo->linkTask,
+ )
+);
+
+$footToolbar = array('items' => array
+(
+ array('text' => $lang->repo->linkTask, 'className' => 'batch-btn ajax-btn', 'data-url' => helper::createLink('repo', 'linkTask', "repoID=$repoID&revision=$revision&browseType=$browseType¶m=$param&orderBy=$orderBy"))
+), 'btnProps' => array('size' => 'sm', 'btnType' => 'secondary', 'data-type' => 'tasks'));
+
+div(setID('searchFormPanel'), set('data-module', 'task'), searchToggle(set::open(true), set::module('task')));
+
+div
+(
+ set('class', 'repo-linkstory-title'),
+ icon('unlink'),
+ span
+ (
+ set('class', 'font-semibold ml-2'),
+ $lang->repo->unlinkedTasks . "({$pager->recTotal})"
+ )
+);
+$allTasks = initTableData($allTasks, $config->repo->taskDtable->fieldList);
+$data = array_values($allTasks);
+dtable
+(
+ set::userMap($users),
+ set::data($data),
+ set::cols($config->repo->taskDtable->fieldList),
+ set::checkable(true),
+ set::footToolbar($footToolbar),
+ set::sortLink(jsRaw('createSortLink')),
+ set::footPager(usePager()),
+);
+
+render();
diff --git a/module/repo/ui/log.html.php b/module/repo/ui/log.html.php
new file mode 100644
index 0000000000..1630b86802
--- /dev/null
+++ b/module/repo/ui/log.html.php
@@ -0,0 +1,83 @@
+
+ * @package repo
+ * @link https://www.zentao.net
+ */
+namespace zin;
+
+dropmenu(set::module('repo'), set::tab('repo'));
+
+$diffLink = $this->repo->createLink('diff', "repoID=$repoID&objectID=$objectID&entry=" . $this->repo->encodePath($entry) . "&oldrevision={oldRevision}&newRevision={newRevision}");
+
+jsVar('diffLink', $diffLink);
+
+/* Prepare breadcrumb navigation data. */
+$base64BranchID = helper::safe64Encode(base64_encode($branchID));
+$breadcrumbItems = array();
+$breadcrumbItems[] = h::a
+(
+ set::href($this->repo->createLink('log', "repoID=$repoID&objectID=$objectID")),
+ $repo->name,
+);
+$breadcrumbItems[] = h::span('/', setStyle('margin', '0 5px'));
+
+$paths = explode('/', $entry);
+$fileName = array_pop($paths);
+$postPath = '';
+foreach($paths as $pathName)
+{
+ $postPath .= $pathName . '/';
+ $breadcrumbItems[] = h::a
+ (
+ set::href($this->repo->createLink('log', "repoID=$repoID&objectID=$objectID&entry=" . $this->repo->encodePath($postPath))),
+ trim($pathName, '/'),
+ );
+ $breadcrumbItems[] = h::span('/', setStyle('margin', '0 5px'));
+}
+if($fileName) $breadcrumbItems[] = h::span($fileName);
+
+foreach($logs as $log)
+{
+ $log->revision = substr($log->revision, 0, 10);
+}
+
+/* Disbale check all checkbox of table header */
+$config->repo->logDtable->fieldList['revision']['checkbox'] = jsRaw('(rowID) => rowID !== \'HEADER\'');
+
+$config->repo->logDtable->fieldList['revision']['link'] = array('module' => 'repo', 'method' => 'revision', 'params' => "repoID={$repoID}&objectID={$objectID}&revision={revision}");
+
+$logs = initTableData($logs, $config->repo->logDtable->fieldList);
+
+$footToolbar['items'][] = array('text' => $lang->repo->diff, 'class' => "btn primary size-sm btn-diff", 'btnType' => 'primary', 'onClick' => jsRaw('window.diffClick'));
+
+featureBar(
+ backBtn
+ (
+ setClass('mr-5'),
+ set::icon('back'),
+ set::type('secondary'),
+ set::back('repo-browse'),
+ $lang->goback
+ ),
+ ...$breadcrumbItems
+);
+
+dtable
+(
+ set::id('repo-logs-table'),
+ set::cols($config->repo->logDtable->fieldList),
+ set::data($logs),
+ set::onCheckChange(jsRaw('window.checkedChange')),
+ set::canRowCheckable(jsRaw('function(rowID){return canRowCheckable(rowID);}')),
+ set::footToolbar($footToolbar),
+ set::footer(array('toolbar', 'flex', 'pager')),
+ set::footPager(usePager('pager', 'noTotalCount')),
+ set::showToolbarOnChecked(false),
+);
+
+render();
diff --git a/module/repo/ui/maintain.html.php b/module/repo/ui/maintain.html.php
new file mode 100644
index 0000000000..1e12c8c2d4
--- /dev/null
+++ b/module/repo/ui/maintain.html.php
@@ -0,0 +1,96 @@
+
+ * @package repo
+ * @link https://www.zentao.net
+ */
+namespace zin;
+
+$createItem = array('text' => $lang->repo->createAction, 'url' => createLink('repo', 'create'));
+$batchCreateItem = array('text' => $lang->repo->batchCreate, 'url' => createLink('repo', 'import'));
+
+foreach($repoList as $repo)
+{
+ $jobID = 0;
+ $repo->exec = 'disabled';
+ $repo->report = 'disabled';
+ if(isset($sonarRepoList[$repo->id]))
+ {
+ $repo->exec = '';
+ $repo->job = $sonarRepoList[$repo->id]->id;
+ if(in_array($repo->job, $successJobs)) $repo->report = '';
+ }
+
+ $repo->productNames = '';
+ $productList = explode(',', str_replace(' ', '', $repo->product));
+ if($productList)
+ {
+ foreach($productList as $productID)
+ {
+ if(!isset($products[$productID])) continue;
+ $repo->productNames .= ' ' . zget($products, $productID, $productID);
+ }
+ }
+
+ $repo->projectNames = '';
+ $projectList = explode(',', str_replace(' ', '', $repo->projects));
+ if($projectList)
+ {
+ foreach($projectList as $projectID)
+ {
+ if(!isset($projects[$projectID])) continue;
+ $repo->projectNames .= ' ' . zget($projects, $projectID, $projectID);
+ }
+ }
+}
+
+$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}");
+
+$repos = initTableData($repoList, $config->repo->dtable->fieldList, $this->repo);
+
+featureBar
+ (
+ set::current('all'),
+ li(searchToggle()),
+);
+
+toolBar
+(
+ !hasPriv('repo', 'create') && hasPriv('repo', 'import') ? item(set($batchCreateItem + array
+ (
+ 'icon' => 'plus',
+ 'class' => 'btn primary',
+ ))) : null,
+ !hasPriv('repo', 'import') && hasPriv('repo', 'create') ? item(set($createItem + array
+ (
+ 'icon' => 'plus',
+ 'class' => 'btn primary',
+ ))) : null,
+ hasPriv('repo', 'import') && hasPriv('repo', 'create') ? btnGroup
+ (
+ btn(setClass('btn primary'), set::icon('plus'), set::url(createLink('repo', 'create')), $lang->repo->createAction),
+ dropDown
+ (
+ btn(setClass('btn primary dropdown-toggle'),
+ setStyle(array('padding' => '6px', 'border-radius' => '0 2px 2px 0'))),
+ set::placement('bottom-end'),
+ set::items(array($createItem, $batchCreateItem)),
+ ),
+ ) : null,
+);
+
+dtable
+(
+ set::cols($config->repo->dtable->fieldList),
+ set::data($repos),
+ set::footPager(usePager()),
+);
+
+render();
+
diff --git a/module/repo/ui/monaco.html.php b/module/repo/ui/monaco.html.php
new file mode 100644
index 0000000000..0189a9f04c
--- /dev/null
+++ b/module/repo/ui/monaco.html.php
@@ -0,0 +1,110 @@
+
+ * @package repo
+ * @link http://www.zentao.net
+ */
+
+namespace zin;
+
+dropmenu(set::module('repo'), set::tab('repo'));
+
+$tree = $this->repo->getFileTree($repo);
+
+jsVar('isonlybody', isonlybody());
+jsVar('entry', $entry);
+jsVar('repoID', $repoID);
+jsVar('repo', $repo);
+jsVar('revision', $revision);
+jsVar('branchID', $branchID);
+jsVar('branchMenus', $dropMenus);
+jsVar('file', $file);
+jsVar('tree', $tree);
+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();
+
+$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');
+if(common::hasPriv('repo', 'download')) $monacoDropMenus[] = array('text' => $this->lang->repo->download, 'icon' => 'download', 'data-link' => $this->repo->createLink('download', "repoID=$repoID&path={path}&fromRevision=$revision"), 'class' => 'repoDropDownMenu');
+
+$tabs = array(array('name' => 'branch', 'text' => $lang->repo->branch), array('name' => 'tag', 'text' => $lang->repo->tag));
+$menuData = array('branch' => $dropMenus['branchMenus'], 'tag' => $dropMenus['tagMenus']);
+
+div(
+ set::id('fileTabs'),
+ setStyle('position', 'relative'),
+ tabs
+ (
+ set::id('monacoTabs'),
+ set::class('relative'),
+ div(setStyle(array('position' => 'absolute', 'width' => '100%', 'height' => '40px', 'background' => '#efefef', 'top' => '0px'))),
+ tabPane
+ (
+ set::title($pathInfo['basename']),
+ set::active(true),
+ set::key('tab-' . str_replace('=', '-', $file)),
+ to::suffix
+ (
+ icon
+ (
+ 'close',
+ set::class('monaco-close'),
+ )
+ ),
+ div(set::id('tab-' . $file)),
+ ),
+ dropdown
+ (
+ set::arrow(false),
+ set::staticMenu(true),
+ set::key('dropdown'),
+ set::class('absolute top-0 right-0 z-10 monaco-dropmenu'),
+ btn
+ (
+ setClass('ghost text-black pull-right'),
+ set::icon('ellipsis-v rotate-90'),
+ ),
+ set::items
+ (
+ $monacoDropMenus
+ ),
+ ),
+ div(set::class('absolute top-0 left-0 z-20 arrow-left btn-left'), icon('chevron-left')),
+ div(set::class('absolute top-0 right-0 z-20 arrow-right btn-right'), icon('chevron-right')),
+ )
+);
+
+sidebar
+(
+ set::side('left'),
+ dropmenu
+ (
+ setID('repoBranchDropMenu'),
+ on::click('window.changeBranch'),
+ on::change('window.changeBranch'),
+ set::objectID($dropMenus['selected']),
+ set::text($dropMenus['selected']),
+ set::data(array('data' => $menuData, 'tabs' => $tabs)),
+ ),
+ tree
+ (
+ set::id('monacoTree'),
+ set::items($tree),
+ set::canSplit(false),
+ set::collapsedIcon('folder'),
+ set::expandedIcon('folder-open'),
+ set::normalIcon('file-text-alt'),
+ set::activeKey($entry),
+ set::onClickItem(jsRaw('window.treeClick')),
+ )
+);
+
+a(set::class('iframe'), setData('size', '1000px'), setData('toggle', 'modal'), set::id('linkObject'));
diff --git a/module/repo/ui/showsynccommit.html.php b/module/repo/ui/showsynccommit.html.php
new file mode 100644
index 0000000000..a928eaa759
--- /dev/null
+++ b/module/repo/ui/showsynccommit.html.php
@@ -0,0 +1,49 @@
+
+ * @package repo
+ * @link http://www.zentao.net
+ */
+
+namespace zin;
+
+featureBar();
+
+if(empty($branch))
+{
+ $link = helper::createLink('repo', 'ajaxSyncCommit', "repoID=$repoID");
+}
+else
+{
+ $link = helper::createLink('repo', 'ajaxSyncBranchCommit', "repoID=$repoID&branch=" . helper::safe64Encode(base64_encode($branch)));
+}
+
+jsVar('link', $link);
+jsVar('browseLink', $browseLink);
+jsVar('syncComplete', $lang->repo->notice->syncComplete);
+div
+(
+ setClass('sync-main-content'),
+ div
+ (
+ set::class('sync-content'),
+ div
+ (
+ setClass('alert light-pale flex items-center'),
+ icon(setClass('icon icon-info-sign icon-2x alert-icon'), 'check-sign'),
+ div
+ (
+ h3($lang->repo->notice->syncing),
+ div(setClass('sync-line')),
+ p($lang->repo->notice->syncedCount, span($version, set::id('commits')))
+ )
+ )
+ )
+);
+
+render();
diff --git a/module/repo/zen.php b/module/repo/zen.php
new file mode 100644
index 0000000000..c9e9bfaa36
--- /dev/null
+++ b/module/repo/zen.php
@@ -0,0 +1,406 @@
+
+ * @package repo
+ * @link https://www.zentao.net
+ */
+class repoZen extends repo
+{
+ /**
+ * 准备创建版本库的数据。
+ * Prepare create repo data.
+ *
+ * @param form $formData
+ * @param bool $isPipelineServer
+ * @access protected
+ * @return object|false
+ */
+ protected function prepareCreate(form $formData, bool $isPipelineServer): object|false
+ {
+ if(!$this->checkClient()) return false;
+ if(!$this->checkConnection()) return false;
+
+ $repo = $formData
+ ->setIf($isPipelineServer, 'password', $this->post->serviceToken)
+ ->setIf($this->post->SCM == 'Gitlab', 'path', '')
+ ->setIf($this->post->SCM == 'Gitlab', 'client', '')
+ ->setIf($this->post->SCM == 'Gitlab', 'extra', $this->post->serviceProject)
+ ->setIf($isPipelineServer, 'prefix', '')
+ ->setIf($this->post->SCM == 'Git', 'account', '')
+ ->setIf($this->post->SCM == 'Git', 'password', '')
+ ->setIf(in_array($this->post->SCM, array('Gitea', 'Gogs')), 'path', $_POST['path'])
+ ->setIf($this->post->acl != '', 'acl', json_encode($this->post->acl))
+ ->setIf($this->post->encrypt == 'base64', 'password', base64_encode($this->post->password))
+ ->skipSpecial('path,client,account,password')
+ ->setDefault('product', '')->join('product', ',')
+ ->setDefault('projects', '')->join('projects', ',')
+ ->get();
+
+ if($repo->SCM == 'Subversion')
+ {
+ $scm = $this->app->loadClass('scm');
+ $scm->setEngine($repo);
+ $info = $scm->info('');
+ $infoRoot = urldecode($info->root);
+ $repo->prefix = empty($infoRoot) ? '' : trim(str_ireplace($infoRoot, '', str_replace('\\', '/', $repo->path)), '/');
+ if($repo->prefix) $repo->prefix = '/' . $repo->prefix;
+ }
+ return $repo;
+ }
+
+ /**
+ * 检查svn、git客户端。
+ * Check svn/git client.
+ *
+ * @access public
+ * @return bool
+ */
+ protected function checkClient(): bool
+ {
+ if($this->post->SCM == 'Gitlab') return true;
+ if(!$this->config->features->checkClient) return true;
+
+ if(!$this->post->client)
+ {
+ dao::$errors['client'] = sprintf($this->lang->error->notempty, $this->lang->repo->client);
+ return false;
+ }
+
+ if(strpos($this->post->client, ' '))
+ {
+ dao::$errors['client'] = $this->lang->repo->error->clientPath;
+ return false;
+ }
+
+ $clientVersionFile = $this->session->clientVersionFile;
+ if(empty($clientVersionFile))
+ {
+ $clientVersionFile = $this->app->getLogRoot() . uniqid('version_') . '.log';
+
+ session_start();
+ $this->session->set('clientVersionFile', $clientVersionFile);
+ session_write_close();
+ }
+
+ if(file_exists($clientVersionFile)) return true;
+
+ $cmd = $this->post->client . " --version > $clientVersionFile";
+ dao::$errors['client'] = sprintf($this->lang->repo->error->safe, $clientVersionFile, $cmd);
+
+ return false;
+ }
+
+
+ /**
+ * 检查连接。
+ * Check connection
+ *
+ * @access public
+ * @return bool
+ */
+ protected function checkConnection(): bool
+ {
+ if(empty($_POST)) return false;
+
+ $scm = $this->post->SCM;
+ $client = $this->post->client;
+ $account = $this->post->account;
+ $password = $this->post->password;
+ $encoding = strtoupper($this->post->encoding);
+ $path = $this->post->path;
+ if($encoding != 'UTF8' and $encoding != 'UTF-8') $path = helper::convertEncoding($path, 'utf-8', $encoding);
+
+ if($scm == 'Subversion')
+ {
+ /* Get svn version. */
+ $versionCommand = "$client --version --quiet 2>&1";
+ exec($versionCommand, $versionOutput, $versionResult);
+ if($versionResult)
+ {
+ $message = sprintf($this->lang->repo->error->output, $versionCommand, $versionResult, implode("
", $versionOutput));
+ dao::$errors['client'] = $this->lang->repo->error->cmd . "
" . nl2br($message);
+ return false;
+ }
+ $svnVersion = end($versionOutput);
+
+ $path = '"' . str_replace(array('%3A', '%2F', '+'), array(':', '/', ' '), urlencode($path)) . '"';
+ if(stripos($path, 'https://') === 1 or stripos($path, 'svn://') === 1)
+ {
+ if(version_compare($svnVersion, '1.6', '<'))
+ {
+ dao::$errors['client'] = $this->lang->repo->error->version;
+ return false;
+ }
+
+ $command = "$client info --username $account --password $password --non-interactive --trust-server-cert-failures=cn-mismatch --trust-server-cert --no-auth-cache $path 2>&1";
+ if(version_compare($svnVersion, '1.9', '<')) $command = "$client info --username $account --password $password --non-interactive --trust-server-cert --no-auth-cache $path 2>&1";
+ }
+ elseif(stripos($path, 'file://') === 1)
+ {
+ $command = "$client info --non-interactive --no-auth-cache $path 2>&1";
+ }
+ else
+ {
+ $command = "$client info --username $account --password $password --non-interactive --no-auth-cache $path 2>&1";
+ }
+
+ exec($command, $output, $result);
+ if($result)
+ {
+ $message = sprintf($this->lang->repo->error->output, $command, $result, implode("
", $output));
+ if(stripos($message, 'Expected FS format between') !== false and strpos($message, 'found format') !== false)
+ {
+ dao::$errors['client'] = $this->lang->repo->error->clientVersion;
+ return false;
+ }
+ if(preg_match('/[^\:\/A-Za-z0-9_\-\'\"\.]/', $path))
+ {
+ dao::$errors['encoding'] = $this->lang->repo->error->encoding . "
" . nl2br($message);
+ return false;
+ }
+
+ dao::$errors['submit'] = $this->lang->repo->error->connect . "
" . nl2br($message);
+ return false;
+ }
+ }
+ elseif(in_array($scm, array('Gitea', 'Gogs')))
+ {
+ if($this->post->name != '' and $this->post->serviceProject != '')
+ {
+ $module = strtolower($scm);
+ $project = $this->loadModel($module)->apiGetSingleProject($this->post->serviceHost, $this->post->serviceProject);
+ if(isset($project->tokenCloneUrl))
+ {
+ $path = $this->app->getAppRoot() . 'www/data/repo/' . $this->post->name . '_' . $module;
+ if(!realpath($path))
+ {
+ $cmd = 'git clone --progress -v "' . $project->tokenCloneUrl . '" "' . $path . '" > "' . $this->app->getTmpRoot() . "log/clone.progress.$module.{$this->post->name}.log\" 2>&1 &";
+ if(PHP_OS == 'WINNT') $cmd = "start /b $cmd";
+ exec($cmd);
+ }
+ $_POST['path'] = $path;
+ }
+ else
+ {
+ dao::$errors['serviceProject'] = $this->lang->repo->error->noCloneAddr;
+ return false;
+ }
+ }
+ }
+ elseif($scm == 'Git')
+ {
+ if(!is_dir($path))
+ {
+ dao::$errors['path'] = sprintf($this->lang->repo->error->noFile, $path);
+ return false;
+ }
+
+ if(!chdir($path))
+ {
+ if(!is_executable($path))
+ {
+ dao::$errors['path'] = sprintf($this->lang->repo->error->noPriv, $path);
+ return false;
+ }
+ dao::$errors['path'] = $this->lang->repo->error->path;
+ return false;
+ }
+
+ $command = "$client tag 2>&1";
+ exec($command, $output, $result);
+ if($result)
+ {
+ dao::$errors['submit'] = $this->lang->repo->error->connect . "
" . sprintf($this->lang->repo->error->output, $command, $result, implode("
", $output));
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * 构建创建版本库页面数据。
+ * Build form fields for create repo.
+ *
+ * @param int $objectID
+ * @access protected
+ * @return void
+ */
+ protected function buildCreateForm(int $objectID): void
+ {
+ $repoID = $this->repo->saveState(0, $objectID);
+ $this->commonAction($repoID, $objectID);
+
+ $this->app->loadLang('action');
+
+ if($this->app->tab == 'project' or $this->app->tab == 'execution')
+ {
+ $products = $this->loadModel('product')->getProductPairsByProject($objectID);
+ }
+ else
+ {
+ $products = $this->loadModel('product')->getPairs('', 0, '', 'all');
+ }
+
+ $this->view->title = $this->lang->repo->common . $this->lang->colon . $this->lang->repo->create;
+ $this->view->groups = $this->loadModel('group')->getPairs();
+ $this->view->users = $this->loadModel('user')->getPairs('noletter|noempty|nodeleted|noclosed');
+ $this->view->products = $products;
+ $this->view->projects = $this->loadModel('product')->getProjectPairsByProductIDList(array_keys($products));
+ $this->view->relatedProjects = ($this->app->tab == 'project' or $this->app->tab == 'execution') ? array($objectID) : array();
+ $this->view->serviceHosts = $this->loadModel('gitlab')->getPairs();
+ $this->view->objectID = $objectID;
+
+ $this->display();
+ }
+
+ /**
+ * 准备批量创建版本库的数据。
+ * Prepare batch create repo data.
+ *
+ * @access protected
+ * @return array|false
+ */
+ protected function prepareBatchCreate(): array|false
+ {
+ $data = array();
+ foreach($_POST as $key => $val)
+ {
+ if(strpos($key, 'serviceProject') === 0)
+ {
+ $i = substr($key, 14);
+ if($this->post->{'product' . $i} == '') dao::$errors['product' . $i][] = sprintf($this->lang->error->notempty, $this->lang->repo->product);
+ if($this->post->{'name' . $i} == '') dao::$errors['name' . $i][] = sprintf($this->lang->error->notempty, $this->lang->repo->name);
+ if(dao::isError()) continue;
+
+ $data[] = array('serviceProject' => $this->post->{'serviceProject' . $i}, '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;
+
+ return $data;
+ }
+
+ /**
+ * 获取gitlab还没存在禅道的项目列表。
+ * Get gitlab not exist repos.
+ *
+ * @param object $gitlab
+ * @access protected
+ * @return array
+ */
+ protected function getGitlabNotExistRepos(object $gitlab): array
+ {
+ $repoList = array();
+ if(!empty($gitlab))
+ {
+ $repoList = $this->loadModel('gitlab')->apiGetProjects($gitlab->id);
+ $existRepoList = $this->dao->select('serviceProject,name')->from(TABLE_REPO)
+ ->where('SCM')->eq(ucfirst($gitlab->type))
+ ->andWhere('serviceHost')->eq($gitlab->id)
+ ->fetchPairs();
+ foreach($repoList as $key => $repo)
+ {
+ if(isset($existRepoList[$repo->id])) unset($repoList[$key]);
+ }
+ }
+ return $repoList;
+ }
+
+ /**
+ * 获取版本库文件列表信息。
+ * Get repo files info.
+ *
+ * @param object $repo
+ * @param string $path
+ * @param string $branchID
+ * @param int $refresh
+ * @param string $revision
+ * @param object $lastRevision
+ * @access protected
+ * @return array
+ */
+ protected function getFilesInfo(object $repo, string $path, string $branchID, int $refresh, string $revision, object $lastRevision): array
+ {
+ $cacheFile = $this->repo->getCacheFile($repo->id, $path, $branchID);
+ $cacheRefreshTime = isset($lastRevision->time) ? date('Y-m-d H:i', strtotime($lastRevision->time)) : date('Y-m-d H:i');
+ $this->scm->setEngine($repo);
+ if($refresh or !$cacheFile or !file_exists($cacheFile) or filemtime($cacheFile) < strtotime($cacheRefreshTime))
+ {
+ if($repo->SCM == 'Gitlab')
+ {
+ $infos = $this->repo->getFileList($repo, $branchID, $path);
+ }
+ else
+ {
+ $infos = $this->scm->ls($path, $revision);
+ $revisionList = array_column($infos, 'revision', 'revision');
+ $comments = $this->repo->getHistory($repo->id, $revisionList);
+ foreach($infos as $info)
+ {
+ if(isset($comments[$info->revision]))
+ {
+ $comment = $comments[$info->revision];
+ $info->comment = $comment->comment;
+ }
+ }
+ }
+ if($cacheFile) file_put_contents($cacheFile, serialize($infos), LOCK_EX);
+ }
+ else
+ {
+ $infos = unserialize(file_get_contents($cacheFile));
+ }
+
+ foreach($infos as $info)
+ {
+ $info->originalComment = $info->comment;
+ $info->comment = $this->repo->replaceCommentLink($info->comment);
+ }
+
+ return $infos;
+ }
+
+ /**
+ * 获取分支与tag下拉菜单组件配置。
+ * Get items of branch and tags menu.
+ *
+ * @param object $repo
+ * @param string $branchID
+ * @access protected
+ * @return array
+ */
+ protected function getBranchAndTagItems(object $repo, string $branchID): array
+ {
+ /* Set branch or tag for git. */
+ $branches = $tags = array();
+ if(!in_array($repo->SCM, $this->config->repo->gitTypeList)) return array();
+
+ $scm = $this->app->loadClass('scm');
+ $scm->setEngine($repo);
+ $branches = $scm->branch();
+ $initTags = $scm->tags('');
+ foreach($initTags as $tag) $tags[$tag] = $tag;
+
+ $selected = '';
+ $branchMenus = array();
+ $tagMenus = array();
+ foreach($branches as $branchName)
+ {
+ $selected = ($branchName == $branchID) ? $branchName : $selected;
+ $branchMenus[] = array('text' => $branchName, 'id' => $branchName, 'keys' => zget(common::convert2Pinyin(array($branchName)), $branchName, ''), 'url' => 'javascript:;');
+ }
+ foreach($tags as $tagName)
+ {
+ $selected = ($tagName == $branchID) ? $tagName : $selected;
+ $tagMenus[] = array('text' => $tagName, 'id' => $tagName, 'keys' => zget(common::convert2Pinyin(array($tagName)), $tagName, ''), 'url' => 'javascript:;');
+ }
+
+ return array('branchMenus' => $branchMenus, 'tagMenus' => $tagMenus, 'selected' => $selected);
+ }
+}
+