mirror of
https://github.com/goharbor/harbor.git
synced 2025-02-21 06:11:45 +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
|
Deploy/config/jobservice/env
|
||||||
ui/ui
|
ui/ui
|
||||||
*.pyc
|
*.pyc
|
||||||
jobservice/*.sql
|
jobservice/test
|
||||||
jobservice/*.sh
|
|
||||||
jobservice/*.json
|
|
||||||
jobservice/jobservice
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -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
|
// Enter deletes repository or tags
|
||||||
func (d *Deleter) Enter() (string, error) {
|
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 {
|
if len(d.tags) == 0 {
|
||||||
tags, err := d.dstClient.ListTag()
|
tags, err := d.dstClient.ListTag()
|
||||||
|
@ -19,12 +19,10 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ReplicaRetryChecker determines whether a job should be retried when an error occurred
|
func retry(err error) bool {
|
||||||
type ReplicaRetryChecker struct {
|
if err == nil {
|
||||||
}
|
return false
|
||||||
|
}
|
||||||
// Retry ...
|
|
||||||
func (r *ReplicaRetryChecker) Retry(err error) bool {
|
|
||||||
return isTemporary(err)
|
return isTemporary(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -152,6 +152,17 @@ type Checker struct {
|
|||||||
// Enter check existence of project, if it does not exist, create it,
|
// Enter check existence of project, if it does not exist, create it,
|
||||||
// if it exists, check whether the user has write privilege to it.
|
// if it exists, check whether the user has write privilege to it.
|
||||||
func (c *Checker) Enter() (string, error) {
|
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:
|
enter:
|
||||||
exist, canWrite, err := c.projectExist()
|
exist, canWrite, err := c.projectExist()
|
||||||
if err != nil {
|
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
|
// Enter pulls manifest of a tag and checks if all blobs exist in the destination registry
|
||||||
func (m *ManifestPuller) Enter() (string, error) {
|
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 {
|
if len(m.tags) == 0 {
|
||||||
m.logger.Infof("no tag needs to be replicated, next state is \"finished\"")
|
m.logger.Infof("no tag needs to be replicated, next state is \"finished\"")
|
||||||
return models.JobFinished, nil
|
return models.JobFinished, nil
|
||||||
@ -389,6 +411,17 @@ type BlobTransfer struct {
|
|||||||
|
|
||||||
// Enter pulls blobs and then pushs them to destination registry.
|
// Enter pulls blobs and then pushs them to destination registry.
|
||||||
func (b *BlobTransfer) Enter() (string, error) {
|
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
|
name := b.repository
|
||||||
tag := b.tags[0]
|
tag := b.tags[0]
|
||||||
for _, blob := range b.blobs {
|
for _, blob := range b.blobs {
|
||||||
@ -417,6 +450,17 @@ type ManifestPusher struct {
|
|||||||
// exists, pushs it to destination registry. The checking operation is to avoid
|
// exists, pushs it to destination registry. The checking operation is to avoid
|
||||||
// the situation that the tag is deleted during the blobs transfering
|
// the situation that the tag is deleted during the blobs transfering
|
||||||
func (m *ManifestPusher) Enter() (string, error) {
|
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
|
name := m.repository
|
||||||
tag := m.tags[0]
|
tag := m.tags[0]
|
||||||
_, exist, err := m.srcClient.ManifestExist(tag)
|
_, exist, err := m.srcClient.ManifestExist(tag)
|
||||||
|
@ -15,9 +15,22 @@
|
|||||||
|
|
||||||
package job
|
package job
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/vmware/harbor/utils/log"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
var jobQueue = make(chan int64)
|
var jobQueue = make(chan int64)
|
||||||
|
|
||||||
// Schedule put a job id into job queue.
|
// Schedule put a job id into job queue.
|
||||||
func Schedule(jobID int64) {
|
func Schedule(jobID int64) {
|
||||||
jobQueue <- jobID
|
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)
|
||||||
|
}
|
||||||
|
@ -33,25 +33,10 @@ type StateHandler interface {
|
|||||||
Exit() error
|
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
|
// StatusUpdater implements the StateHandler interface which updates the status of a job in DB when the job enters
|
||||||
// a status.
|
// a status.
|
||||||
type StatusUpdater struct {
|
type StatusUpdater struct {
|
||||||
DummyHandler
|
JobID int64
|
||||||
State string
|
State string
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -69,9 +54,34 @@ func (su StatusUpdater) Enter() (string, error) {
|
|||||||
return next, err
|
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
|
// ImgPuller was for testing
|
||||||
type ImgPuller struct {
|
type ImgPuller struct {
|
||||||
DummyHandler
|
|
||||||
img string
|
img string
|
||||||
logger *log.Logger
|
logger *log.Logger
|
||||||
}
|
}
|
||||||
@ -80,13 +90,17 @@ type ImgPuller struct {
|
|||||||
func (ip ImgPuller) Enter() (string, error) {
|
func (ip ImgPuller) Enter() (string, error) {
|
||||||
ip.logger.Infof("I'm pretending to pull img:%s, then sleep 30s", ip.img)
|
ip.logger.Infof("I'm pretending to pull img:%s, then sleep 30s", ip.img)
|
||||||
time.Sleep(30 * time.Second)
|
time.Sleep(30 * time.Second)
|
||||||
ip.logger.Infof("wake up from sleep....")
|
ip.logger.Infof("wake up from sleep.... testing retry")
|
||||||
return "push-img", nil
|
return models.JobRetrying, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exit ...
|
||||||
|
func (ip ImgPuller) Exit() error {
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ImgPusher is a statehandler for testing
|
// ImgPusher is a statehandler for testing
|
||||||
type ImgPusher struct {
|
type ImgPusher struct {
|
||||||
DummyHandler
|
|
||||||
targetURL string
|
targetURL string
|
||||||
logger *log.Logger
|
logger *log.Logger
|
||||||
}
|
}
|
||||||
@ -95,6 +109,11 @@ type ImgPusher struct {
|
|||||||
func (ip ImgPusher) Enter() (string, error) {
|
func (ip ImgPusher) Enter() (string, error) {
|
||||||
ip.logger.Infof("I'm pretending to push img to:%s, then sleep 30s", ip.targetURL)
|
ip.logger.Infof("I'm pretending to push img to:%s, then sleep 30s", ip.targetURL)
|
||||||
time.Sleep(30 * time.Second)
|
time.Sleep(30 * time.Second)
|
||||||
ip.logger.Infof("wake up from sleep....")
|
ip.logger.Infof("wake up from sleep.... testing retry")
|
||||||
return models.JobContinue, nil
|
return models.JobRetrying, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exit ...
|
||||||
|
func (ip ImgPusher) Exit() error {
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -179,6 +179,7 @@ func (sm *SM) Init() {
|
|||||||
models.JobError: struct{}{},
|
models.JobError: struct{}{},
|
||||||
models.JobStopped: struct{}{},
|
models.JobStopped: struct{}{},
|
||||||
models.JobCanceled: 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.Transitions = make(map[string]map[string]struct{})
|
||||||
sm.CurrentState = models.JobPending
|
sm.CurrentState = models.JobPending
|
||||||
|
|
||||||
sm.AddTransition(models.JobPending, models.JobRunning, StatusUpdater{DummyHandler{JobID: sm.JobID}, models.JobRunning})
|
sm.AddTransition(models.JobPending, models.JobRunning, StatusUpdater{sm.JobID, models.JobRunning})
|
||||||
sm.Handlers[models.JobError] = StatusUpdater{DummyHandler{JobID: sm.JobID}, models.JobError}
|
sm.AddTransition(models.JobRetrying, models.JobRunning, StatusUpdater{sm.JobID, models.JobRunning})
|
||||||
sm.Handlers[models.JobStopped] = StatusUpdater{DummyHandler{JobID: sm.JobID}, models.JobStopped}
|
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 {
|
switch sm.Parms.Operation {
|
||||||
case models.RepOpTransfer:
|
case models.RepOpTransfer:
|
||||||
@ -259,6 +262,12 @@ func (sm *SM) Reset(jid int64) error {
|
|||||||
return err
|
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 {
|
func addImgTransferTransition(sm *SM) error {
|
||||||
base, err := replication.InitBaseHandler(sm.Parms.Repository, sm.Parms.LocalRegURL, config.UISecret(),
|
base, err := replication.InitBaseHandler(sm.Parms.Repository, sm.Parms.LocalRegURL, config.UISecret(),
|
||||||
sm.Parms.TargetURL, sm.Parms.TargetUsername, sm.Parms.TargetPassword,
|
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(models.JobRunning, replication.StateCheck, &replication.Checker{BaseHandler: base})
|
||||||
sm.AddTransition(replication.StateCheck, replication.StatePullManifest, &replication.ManifestPuller{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, 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.StateTransferBlob, replication.StatePushManifest, &replication.ManifestPusher{BaseHandler: base})
|
||||||
sm.AddTransition(replication.StatePushManifest, replication.StatePullManifest, &replication.ManifestPuller{BaseHandler: base})
|
sm.AddTransition(replication.StatePushManifest, replication.StatePullManifest, &replication.ManifestPuller{BaseHandler: base})
|
||||||
return nil
|
return nil
|
||||||
@ -283,7 +292,7 @@ func addImgDeleteTransition(sm *SM) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
sm.AddTransition(models.JobRunning, replication.StateDelete, deleter)
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -38,13 +38,13 @@ func resumeJobs() {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warningf("Failed to reset all running jobs to pending, error: %v", err)
|
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 {
|
if err == nil {
|
||||||
for _, j := range jobs {
|
for _, j := range jobs {
|
||||||
log.Debugf("Rescheduling job: %d", j.ID)
|
log.Debugf("Resuming job: %d", j.ID)
|
||||||
job.Schedule(j.ID)
|
job.Schedule(j.ID)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
log.Warningf("Failed to get pending jobs, error: %v", err)
|
log.Warningf("Failed to jobs to resume, error: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -125,3 +125,7 @@ nav .container-custom {
|
|||||||
margin-bottom: 2px;
|
margin-bottom: 2px;
|
||||||
vertical-align: middle;
|
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() {
|
function optionalMenu() {
|
||||||
var directive = {
|
var directive = {
|
||||||
'restrict': 'E',
|
'restrict': 'E',
|
||||||
'templateUrl': '/optional_menu',
|
'templateUrl': '/optional_menu?timestamp=' + new Date().getTime(),
|
||||||
'scope': true,
|
'scope': true,
|
||||||
'controller': OptionalMenuController,
|
'controller': OptionalMenuController,
|
||||||
'controllerAs': 'vm',
|
'controllerAs': 'vm',
|
||||||
|
@ -12,9 +12,10 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<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">
|
<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>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -9,15 +9,15 @@
|
|||||||
|
|
||||||
function roles() {
|
function roles() {
|
||||||
return [
|
return [
|
||||||
{'id': '1', 'name': 'Project Admin', 'roleName': 'projectAdmin'},
|
{'id': '1', 'name': 'project_admin', 'roleName': 'projectAdmin'},
|
||||||
{'id': '2', 'name': 'Developer', 'roleName': 'developer'},
|
{'id': '2', 'name': 'developer', 'roleName': 'developer'},
|
||||||
{'id': '3', 'name': 'Guest', 'roleName': 'guest'}
|
{'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();
|
var r = roles();
|
||||||
return get;
|
return get;
|
||||||
function get(query) {
|
function get(query) {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<ng-switch on="vm.editMode">
|
<ng-switch on="vm.editMode">
|
||||||
<span ng-switch-default>//vm.currentRole.name//</span>
|
<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 for role in vm.roles track by role.roleName" ng-change="vm.selectRole(vm.currentRole)">
|
<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>
|
</select>
|
||||||
</ng-switch>
|
</ng-switch>
|
@ -14,6 +14,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="form-group" style="margin-top: 5px;">
|
<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>
|
</div>
|
||||||
<div class="col-xs-2 col-md-2">
|
<div class="col-xs-2 col-md-2">
|
||||||
|
@ -409,7 +409,7 @@
|
|||||||
if(!ctrl.toggleErrorMessage) {
|
if(!ctrl.toggleErrorMessage) {
|
||||||
element.find('#createPolicyModal').modal('hide');
|
element.find('#createPolicyModal').modal('hide');
|
||||||
}
|
}
|
||||||
}, 50);
|
}, 150);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -21,10 +21,10 @@
|
|||||||
return {
|
return {
|
||||||
'responseError': function(rejection) {
|
'responseError': function(rejection) {
|
||||||
var pathname = $window.location.pathname;
|
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;
|
var isExcluded = false;
|
||||||
for(var i in exclusions) {
|
for(var i in exclusion) {
|
||||||
if(exclusions[i] === pathname) {
|
if(exclusion[i] === pathname) {
|
||||||
isExcluded = true;
|
isExcluded = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -44,6 +44,7 @@
|
|||||||
'harbor.validator',
|
'harbor.validator',
|
||||||
'harbor.replication',
|
'harbor.replication',
|
||||||
'harbor.system.management',
|
'harbor.system.management',
|
||||||
'harbor.loading.progress'
|
'harbor.loading.progress',
|
||||||
|
'harbor.inline.help'
|
||||||
]);
|
]);
|
||||||
})();
|
})();
|
@ -23,7 +23,7 @@
|
|||||||
function navigationDetails() {
|
function navigationDetails() {
|
||||||
var directive = {
|
var directive = {
|
||||||
restrict: 'E',
|
restrict: 'E',
|
||||||
templateUrl: '/navigation_detail',
|
templateUrl: '/navigation_detail?timestamp=' + new Date().getTime(),
|
||||||
link: link,
|
link: link,
|
||||||
scope: {
|
scope: {
|
||||||
'target': '='
|
'target': '='
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
function navigationHeader() {
|
function navigationHeader() {
|
||||||
var directive = {
|
var directive = {
|
||||||
restrict: 'E',
|
restrict: 'E',
|
||||||
templateUrl: '/navigation_header',
|
templateUrl: '/navigation_header?timestamp=' + new Date().getTime(),
|
||||||
link: link,
|
link: link,
|
||||||
scope: true,
|
scope: true,
|
||||||
controller: NavigationHeaderController,
|
controller: NavigationHeaderController,
|
||||||
|
@ -235,5 +235,14 @@ var locale_messages = {
|
|||||||
'failed_to_delete_destination': 'Failed to delete destination.',
|
'failed_to_delete_destination': 'Failed to delete destination.',
|
||||||
'failed_to_create_destination': 'Failed to create destination.',
|
'failed_to_create_destination': 'Failed to create destination.',
|
||||||
'failed_to_update_destination': 'Failed to update 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_update_replication_policy': '修改复制策略失败。',
|
||||||
'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': '项目管理员',
|
||||||
|
'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': '设置该项目为公开。'
|
||||||
};
|
};
|
@ -238,3 +238,6 @@
|
|||||||
|
|
||||||
<script src="/static/resources/js/components/loading-progress/loading-progress.module.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