Merge remote-tracking branch 'upstream/new-ui-with-sync-image' into job-service

This commit is contained in:
Tan Jiang 2016-06-27 17:40:43 +08:00
commit 6397e92193
16 changed files with 76 additions and 94 deletions

View File

@ -58,7 +58,7 @@ func (ra *RepJobAPI) Prepare() {
// List filters jobs according to the policy and repository // List filters jobs according to the policy and repository
func (ra *RepJobAPI) List() { func (ra *RepJobAPI) List() {
var policyID int64 var policyID int64
var repository string var repository, status string
var err error var err error
policyIDStr := ra.GetString("policy_id") policyIDStr := ra.GetString("policy_id")
@ -70,10 +70,11 @@ func (ra *RepJobAPI) List() {
} }
repository = ra.GetString("repository") repository = ra.GetString("repository")
status = ra.GetString("status")
jobs, err := dao.FilterRepJobs(repository, policyID) jobs, err := dao.FilterRepJobs(policyID, repository, status)
if err != nil { if err != nil {
log.Errorf("failed to filter jobs according policy ID %d and repository %s: %v", policyID, repository, err) 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") ra.RenderError(http.StatusInternalServerError, "Failed to query job")
return return
} }

View File

@ -68,7 +68,7 @@ func (s *SearchAPI) Get() {
} }
} }
projectSorter := &utils.ProjectSorter{Projects: projects} projectSorter := &models.ProjectSorter{Projects: projects}
sort.Sort(projectSorter) sort.Sort(projectSorter)
projectResult := []map[string]interface{}{} projectResult := []map[string]interface{}{}
for _, p := range projects { for _, p := range projects {

View File

@ -1141,7 +1141,7 @@ func TestDeleteRepJob(t *testing.T) {
} }
func TestFilterRepJobs(t *testing.T) { func TestFilterRepJobs(t *testing.T) {
jobs, err := FilterRepJobs("", policyID) jobs, err := FilterRepJobs(policyID, "", "")
if err != nil { if err != nil {
log.Errorf("Error occured in FilterRepJobs: %v, policy ID: %d", err, policyID) log.Errorf("Error occured in FilterRepJobs: %v, policy ID: %d", err, policyID)
return return

View File

@ -302,29 +302,24 @@ func GetRepJobByPolicy(policyID int64) ([]*models.RepJob, error) {
} }
// FilterRepJobs filters jobs by repo and policy ID // FilterRepJobs filters jobs by repo and policy ID
func FilterRepJobs(repo string, policyID int64) ([]*models.RepJob, error) { func FilterRepJobs(policyID int64, repository, status string) ([]*models.RepJob, error) {
o := GetOrmer() o := GetOrmer()
var args []interface{} qs := o.QueryTable(new(models.RepJob))
if policyID != 0 {
sql := `select * from replication_job ` qs = qs.Filter("PolicyID", policyID)
if len(repo) != 0 && policyID != 0 {
sql += `where repository like ? and policy_id = ? `
args = append(args, "%"+repo+"%")
args = append(args, policyID)
} else if len(repo) != 0 {
sql += `where repository like ? `
args = append(args, "%"+repo+"%")
} else if policyID != 0 {
sql += `where policy_id = ? `
args = append(args, policyID)
} }
if len(repository) != 0 {
sql += `order by creation_time` qs = qs.Filter("Repository__icontains", repository)
}
if len(status) != 0 {
qs = qs.Filter("Status__icontains", status)
}
qs = qs.OrderBy("CreationTime")
var jobs []*models.RepJob var jobs []*models.RepJob
if _, err := o.Raw(sql, args).QueryRows(&jobs); err != nil { _, err := qs.All(&jobs)
if err != nil {
return nil, err return nil, err
} }

View File

@ -37,3 +37,23 @@ type Project struct {
Role int `json:"current_user_role_id"` Role int `json:"current_user_role_id"`
RepoCount int `json:"repo_count"` RepoCount int `json:"repo_count"`
} }
// ProjectSorter holds an array of projects
type ProjectSorter struct {
Projects []Project
}
// Len returns the length of array in ProjectSorter
func (ps *ProjectSorter) Len() int {
return len(ps.Projects)
}
// Less defines the comparison rules of project
func (ps *ProjectSorter) Less(i, j int) bool {
return ps.Projects[i].Name < ps.Projects[j].Name
}
// Swap swaps the position of i and j
func (ps *ProjectSorter) Swap(i, j int) {
ps.Projects[i], ps.Projects[j] = ps.Projects[j], ps.Projects[i]
}

View File

@ -19,6 +19,7 @@ import (
"time" "time"
"github.com/astaxie/beego/validation" "github.com/astaxie/beego/validation"
"github.com/vmware/harbor/utils"
) )
const ( const (
@ -129,6 +130,8 @@ func (r *RepTarget) Valid(v *validation.Validation) {
v.SetError("endpoint", "can not be empty") v.SetError("endpoint", "can not be empty")
} }
r.URL = utils.FormatEndpoint(r.URL)
if len(r.URL) > 64 { if len(r.URL) > 64 {
v.SetError("endpoint", "max length is 64") v.SetError("endpoint", "max length is 64")
} }

View File

@ -32,6 +32,7 @@ body {
.container-fluid-custom { .container-fluid-custom {
background-color: #EFEFEF; background-color: #EFEFEF;
width: 100%; width: 100%;
height: 100%;
min-width: 1px; min-width: 1px;
overflow: auto; overflow: auto;
min-height: 1px; min-height: 1px;

View File

@ -23,15 +23,13 @@
if(!angular.isDefined(scope.subsHeight)) scope.subsHeight = 110; if(!angular.isDefined(scope.subsHeight)) scope.subsHeight = 110;
if(!angular.isDefined(scope.subsSection)) scope.subsSection = 32; if(!angular.isDefined(scope.subsSection)) scope.subsSection = 32;
if(!angular.isDefined(scope.subsSubPane)) scope.subsSubPane = 226; if(!angular.isDefined(scope.subsSubPane)) scope.subsSubPane = 226;
if(!angular.isDefined(scope.subsTabPane)) scope.subsTabPane = 66;
scope.$watch(scope.getDimension, function(current) { scope.$watch(scope.getDimension, function(current) {
if(current) { if(current) {
var h = current.h; var h = current.h;
element.css({'height' : (h - scope.subsHeight) + 'px'});
element.find('.section').css({'height': (h - scope.subsHeight - scope.subsSection) + 'px'}); element.find('.section').css({'height': (h - scope.subsHeight - scope.subsSection) + 'px'});
element.find('.sub-pane').css({'height': (h - scope.subsHeight - scope.subsSubPane) + 'px'}); element.find('.sub-pane').css({'height': (h - scope.subsHeight - scope.subsSubPane) + 'px'});
element.find('.tab-pane').css({'height': (h - scope.subsHeight - scope.subsTabPane) + 'px'}); element.find('.tab-pane').css({'height': (h - scope.subsHeight - scope.subsSubPane) + 'px'});
} }
}, true); }, true);

View File

@ -138,7 +138,7 @@
}else{ }else{
element.find('#down-pane').css({'height' : (maxDownPaneHeight) + 'px'}); element.find('#down-pane').css({'height' : (maxDownPaneHeight) + 'px'});
$(document).off('mousemove'); $(document).off('mousemove');
} }
} }
function mouseupHandler(e) { function mouseupHandler(e) {
$(document).off('mousedown'); $(document).off('mousedown');
@ -171,7 +171,8 @@
element element
.find('#upon-pane table>tbody>tr') .find('#upon-pane table>tbody>tr')
.css({'background-color': '#FFFFFF'}) .css({'background-color': '#FFFFFF'})
.css({'color': '#000'}); .css({'color': '#000'})
.css({'cursor': 'default'});
element element
.find('#upon-pane table>tbody>tr a') .find('#upon-pane table>tbody>tr a')
.css({'color': '#337ab7'}); .css({'color': '#337ab7'});

View File

@ -1,4 +1,4 @@
<div class="tab-pane active" id="repositories" > <div class="tab-pane active" id="repositories" element-height>
<div class="col-xs-12 col-md-12 each-tab-pane"> <div class="col-xs-12 col-md-12 each-tab-pane">
<div class="form-inline"> <div class="form-inline">
<div class="input-group"> <div class="input-group">
@ -11,6 +11,7 @@
<div ng-if="vm.repositories.length === 0" class="empty-hint"> <div ng-if="vm.repositories.length === 0" class="empty-hint">
<h3 style="margin-top: 200px;" class="text-muted">// 'no_repositories' | tr //</h3> <h3 style="margin-top: 200px;" class="text-muted">// 'no_repositories' | tr //</h3>
</div> </div>
<div class="sub-pane">
<div ng-if="vm.repositories.length > 0" class="panel-group" id="accordion" role="tablist" aria-multiselectable="true"> <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> <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"> <div class="panel panel-default" ng-repeat="repo in vm.repositories">
@ -25,5 +26,6 @@
<list-tag associate-id="$index + 1" repo-name="repo" tag-count="vm.tagCount"></list-tag> <list-tag associate-id="$index + 1" repo-name="repo" tag-count="vm.tagCount"></list-tag>
</div> </div>
</div> </div>
</div>
</div> </div>
</div> </div>

View File

@ -8,6 +8,9 @@
ListRepositoryController.$inject = ['$scope', 'ListRepositoryService', 'DeleteRepositoryService', '$filter', 'trFilter', '$location', 'getParameterByName']; ListRepositoryController.$inject = ['$scope', 'ListRepositoryService', 'DeleteRepositoryService', '$filter', 'trFilter', '$location', 'getParameterByName'];
function ListRepositoryController($scope, ListRepositoryService, DeleteRepositoryService, $filter, trFilter, $location, getParameterByName) { function ListRepositoryController($scope, ListRepositoryService, DeleteRepositoryService, $filter, trFilter, $location, getParameterByName) {
$scope.subsTabPane = 30;
var vm = this; var vm = this;
vm.filterInput = ''; vm.filterInput = '';

View File

@ -21,7 +21,7 @@ import (
"net/http" "net/http"
au "github.com/docker/distribution/registry/client/auth" au "github.com/docker/distribution/registry/client/auth"
"github.com/vmware/harbor/utils/registry/utils" "github.com/vmware/harbor/utils"
) )
// Authorizer authorizes requests according to the schema // Authorizer authorizes requests according to the schema

View File

@ -21,8 +21,8 @@ import (
"net/url" "net/url"
"strings" "strings"
"github.com/vmware/harbor/utils"
registry_error "github.com/vmware/harbor/utils/registry/error" registry_error "github.com/vmware/harbor/utils/registry/error"
"github.com/vmware/harbor/utils/registry/utils"
) )
// Registry holds information of a registry entity // Registry holds information of a registry entity

View File

@ -30,8 +30,8 @@ import (
"github.com/docker/distribution/manifest/schema1" "github.com/docker/distribution/manifest/schema1"
"github.com/docker/distribution/manifest/schema2" "github.com/docker/distribution/manifest/schema2"
"github.com/vmware/harbor/utils"
registry_error "github.com/vmware/harbor/utils/registry/error" registry_error "github.com/vmware/harbor/utils/registry/error"
"github.com/vmware/harbor/utils/registry/utils"
) )
// Repository holds information of a repository entity // Repository holds information of a repository entity

View File

@ -1,44 +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 utils
import (
"net/url"
"strings"
)
// FormatEndpoint formats endpoint
func FormatEndpoint(endpoint string) string {
endpoint = strings.TrimSpace(endpoint)
endpoint = strings.TrimRight(endpoint, "/")
if !strings.HasPrefix(endpoint, "http://") &&
!strings.HasPrefix(endpoint, "https://") {
endpoint = "http://" + endpoint
}
return endpoint
}
// ParseEndpoint parses endpoint to a URL
func ParseEndpoint(endpoint string) (*url.URL, error) {
endpoint = FormatEndpoint(endpoint)
u, err := url.Parse(endpoint)
if err != nil {
return nil, err
}
return u, nil
}

View File

@ -16,9 +16,8 @@
package utils package utils
import ( import (
"net/url"
"strings" "strings"
"github.com/vmware/harbor/models"
) )
// Repository holds information about repository // Repository holds information about repository
@ -34,22 +33,25 @@ func (r *Repository) GetProject() string {
return r.Name[0:strings.LastIndex(r.Name, "/")] return r.Name[0:strings.LastIndex(r.Name, "/")]
} }
// ProjectSorter holds an array of projects // FormatEndpoint formats endpoint
type ProjectSorter struct { func FormatEndpoint(endpoint string) string {
Projects []models.Project endpoint = strings.TrimSpace(endpoint)
endpoint = strings.TrimRight(endpoint, "/")
if !strings.HasPrefix(endpoint, "http://") &&
!strings.HasPrefix(endpoint, "https://") {
endpoint = "http://" + endpoint
}
return endpoint
} }
// Len returns the length of array in ProjectSorter // ParseEndpoint parses endpoint to a URL
func (ps *ProjectSorter) Len() int { func ParseEndpoint(endpoint string) (*url.URL, error) {
return len(ps.Projects) endpoint = FormatEndpoint(endpoint)
}
// Less defines the comparison rules of project u, err := url.Parse(endpoint)
func (ps *ProjectSorter) Less(i, j int) bool { if err != nil {
return ps.Projects[i].Name < ps.Projects[j].Name return nil, err
} }
return u, nil
// Swap swaps the position of i and j
func (ps *ProjectSorter) Swap(i, j int) {
ps.Projects[i], ps.Projects[j] = ps.Projects[j], ps.Projects[i]
} }