From 6dca989859eb4dc50cda59a6700b9d55d2c440e7 Mon Sep 17 00:00:00 2001 From: zhaoke Date: Mon, 31 Jul 2023 17:10:37 +0800 Subject: [PATCH] * Refactor repo pages. --- config/zentaopms.php | 3 + module/common/lang/de.php | 2 + module/common/lang/en.php | 2 + module/common/lang/fr.php | 2 + module/common/lang/zh-cn.php | 2 + module/repo/config.php | 20 +- module/repo/config/dtable.php | 222 +++++++++ module/repo/config/form.php | 19 + module/repo/control.php | 483 +++++++++---------- module/repo/css/ajaxgeteditorcontent.ui.css | 39 ++ module/repo/css/blame.ui.css | 1 + module/repo/css/browse.ui.css | 7 + module/repo/css/common.ui.css | 53 ++ module/repo/css/diff.ui.css | 18 + module/repo/css/import.ui.css | 3 + module/repo/css/log.ui.css | 1 + module/repo/css/maintain.ui.css | 1 + module/repo/css/monaco.ui.css | 7 + module/repo/js/ajaxgeteditorcontent.ui.js | 236 +++++++++ module/repo/js/browse.ui.js | 248 ++++++++++ module/repo/js/common.ui.js | 223 +++++++++ module/repo/js/create.ui.js | 107 ++++ module/repo/js/diff.ui.js | 245 ++++++++++ module/repo/js/edit.ui.js | 110 +++++ module/repo/js/import.ui.js | 46 ++ module/repo/js/linkbug.ui.js | 23 + module/repo/js/linkstory.ui.js | 23 + module/repo/js/linktask.ui.js | 23 + module/repo/js/log.ui.js | 106 ++++ module/repo/js/monaco.ui.js | 135 ++++++ module/repo/js/showsynccommit.ui.js | 15 + module/repo/lang/de.php | 8 +- module/repo/lang/en.php | 8 +- module/repo/lang/fr.php | 8 +- module/repo/lang/vi.php | 5 +- module/repo/lang/zh-cn.php | 8 +- module/repo/lang/zh-tw.php | 2 + module/repo/model.php | 370 +++++++------- module/repo/tao.php | 28 ++ module/repo/ui/ajaxgetdropmenu.html.php | 26 + module/repo/ui/ajaxgeteditorcontent.html.php | 159 ++++++ module/repo/ui/blame.html.php | 106 ++++ module/repo/ui/browse.html.php | 260 ++++++++++ module/repo/ui/create.html.php | 204 +++++++- module/repo/ui/diff.html.php | 76 +++ module/repo/ui/diffeditor.html.php | 95 ++++ module/repo/ui/edit.html.php | 212 ++++++++ module/repo/ui/import.html.php | 61 +++ module/repo/ui/linkbug.html.php | 60 +++ module/repo/ui/linkstory.html.php | 63 +++ module/repo/ui/linktask.html.php | 55 +++ module/repo/ui/log.html.php | 83 ++++ module/repo/ui/maintain.html.php | 96 ++++ module/repo/ui/monaco.html.php | 110 +++++ module/repo/ui/showsynccommit.html.php | 49 ++ module/repo/zen.php | 406 ++++++++++++++++ 56 files changed, 4542 insertions(+), 441 deletions(-) create mode 100644 module/repo/config/dtable.php create mode 100644 module/repo/config/form.php create mode 100644 module/repo/css/ajaxgeteditorcontent.ui.css create mode 100644 module/repo/css/blame.ui.css create mode 100644 module/repo/css/browse.ui.css create mode 100644 module/repo/css/common.ui.css create mode 100644 module/repo/css/diff.ui.css create mode 100644 module/repo/css/import.ui.css create mode 100644 module/repo/css/log.ui.css create mode 100644 module/repo/css/maintain.ui.css create mode 100644 module/repo/css/monaco.ui.css create mode 100644 module/repo/js/ajaxgeteditorcontent.ui.js create mode 100644 module/repo/js/browse.ui.js create mode 100644 module/repo/js/common.ui.js create mode 100644 module/repo/js/create.ui.js create mode 100644 module/repo/js/diff.ui.js create mode 100644 module/repo/js/edit.ui.js create mode 100644 module/repo/js/import.ui.js create mode 100644 module/repo/js/linkbug.ui.js create mode 100644 module/repo/js/linkstory.ui.js create mode 100644 module/repo/js/linktask.ui.js create mode 100644 module/repo/js/log.ui.js create mode 100644 module/repo/js/monaco.ui.js create mode 100644 module/repo/js/showsynccommit.ui.js create mode 100644 module/repo/tao.php create mode 100644 module/repo/ui/ajaxgetdropmenu.html.php create mode 100644 module/repo/ui/ajaxgeteditorcontent.html.php create mode 100644 module/repo/ui/blame.html.php create mode 100644 module/repo/ui/browse.html.php create mode 100644 module/repo/ui/diff.html.php create mode 100644 module/repo/ui/diffeditor.html.php create mode 100644 module/repo/ui/edit.html.php create mode 100644 module/repo/ui/import.html.php create mode 100644 module/repo/ui/linkbug.html.php create mode 100644 module/repo/ui/linkstory.html.php create mode 100644 module/repo/ui/linktask.html.php create mode 100644 module/repo/ui/log.html.php create mode 100644 module/repo/ui/maintain.html.php create mode 100644 module/repo/ui/monaco.html.php create mode 100644 module/repo/ui/showsynccommit.html.php create mode 100644 module/repo/zen.php 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 = ``; + + 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:'' + row.data.comment + ''}; + + 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:'' + row.data.comment + '', 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 = ''; - - 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); + } +} +