mirror of
https://github.com/goharbor/harbor.git
synced 2024-12-18 14:47:38 +01:00
Merge remote-tracking branch 'upstream/new-ui-with-sync-image' into job-service
This commit is contained in:
commit
9107de8744
@ -98,7 +98,8 @@ render(os.path.join(templates_dir, "ui", "env"),
|
||||
ldap_url=ldap_url,
|
||||
ldap_basedn=ldap_basedn,
|
||||
self_registration=self_registration,
|
||||
ui_secret=ui_secret)
|
||||
ui_secret=ui_secret,
|
||||
verify_remote_cert=verify_remote_cert)
|
||||
|
||||
render(os.path.join(templates_dir, "ui", "app.conf"),
|
||||
ui_conf,
|
||||
|
@ -17,3 +17,4 @@ LOG_LEVEL=debug
|
||||
GODEBUG=netdns=cgo
|
||||
EXT_ENDPOINT=$ui_url
|
||||
TOKEN_URL=http://ui
|
||||
VERIFY_REMOTE_CERT=$verify_remote_cert
|
||||
|
12
api/base.go
12
api/base.go
@ -19,6 +19,7 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"strconv"
|
||||
|
||||
"github.com/astaxie/beego/validation"
|
||||
@ -136,3 +137,14 @@ func (b *BaseAPI) GetIDFromURL() int64 {
|
||||
|
||||
return id
|
||||
}
|
||||
|
||||
func getIsInsecure() bool {
|
||||
insecure := false
|
||||
|
||||
verifyRemoteCert := os.Getenv("VERIFY_REMOTE_CERT")
|
||||
if verifyRemoteCert == "off" {
|
||||
insecure = true
|
||||
}
|
||||
|
||||
return insecure
|
||||
}
|
||||
|
@ -21,6 +21,7 @@ import (
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/vmware/harbor/dao"
|
||||
"github.com/vmware/harbor/models"
|
||||
@ -59,6 +60,7 @@ func (ra *RepJobAPI) Prepare() {
|
||||
func (ra *RepJobAPI) List() {
|
||||
var policyID int64
|
||||
var repository, status string
|
||||
var startTime, endTime *time.Time
|
||||
var err error
|
||||
|
||||
policyIDStr := ra.GetString("policy_id")
|
||||
@ -69,10 +71,36 @@ func (ra *RepJobAPI) List() {
|
||||
}
|
||||
}
|
||||
|
||||
endTimeStr := ra.GetString("end_time")
|
||||
if len(endTimeStr) != 0 {
|
||||
i, err := strconv.ParseInt(endTimeStr, 10, 64)
|
||||
if err != nil {
|
||||
ra.CustomAbort(http.StatusBadRequest, "invalid end_time")
|
||||
}
|
||||
t := time.Unix(i, 0)
|
||||
endTime = &t
|
||||
}
|
||||
|
||||
startTimeStr := ra.GetString("start_time")
|
||||
if len(startTimeStr) != 0 {
|
||||
i, err := strconv.ParseInt(startTimeStr, 10, 64)
|
||||
if err != nil {
|
||||
ra.CustomAbort(http.StatusBadRequest, "invalid start_time")
|
||||
}
|
||||
t := time.Unix(i, 0)
|
||||
startTime = &t
|
||||
}
|
||||
|
||||
if startTime == nil && endTime == nil {
|
||||
// if start_time and end_time are both null, list jobs of last 10 days
|
||||
t := time.Now().UTC().AddDate(0, 0, -10)
|
||||
startTime = &t
|
||||
}
|
||||
|
||||
repository = ra.GetString("repository")
|
||||
status = ra.GetString("status")
|
||||
|
||||
jobs, err := dao.FilterRepJobs(policyID, repository, status)
|
||||
jobs, err := dao.FilterRepJobs(policyID, repository, status, startTime, endTime, 1000)
|
||||
if err != nil {
|
||||
log.Errorf("failed to filter jobs according policy ID %d, repository %s, status %s: %v", policyID, repository, status, err)
|
||||
ra.RenderError(http.StatusInternalServerError, "Failed to query job")
|
||||
|
@ -144,6 +144,18 @@ func (ra *RepositoryAPI) Delete() {
|
||||
tags = append(tags, tag)
|
||||
}
|
||||
|
||||
project := ""
|
||||
if strings.Contains(repoName, "/") {
|
||||
project = repoName[0:strings.LastIndex(repoName, "/")]
|
||||
}
|
||||
user, _, ok := ra.Ctx.Request.BasicAuth()
|
||||
if !ok {
|
||||
user, err = ra.getUsername()
|
||||
if err != nil {
|
||||
log.Errorf("failed to get user: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
for _, t := range tags {
|
||||
if err := rc.DeleteTag(t); err != nil {
|
||||
if regErr, ok := err.(*registry_error.Error); ok {
|
||||
@ -156,6 +168,11 @@ func (ra *RepositoryAPI) Delete() {
|
||||
log.Infof("delete tag: %s %s", repoName, t)
|
||||
go TriggerReplicationByRepository(repoName, []string{t}, models.RepOpDelete)
|
||||
|
||||
go func(tag string) {
|
||||
if err := dao.AccessLog(user, project, repoName, tag, "delete"); err != nil {
|
||||
log.Errorf("failed to add access log: %v", err)
|
||||
}
|
||||
}(t)
|
||||
}
|
||||
|
||||
go func() {
|
||||
@ -164,7 +181,6 @@ func (ra *RepositoryAPI) Delete() {
|
||||
log.Errorf("error occurred while refresh catalog cache: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
}
|
||||
|
||||
type tag struct {
|
||||
@ -255,12 +271,10 @@ func (ra *RepositoryAPI) GetManifests() {
|
||||
|
||||
func (ra *RepositoryAPI) initRepositoryClient(repoName string) (r *registry.Repository, err error) {
|
||||
endpoint := os.Getenv("REGISTRY_URL")
|
||||
// TODO read variable from config file
|
||||
insecure := true
|
||||
|
||||
username, password, ok := ra.Ctx.Request.BasicAuth()
|
||||
if ok {
|
||||
return newRepositoryClient(endpoint, insecure, username, password,
|
||||
return newRepositoryClient(endpoint, getIsInsecure(), username, password,
|
||||
repoName, "repository", repoName, "pull", "push", "*")
|
||||
}
|
||||
|
||||
@ -269,7 +283,7 @@ func (ra *RepositoryAPI) initRepositoryClient(repoName string) (r *registry.Repo
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return cache.NewRepositoryClient(endpoint, insecure, username, repoName,
|
||||
return cache.NewRepositoryClient(endpoint, getIsInsecure(), username, repoName,
|
||||
"repository", repoName, "pull", "push", "*")
|
||||
}
|
||||
|
||||
|
@ -92,9 +92,7 @@ func (t *TargetAPI) Ping() {
|
||||
password = t.GetString("password")
|
||||
}
|
||||
|
||||
// TODO read variable from config file
|
||||
insecure := true
|
||||
registry, err := newRegistryClient(endpoint, insecure, username, password,
|
||||
registry, err := newRegistryClient(endpoint, getIsInsecure(), username, password,
|
||||
"", "", "")
|
||||
if err != nil {
|
||||
// timeout, dns resolve error, connection refused, etc.
|
||||
|
@ -55,16 +55,23 @@ func (b *BaseController) Prepare() {
|
||||
b.SetSession("Lang", lang)
|
||||
lang = sessionLang.(string)
|
||||
} else {
|
||||
lang = defaultLang
|
||||
al := b.Ctx.Request.Header.Get("Accept-Language")
|
||||
if len(al) > 4 {
|
||||
al = al[:5] // Only compare first 5 letters.
|
||||
if i18n.IsExist(al) {
|
||||
lang = al
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
b.SetSession("Lang", lang)
|
||||
|
||||
if _, exist := supportLanguages[lang]; !exist { //Check if support the request language.
|
||||
lang = defaultLang //Set default language if not supported.
|
||||
}
|
||||
|
||||
b.Ctx.SetCookie("language", lang, 0, "/")
|
||||
b.SetSession("Lang", lang)
|
||||
|
||||
curLang := langType{
|
||||
Lang: lang,
|
||||
}
|
||||
|
@ -1180,7 +1180,7 @@ func TestDeleteRepJob(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestFilterRepJobs(t *testing.T) {
|
||||
jobs, err := FilterRepJobs(policyID, "", "")
|
||||
jobs, err := FilterRepJobs(policyID, "", "", nil, nil, 1000)
|
||||
if err != nil {
|
||||
log.Errorf("Error occured in FilterRepJobs: %v, policy ID: %d", err, policyID)
|
||||
return
|
||||
|
@ -311,7 +311,8 @@ func GetRepJobByPolicy(policyID int64) ([]*models.RepJob, error) {
|
||||
}
|
||||
|
||||
// FilterRepJobs filters jobs by repo and policy ID
|
||||
func FilterRepJobs(policyID int64, repository, status string) ([]*models.RepJob, error) {
|
||||
func FilterRepJobs(policyID int64, repository, status string, startTime,
|
||||
endTime *time.Time, limit int) ([]*models.RepJob, error) {
|
||||
o := GetOrmer()
|
||||
|
||||
qs := o.QueryTable(new(models.RepJob))
|
||||
@ -324,6 +325,21 @@ func FilterRepJobs(policyID int64, repository, status string) ([]*models.RepJob,
|
||||
if len(status) != 0 {
|
||||
qs = qs.Filter("Status__icontains", status)
|
||||
}
|
||||
|
||||
if startTime != nil {
|
||||
fmt.Printf("%v\n", startTime)
|
||||
qs = qs.Filter("CreationTime__gte", startTime)
|
||||
}
|
||||
|
||||
if endTime != nil {
|
||||
fmt.Printf("%v\n", endTime)
|
||||
qs = qs.Filter("CreationTime__lte", endTime)
|
||||
}
|
||||
|
||||
if limit != 0 {
|
||||
qs = qs.Limit(limit)
|
||||
}
|
||||
|
||||
qs = qs.OrderBy("-CreationTime")
|
||||
|
||||
var jobs []*models.RepJob
|
||||
|
@ -39,55 +39,85 @@ const manifestPattern = `^application/vnd.docker.distribution.manifest.v\d\+json
|
||||
// Post handles POST request, and records audit log or refreshes cache based on event.
|
||||
func (n *NotificationHandler) Post() {
|
||||
var notification models.Notification
|
||||
//log.Info("Notification Handler triggered!\n")
|
||||
// log.Infof("request body in string: %s", string(n.Ctx.Input.CopyBody()))
|
||||
err := json.Unmarshal(n.Ctx.Input.CopyBody(1<<32), ¬ification)
|
||||
|
||||
if err != nil {
|
||||
log.Errorf("error while decoding json: %v", err)
|
||||
log.Errorf("failed to decode notification: %v", err)
|
||||
return
|
||||
}
|
||||
var username, action, repo, project, repoTag string
|
||||
var matched bool
|
||||
for _, e := range notification.Events {
|
||||
matched, err = regexp.MatchString(manifestPattern, e.Target.MediaType)
|
||||
if err != nil {
|
||||
log.Errorf("Failed to match the media type against pattern, error: %v", err)
|
||||
matched = false
|
||||
|
||||
events, err := filterEvents(¬ification)
|
||||
if err != nil {
|
||||
log.Errorf("failed to filter events: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
for _, event := range events {
|
||||
repository := event.Target.Repository
|
||||
|
||||
project := ""
|
||||
if strings.Contains(repository, "/") {
|
||||
project = repository[0:strings.LastIndex(repository, "/")]
|
||||
}
|
||||
if matched && (strings.HasPrefix(e.Request.UserAgent, "docker") ||
|
||||
strings.ToLower(strings.TrimSpace(e.Request.UserAgent)) == "harbor-registry-client") {
|
||||
username = e.Actor.Name
|
||||
action = e.Action
|
||||
repo = e.Target.Repository
|
||||
repoTag = e.Target.Tag
|
||||
log.Debugf("repo tag is : %v ", repoTag)
|
||||
|
||||
if strings.Contains(repo, "/") {
|
||||
project = repo[0:strings.LastIndex(repo, "/")]
|
||||
}
|
||||
if username == "" {
|
||||
username = "anonymous"
|
||||
}
|
||||
tag := event.Target.Tag
|
||||
action := event.Action
|
||||
|
||||
if action == "pull" && username == "job-service-user" {
|
||||
return
|
||||
}
|
||||
user := event.Actor.Name
|
||||
if len(user) == 0 {
|
||||
user = "anonymous"
|
||||
}
|
||||
|
||||
go dao.AccessLog(username, project, repo, repoTag, action)
|
||||
go func() {
|
||||
if err := dao.AccessLog(user, project, repository, tag, action); err != nil {
|
||||
log.Errorf("failed to add access log: %v", err)
|
||||
}
|
||||
}()
|
||||
if action == "push" {
|
||||
go func() {
|
||||
if err := cache.RefreshCatalogCache(); err != nil {
|
||||
log.Errorf("failed to refresh cache: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
operation := ""
|
||||
if action == "push" {
|
||||
go func() {
|
||||
err2 := cache.RefreshCatalogCache()
|
||||
if err2 != nil {
|
||||
log.Errorf("Error happens when refreshing cache: %v", err2)
|
||||
}
|
||||
}()
|
||||
|
||||
go api.TriggerReplicationByRepository(repo, []string{repoTag}, models.RepOpTransfer)
|
||||
operation = models.RepOpTransfer
|
||||
}
|
||||
|
||||
go api.TriggerReplicationByRepository(repository, []string{tag}, operation)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func filterEvents(notification *models.Notification) ([]*models.Event, error) {
|
||||
events := []*models.Event{}
|
||||
|
||||
for _, event := range notification.Events {
|
||||
isManifest, err := regexp.MatchString(manifestPattern, event.Target.MediaType)
|
||||
if err != nil {
|
||||
log.Errorf("failed to match the media type against pattern: %v", err)
|
||||
continue
|
||||
}
|
||||
|
||||
if !isManifest {
|
||||
continue
|
||||
}
|
||||
|
||||
//pull and push manifest by docker-client
|
||||
if strings.HasPrefix(event.Request.UserAgent, "docker") && (event.Action == "pull" || event.Action == "push") {
|
||||
events = append(events, &event)
|
||||
continue
|
||||
}
|
||||
|
||||
//push manifest by docker-client or job-service
|
||||
if strings.ToLower(strings.TrimSpace(event.Request.UserAgent)) == "harbor-registry-client" && event.Action == "push" {
|
||||
events = append(events, &event)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
return events, nil
|
||||
}
|
||||
|
||||
// Render returns nil as it won't render any template.
|
||||
|
@ -1,6 +1,6 @@
|
||||
.up-section .up-table-pane {
|
||||
overflow-y: auto;
|
||||
height: 180px;
|
||||
height: 220px;
|
||||
margin-top: -10px;
|
||||
}
|
||||
|
||||
|
@ -73,4 +73,8 @@
|
||||
|
||||
.color-warning {
|
||||
color: #f0ad4e;
|
||||
}
|
||||
|
||||
.label-custom {
|
||||
margin: 0 5px 0 10px;
|
||||
}
|
@ -53,7 +53,11 @@
|
||||
console.log('vm.projects is undefined, load public projects.');
|
||||
}
|
||||
|
||||
vm.selectedProject = vm.projects[0];
|
||||
if(angular.isArray(vm.projects) && vm.projects.length > 0) {
|
||||
vm.selectedProject = vm.projects[0];
|
||||
}else{
|
||||
$window.location.href = '/project';
|
||||
}
|
||||
|
||||
if(getParameterByName('project_id', $location.absUrl())){
|
||||
angular.forEach(vm.projects, function(value, index) {
|
||||
@ -64,8 +68,8 @@
|
||||
}
|
||||
|
||||
$location.search('project_id', vm.selectedProject.project_id);
|
||||
vm.checkProjectMember(vm.selectedProject.project_id);
|
||||
|
||||
vm.checkProjectMember(vm.selectedProject.project_id);
|
||||
vm.resultCount = vm.projects.length;
|
||||
|
||||
$scope.$watch('vm.filterInput', function(current, origin) {
|
||||
|
@ -49,6 +49,7 @@
|
||||
function addProjectMemberComplete(data, status, header) {
|
||||
console.log('addProjectMemberComplete: status:' + status + ', data:' + data);
|
||||
vm.reload();
|
||||
vm.isOpen = false;
|
||||
}
|
||||
|
||||
function addProjectMemberFailed(data, status, headers) {
|
||||
@ -97,4 +98,4 @@
|
||||
}
|
||||
}
|
||||
|
||||
})();
|
||||
})();
|
||||
|
@ -36,6 +36,7 @@
|
||||
$scope.$emit('addedSuccess', true);
|
||||
vm.hasError = false;
|
||||
vm.errorMessage = '';
|
||||
vm.isOpen = false;
|
||||
}
|
||||
|
||||
function addProjectFailed(data, status) {
|
||||
@ -91,4 +92,4 @@
|
||||
}
|
||||
}
|
||||
|
||||
})();
|
||||
})();
|
||||
|
@ -92,7 +92,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="submit" class="btn btn-primary" id="btnOk" ng-click="form.$valid && vm.save(replication)">// 'ok' | tr //</button>
|
||||
<button ng-show="vm.targetEditable" type="submit" class="btn btn-primary" ng-click="vm.save(replication)">// 'ok' | tr //</button>
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal">// 'close' | tr //</button>
|
||||
</div>
|
||||
</form>
|
||||
|
@ -69,7 +69,7 @@
|
||||
|
||||
$scope.$watch('vm.targetId', function(current) {
|
||||
if(current) {
|
||||
vm1.selection.id = current;
|
||||
vm1.selection.id = current || vm.destinations[0].id;
|
||||
}
|
||||
});
|
||||
|
||||
@ -125,15 +125,19 @@
|
||||
function saveOrUpdateDestination() {
|
||||
|
||||
var target = {
|
||||
'name' : vm1.name,
|
||||
'endpoint': vm1.endpoint,
|
||||
'username': vm1.username,
|
||||
'password': vm1.password
|
||||
};
|
||||
if(vm.checkedAddTarget) {
|
||||
target.name = vm1.name;
|
||||
|
||||
if(vm.checkedAddTarget){
|
||||
CreateDestinationService(target.name, target.endpoint, target.username, target.password)
|
||||
.success(createDestinationSuccess)
|
||||
.error(createDestinationFailed);
|
||||
}else{
|
||||
vm.policy.targetId = vm1.selection.id;
|
||||
saveOrUpdatePolicy();
|
||||
}
|
||||
}
|
||||
|
||||
@ -154,7 +158,10 @@
|
||||
|
||||
function update(policy) {
|
||||
vm.policy = policy;
|
||||
saveOrUpdateDestination();
|
||||
if(vm.targetEditable) {
|
||||
vm.policy.targetId = vm1.selection.id;
|
||||
saveOrUpdatePolicy();
|
||||
}
|
||||
}
|
||||
|
||||
function pingDestination() {
|
||||
|
@ -28,18 +28,18 @@
|
||||
<table class="table table-pane">
|
||||
<tbody>
|
||||
<tr ng-if="vm.replicationPolicies.length == 0">
|
||||
<td colspan="7" height="100%" class="empty-hint" ><h3 class="text-muted">// 'no_replication_policies' | tr //</h3></td>
|
||||
<td colspan="7" height="100%" class="empty-hint" ><h3 class="text-muted">// 'no_replication_policies_add_new' | tr //</h3></td>
|
||||
</tr>
|
||||
<tr policy_id="//r.id//" ng-if="vm.replicationPolicies.length > 0" ng-repeat="r in vm.replicationPolicies" value="//vm.last = $last//">
|
||||
<td width="10%">//r.name//</td>
|
||||
<td width="18%">//r.description//</td>
|
||||
<td width="18%">//r.target_name//</td>
|
||||
<td width="18%">//r.start_time | dateL : 'YYYY-MM-DD HH:mm:ss'//</td>
|
||||
<td width="14%" ng-switch on="//r.enabled//">
|
||||
<td width="18%" style="padding-left: 10px;">//r.description//</td>
|
||||
<td width="18%" style="padding-left: 15px;">//r.target_name//</td>
|
||||
<td width="18%" style="padding-left: 18px;">//r.start_time | dateL : 'YYYY-MM-DD HH:mm:ss'//</td>
|
||||
<td width="14%" style="padding-left: 18px;" ng-switch on="//r.enabled//">
|
||||
<span ng-switch-when="1">// 'enabled' | tr //</span>
|
||||
<span ng-switch-when="0">// 'disabled' | tr //</span>
|
||||
</td>
|
||||
<td width="15%">
|
||||
<td width="15%" style="padding-left: 20px;">
|
||||
<div class="display-inline-block" ng-switch on="//r.enabled//">
|
||||
<a href="javascript:void(0);" ng-click="vm.togglePolicy(r.id, 0)" title="// 'disabled' | tr //"><span ng-switch-when="1" class="glyphicon glyphicon-stop color-danger"></span></a>
|
||||
<a href="javascript:void(0);" ng-click="vm.togglePolicy(r.id, 1)" title="// 'enabled' | tr //"><span ng-switch-when="0" class="glyphicon glyphicon-play color-success"></span></a>
|
||||
@ -66,6 +66,30 @@
|
||||
<button class="btn btn-primary" type="button" ng-click="vm.searchReplicationJob()" ng-disabled="vm.refreshPending"><span class="glyphicon glyphicon-search"></span></button>
|
||||
</span>
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<label for="selStatus" class="control-label label-custom">// 'status' | tr //:</label>
|
||||
<div class="input-group">
|
||||
<select class="form-control" id="selStatus" ng-options="st.value for st in vm.jobStatus() track by st.key" ng-model="vm.currentStatus" ng-change="vm.searchReplicationJob()"></select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<label for="fromDatePicker" class="control-label label-custom">// 'from' | tr //:</label>
|
||||
<div class="input-group datetimepicker">
|
||||
<input id="fromDatePicker" class="form-control" type="text" readonly="readonly" ng-model="vm.fromDate" ng-change="vm.pickUp({key:'fromDate', value: vm.fromDate})" size="10">
|
||||
<span class="input-group-addon">
|
||||
<a href="javascript:void(0);"><span class="glyphicon glyphicon-calendar"></span></a>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<label for="toDatePicker" class="control-label label-custom">// 'to' | tr //:</label>
|
||||
<div class="input-group datetimepicker">
|
||||
<input id="toDatePicker" class="form-control" type="text" readonly="readonly" ng-model="vm.toDate" ng-change="vm.pickUp({key:'toDate', value: vm.toDate})" size="10">
|
||||
<span class="input-group-addon">
|
||||
<a href="javascript:void(0);"><span class="glyphicon glyphicon-calendar"></span></a>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<button type="button" class="btn btn-success" ng-click="vm.searchReplicationJob()"><span class="glyphicon glyphicon-refresh"></span></button>
|
||||
</div>
|
||||
@ -94,11 +118,13 @@
|
||||
<tr ng-if="vm.replicationJobs.length > 0" ng-repeat="r in vm.replicationJobs">
|
||||
<td width="15%">//r.repository//</td>
|
||||
<td width="12%">//r.operation//</td>
|
||||
<td width="18%">//r.creation_time | dateL : 'YYYY-MM-DD HH:mm:ss'//</td>
|
||||
<td width="18%">//r.creation_time | dateL : 'YYYY-MM-DD HH:mm:ss'//</td>
|
||||
<td width="18%">//r.update_time | dateL : 'YYYY-MM-DD HH:mm:ss'//</td>
|
||||
<td width="9%">//r.status//</td>
|
||||
<td width="10%" align="left"><a href="javascript:void(0);" ng-click="vm.downloadLog(r.id)" title="// 'download_log' | tr //"><span style="margin-left: 10px;" class="glyphicon glyphicon-file"></span></a></td>
|
||||
<td width="18%" style="padding-left: 15px;">//r.creation_time | dateL : 'YYYY-MM-DD HH:mm:ss'//</td>
|
||||
<td width="18%" style="padding-left: 15px;">//r.creation_time | dateL : 'YYYY-MM-DD HH:mm:ss'//</td>
|
||||
<td width="18%" style="padding-left: 18px;">//r.update_time | dateL : 'YYYY-MM-DD HH:mm:ss'//</td>
|
||||
<td width="9%" style="padding-left: 20px;">//r.status//</td>
|
||||
<td width="10%" align="left">
|
||||
<a ng-show="r.status != 'canceled' && r.status != 'pending'" href="javascript:void(0);" ng-click="vm.downloadLog(r.id)" title="// 'download_log' | tr //"><span style="margin-left: 10px;" class="glyphicon glyphicon-file"></span></a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
@ -4,11 +4,27 @@
|
||||
|
||||
angular
|
||||
.module('harbor.replication')
|
||||
.directive('listReplication', listReplication);
|
||||
|
||||
ListReplicationController.$inject = ['$scope', 'getParameterByName', '$location', 'ListReplicationPolicyService', 'ToggleReplicationPolicyService', 'ListReplicationJobService', '$window', '$filter', 'trFilter'];
|
||||
.directive('listReplication', listReplication)
|
||||
.factory('jobStatus', jobStatus);
|
||||
|
||||
jobStatus.inject = ['$filter', 'trFilter'];
|
||||
function jobStatus($filter, trFilter) {
|
||||
return function() {
|
||||
return [
|
||||
{'key': 'all' , 'value': $filter('tr')('all')},
|
||||
{'key': 'pending', 'value': $filter('tr')('pending')},
|
||||
{'key': 'running', 'value': $filter('tr')('running')},
|
||||
{'key': 'error' , 'value': $filter('tr')('error')},
|
||||
{'key': 'stopped', 'value': $filter('tr')('stopped')},
|
||||
{'key': 'finished', 'value':$filter('tr')('finished')},
|
||||
{'key': 'canceled', 'value': $filter('tr')('canceled')}
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
function ListReplicationController($scope, getParameterByName, $location, ListReplicationPolicyService, ToggleReplicationPolicyService, ListReplicationJobService, $window, $filter, trFilter) {
|
||||
ListReplicationController.$inject = ['$scope', 'getParameterByName', '$location', 'ListReplicationPolicyService', 'ToggleReplicationPolicyService', 'ListReplicationJobService', '$window', '$filter', 'trFilter', 'jobStatus'];
|
||||
|
||||
function ListReplicationController($scope, getParameterByName, $location, ListReplicationPolicyService, ToggleReplicationPolicyService, ListReplicationJobService, $window, $filter, trFilter, jobStatus) {
|
||||
var vm = this;
|
||||
|
||||
vm.sectionHeight = {'min-height': '1200px'};
|
||||
@ -36,6 +52,9 @@
|
||||
vm.retrievePolicy();
|
||||
vm.refreshPending = false;
|
||||
|
||||
vm.jobStatus = jobStatus;
|
||||
vm.currentStatus = vm.jobStatus()[0];
|
||||
|
||||
function searchReplicationPolicy() {
|
||||
vm.refreshPending = true;
|
||||
vm.retrievePolicy();
|
||||
@ -54,7 +73,8 @@
|
||||
}
|
||||
|
||||
function retrieveJob(policyId) {
|
||||
ListReplicationJobService(policyId, vm.replicationJobName)
|
||||
var status = (vm.currentStatus.key === 'all' ? '' : vm.currentStatus.key);
|
||||
ListReplicationJobService(policyId, vm.replicationJobName, status)
|
||||
.success(listReplicationJobSuccess)
|
||||
.error(listReplicationJobFailed);
|
||||
}
|
||||
@ -70,6 +90,18 @@
|
||||
|
||||
function listReplicationJobSuccess(data, status) {
|
||||
vm.replicationJobs = data || [];
|
||||
angular.forEach(vm.replicationJobs, function(item) {
|
||||
for(var key in item) {
|
||||
var value = item[key]
|
||||
switch(key) {
|
||||
case 'operation':
|
||||
case 'status':
|
||||
item[key] = $filter('tr')(value);
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
vm.refreshPending = false;
|
||||
}
|
||||
|
||||
@ -193,6 +225,19 @@
|
||||
ctrl.retrieveJob($(this).attr('policy_id'));
|
||||
ctrl.lastPolicyId = $(this).attr('policy_id');
|
||||
}
|
||||
|
||||
element.find('.datetimepicker').datetimepicker({
|
||||
locale: 'en-US',
|
||||
ignoreReadonly: true,
|
||||
format: 'L',
|
||||
showClear: true
|
||||
});
|
||||
element.find('#fromDatePicker').on('blur', function(){
|
||||
ctrl.pickUp({'key': 'fromDate', 'value': $(this).val()});
|
||||
});
|
||||
element.find('#toDatePicker').on('blur', function(){
|
||||
ctrl.pickUp({'key': 'toDate', 'value': $(this).val()});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
<div class="tab-pane active" id="repositories" element-height>
|
||||
<div class="tab-pane" id="repositories" element-height>
|
||||
<div class="col-xs-12 col-md-12 each-tab-pane">
|
||||
<div class="form-inline">
|
||||
<div class="input-group">
|
||||
@ -8,24 +8,26 @@
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div ng-if="vm.repositories.length === 0" class="empty-hint">
|
||||
<h3 style="margin-top: 200px;" class="text-muted">// 'no_repositories' | tr //</h3>
|
||||
</div>
|
||||
<div class="sub-pane">
|
||||
<div ng-if="vm.repositories.length > 0" class="panel-group" id="accordion" role="tablist" aria-multiselectable="true">
|
||||
<modal-dialog action="vm.deleteImage()" content-type="text/html" modal-title="//vm.modalTitle//" modal-message="//vm.modalMessage//"></modal-dialog>
|
||||
<div class="panel panel-default" ng-repeat="repo in vm.repositories" ng-show="vm.tagCount[repo] > 0">
|
||||
<div class="panel-heading" role="tab" id="heading//$index + 1//">
|
||||
<h4 class="panel-title">
|
||||
<a role="button" data-toggle="collapse" data-parent="" href="?project_id=//vm.projectId//#collapse//$index + 1//" aria-expanded="true" aria-controls="collapse//$index+1//">
|
||||
<span class="glyphicon glyphicon-book"></span> //repo// <span class="badge">//vm.tagCount[repo]//</span>
|
||||
</a>
|
||||
<a class="pull-right" style="margin-right: 78px;" href="javascript:void(0)" data-toggle="modal" data-target="#myModal" ng-click="vm.deleteByRepo(repo)" title="// 'delete_repo' | tr //"><span class="glyphicon glyphicon-trash"></span></a>
|
||||
</h4>
|
||||
<div class="pane">
|
||||
<div class="sub-pane">
|
||||
<div ng-if="vm.repositories.length === 0" class="empty-hint">
|
||||
<h3 style="margin-top: 120px;" class="text-muted">// 'no_repositories' | tr //</h3>
|
||||
</div>
|
||||
<div ng-if="vm.repositories.length > 0" class="panel-group" id="accordion" role="tablist" aria-multiselectable="true">
|
||||
<modal-dialog action="vm.deleteImage()" content-type="text/html" modal-title="//vm.modalTitle//" modal-message="//vm.modalMessage//"></modal-dialog>
|
||||
<div class="panel panel-default" ng-repeat="repo in vm.repositories" ng-show="vm.tagCount[repo] > 0">
|
||||
<div class="panel-heading" role="tab" id="heading//$index + 1//">
|
||||
<h4 class="panel-title">
|
||||
<a role="button" data-toggle="collapse" data-parent="" href="?project_id=//vm.projectId//#collapse//$index + 1//" aria-expanded="true" aria-controls="collapse//$index+1//">
|
||||
<span class="glyphicon glyphicon-book"></span> //repo// <span class="badge">//vm.tagCount[repo]//</span>
|
||||
</a>
|
||||
<a class="pull-right" style="margin-right: 78px;" href="javascript:void(0)" data-toggle="modal" data-target="#myModal" ng-click="vm.deleteByRepo(repo)" title="// 'delete_repo' | tr //"><span class="glyphicon glyphicon-trash"></span></a>
|
||||
</h4>
|
||||
</div>
|
||||
<list-tag associate-id="$index + 1" repo-name="repo" tag-count="vm.tagCount"></list-tag>
|
||||
</div>
|
||||
</div>
|
||||
<list-tag associate-id="$index + 1" repo-name="repo" tag-count="vm.tagCount"></list-tag>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@ -1,4 +1,7 @@
|
||||
<h4 class="page-header title-color underlined">// 'summary' | tr //</h4>
|
||||
<h4 class="page-header title-color underlined">// 'summary' | tr //</h4>
|
||||
<dl class="page-content dl-horizontal" ng-repeat="(key, value) in vm.statProjects">
|
||||
<dt>// key | tr //:</dt><dd>//value//<dd>
|
||||
<dt ng-if="$index==0"><a href="/project">// key | tr //</a>:</dt><dd ng-if="$index==0">//value//<dd>
|
||||
<dt ng-if="$index==1">// key | tr //:</dt><dd ng-if="$index==1">//value//<dd>
|
||||
<dt ng-if="$index==2"><a href="/search?q=">// key | tr //</a>:</dt><dd ng-if="$index==2">//value//<dd>
|
||||
<dt ng-if="$index>2">// key | tr //:</dt><dd ng-if="$index>2">//value//<dd>
|
||||
</dl>
|
||||
|
@ -53,7 +53,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="submit" class="btn btn-primary" id="btnOk" ng-click="form.$valid && vm.save(destination)">// 'ok' | tr //</button>
|
||||
<button ng-show="vm.editable" type="submit" class="btn btn-primary" id="btnOk" ng-click="vm.save(destination)">// 'ok' | tr //</button>
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal">// 'close' | tr //</button>
|
||||
</div>
|
||||
</div><!-- /.modal-content -->
|
||||
|
@ -28,7 +28,7 @@
|
||||
<table class="table table-pane">
|
||||
<tbody>
|
||||
<tr ng-if="vm.replications.length == 0">
|
||||
<td colspan="7" height="100%" class="empty-hint" ><h3 class="text-muted">// 'no_replications' | tr //</h3></td>
|
||||
<td colspan="7" height="100%" class="empty-hint" ><h3 class="text-muted">// 'no_replication_policies' | tr //</h3></td>
|
||||
</tr>
|
||||
<tr ng-if="vm.replications.length > 0" ng-repeat="r in vm.replications">
|
||||
<td><a href="repository#/replication?project_id=//r.project_id//">//r.name//</a></td>
|
||||
|
@ -1,6 +1,6 @@
|
||||
<h4 class="page-header title-color underlined">// 'popular_repositories' | tr //</h4>
|
||||
<div class="col-xs-4 col-md-12 up-table-pane">
|
||||
<div class="table-head-container" style="width: 650px">
|
||||
<div class="table-head-container">
|
||||
<table class="table table-pane table-header">
|
||||
<thead>
|
||||
<th width="60%">// 'repository_name' | tr //</th>
|
||||
@ -9,7 +9,7 @@
|
||||
</thead>
|
||||
</table>
|
||||
</div>
|
||||
<div class="table-body-container" style="height: 120px; width: 650px">
|
||||
<div class="table-body-container" ng-style="vm.customBodyHeight">
|
||||
<table class="table table-pane">
|
||||
<tbody>
|
||||
<tr>
|
||||
|
@ -30,7 +30,9 @@
|
||||
'restrict': 'E',
|
||||
'templateUrl': '/static/resources/js/components/top-repository/top-repository.directive.html',
|
||||
'controller': TopRepositoryController,
|
||||
'scope' : true,
|
||||
'scope' : {
|
||||
'customBodyHeight': '='
|
||||
},
|
||||
'controllerAs': 'vm',
|
||||
'bindToController': true
|
||||
};
|
||||
|
@ -9,7 +9,7 @@
|
||||
</thead>
|
||||
</table>
|
||||
</div>
|
||||
<div class="table-body-container">
|
||||
<div class="table-body-container" style="height: 220px;">
|
||||
<table class="table table-pane">
|
||||
<tbody>
|
||||
<tr>
|
||||
|
@ -9,7 +9,8 @@
|
||||
DashboardController.$inject = ['$scope'];
|
||||
|
||||
function DashboardController($scope) {
|
||||
|
||||
var vm = this;
|
||||
vm.customBodyHeight = {'height': '165px'};
|
||||
}
|
||||
|
||||
})();
|
@ -14,8 +14,11 @@
|
||||
$scope.subsSection = 32;
|
||||
$scope.subsSubPane = 226;
|
||||
|
||||
|
||||
var vm = this;
|
||||
|
||||
|
||||
vm.customBodyHeight = {'height': '180px'};
|
||||
|
||||
var indexDesc = $filter('tr')('index_desc', []);
|
||||
var indexDesc1 = $filter('tr')('index_desc_1', []);
|
||||
var indexDesc2 = $filter('tr')('index_desc_2', []);
|
||||
|
@ -154,8 +154,8 @@ var locale_messages = {
|
||||
'logs' : 'Logs',
|
||||
'enabled': 'Enabled',
|
||||
'disabled': 'Disabled',
|
||||
'no_replication_policies': 'No replication policies, add new replication policy.',
|
||||
'no_replications': 'No replications.',
|
||||
'no_replication_policies_add_new': 'No replication policies, add new replication policy.',
|
||||
'no_replication_policies': 'No replication policies.',
|
||||
'no_replication_jobs': 'No replication jobs.',
|
||||
'no_destinations': 'No destinations, add new destination.',
|
||||
'name_is_required': 'Name is required.',
|
||||
@ -200,5 +200,13 @@ var locale_messages = {
|
||||
'delete_repo': 'Delete Repo',
|
||||
'download_log': 'Download Logs',
|
||||
'edit': 'Edit',
|
||||
'delete': 'Delete'
|
||||
'delete': 'Delete',
|
||||
'transfer': 'Transfer',
|
||||
'all': 'All',
|
||||
'pending': 'Pending',
|
||||
'running': 'Running',
|
||||
'finished': 'Finished',
|
||||
'canceled': 'Canceled',
|
||||
'stopped': 'Stopped',
|
||||
'error': 'Error'
|
||||
};
|
@ -152,7 +152,8 @@ var locale_messages = {
|
||||
'logs': '日志',
|
||||
'enabled': '启用',
|
||||
'disabled': '停用',
|
||||
'no_replication_policies': '没有复制策略,请新增复制策略。',
|
||||
'no_replication_policies_add_new': '没有复制策略,请新增复制策略。',
|
||||
'no_replication_policies': '没有复制策略。',
|
||||
'no_replications': '没有复制策略。',
|
||||
'no_replication_jobs': '没有复制任务。',
|
||||
'no_destinations': '没有目标设置,请新增目标。',
|
||||
@ -199,5 +200,13 @@ var locale_messages = {
|
||||
'delete_repo': '删除镜像仓库',
|
||||
'download_log': '下载日志',
|
||||
'edit': '修改',
|
||||
'delete': '删除'
|
||||
'delete': '删除',
|
||||
'all': '全部',
|
||||
'transfer': '传输',
|
||||
'pending': '挂起',
|
||||
'running': '进行中',
|
||||
'finished': '已完成',
|
||||
'canceled': '取消',
|
||||
'stopped': '停止',
|
||||
'error': '错误'
|
||||
};
|
@ -12,12 +12,13 @@
|
||||
|
||||
return listReplicationJob;
|
||||
|
||||
function listReplicationJob(policyId, repository) {
|
||||
function listReplicationJob(policyId, repository, status) {
|
||||
return $http
|
||||
.get('/api/jobs/replication/', {
|
||||
'params': {
|
||||
'policy_id': policyId,
|
||||
'repository': repository
|
||||
'repository': repository,
|
||||
'status': status
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -10,7 +10,7 @@
|
||||
</div>
|
||||
<div class="col-xs-8 col-md-8">
|
||||
<div class="up-section">
|
||||
<top-repository></top-repository>
|
||||
<top-repository custom-body-height="vm.customBodyHeight"></top-repository>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -20,7 +20,7 @@
|
||||
<div class="col-xs-12 col-md-12">
|
||||
<div class="down-section">
|
||||
<h4 class="page-header title-color underlined">// 'logs' | tr //</h4>
|
||||
<user-log></user-log>
|
||||
<user-log></user-log>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -52,7 +52,7 @@
|
||||
</div>
|
||||
<div class="col-md-8">
|
||||
<div class="down-section">
|
||||
<top-repository></top-repository>
|
||||
<top-repository custom-body-height="vm.customBodyHeight"></top-repository>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
Loading…
Reference in New Issue
Block a user