mirror of
https://github.com/goharbor/harbor.git
synced 2025-02-17 04:11:24 +01:00
Merge remote-tracking branch 'upstream/new-ui-with-sync-image' into new-ui-with-sync-image
This commit is contained in:
commit
d6abff410f
7
.gitignore
vendored
7
.gitignore
vendored
@ -6,9 +6,4 @@ Deploy/config/db/env
|
||||
Deploy/config/jobservice/env
|
||||
ui/ui
|
||||
*.pyc
|
||||
jobservice/*.sql
|
||||
jobservice/*.sh
|
||||
jobservice/*.json
|
||||
jobservice/jobservice
|
||||
|
||||
|
||||
jobservice/test
|
||||
|
@ -1,23 +0,0 @@
|
||||
/*
|
||||
Copyright (c) 2016 VMware, Inc. All Rights Reserved.
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package error
|
||||
|
||||
// RetryChecker checks whether a job should retry if encounters an error
|
||||
type RetryChecker interface {
|
||||
// Retry : if the error can be disappear after retrying the job, Retry
|
||||
// returns true
|
||||
Retry(error) bool
|
||||
}
|
@ -74,6 +74,17 @@ func (d *Deleter) Exit() error {
|
||||
|
||||
// Enter deletes repository or tags
|
||||
func (d *Deleter) Enter() (string, error) {
|
||||
state, err := d.enter()
|
||||
if err != nil && retry(err) {
|
||||
d.logger.Info("waiting for retrying...")
|
||||
return models.JobRetrying, nil
|
||||
}
|
||||
|
||||
return state, err
|
||||
|
||||
}
|
||||
|
||||
func (d *Deleter) enter() (string, error) {
|
||||
|
||||
if len(d.tags) == 0 {
|
||||
tags, err := d.dstClient.ListTag()
|
||||
|
@ -19,12 +19,10 @@ import (
|
||||
"net"
|
||||
)
|
||||
|
||||
// ReplicaRetryChecker determines whether a job should be retried when an error occurred
|
||||
type ReplicaRetryChecker struct {
|
||||
}
|
||||
|
||||
// Retry ...
|
||||
func (r *ReplicaRetryChecker) Retry(err error) bool {
|
||||
func retry(err error) bool {
|
||||
if err == nil {
|
||||
return false
|
||||
}
|
||||
return isTemporary(err)
|
||||
}
|
||||
|
||||
|
@ -152,6 +152,17 @@ type Checker struct {
|
||||
// Enter check existence of project, if it does not exist, create it,
|
||||
// if it exists, check whether the user has write privilege to it.
|
||||
func (c *Checker) Enter() (string, error) {
|
||||
state, err := c.enter()
|
||||
if err != nil && retry(err) {
|
||||
c.logger.Info("waiting for retrying...")
|
||||
return models.JobRetrying, nil
|
||||
}
|
||||
|
||||
return state, err
|
||||
|
||||
}
|
||||
|
||||
func (c *Checker) enter() (string, error) {
|
||||
enter:
|
||||
exist, canWrite, err := c.projectExist()
|
||||
if err != nil {
|
||||
@ -316,6 +327,17 @@ type ManifestPuller struct {
|
||||
|
||||
// Enter pulls manifest of a tag and checks if all blobs exist in the destination registry
|
||||
func (m *ManifestPuller) Enter() (string, error) {
|
||||
state, err := m.enter()
|
||||
if err != nil && retry(err) {
|
||||
m.logger.Info("waiting for retrying...")
|
||||
return models.JobRetrying, nil
|
||||
}
|
||||
|
||||
return state, err
|
||||
|
||||
}
|
||||
|
||||
func (m *ManifestPuller) enter() (string, error) {
|
||||
if len(m.tags) == 0 {
|
||||
m.logger.Infof("no tag needs to be replicated, next state is \"finished\"")
|
||||
return models.JobFinished, nil
|
||||
@ -389,6 +411,17 @@ type BlobTransfer struct {
|
||||
|
||||
// Enter pulls blobs and then pushs them to destination registry.
|
||||
func (b *BlobTransfer) Enter() (string, error) {
|
||||
state, err := b.enter()
|
||||
if err != nil && retry(err) {
|
||||
b.logger.Info("waiting for retrying...")
|
||||
return models.JobRetrying, nil
|
||||
}
|
||||
|
||||
return state, err
|
||||
|
||||
}
|
||||
|
||||
func (b *BlobTransfer) enter() (string, error) {
|
||||
name := b.repository
|
||||
tag := b.tags[0]
|
||||
for _, blob := range b.blobs {
|
||||
@ -417,6 +450,17 @@ type ManifestPusher struct {
|
||||
// exists, pushs it to destination registry. The checking operation is to avoid
|
||||
// the situation that the tag is deleted during the blobs transfering
|
||||
func (m *ManifestPusher) Enter() (string, error) {
|
||||
state, err := m.enter()
|
||||
if err != nil && retry(err) {
|
||||
m.logger.Info("waiting for retrying...")
|
||||
return models.JobRetrying, nil
|
||||
}
|
||||
|
||||
return state, err
|
||||
|
||||
}
|
||||
|
||||
func (m *ManifestPusher) enter() (string, error) {
|
||||
name := m.repository
|
||||
tag := m.tags[0]
|
||||
_, exist, err := m.srcClient.ManifestExist(tag)
|
||||
|
@ -1,23 +1,36 @@
|
||||
/*
|
||||
Copyright (c) 2016 VMware, Inc. All Rights Reserved.
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
Copyright (c) 2016 VMware, Inc. All Rights Reserved.
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package job
|
||||
|
||||
import (
|
||||
"github.com/vmware/harbor/utils/log"
|
||||
"time"
|
||||
)
|
||||
|
||||
var jobQueue = make(chan int64)
|
||||
|
||||
// Schedule put a job id into job queue.
|
||||
func Schedule(jobID int64) {
|
||||
jobQueue <- jobID
|
||||
}
|
||||
|
||||
// Reschedule is called by statemachine to retry a job
|
||||
func Reschedule(jobID int64) {
|
||||
log.Debugf("Job %d will be rescheduled in 5 minutes", jobID)
|
||||
time.Sleep(5 * time.Minute)
|
||||
log.Debugf("Rescheduling job %d", jobID)
|
||||
Schedule(jobID)
|
||||
}
|
||||
|
@ -1,16 +1,16 @@
|
||||
/*
|
||||
Copyright (c) 2016 VMware, Inc. All Rights Reserved.
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
Copyright (c) 2016 VMware, Inc. All Rights Reserved.
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package job
|
||||
@ -33,25 +33,10 @@ type StateHandler interface {
|
||||
Exit() error
|
||||
}
|
||||
|
||||
// DummyHandler is the default implementation of StateHander interface, which has empty Enter and Exit methods.
|
||||
type DummyHandler struct {
|
||||
JobID int64
|
||||
}
|
||||
|
||||
// Enter ...
|
||||
func (dh DummyHandler) Enter() (string, error) {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
// Exit ...
|
||||
func (dh DummyHandler) Exit() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// StatusUpdater implements the StateHandler interface which updates the status of a job in DB when the job enters
|
||||
// a status.
|
||||
type StatusUpdater struct {
|
||||
DummyHandler
|
||||
JobID int64
|
||||
State string
|
||||
}
|
||||
|
||||
@ -69,9 +54,34 @@ func (su StatusUpdater) Enter() (string, error) {
|
||||
return next, err
|
||||
}
|
||||
|
||||
// Exit ...
|
||||
func (su StatusUpdater) Exit() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Retry handles a special "retrying" in which case it will update the status in DB and reschedule the job
|
||||
// via scheduler
|
||||
type Retry struct {
|
||||
JobID int64
|
||||
}
|
||||
|
||||
// Enter ...
|
||||
func (jr Retry) Enter() (string, error) {
|
||||
err := dao.UpdateRepJobStatus(jr.JobID, models.JobRetrying)
|
||||
if err != nil {
|
||||
log.Errorf("Failed to update state of job :%d to Retrying, error: %v", jr.JobID, err)
|
||||
}
|
||||
go Reschedule(jr.JobID)
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Exit ...
|
||||
func (jr Retry) Exit() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// ImgPuller was for testing
|
||||
type ImgPuller struct {
|
||||
DummyHandler
|
||||
img string
|
||||
logger *log.Logger
|
||||
}
|
||||
@ -80,13 +90,17 @@ type ImgPuller struct {
|
||||
func (ip ImgPuller) Enter() (string, error) {
|
||||
ip.logger.Infof("I'm pretending to pull img:%s, then sleep 30s", ip.img)
|
||||
time.Sleep(30 * time.Second)
|
||||
ip.logger.Infof("wake up from sleep....")
|
||||
return "push-img", nil
|
||||
ip.logger.Infof("wake up from sleep.... testing retry")
|
||||
return models.JobRetrying, nil
|
||||
}
|
||||
|
||||
// Exit ...
|
||||
func (ip ImgPuller) Exit() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// ImgPusher is a statehandler for testing
|
||||
type ImgPusher struct {
|
||||
DummyHandler
|
||||
targetURL string
|
||||
logger *log.Logger
|
||||
}
|
||||
@ -95,6 +109,11 @@ type ImgPusher struct {
|
||||
func (ip ImgPusher) Enter() (string, error) {
|
||||
ip.logger.Infof("I'm pretending to push img to:%s, then sleep 30s", ip.targetURL)
|
||||
time.Sleep(30 * time.Second)
|
||||
ip.logger.Infof("wake up from sleep....")
|
||||
return models.JobContinue, nil
|
||||
ip.logger.Infof("wake up from sleep.... testing retry")
|
||||
return models.JobRetrying, nil
|
||||
}
|
||||
|
||||
// Exit ...
|
||||
func (ip ImgPusher) Exit() error {
|
||||
return nil
|
||||
}
|
||||
|
@ -179,6 +179,7 @@ func (sm *SM) Init() {
|
||||
models.JobError: struct{}{},
|
||||
models.JobStopped: struct{}{},
|
||||
models.JobCanceled: struct{}{},
|
||||
models.JobRetrying: struct{}{},
|
||||
}
|
||||
}
|
||||
|
||||
@ -243,9 +244,11 @@ func (sm *SM) Reset(jid int64) error {
|
||||
sm.Transitions = make(map[string]map[string]struct{})
|
||||
sm.CurrentState = models.JobPending
|
||||
|
||||
sm.AddTransition(models.JobPending, models.JobRunning, StatusUpdater{DummyHandler{JobID: sm.JobID}, models.JobRunning})
|
||||
sm.Handlers[models.JobError] = StatusUpdater{DummyHandler{JobID: sm.JobID}, models.JobError}
|
||||
sm.Handlers[models.JobStopped] = StatusUpdater{DummyHandler{JobID: sm.JobID}, models.JobStopped}
|
||||
sm.AddTransition(models.JobPending, models.JobRunning, StatusUpdater{sm.JobID, models.JobRunning})
|
||||
sm.AddTransition(models.JobRetrying, models.JobRunning, StatusUpdater{sm.JobID, models.JobRunning})
|
||||
sm.Handlers[models.JobError] = StatusUpdater{sm.JobID, models.JobError}
|
||||
sm.Handlers[models.JobStopped] = StatusUpdater{sm.JobID, models.JobStopped}
|
||||
sm.Handlers[models.JobRetrying] = Retry{sm.JobID}
|
||||
|
||||
switch sm.Parms.Operation {
|
||||
case models.RepOpTransfer:
|
||||
@ -259,6 +262,12 @@ func (sm *SM) Reset(jid int64) error {
|
||||
return err
|
||||
}
|
||||
|
||||
//for testing onlly
|
||||
func addTestTransition(sm *SM) error {
|
||||
sm.AddTransition(models.JobRunning, "pull-img", ImgPuller{img: sm.Parms.Repository, logger: sm.Logger})
|
||||
return nil
|
||||
}
|
||||
|
||||
func addImgTransferTransition(sm *SM) error {
|
||||
base, err := replication.InitBaseHandler(sm.Parms.Repository, sm.Parms.LocalRegURL, config.UISecret(),
|
||||
sm.Parms.TargetURL, sm.Parms.TargetUsername, sm.Parms.TargetPassword,
|
||||
@ -269,7 +278,7 @@ func addImgTransferTransition(sm *SM) error {
|
||||
sm.AddTransition(models.JobRunning, replication.StateCheck, &replication.Checker{BaseHandler: base})
|
||||
sm.AddTransition(replication.StateCheck, replication.StatePullManifest, &replication.ManifestPuller{BaseHandler: base})
|
||||
sm.AddTransition(replication.StatePullManifest, replication.StateTransferBlob, &replication.BlobTransfer{BaseHandler: base})
|
||||
sm.AddTransition(replication.StatePullManifest, models.JobFinished, &StatusUpdater{DummyHandler{JobID: sm.JobID}, models.JobFinished})
|
||||
sm.AddTransition(replication.StatePullManifest, models.JobFinished, &StatusUpdater{sm.JobID, models.JobFinished})
|
||||
sm.AddTransition(replication.StateTransferBlob, replication.StatePushManifest, &replication.ManifestPusher{BaseHandler: base})
|
||||
sm.AddTransition(replication.StatePushManifest, replication.StatePullManifest, &replication.ManifestPuller{BaseHandler: base})
|
||||
return nil
|
||||
@ -283,7 +292,7 @@ func addImgDeleteTransition(sm *SM) error {
|
||||
}
|
||||
|
||||
sm.AddTransition(models.JobRunning, replication.StateDelete, deleter)
|
||||
sm.AddTransition(replication.StateDelete, models.JobFinished, &StatusUpdater{DummyHandler{JobID: sm.JobID}, models.JobFinished})
|
||||
sm.AddTransition(replication.StateDelete, models.JobFinished, &StatusUpdater{sm.JobID, models.JobFinished})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -38,13 +38,13 @@ func resumeJobs() {
|
||||
if err != nil {
|
||||
log.Warningf("Failed to reset all running jobs to pending, error: %v", err)
|
||||
}
|
||||
jobs, err := dao.GetRepJobByStatus(models.JobPending)
|
||||
jobs, err := dao.GetRepJobByStatus(models.JobPending, models.JobRetrying)
|
||||
if err == nil {
|
||||
for _, j := range jobs {
|
||||
log.Debugf("Rescheduling job: %d", j.ID)
|
||||
log.Debugf("Resuming job: %d", j.ID)
|
||||
job.Schedule(j.ID)
|
||||
}
|
||||
} else {
|
||||
log.Warningf("Failed to get pending jobs, error: %v", err)
|
||||
log.Warningf("Failed to jobs to resume, error: %v", err)
|
||||
}
|
||||
}
|
||||
|
@ -124,4 +124,8 @@ nav .container-custom {
|
||||
height: 1.2em;
|
||||
margin-bottom: 2px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
|
||||
a:hover, a:visited, a:link {
|
||||
text-decoration: none;
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
<a href="javascript:void(0)" role="button" tab-index="0"
|
||||
data-trigger="focus" data-toggle="popover" data-placement="right"
|
||||
data-title="//vm.helpTitle//">
|
||||
<span class="glyphicon glyphicon-info-sign"></span>
|
||||
</a>
|
@ -0,0 +1,34 @@
|
||||
(function() {
|
||||
|
||||
'use strict';
|
||||
|
||||
angular
|
||||
.module('harbor.inline.help')
|
||||
.directive('inlineHelp', inlineHelp);
|
||||
function InlineHelpController() {
|
||||
var vm = this;
|
||||
}
|
||||
function inlineHelp() {
|
||||
var directive = {
|
||||
'restrict': 'E',
|
||||
'templateUrl': '/static/resources/js/components/inline-help/inline-help.directive.html',
|
||||
'scope': {
|
||||
'helpTitle': '@',
|
||||
'content': '@'
|
||||
},
|
||||
'link': link,
|
||||
'controller': InlineHelpController,
|
||||
'controllerAs': 'vm',
|
||||
'bindToController': true
|
||||
};
|
||||
return directive;
|
||||
function link(scope, element, attr, ctrl) {
|
||||
element.popover({
|
||||
'title': ctrl.helpTitle,
|
||||
'content': ctrl.content,
|
||||
'html': true
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
})();
|
@ -0,0 +1,8 @@
|
||||
(function() {
|
||||
|
||||
'use strict';
|
||||
|
||||
angular
|
||||
.module('harbor.inline.help', []);
|
||||
|
||||
})();
|
@ -45,7 +45,7 @@
|
||||
function optionalMenu() {
|
||||
var directive = {
|
||||
'restrict': 'E',
|
||||
'templateUrl': '/optional_menu',
|
||||
'templateUrl': '/optional_menu?timestamp=' + new Date().getTime(),
|
||||
'scope': true,
|
||||
'controller': OptionalMenuController,
|
||||
'controllerAs': 'vm',
|
||||
|
@ -12,9 +12,10 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="roleIdList">// 'role' | tr //:</label>
|
||||
<label for="roleIdList">// 'role' | tr //:</label>
|
||||
<inline-help help-title="//'inline_help_role_title' | tr//" content="//'inline_help_role' | tr//"></inline-help>
|
||||
<span ng-repeat="role in vm.roles">
|
||||
<input type="radio" name="role" ng-model="vm.optRole" value="//role.id//"> //role.name//
|
||||
<input type="radio" name="role" ng-model="vm.optRole" value="//role.id//"> //role.name | tr//
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -9,15 +9,15 @@
|
||||
|
||||
function roles() {
|
||||
return [
|
||||
{'id': '1', 'name': 'Project Admin', 'roleName': 'projectAdmin'},
|
||||
{'id': '2', 'name': 'Developer', 'roleName': 'developer'},
|
||||
{'id': '3', 'name': 'Guest', 'roleName': 'guest'}
|
||||
{'id': '1', 'name': 'project_admin', 'roleName': 'projectAdmin'},
|
||||
{'id': '2', 'name': 'developer', 'roleName': 'developer'},
|
||||
{'id': '3', 'name': 'guest', 'roleName': 'guest'}
|
||||
];
|
||||
}
|
||||
|
||||
getRole.$inject = ['roles'];
|
||||
getRole.$inject = ['roles', '$filter', 'trFilter'];
|
||||
|
||||
function getRole(roles) {
|
||||
function getRole(roles, $filter, trFilter) {
|
||||
var r = roles();
|
||||
return get;
|
||||
function get(query) {
|
||||
|
@ -1,5 +1,5 @@
|
||||
<ng-switch on="vm.editMode">
|
||||
<span ng-switch-default>//vm.currentRole.name//</span>
|
||||
<select class="form-control" style="width: auto; height: auto; padding: 0;" ng-switch-when="true" ng-model="vm.currentRole" ng-options="role as role.name for role in vm.roles track by role.roleName" ng-change="vm.selectRole(vm.currentRole)">
|
||||
<span ng-switch-default>//vm.currentRole.name | tr//</span>
|
||||
<select class="form-control" style="width: auto; height: auto; padding: 0;" ng-switch-when="true" ng-model="vm.currentRole" ng-options="role as (role.name | tr) for role in vm.roles track by role.roleName" ng-change="vm.selectRole(vm.currentRole)">
|
||||
</select>
|
||||
</ng-switch>
|
@ -13,7 +13,8 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group" style="margin-top: 5px;">
|
||||
<input type="checkbox" ng-model="vm.isPublic"> // 'public' | tr //
|
||||
<input type="checkbox" ng-model="vm.isPublic"> // 'public' | tr //
|
||||
<inline-help help-title="// 'inline_help_publicity_title' | tr //" content="// 'inline_help_publicity' | tr //"></inline-help>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xs-2 col-md-2">
|
||||
|
@ -409,7 +409,7 @@
|
||||
if(!ctrl.toggleErrorMessage) {
|
||||
element.find('#createPolicyModal').modal('hide');
|
||||
}
|
||||
}, 50);
|
||||
}, 150);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -21,10 +21,10 @@
|
||||
return {
|
||||
'responseError': function(rejection) {
|
||||
var pathname = $window.location.pathname;
|
||||
var exclusions = ['/', '/search', '/reset_password', '/sign_up', '/forgot_password'];
|
||||
var exclusion = ['/', '/search', '/reset_password', '/sign_up', '/forgot_password', '/repository'];
|
||||
var isExcluded = false;
|
||||
for(var i in exclusions) {
|
||||
if(exclusions[i] === pathname) {
|
||||
for(var i in exclusion) {
|
||||
if(exclusion[i] === pathname) {
|
||||
isExcluded = true;
|
||||
break;
|
||||
}
|
||||
|
@ -44,6 +44,7 @@
|
||||
'harbor.validator',
|
||||
'harbor.replication',
|
||||
'harbor.system.management',
|
||||
'harbor.loading.progress'
|
||||
'harbor.loading.progress',
|
||||
'harbor.inline.help'
|
||||
]);
|
||||
})();
|
@ -23,7 +23,7 @@
|
||||
function navigationDetails() {
|
||||
var directive = {
|
||||
restrict: 'E',
|
||||
templateUrl: '/navigation_detail',
|
||||
templateUrl: '/navigation_detail?timestamp=' + new Date().getTime(),
|
||||
link: link,
|
||||
scope: {
|
||||
'target': '='
|
||||
|
@ -16,7 +16,7 @@
|
||||
function navigationHeader() {
|
||||
var directive = {
|
||||
restrict: 'E',
|
||||
templateUrl: '/navigation_header',
|
||||
templateUrl: '/navigation_header?timestamp=' + new Date().getTime(),
|
||||
link: link,
|
||||
scope: true,
|
||||
controller: NavigationHeaderController,
|
||||
|
@ -235,5 +235,14 @@ var locale_messages = {
|
||||
'failed_to_delete_destination': 'Failed to delete destination.',
|
||||
'failed_to_create_destination': 'Failed to create destination.',
|
||||
'failed_to_update_destination': 'Failed to update destination.',
|
||||
'project_admin': 'Project Admin',
|
||||
'developer': 'Developer',
|
||||
'guest': 'Guest',
|
||||
'inline_help_role_title': '<strong>The Definitions of Roles</strong>',
|
||||
'inline_help_role': '<strong>Project Admin</strong>: When creating a new project, you will be assigned the "ProjectAdmin" role to the project. Besides read-write privileges, the "ProjectAdmin" also has some management privileges, such as adding and removing members.<br/>' +
|
||||
'<strong>Developer</strong>: Developer has read and write privileges for a project.<br/>' +
|
||||
'<strong>Guest</strong>: Guest has read-only privilege for a specified project.',
|
||||
'inline_help_publicity_title': '<strong>Publicity of Project</strong>',
|
||||
'inline_help_publicity': 'Setting the project as public.'
|
||||
|
||||
};
|
@ -234,5 +234,14 @@ var locale_messages = {
|
||||
'failed_to_update_replication_policy': '修改复制策略失败。',
|
||||
'failed_to_delete_destination': '删除目标失败。',
|
||||
'failed_to_create_destination': '创建目标失败。',
|
||||
'failed_to_update_destination': '修改目标失败。'
|
||||
'failed_to_update_destination': '修改目标失败。',
|
||||
'project_admin': '项目管理员',
|
||||
'developer': '开发人员',
|
||||
'guest': '来宾用户',
|
||||
'inline_help_role_title': '<strong>角色定义</strong>',
|
||||
'inline_help_role': '<strong>项目管理员</strong>: 当创建一个新项目后,您将被指派一个“项目管理员”角色。除了具备读/写权限外,“项目管理员”还拥有添加、删除其他项目成员的管理权限。<br/>'+
|
||||
'<strong>开发人员</strong>: “开发人员” 拥有一个项目的读/写权限。<br/>' +
|
||||
'<strong>来宾用户</strong>: “来宾用户”拥有特定项目的只读权限。',
|
||||
'inline_help_publicity_title': '<strong>公开项目</strong>',
|
||||
'inline_help_publicity': '设置该项目为公开。'
|
||||
};
|
@ -237,4 +237,7 @@
|
||||
<script src="/static/resources/js/components/top-repository/top-repository.directive.js"></script>
|
||||
|
||||
<script src="/static/resources/js/components/loading-progress/loading-progress.module.js"></script>
|
||||
<script src="/static/resources/js/components/loading-progress/loading-progress.directive.js"></script>
|
||||
<script src="/static/resources/js/components/loading-progress/loading-progress.directive.js"></script>
|
||||
|
||||
<script src="/static/resources/js/components/inline-help/inline-help.module.js"></script>
|
||||
<script src="/static/resources/js/components/inline-help/inline-help.directive.js"></script>
|
Loading…
Reference in New Issue
Block a user