mirror of
https://github.com/goharbor/harbor.git
synced 2024-12-26 18:48:02 +01:00
Merge branch 'dev' of https://github.com/vmware/harbor into dev
This commit is contained in:
commit
285ef73653
@ -59,7 +59,8 @@ install:
|
||||
- curl -L https://github.com/docker/compose/releases/download/${DOCKER_COMPOSE_VERSION}/docker-compose-`uname -s`-`uname -m` > docker-compose
|
||||
- chmod +x docker-compose
|
||||
- sudo mv docker-compose /usr/local/bin
|
||||
- sudo sed -i '$a DOCKER_OPTS=\"$DOCKER_OPTS --insecure-registry 127.0.0.1\"' /etc/default/docker
|
||||
- IP=`ip addr s eth0 |grep "inet "|awk '{print $2}' |awk -F "/" '{print $1}'`
|
||||
- sudo sed -i '$a DOCKER_OPTS=\"--insecure-registry '$IP':5000\"' /etc/default/docker
|
||||
- sudo service docker restart
|
||||
- go get github.com/dghubble/sling
|
||||
- go get github.com/stretchr/testify
|
||||
@ -75,10 +76,10 @@ script:
|
||||
- docker-compose -f Deploy/docker-compose.test.yml up -d
|
||||
- go list ./... | grep -v -E 'vendor|tests' | xargs -L1 fgt golint
|
||||
- go list ./... | grep -v -E 'vendor|tests' | xargs -L1 go vet
|
||||
- IP=`ip addr s eth0 |grep "inet "|awk '{print $2}' |awk -F "/" '{print $1}'`
|
||||
- export MYSQL_HOST=$IP
|
||||
- export REGISTRY_URL=http://$IP:5000
|
||||
- export REGISTRY_URL=$IP:5000
|
||||
- echo $REGISTRY_URL
|
||||
- ./tests/pushimage.sh
|
||||
- ./Deploy/coverage4gotest.sh
|
||||
- goveralls -coverprofile=profile.cov -service=travis-ci
|
||||
|
||||
|
@ -38,8 +38,14 @@ insert into role (role_code, name) values
|
||||
|
||||
create table user (
|
||||
user_id int NOT NULL AUTO_INCREMENT,
|
||||
username varchar(15),
|
||||
email varchar(128),
|
||||
# The max length of username controlled by API is 20,
|
||||
# and 11 is reserved for marking the deleted users.
|
||||
# The mark of deleted user is "#user_id".
|
||||
# The 11 consist of 10 for the max value of user_id(4294967295)
|
||||
# in MySQL and 1 of '#'.
|
||||
username varchar(32),
|
||||
# 11 bytes is reserved for marking the deleted users.
|
||||
email varchar(255),
|
||||
password varchar(40) NOT NULL,
|
||||
realname varchar (20) NOT NULL,
|
||||
comment varchar (30),
|
||||
@ -62,7 +68,7 @@ create table project (
|
||||
project_id int NOT NULL AUTO_INCREMENT,
|
||||
owner_id int NOT NULL,
|
||||
# The max length of name controlled by API is 30,
|
||||
# and 11 bytes is reserved for marking the deleted project.
|
||||
# and 11 is reserved for marking the deleted project.
|
||||
name varchar (41) NOT NULL,
|
||||
creation_time timestamp,
|
||||
update_time timestamp,
|
||||
@ -101,10 +107,27 @@ create table access_log (
|
||||
operation varchar(20) NOT NULL,
|
||||
op_time timestamp,
|
||||
primary key (log_id),
|
||||
INDEX pid_optime (project_id, op_time),
|
||||
FOREIGN KEY (user_id) REFERENCES user(user_id),
|
||||
FOREIGN KEY (project_id) REFERENCES project (project_id)
|
||||
);
|
||||
|
||||
create table repository (
|
||||
repository_id int NOT NULL AUTO_INCREMENT,
|
||||
name varchar(255) NOT NULL,
|
||||
project_id int NOT NULL,
|
||||
owner_id int NOT NULL,
|
||||
description text,
|
||||
pull_count int DEFAULT 0 NOT NULL,
|
||||
star_count int DEFAULT 0 NOT NULL,
|
||||
creation_time timestamp default CURRENT_TIMESTAMP,
|
||||
update_time timestamp default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP,
|
||||
primary key (repository_id),
|
||||
FOREIGN KEY (owner_id) REFERENCES user(user_id),
|
||||
FOREIGN KEY (project_id) REFERENCES project(project_id),
|
||||
UNIQUE (name)
|
||||
);
|
||||
|
||||
create table replication_policy (
|
||||
id int NOT NULL AUTO_INCREMENT,
|
||||
name varchar(256),
|
||||
@ -147,7 +170,8 @@ create table replication_job (
|
||||
creation_time timestamp default CURRENT_TIMESTAMP,
|
||||
update_time timestamp default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (id),
|
||||
INDEX policy (policy_id)
|
||||
INDEX policy (policy_id),
|
||||
INDEX poid_uptime (policy_id, update_time)
|
||||
);
|
||||
|
||||
create table properties (
|
||||
|
@ -25,8 +25,12 @@ import (
|
||||
const (
|
||||
jsonAcceptHeader = "application/json"
|
||||
testAcceptHeader = "text/plain"
|
||||
adminName = "admin"
|
||||
adminPwd = "Harbor12345"
|
||||
)
|
||||
|
||||
var admin, unknownUsr *usrInfo
|
||||
|
||||
type api struct {
|
||||
basePath string
|
||||
}
|
||||
@ -57,13 +61,20 @@ func init() {
|
||||
|
||||
beego.Router("/api/search/", &SearchAPI{})
|
||||
beego.Router("/api/projects/", &ProjectAPI{}, "get:List;post:Post;head:Head")
|
||||
beego.Router("/api/projects/:id/delete", &ProjectAPI{}, "delete:Delete")
|
||||
beego.Router("/api/projects/:id", &ProjectAPI{}, "delete:Delete;get:Get")
|
||||
beego.Router("/api/users/:id([0-9]+)/password", &UserAPI{}, "put:ChangePassword")
|
||||
beego.Router("/api/projects/:id/publicity", &ProjectAPI{}, "put:ToggleProjectPublic")
|
||||
beego.Router("/api/projects/:id([0-9]+)/logs/filter", &ProjectAPI{}, "post:FilterAccessLog")
|
||||
beego.Router("/api/projects/:pid([0-9]+)/members/?:mid", &ProjectMemberAPI{}, "get:Get")
|
||||
beego.Router("/api/statistics", &StatisticAPI{})
|
||||
beego.Router("/api/logs", &LogAPI{})
|
||||
|
||||
_ = updateInitPassword(1, "Harbor12345")
|
||||
|
||||
//Init user Info
|
||||
admin = &usrInfo{adminName, adminPwd}
|
||||
unknownUsr = &usrInfo{"unknown", "unknown"}
|
||||
|
||||
}
|
||||
|
||||
func request(_sling *sling.Sling, acceptHeader string, authInfo ...usrInfo) (int, []byte, error) {
|
||||
@ -115,7 +126,7 @@ func (a api) SearchGet(q string) (apilib.Search, error) {
|
||||
//@param project New created project.
|
||||
//@return void
|
||||
//func (a api) ProjectsPost (prjUsr usrInfo, project apilib.Project) (int, error) {
|
||||
func (a api) ProjectsPost(prjUsr usrInfo, project apilib.Project) (int, error) {
|
||||
func (a api) ProjectsPost(prjUsr usrInfo, project apilib.ProjectReq) (int, error) {
|
||||
|
||||
_sling := sling.New().Post(a.basePath)
|
||||
|
||||
@ -152,7 +163,43 @@ func (a api) UsersUserIDPasswordPut(user usrInfo, userID int32, password apilib.
|
||||
|
||||
httpStatusCode, _, _ := request(_sling, jsonAcceptHeader, user)
|
||||
return httpStatusCode
|
||||
}
|
||||
|
||||
func (a api) StatisticGet(user usrInfo) (apilib.StatisticMap, error) {
|
||||
_sling := sling.New().Get(a.basePath)
|
||||
|
||||
// create path and map variables
|
||||
path := "/api/statistics/"
|
||||
fmt.Printf("project statistic path: %s\n", path)
|
||||
_sling = _sling.Path(path)
|
||||
var successPayload = new(apilib.StatisticMap)
|
||||
code, body, err := request(_sling, jsonAcceptHeader, user)
|
||||
if 200 == code && nil == err {
|
||||
err = json.Unmarshal(body, &successPayload)
|
||||
}
|
||||
return *successPayload, err
|
||||
}
|
||||
|
||||
func (a api) LogGet(user usrInfo, startTime, endTime, lines string) (int, []apilib.AccessLog, error) {
|
||||
_sling := sling.New().Get(a.basePath)
|
||||
|
||||
// create path and map variables
|
||||
path := "/api/logs/"
|
||||
fmt.Printf("logs path: %s\n", path)
|
||||
_sling = _sling.Path(path)
|
||||
type QueryParams struct {
|
||||
StartTime string `url:"start_time,omitempty"`
|
||||
EndTime string `url:"end_time,omitempty"`
|
||||
Lines string `url:"lines,omitempty"`
|
||||
}
|
||||
|
||||
_sling = _sling.QueryStruct(&QueryParams{StartTime: startTime, EndTime: endTime, Lines: lines})
|
||||
var successPayload []apilib.AccessLog
|
||||
code, body, err := request(_sling, jsonAcceptHeader, user)
|
||||
if 200 == code && nil == err {
|
||||
err = json.Unmarshal(body, &successPayload)
|
||||
}
|
||||
return code, successPayload, err
|
||||
}
|
||||
|
||||
////Delete a repository or a tag in a repository.
|
||||
@ -202,12 +249,9 @@ func (a api) ProjectsDelete(prjUsr usrInfo, projectID string) (int, error) {
|
||||
_sling := sling.New().Delete(a.basePath)
|
||||
|
||||
//create api path
|
||||
path := "api/projects/" + projectID + "/delete"
|
||||
path := "api/projects/" + projectID
|
||||
_sling = _sling.Path(path)
|
||||
|
||||
//_sling = _sling.BodyJSON(project)
|
||||
httpStatusCode, body, err := request(_sling, jsonAcceptHeader, prjUsr)
|
||||
fmt.Println(string(body))
|
||||
httpStatusCode, _, err := request(_sling, jsonAcceptHeader, prjUsr)
|
||||
return httpStatusCode, err
|
||||
}
|
||||
|
||||
@ -227,6 +271,23 @@ func (a api) ProjectsHead(prjUsr usrInfo, projectName string) (int, error) {
|
||||
return httpStatusCode, err
|
||||
}
|
||||
|
||||
//Return specific project detail infomation
|
||||
func (a api) ProjectsGetByPID(projectID string) (int, apilib.Project, error) {
|
||||
_sling := sling.New().Get(a.basePath)
|
||||
|
||||
//create api path
|
||||
path := "api/projects/" + projectID
|
||||
_sling = _sling.Path(path)
|
||||
|
||||
var successPayload apilib.Project
|
||||
|
||||
httpStatusCode, body, err := request(_sling, jsonAcceptHeader)
|
||||
if err == nil && httpStatusCode == 200 {
|
||||
err = json.Unmarshal(body, &successPayload)
|
||||
}
|
||||
return httpStatusCode, successPayload, err
|
||||
}
|
||||
|
||||
//Search projects by projectName and isPublic
|
||||
func (a api) ProjectsGet(projectName string, isPublic int32) (int, []apilib.Project, error) {
|
||||
_sling := sling.New().Get(a.basePath)
|
||||
@ -281,7 +342,7 @@ func (a api) ProjectLogsFilter(prjUsr usrInfo, projectID string, accessLog apili
|
||||
// body params
|
||||
_sling = _sling.BodyJSON(accessLog)
|
||||
|
||||
// var successPayload []apilib.AccessLog
|
||||
//var successPayload []apilib.AccessLog
|
||||
|
||||
httpStatusCode, body, err := request(_sling, jsonAcceptHeader, prjUsr)
|
||||
/*
|
||||
@ -293,17 +354,23 @@ func (a api) ProjectLogsFilter(prjUsr usrInfo, projectID string, accessLog apili
|
||||
// return httpStatusCode, successPayload, err
|
||||
}
|
||||
|
||||
//-------------------------Member Test---------------------------------------//
|
||||
|
||||
//Return relevant role members of projectID
|
||||
func (a api) GetProjectMembersByProID(prjUsr usrInfo, projectID string) (int, []byte, error) {
|
||||
_sling := sling.New().Post(a.basePath)
|
||||
func (a api) GetProjectMembersByProID(prjUsr usrInfo, projectID string) (int, []apilib.User, error) {
|
||||
_sling := sling.New().Get(a.basePath)
|
||||
|
||||
path := "/api/projects/" + projectID + "/members/"
|
||||
|
||||
_sling = _sling.Path(path)
|
||||
|
||||
httpStatusCode, body, err := request(_sling, jsonAcceptHeader, prjUsr)
|
||||
return httpStatusCode, body, err
|
||||
var successPayload []apilib.User
|
||||
|
||||
httpStatusCode, body, err := request(_sling, jsonAcceptHeader, prjUsr)
|
||||
if err == nil && httpStatusCode == 200 {
|
||||
err = json.Unmarshal(body, &successPayload)
|
||||
}
|
||||
return httpStatusCode, successPayload, err
|
||||
}
|
||||
|
||||
//Add project role member accompany with projectID
|
||||
|
51
api/internal.go
Normal file
51
api/internal.go
Normal file
@ -0,0 +1,51 @@
|
||||
/*
|
||||
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 api
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/vmware/harbor/dao"
|
||||
"github.com/vmware/harbor/utils/log"
|
||||
)
|
||||
|
||||
// InternalAPI handles request of harbor admin...
|
||||
type InternalAPI struct {
|
||||
BaseAPI
|
||||
}
|
||||
|
||||
// Prepare validates the URL and parms
|
||||
func (ia *InternalAPI) Prepare() {
|
||||
var currentUserID int
|
||||
currentUserID = ia.ValidateUser()
|
||||
isAdmin, err := dao.IsAdminRole(currentUserID)
|
||||
if err != nil {
|
||||
log.Errorf("Error occurred in IsAdminRole:%v", err)
|
||||
ia.CustomAbort(http.StatusInternalServerError, "Internal error.")
|
||||
}
|
||||
if !isAdmin {
|
||||
log.Error("Guests doesn't have the permisson to request harbor internal API.")
|
||||
ia.CustomAbort(http.StatusForbidden, "Guests doesn't have the permisson to request harbor internal API.")
|
||||
}
|
||||
}
|
||||
|
||||
// SyncRegistry ...
|
||||
func (ia *InternalAPI) SyncRegistry() {
|
||||
err := SyncRegistry()
|
||||
if err != nil {
|
||||
ia.CustomAbort(http.StatusInternalServerError, "internal error")
|
||||
}
|
||||
}
|
125
api/log_test.go
Normal file
125
api/log_test.go
Normal file
@ -0,0 +1,125 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/vmware/harbor/tests/apitests/apilib"
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestLogGet(t *testing.T) {
|
||||
|
||||
fmt.Println("Testing Log API")
|
||||
assert := assert.New(t)
|
||||
apiTest := newHarborAPI()
|
||||
|
||||
//prepare for test
|
||||
|
||||
admin := &usrInfo{"admin", "Harbor12345"}
|
||||
var project apilib.ProjectReq
|
||||
project.ProjectName = "my_project"
|
||||
project.Public = 1
|
||||
|
||||
//add the project first.
|
||||
fmt.Println("add the project first.")
|
||||
reply, err := apiTest.ProjectsPost(*admin, project)
|
||||
if err != nil {
|
||||
t.Error("Error while creat project", err.Error())
|
||||
t.Log(err)
|
||||
} else {
|
||||
assert.Equal(int(201), reply, "Case 2: Project creation status should be 201")
|
||||
}
|
||||
//case 1: right parameters, expect the right output
|
||||
now := fmt.Sprintf("%v", time.Now().Unix())
|
||||
statusCode, result, err := apiTest.LogGet(*admin, "0", now, "3")
|
||||
if err != nil {
|
||||
t.Error("Error while get log information", err.Error())
|
||||
t.Log(err)
|
||||
} else {
|
||||
assert.Equal(1, len(result), "lines of logs should be equal")
|
||||
assert.Equal(int32(1), result[0].LogId, "LogId should be equal")
|
||||
assert.Equal("my_project/", result[0].RepoName, "RepoName should be equal")
|
||||
assert.Equal("N/A", result[0].RepoTag, "RepoTag should be equal")
|
||||
assert.Equal("create", result[0].Operation, "Operation should be equal")
|
||||
}
|
||||
|
||||
//case 2: wrong format of start_time parameter, expect the wrong output
|
||||
now = fmt.Sprintf("%v", time.Now().Unix())
|
||||
statusCode, result, err = apiTest.LogGet(*admin, "ss", now, "3")
|
||||
if err != nil {
|
||||
t.Error("Error occured while get log information since the format of start_time parameter is not right.", err.Error())
|
||||
t.Log(err)
|
||||
} else {
|
||||
assert.Equal(int(400), statusCode, "Http status code should be 400")
|
||||
}
|
||||
|
||||
//case 3: wrong format of end_time parameter, expect the wrong output
|
||||
statusCode, result, err = apiTest.LogGet(*admin, "0", "cc", "3")
|
||||
if err != nil {
|
||||
t.Error("Error occured while get log information since the format of end_time parameter is not right.", err.Error())
|
||||
t.Log(err)
|
||||
} else {
|
||||
assert.Equal(int(400), statusCode, "Http status code should be 400")
|
||||
}
|
||||
|
||||
//case 4: wrong format of lines parameter, expect the wrong output
|
||||
now = fmt.Sprintf("%v", time.Now().Unix())
|
||||
statusCode, result, err = apiTest.LogGet(*admin, "0", now, "s")
|
||||
if err != nil {
|
||||
t.Error("Error occured while get log information since the format of lines parameter is not right.", err.Error())
|
||||
t.Log(err)
|
||||
} else {
|
||||
assert.Equal(int(400), statusCode, "Http status code should be 400")
|
||||
}
|
||||
|
||||
//case 5: wrong format of lines parameter, expect the wrong output
|
||||
now = fmt.Sprintf("%v", time.Now().Unix())
|
||||
statusCode, result, err = apiTest.LogGet(*admin, "0", now, "-5")
|
||||
if err != nil {
|
||||
t.Error("Error occured while get log information since the format of lines parameter is not right.", err.Error())
|
||||
t.Log(err)
|
||||
} else {
|
||||
assert.Equal(int(400), statusCode, "Http status code should be 400")
|
||||
}
|
||||
|
||||
//case 6: all parameters are null, expect the right output
|
||||
statusCode, result, err = apiTest.LogGet(*admin, "", "", "")
|
||||
if err != nil {
|
||||
t.Error("Error while get log information", err.Error())
|
||||
t.Log(err)
|
||||
} else {
|
||||
assert.Equal(1, len(result), "lines of logs should be equal")
|
||||
assert.Equal(int32(1), result[0].LogId, "LogId should be equal")
|
||||
assert.Equal("my_project/", result[0].RepoName, "RepoName should be equal")
|
||||
assert.Equal("N/A", result[0].RepoTag, "RepoTag should be equal")
|
||||
assert.Equal("create", result[0].Operation, "Operation should be equal")
|
||||
}
|
||||
|
||||
//get the project
|
||||
var projects []apilib.Project
|
||||
var addProjectID int32
|
||||
httpStatusCode, projects, err := apiTest.ProjectsGet(project.ProjectName, 1)
|
||||
if err != nil {
|
||||
t.Error("Error while search project by proName and isPublic", err.Error())
|
||||
t.Log(err)
|
||||
} else {
|
||||
assert.Equal(int(200), httpStatusCode, "httpStatusCode should be 200")
|
||||
addProjectID = projects[0].ProjectId
|
||||
}
|
||||
|
||||
//delete the project
|
||||
projectID := strconv.Itoa(int(addProjectID))
|
||||
httpStatusCode, err = apiTest.ProjectsDelete(*admin, projectID)
|
||||
if err != nil {
|
||||
t.Error("Error while delete project", err.Error())
|
||||
t.Log(err)
|
||||
} else {
|
||||
assert.Equal(int(200), httpStatusCode, "Case 1: Project creation status should be 200")
|
||||
//t.Log(result)
|
||||
}
|
||||
|
||||
fmt.Printf("\n")
|
||||
|
||||
}
|
54
api/member_test.go
Normal file
54
api/member_test.go
Normal file
@ -0,0 +1,54 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/vmware/harbor/tests/apitests/apilib"
|
||||
)
|
||||
|
||||
func TestMemGet(t *testing.T) {
|
||||
var result []apilib.User
|
||||
var httpStatusCode int
|
||||
var err error
|
||||
|
||||
assert := assert.New(t)
|
||||
apiTest := newHarborAPI()
|
||||
projectID := "1"
|
||||
|
||||
fmt.Println("Testing Member Get API")
|
||||
//-------------------case 1 : response code = 200------------------------//
|
||||
httpStatusCode, result, err = apiTest.GetProjectMembersByProID(*admin, projectID)
|
||||
if err != nil {
|
||||
t.Error("Error whihle get members by projectID", err.Error())
|
||||
t.Log(err)
|
||||
} else {
|
||||
assert.Equal(int(200), httpStatusCode, "httpStatusCode should be 200")
|
||||
assert.Equal(int(1), result[0].UserId, "User Id should be 1")
|
||||
assert.Equal("admin", result[0].Username, "User name should be admin")
|
||||
}
|
||||
|
||||
//---------case 2: Response Code=401,User need to log in first.----------//
|
||||
fmt.Println("case 2: Response Code=401,User need to log in first.")
|
||||
httpStatusCode, result, err = apiTest.GetProjectMembersByProID(*unknownUsr, projectID)
|
||||
if err != nil {
|
||||
t.Error("Error while get members by projectID", err.Error())
|
||||
t.Log(err)
|
||||
} else {
|
||||
assert.Equal(int(401), httpStatusCode, "Case 2: Project creation status should be 401")
|
||||
}
|
||||
|
||||
//------------case 3: Response Code=404,Project does not exist-----------//
|
||||
fmt.Println("case 3: Response Code=404,Project does not exist")
|
||||
projectID = "11"
|
||||
httpStatusCode, result, err = apiTest.GetProjectMembersByProID(*admin, projectID)
|
||||
if err != nil {
|
||||
t.Error("Error while get members by projectID", err.Error())
|
||||
t.Log(err)
|
||||
} else {
|
||||
assert.Equal(int(404), httpStatusCode, "Case 3: Project creation status should be 404")
|
||||
}
|
||||
fmt.Printf("\n")
|
||||
|
||||
}
|
@ -290,7 +290,14 @@ func (p *ProjectAPI) List() {
|
||||
projectList[i].Togglable = true
|
||||
}
|
||||
}
|
||||
projectList[i].RepoCount = getRepoCountByProject(projectList[i].Name)
|
||||
|
||||
repos, err := dao.GetRepositoryByProjectName(projectList[i].Name)
|
||||
if err != nil {
|
||||
log.Errorf("failed to get repositories of project %s: %v", projectList[i].Name, err)
|
||||
p.CustomAbort(http.StatusInternalServerError, "")
|
||||
}
|
||||
|
||||
projectList[i].RepoCount = len(repos)
|
||||
}
|
||||
|
||||
p.setPaginationHeader(total, page, pageSize)
|
||||
@ -332,13 +339,18 @@ func (p *ProjectAPI) FilterAccessLog() {
|
||||
p.DecodeJSONReq(&query)
|
||||
|
||||
query.ProjectID = p.projectID
|
||||
query.Username = "%" + query.Username + "%"
|
||||
query.BeginTime = time.Unix(query.BeginTimestamp, 0)
|
||||
query.EndTime = time.Unix(query.EndTimestamp, 0)
|
||||
|
||||
page, pageSize := p.getPaginationParams()
|
||||
|
||||
logs, total, err := dao.GetAccessLogs(query, pageSize, pageSize*(page-1))
|
||||
total, err := dao.GetTotalOfAccessLogs(query)
|
||||
if err != nil {
|
||||
log.Errorf("failed to get total of access log: %v", err)
|
||||
p.CustomAbort(http.StatusInternalServerError, "")
|
||||
}
|
||||
|
||||
logs, err := dao.GetAccessLogs(query, pageSize, pageSize*(page-1))
|
||||
if err != nil {
|
||||
log.Errorf("failed to get access log: %v", err)
|
||||
p.CustomAbort(http.StatusInternalServerError, "")
|
||||
|
@ -9,15 +9,11 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
var admin, unknownUsr *usrInfo
|
||||
var addProject apilib.Project
|
||||
|
||||
func Init() {
|
||||
admin = &usrInfo{"admin", "Harbor12345"}
|
||||
unknownUsr = &usrInfo{"unknown", "unknown"}
|
||||
addProject.ProjectName = "test_project"
|
||||
addProject.Public = 1
|
||||
var addProject *apilib.ProjectReq
|
||||
var addPID int
|
||||
|
||||
func InitAddPro() {
|
||||
addProject = &apilib.ProjectReq{"test_project", 1}
|
||||
}
|
||||
|
||||
func TestAddProject(t *testing.T) {
|
||||
@ -28,11 +24,11 @@ func TestAddProject(t *testing.T) {
|
||||
apiTest := newHarborAPI()
|
||||
|
||||
//prepare for test
|
||||
Init()
|
||||
InitAddPro()
|
||||
|
||||
//case 1: admin not login, expect project creation fail.
|
||||
|
||||
result, err := apiTest.ProjectsPost(*unknownUsr, addProject)
|
||||
result, err := apiTest.ProjectsPost(*unknownUsr, *addProject)
|
||||
if err != nil {
|
||||
t.Error("Error while creat project", err.Error())
|
||||
t.Log(err)
|
||||
@ -44,9 +40,7 @@ func TestAddProject(t *testing.T) {
|
||||
//case 2: admin successful login, expect project creation success.
|
||||
fmt.Println("case 2: admin successful login, expect project creation success.")
|
||||
|
||||
unknownUsr = admin
|
||||
|
||||
result, err = apiTest.ProjectsPost(*admin, addProject)
|
||||
result, err = apiTest.ProjectsPost(*admin, *addProject)
|
||||
if err != nil {
|
||||
t.Error("Error while creat project", err.Error())
|
||||
t.Log(err)
|
||||
@ -58,7 +52,7 @@ func TestAddProject(t *testing.T) {
|
||||
//case 3: duplicate project name, create project fail
|
||||
fmt.Println("case 3: duplicate project name, create project fail")
|
||||
|
||||
result, err = apiTest.ProjectsPost(*admin, addProject)
|
||||
result, err = apiTest.ProjectsPost(*admin, *addProject)
|
||||
if err != nil {
|
||||
t.Error("Error while creat project", err.Error())
|
||||
t.Log(err)
|
||||
@ -66,12 +60,25 @@ func TestAddProject(t *testing.T) {
|
||||
assert.Equal(int(409), result, "Case 3: Project creation status should be 409")
|
||||
//t.Log(result)
|
||||
}
|
||||
|
||||
//case 4: reponse code = 400 : Project name is illegal in length
|
||||
fmt.Println("case 4 : reponse code = 400 : Project name is illegal in length ")
|
||||
|
||||
result, err = apiTest.ProjectsPost(*admin, apilib.ProjectReq{"t", 1})
|
||||
if err != nil {
|
||||
t.Error("Error while creat project", err.Error())
|
||||
t.Log(err)
|
||||
} else {
|
||||
assert.Equal(int(400), result, "case 4 : reponse code = 400 : Project name is illegal in length ")
|
||||
}
|
||||
|
||||
fmt.Printf("\n")
|
||||
|
||||
}
|
||||
|
||||
func TestProGet(t *testing.T) {
|
||||
fmt.Println("\nTest for Project GET API")
|
||||
//Get project by proName
|
||||
func TestProGetByName(t *testing.T) {
|
||||
fmt.Println("\nTest for Project GET API by project name")
|
||||
assert := assert.New(t)
|
||||
|
||||
apiTest := newHarborAPI()
|
||||
@ -85,10 +92,11 @@ func TestProGet(t *testing.T) {
|
||||
t.Log(err)
|
||||
} else {
|
||||
assert.Equal(int(200), httpStatusCode, "httpStatusCode should be 200")
|
||||
assert.Equal(addProject.ProjectName, result[0].ProjectName, "Project name is wrong")
|
||||
assert.Equal(int32(1), result[0].Public, "Public is wrong")
|
||||
//find add projectID
|
||||
addProject.ProjectId = int32(result[0].ProjectId)
|
||||
addPID = int(result[0].ProjectId)
|
||||
}
|
||||
|
||||
//----------------------------case 2 : Response Code=401:is_public=0----------------------------//
|
||||
fmt.Println("case 2: respose code:401,isPublic = 0")
|
||||
httpStatusCode, result, err = apiTest.ProjectsGet("library", 0)
|
||||
@ -98,10 +106,31 @@ func TestProGet(t *testing.T) {
|
||||
} else {
|
||||
assert.Equal(int(401), httpStatusCode, "httpStatusCode should be 200")
|
||||
}
|
||||
|
||||
fmt.Printf("\n")
|
||||
}
|
||||
|
||||
//Get project by proID
|
||||
func TestProGetByID(t *testing.T) {
|
||||
fmt.Println("\nTest for Project GET API by project id")
|
||||
assert := assert.New(t)
|
||||
|
||||
apiTest := newHarborAPI()
|
||||
var result apilib.Project
|
||||
projectID := strconv.Itoa(addPID)
|
||||
|
||||
//----------------------------case 1 : Response Code=200----------------------------//
|
||||
fmt.Println("case 1: respose code:200")
|
||||
httpStatusCode, result, err := apiTest.ProjectsGetByPID(projectID)
|
||||
if err != nil {
|
||||
t.Error("Error while search project by proID", err.Error())
|
||||
t.Log(err)
|
||||
} else {
|
||||
assert.Equal(int(200), httpStatusCode, "httpStatusCode should be 200")
|
||||
assert.Equal(addProject.ProjectName, result.ProjectName, "ProjectName is wrong")
|
||||
assert.Equal(int32(1), result.Public, "Public is wrong")
|
||||
}
|
||||
fmt.Printf("\n")
|
||||
}
|
||||
func TestDeleteProject(t *testing.T) {
|
||||
|
||||
fmt.Println("\nTesting Delete Project(ProjectsPost) API")
|
||||
@ -109,23 +138,53 @@ func TestDeleteProject(t *testing.T) {
|
||||
|
||||
apiTest := newHarborAPI()
|
||||
|
||||
projectID := strconv.Itoa(int(addProject.ProjectId))
|
||||
//--------------------------case 1: Response Code=200---------------------------------//
|
||||
projectID := strconv.Itoa(addPID)
|
||||
|
||||
httpStatusCode, err := apiTest.ProjectsDelete(*admin, projectID)
|
||||
//--------------------------case 1: Response Code=401,User need to log in first.-----------------------//
|
||||
fmt.Println("case 1: Response Code=401,User need to log in first.")
|
||||
httpStatusCode, err := apiTest.ProjectsDelete(*unknownUsr, projectID)
|
||||
if err != nil {
|
||||
t.Error("Error while delete project", err.Error())
|
||||
t.Log(err)
|
||||
} else {
|
||||
assert.Equal(int(200), httpStatusCode, "Case 1: Project creation status should be 200")
|
||||
//t.Log(result)
|
||||
assert.Equal(int(401), httpStatusCode, "Case 1: Project creation status should be 401")
|
||||
}
|
||||
|
||||
//--------------------------case 2: Response Code=200---------------------------------//
|
||||
fmt.Println("case2: respose code:200")
|
||||
httpStatusCode, err = apiTest.ProjectsDelete(*admin, projectID)
|
||||
if err != nil {
|
||||
t.Error("Error while delete project", err.Error())
|
||||
t.Log(err)
|
||||
} else {
|
||||
assert.Equal(int(200), httpStatusCode, "Case 2: Project creation status should be 200")
|
||||
}
|
||||
|
||||
//--------------------------case 3: Response Code=404,Project does not exist---------------------------------//
|
||||
fmt.Println("case 3: Response Code=404,Project does not exist")
|
||||
projectID = "11"
|
||||
httpStatusCode, err = apiTest.ProjectsDelete(*admin, projectID)
|
||||
if err != nil {
|
||||
t.Error("Error while delete project", err.Error())
|
||||
t.Log(err)
|
||||
} else {
|
||||
assert.Equal(int(404), httpStatusCode, "Case 3: Project creation status should be 404")
|
||||
}
|
||||
|
||||
//--------------------------case 4: Response Code=400,Invalid project id.---------------------------------//
|
||||
fmt.Println("case 4: Response Code=400,Invalid project id.")
|
||||
projectID = "cc"
|
||||
httpStatusCode, err = apiTest.ProjectsDelete(*admin, projectID)
|
||||
if err != nil {
|
||||
t.Error("Error while delete project", err.Error())
|
||||
t.Log(err)
|
||||
} else {
|
||||
assert.Equal(int(400), httpStatusCode, "Case 4: Project creation status should be 400")
|
||||
}
|
||||
fmt.Printf("\n")
|
||||
|
||||
}
|
||||
func TestProHead(t *testing.T) {
|
||||
Init()
|
||||
fmt.Println("\nTest for Project HEAD API")
|
||||
assert := assert.New(t)
|
||||
|
||||
|
@ -91,15 +91,17 @@ func (pa *RepPolicyAPI) Post() {
|
||||
policy := &models.RepPolicy{}
|
||||
pa.DecodeJSONReqAndValidate(policy)
|
||||
|
||||
po, err := dao.GetRepPolicyByName(policy.Name)
|
||||
if err != nil {
|
||||
log.Errorf("failed to get policy %s: %v", policy.Name, err)
|
||||
pa.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
|
||||
}
|
||||
/*
|
||||
po, err := dao.GetRepPolicyByName(policy.Name)
|
||||
if err != nil {
|
||||
log.Errorf("failed to get policy %s: %v", policy.Name, err)
|
||||
pa.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
|
||||
}
|
||||
|
||||
if po != nil {
|
||||
pa.CustomAbort(http.StatusConflict, "name is already used")
|
||||
}
|
||||
if po != nil {
|
||||
pa.CustomAbort(http.StatusConflict, "name is already used")
|
||||
}
|
||||
*/
|
||||
|
||||
project, err := dao.GetProjectByID(policy.ProjectID)
|
||||
if err != nil {
|
||||
@ -169,18 +171,20 @@ func (pa *RepPolicyAPI) Put() {
|
||||
policy.ProjectID = originalPolicy.ProjectID
|
||||
pa.Validate(policy)
|
||||
|
||||
// check duplicate name
|
||||
if policy.Name != originalPolicy.Name {
|
||||
po, err := dao.GetRepPolicyByName(policy.Name)
|
||||
if err != nil {
|
||||
log.Errorf("failed to get policy %s: %v", policy.Name, err)
|
||||
pa.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
|
||||
}
|
||||
/*
|
||||
// check duplicate name
|
||||
if policy.Name != originalPolicy.Name {
|
||||
po, err := dao.GetRepPolicyByName(policy.Name)
|
||||
if err != nil {
|
||||
log.Errorf("failed to get policy %s: %v", policy.Name, err)
|
||||
pa.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
|
||||
}
|
||||
|
||||
if po != nil {
|
||||
pa.CustomAbort(http.StatusConflict, "name is already used")
|
||||
if po != nil {
|
||||
pa.CustomAbort(http.StatusConflict, "name is already used")
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
if policy.TargetID != originalPolicy.TargetID {
|
||||
//target of policy can not be modified when the policy is enabled
|
||||
|
@ -21,8 +21,6 @@ import (
|
||||
"net/http"
|
||||
"os"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/distribution/manifest/schema1"
|
||||
"github.com/docker/distribution/manifest/schema2"
|
||||
@ -35,6 +33,7 @@ import (
|
||||
|
||||
registry_error "github.com/vmware/harbor/utils/registry/error"
|
||||
|
||||
"github.com/vmware/harbor/utils"
|
||||
"github.com/vmware/harbor/utils/registry/auth"
|
||||
)
|
||||
|
||||
@ -108,7 +107,7 @@ func (ra *RepositoryAPI) Delete() {
|
||||
ra.CustomAbort(http.StatusBadRequest, "repo_name is nil")
|
||||
}
|
||||
|
||||
projectName := getProjectName(repoName)
|
||||
projectName, _ := utils.ParseRepository(repoName)
|
||||
project, err := dao.GetProjectByName(projectName)
|
||||
if err != nil {
|
||||
log.Errorf("failed to get project %s: %v", projectName, err)
|
||||
@ -182,6 +181,18 @@ func (ra *RepositoryAPI) Delete() {
|
||||
}(t)
|
||||
}
|
||||
|
||||
exist, err := repositoryExist(repoName, rc)
|
||||
if err != nil {
|
||||
log.Errorf("failed to check the existence of repository %s: %v", repoName, err)
|
||||
ra.CustomAbort(http.StatusInternalServerError, "")
|
||||
}
|
||||
if !exist {
|
||||
if err = dao.DeleteRepository(repoName); err != nil {
|
||||
log.Errorf("failed to delete repository %s: %v", repoName, err)
|
||||
ra.CustomAbort(http.StatusInternalServerError, "")
|
||||
}
|
||||
}
|
||||
|
||||
go func() {
|
||||
log.Debug("refreshing catalog cache")
|
||||
if err := cache.RefreshCatalogCache(); err != nil {
|
||||
@ -202,7 +213,7 @@ func (ra *RepositoryAPI) GetTags() {
|
||||
ra.CustomAbort(http.StatusBadRequest, "repo_name is nil")
|
||||
}
|
||||
|
||||
projectName := getProjectName(repoName)
|
||||
projectName, _ := utils.ParseRepository(repoName)
|
||||
project, err := dao.GetProjectByName(projectName)
|
||||
if err != nil {
|
||||
log.Errorf("failed to get project %s: %v", projectName, err)
|
||||
@ -270,7 +281,7 @@ func (ra *RepositoryAPI) GetManifests() {
|
||||
ra.CustomAbort(http.StatusBadRequest, "version should be v1 or v2")
|
||||
}
|
||||
|
||||
projectName := getProjectName(repoName)
|
||||
projectName, _ := utils.ParseRepository(repoName)
|
||||
project, err := dao.GetProjectByName(projectName)
|
||||
if err != nil {
|
||||
log.Errorf("failed to get project %s: %v", projectName, err)
|
||||
@ -397,25 +408,14 @@ func (ra *RepositoryAPI) getUsername() (string, error) {
|
||||
|
||||
//GetTopRepos handles request GET /api/repositories/top
|
||||
func (ra *RepositoryAPI) GetTopRepos() {
|
||||
var err error
|
||||
var countNum int
|
||||
count := ra.GetString("count")
|
||||
if len(count) == 0 {
|
||||
countNum = 10
|
||||
} else {
|
||||
countNum, err = strconv.Atoi(count)
|
||||
if err != nil {
|
||||
log.Errorf("Get parameters error--count, err: %v", err)
|
||||
ra.CustomAbort(http.StatusBadRequest, "bad request of count")
|
||||
}
|
||||
if countNum <= 0 {
|
||||
log.Warning("count must be a positive integer")
|
||||
ra.CustomAbort(http.StatusBadRequest, "count is 0 or negative")
|
||||
}
|
||||
count, err := ra.GetInt("count", 10)
|
||||
if err != nil || count <= 0 {
|
||||
ra.CustomAbort(http.StatusBadRequest, "invalid count")
|
||||
}
|
||||
repos, err := dao.GetTopRepos(countNum)
|
||||
|
||||
repos, err := dao.GetTopRepos(count)
|
||||
if err != nil {
|
||||
log.Errorf("error occured in get top 10 repos: %v", err)
|
||||
log.Errorf("failed to get top repos: %v", err)
|
||||
ra.CustomAbort(http.StatusInternalServerError, "internal server error")
|
||||
}
|
||||
ra.Data["json"] = repos
|
||||
@ -439,11 +439,3 @@ func newRepositoryClient(endpoint string, insecure bool, username, password, rep
|
||||
}
|
||||
return client, nil
|
||||
}
|
||||
|
||||
func getProjectName(repository string) string {
|
||||
project := ""
|
||||
if strings.Contains(repository, "/") {
|
||||
project = repository[0:strings.LastIndex(repository, "/")]
|
||||
}
|
||||
return project
|
||||
}
|
||||
|
128
api/statistic.go
128
api/statistic.go
@ -17,14 +17,26 @@ package api
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/vmware/harbor/dao"
|
||||
"github.com/vmware/harbor/models"
|
||||
"github.com/vmware/harbor/service/cache"
|
||||
"github.com/vmware/harbor/utils/log"
|
||||
)
|
||||
|
||||
const (
|
||||
// MPC : count of my projects
|
||||
MPC = "my_project_count"
|
||||
// MRC : count of my repositories
|
||||
MRC = "my_repo_count"
|
||||
// PPC : count of public projects
|
||||
PPC = "public_project_count"
|
||||
// PRC : count of public repositories
|
||||
PRC = "public_repo_count"
|
||||
// TPC : total count of projects
|
||||
TPC = "total_project_count"
|
||||
// TRC : total count of repositories
|
||||
TRC = "total_repo_count"
|
||||
)
|
||||
|
||||
// StatisticAPI handles request to /api/statistics/
|
||||
type StatisticAPI struct {
|
||||
BaseAPI
|
||||
@ -38,80 +50,60 @@ func (s *StatisticAPI) Prepare() {
|
||||
|
||||
// Get total projects and repos of the user
|
||||
func (s *StatisticAPI) Get() {
|
||||
statistic := map[string]int64{}
|
||||
|
||||
n, err := dao.GetTotalOfProjects("", 1)
|
||||
if err != nil {
|
||||
log.Errorf("failed to get total of public projects: %v", err)
|
||||
s.CustomAbort(http.StatusInternalServerError, "")
|
||||
}
|
||||
statistic[PPC] = n
|
||||
|
||||
n, err = dao.GetTotalOfPublicRepositories("")
|
||||
if err != nil {
|
||||
log.Errorf("failed to get total of public repositories: %v", err)
|
||||
s.CustomAbort(http.StatusInternalServerError, "")
|
||||
}
|
||||
statistic[PRC] = n
|
||||
|
||||
isAdmin, err := dao.IsAdminRole(s.userID)
|
||||
if err != nil {
|
||||
log.Errorf("Error occured in check admin, error: %v", err)
|
||||
s.CustomAbort(http.StatusInternalServerError, "Internal error.")
|
||||
}
|
||||
var projectList []models.Project
|
||||
|
||||
if isAdmin {
|
||||
projectList, err = dao.GetProjects("")
|
||||
n, err := dao.GetTotalOfProjects("")
|
||||
if err != nil {
|
||||
log.Errorf("failed to get total of projects: %v", err)
|
||||
s.CustomAbort(http.StatusInternalServerError, "")
|
||||
}
|
||||
statistic[MPC] = n
|
||||
statistic[TPC] = n
|
||||
|
||||
n, err = dao.GetTotalOfRepositories("")
|
||||
if err != nil {
|
||||
log.Errorf("failed to get total of repositories: %v", err)
|
||||
s.CustomAbort(http.StatusInternalServerError, "")
|
||||
}
|
||||
statistic[MRC] = n
|
||||
statistic[TRC] = n
|
||||
} else {
|
||||
projectList, err = dao.GetUserRelevantProjects(s.userID, "")
|
||||
}
|
||||
if err != nil {
|
||||
log.Errorf("Error occured in QueryProject, error: %v", err)
|
||||
s.CustomAbort(http.StatusInternalServerError, "Internal error.")
|
||||
}
|
||||
proMap := map[string]int{}
|
||||
proMap["my_project_count"] = 0
|
||||
proMap["my_repo_count"] = 0
|
||||
proMap["public_project_count"] = 0
|
||||
proMap["public_repo_count"] = 0
|
||||
var publicProjects []models.Project
|
||||
publicProjects, err = dao.GetProjects("", 1)
|
||||
if err != nil {
|
||||
log.Errorf("Error occured in QueryPublicProject, error: %v", err)
|
||||
s.CustomAbort(http.StatusInternalServerError, "Internal error.")
|
||||
}
|
||||
proMap["public_project_count"] = len(publicProjects)
|
||||
for i := 0; i < len(publicProjects); i++ {
|
||||
proMap["public_repo_count"] += getRepoCountByProject(publicProjects[i].Name)
|
||||
}
|
||||
if isAdmin {
|
||||
proMap["total_project_count"] = len(projectList)
|
||||
proMap["total_repo_count"] = getTotalRepoCount()
|
||||
}
|
||||
for i := 0; i < len(projectList); i++ {
|
||||
if isAdmin {
|
||||
projectList[i].Role = models.PROJECTADMIN
|
||||
n, err := dao.GetTotalOfUserRelevantProjects(s.userID, "")
|
||||
if err != nil {
|
||||
log.Errorf("failed to get total of projects for user %d: %v", s.userID, err)
|
||||
s.CustomAbort(http.StatusInternalServerError, "")
|
||||
}
|
||||
if projectList[i].Role == models.PROJECTADMIN || projectList[i].Role == models.DEVELOPER ||
|
||||
projectList[i].Role == models.GUEST {
|
||||
proMap["my_project_count"]++
|
||||
proMap["my_repo_count"] += getRepoCountByProject(projectList[i].Name)
|
||||
statistic[MPC] = n
|
||||
|
||||
n, err = dao.GetTotalOfUserRelevantRepositories(s.userID, "")
|
||||
if err != nil {
|
||||
log.Errorf("failed to get total of repositories for user %d: %v", s.userID, err)
|
||||
s.CustomAbort(http.StatusInternalServerError, "")
|
||||
}
|
||||
statistic[MRC] = n
|
||||
}
|
||||
s.Data["json"] = proMap
|
||||
|
||||
s.Data["json"] = statistic
|
||||
s.ServeJSON()
|
||||
}
|
||||
|
||||
//getReposByProject returns repo numbers of specified project
|
||||
func getRepoCountByProject(projectName string) int {
|
||||
repoList, err := cache.GetRepoFromCache()
|
||||
if err != nil {
|
||||
log.Errorf("Failed to get repo from cache, error: %v", err)
|
||||
return 0
|
||||
}
|
||||
var resp int
|
||||
if len(projectName) > 0 {
|
||||
for _, r := range repoList {
|
||||
if strings.Contains(r, "/") && r[0:strings.LastIndex(r, "/")] == projectName {
|
||||
resp++
|
||||
}
|
||||
}
|
||||
return resp
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
//getTotalRepoCount returns total repo count
|
||||
func getTotalRepoCount() int {
|
||||
repoList, err := cache.GetRepoFromCache()
|
||||
if err != nil {
|
||||
log.Errorf("Failed to get repo from cache, error: %v", err)
|
||||
return 0
|
||||
}
|
||||
return len(repoList)
|
||||
|
||||
}
|
||||
|
91
api/statistic_test.go
Normal file
91
api/statistic_test.go
Normal file
@ -0,0 +1,91 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/vmware/harbor/tests/apitests/apilib"
|
||||
)
|
||||
|
||||
func TestStatisticGet(t *testing.T) {
|
||||
if err := SyncRegistry(); err != nil {
|
||||
t.Fatalf("failed to sync repositories from registry: %v", err)
|
||||
}
|
||||
|
||||
fmt.Println("Testing Statistic API")
|
||||
assert := assert.New(t)
|
||||
|
||||
apiTest := newHarborAPI()
|
||||
|
||||
//prepare for test
|
||||
|
||||
admin := &usrInfo{"admin", "Harbor12345"}
|
||||
|
||||
var myProCount, pubProCount, totalProCount int32
|
||||
result, err := apiTest.StatisticGet(*admin)
|
||||
if err != nil {
|
||||
t.Error("Error while get statistic information", err.Error())
|
||||
t.Log(err)
|
||||
} else {
|
||||
myProCount = result.MyProjectCount
|
||||
pubProCount = result.PublicProjectCount
|
||||
totalProCount = result.TotalProjectCount
|
||||
}
|
||||
//post project
|
||||
var project apilib.ProjectReq
|
||||
project.ProjectName = "statistic_project"
|
||||
project.Public = 1
|
||||
|
||||
//case 2: admin successful login, expect project creation success.
|
||||
fmt.Println("case 2: admin successful login, expect project creation success.")
|
||||
reply, err := apiTest.ProjectsPost(*admin, project)
|
||||
if err != nil {
|
||||
t.Error("Error while creat project", err.Error())
|
||||
t.Log(err)
|
||||
} else {
|
||||
assert.Equal(reply, int(201), "Case 2: Project creation status should be 201")
|
||||
}
|
||||
|
||||
//get and compare
|
||||
result, err = apiTest.StatisticGet(*admin)
|
||||
if err != nil {
|
||||
t.Error("Error while get statistic information", err.Error())
|
||||
t.Log(err)
|
||||
} else {
|
||||
assert.Equal(myProCount+1, result.MyProjectCount, "MyProjectCount should be equal")
|
||||
assert.Equal(int32(2), result.MyRepoCount, "MyRepoCount should be equal")
|
||||
assert.Equal(pubProCount+1, result.PublicProjectCount, "PublicProjectCount should be equal")
|
||||
assert.Equal(int32(2), result.PublicRepoCount, "PublicRepoCount should be equal")
|
||||
assert.Equal(totalProCount+1, result.TotalProjectCount, "TotalProCount should be equal")
|
||||
assert.Equal(int32(2), result.TotalRepoCount, "TotalRepoCount should be equal")
|
||||
|
||||
}
|
||||
|
||||
//get the project
|
||||
var projects []apilib.Project
|
||||
var addProjectID int32
|
||||
httpStatusCode, projects, err := apiTest.ProjectsGet(project.ProjectName, 1)
|
||||
if err != nil {
|
||||
t.Error("Error while search project by proName and isPublic", err.Error())
|
||||
t.Log(err)
|
||||
} else {
|
||||
assert.Equal(int(200), httpStatusCode, "httpStatusCode should be 200")
|
||||
addProjectID = projects[0].ProjectId
|
||||
}
|
||||
|
||||
//delete the project
|
||||
projectID := strconv.Itoa(int(addProjectID))
|
||||
httpStatusCode, err = apiTest.ProjectsDelete(*admin, projectID)
|
||||
if err != nil {
|
||||
t.Error("Error while delete project", err.Error())
|
||||
t.Log(err)
|
||||
} else {
|
||||
assert.Equal(int(200), httpStatusCode, "Case 1: Project creation status should be 200")
|
||||
//t.Log(result)
|
||||
}
|
||||
|
||||
fmt.Printf("\n")
|
||||
|
||||
}
|
236
api/utils.go
236
api/utils.go
@ -20,15 +20,20 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/vmware/harbor/dao"
|
||||
"github.com/vmware/harbor/models"
|
||||
"github.com/vmware/harbor/service/cache"
|
||||
"github.com/vmware/harbor/utils"
|
||||
"github.com/vmware/harbor/utils/log"
|
||||
"github.com/vmware/harbor/utils/registry"
|
||||
registry_error "github.com/vmware/harbor/utils/registry/error"
|
||||
)
|
||||
|
||||
func checkProjectPermission(userID int, projectID int64) bool {
|
||||
@ -235,6 +240,207 @@ func addAuthentication(req *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
// SyncRegistry syncs the repositories of registry with database.
|
||||
func SyncRegistry() error {
|
||||
|
||||
log.Debugf("Start syncing repositories from registry to DB... ")
|
||||
|
||||
reposInRegistry, err := catalog()
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return err
|
||||
}
|
||||
|
||||
var repoRecordsInDB []models.RepoRecord
|
||||
repoRecordsInDB, err = dao.GetAllRepositories()
|
||||
if err != nil {
|
||||
log.Errorf("error occurred while getting all registories. %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
var reposInDB []string
|
||||
for _, repoRecordInDB := range repoRecordsInDB {
|
||||
reposInDB = append(reposInDB, repoRecordInDB.Name)
|
||||
}
|
||||
|
||||
var reposToAdd []string
|
||||
var reposToDel []string
|
||||
reposToAdd, reposToDel = diffRepos(reposInRegistry, reposInDB)
|
||||
if len(reposToAdd) > 0 {
|
||||
log.Debugf("Start adding repositories into DB... ")
|
||||
for _, repoToAdd := range reposToAdd {
|
||||
project, _ := utils.ParseRepository(repoToAdd)
|
||||
user, err := dao.GetAccessLogCreator(repoToAdd)
|
||||
if err != nil {
|
||||
log.Errorf("Error happens when getting the repository owner from access log: %v", err)
|
||||
}
|
||||
if len(user) == 0 {
|
||||
user = "anonymous"
|
||||
}
|
||||
pullCount, err := dao.CountPull(repoToAdd)
|
||||
if err != nil {
|
||||
log.Errorf("Error happens when counting pull count from access log: %v", err)
|
||||
}
|
||||
repoRecord := models.RepoRecord{Name: repoToAdd, OwnerName: user, ProjectName: project, PullCount: pullCount}
|
||||
if err := dao.AddRepository(repoRecord); err != nil {
|
||||
log.Errorf("Error happens when adding the missing repository: %v", err)
|
||||
}
|
||||
log.Debugf("Add repository: %s success.", repoToAdd)
|
||||
}
|
||||
}
|
||||
|
||||
if len(reposToDel) > 0 {
|
||||
log.Debugf("Start deleting repositories from DB... ")
|
||||
for _, repoToDel := range reposToDel {
|
||||
if err := dao.DeleteRepository(repoToDel); err != nil {
|
||||
log.Errorf("Error happens when deleting the repository: %v", err)
|
||||
}
|
||||
log.Debugf("Delete repository: %s success.", repoToDel)
|
||||
}
|
||||
}
|
||||
|
||||
log.Debugf("Sync repositories from registry to DB is done.")
|
||||
return nil
|
||||
}
|
||||
|
||||
func catalog() ([]string, error) {
|
||||
repositories := []string{}
|
||||
|
||||
rc, err := initRegistryClient()
|
||||
if err != nil {
|
||||
return repositories, err
|
||||
}
|
||||
|
||||
repos, err := rc.Catalog()
|
||||
if err != nil {
|
||||
return repositories, err
|
||||
}
|
||||
|
||||
for _, repo := range repos {
|
||||
// TODO remove the workaround when the bug of registry is fixed
|
||||
// TODO read it from config
|
||||
endpoint := os.Getenv("REGISTRY_URL")
|
||||
client, err := cache.NewRepositoryClient(endpoint, true,
|
||||
"admin", repo, "repository", repo)
|
||||
if err != nil {
|
||||
return repositories, err
|
||||
}
|
||||
|
||||
exist, err := repositoryExist(repo, client)
|
||||
if err != nil {
|
||||
return repositories, err
|
||||
}
|
||||
|
||||
if !exist {
|
||||
continue
|
||||
}
|
||||
|
||||
repositories = append(repositories, repo)
|
||||
}
|
||||
|
||||
return repositories, nil
|
||||
}
|
||||
|
||||
func diffRepos(reposInRegistry []string, reposInDB []string) ([]string, []string) {
|
||||
var needsAdd []string
|
||||
var needsDel []string
|
||||
|
||||
sort.Strings(reposInRegistry)
|
||||
sort.Strings(reposInDB)
|
||||
|
||||
i, j := 0, 0
|
||||
repoInR, repoInD := "", ""
|
||||
for i < len(reposInRegistry) && j < len(reposInDB) {
|
||||
repoInR = reposInRegistry[i]
|
||||
repoInD = reposInDB[j]
|
||||
d := strings.Compare(repoInR, repoInD)
|
||||
if d < 0 {
|
||||
i++
|
||||
exist, err := projectExists(repoInR)
|
||||
if err != nil {
|
||||
log.Errorf("failed to check the existence of project %s: %v", repoInR, err)
|
||||
continue
|
||||
}
|
||||
|
||||
if !exist {
|
||||
continue
|
||||
}
|
||||
|
||||
needsAdd = append(needsAdd, repoInR)
|
||||
} else if d > 0 {
|
||||
needsDel = append(needsDel, repoInD)
|
||||
j++
|
||||
} else {
|
||||
i++
|
||||
j++
|
||||
}
|
||||
}
|
||||
|
||||
for i < len(reposInRegistry) {
|
||||
repoInR = reposInRegistry[i]
|
||||
i++
|
||||
exist, err := projectExists(repoInR)
|
||||
if err != nil {
|
||||
log.Errorf("failed to check whether project of %s exists: %v", repoInR, err)
|
||||
continue
|
||||
}
|
||||
|
||||
if !exist {
|
||||
continue
|
||||
}
|
||||
needsAdd = append(needsAdd, repoInR)
|
||||
}
|
||||
|
||||
for j < len(reposInDB) {
|
||||
needsDel = append(needsDel, reposInDB[j])
|
||||
j++
|
||||
}
|
||||
|
||||
return needsAdd, needsDel
|
||||
}
|
||||
|
||||
func projectExists(repository string) (bool, error) {
|
||||
project, _ := utils.ParseRepository(repository)
|
||||
return dao.ProjectExists(project)
|
||||
}
|
||||
|
||||
func initRegistryClient() (r *registry.Registry, err error) {
|
||||
endpoint := os.Getenv("REGISTRY_URL")
|
||||
|
||||
addr := endpoint
|
||||
if strings.Contains(endpoint, "/") {
|
||||
addr = endpoint[strings.LastIndex(endpoint, "/")+1:]
|
||||
}
|
||||
|
||||
ch := make(chan int, 1)
|
||||
go func() {
|
||||
var err error
|
||||
var c net.Conn
|
||||
for {
|
||||
c, err = net.DialTimeout("tcp", addr, 20*time.Second)
|
||||
if err == nil {
|
||||
c.Close()
|
||||
ch <- 1
|
||||
} else {
|
||||
log.Errorf("failed to connect to registry client, retry after 2 seconds :%v", err)
|
||||
time.Sleep(2 * time.Second)
|
||||
}
|
||||
}
|
||||
}()
|
||||
select {
|
||||
case <-ch:
|
||||
case <-time.After(60 * time.Second):
|
||||
panic("Failed to connect to registry client after 60 seconds")
|
||||
}
|
||||
|
||||
registryClient, err := cache.NewRegistryClient(endpoint, true, "admin",
|
||||
"registry", "catalog", "*")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return registryClient, nil
|
||||
}
|
||||
|
||||
func buildReplicationURL() string {
|
||||
url := getJobServiceURL()
|
||||
return fmt.Sprintf("%s/api/jobs/replication", url)
|
||||
@ -265,25 +471,20 @@ func getJobServiceURL() string {
|
||||
func getReposByProject(name string, keyword ...string) ([]string, error) {
|
||||
repositories := []string{}
|
||||
|
||||
list, err := getAllRepos()
|
||||
repos, err := dao.GetRepositoryByProjectName(name)
|
||||
if err != nil {
|
||||
return repositories, err
|
||||
}
|
||||
|
||||
project := ""
|
||||
rest := ""
|
||||
for _, repository := range list {
|
||||
project, rest = utils.ParseRepository(repository)
|
||||
if project != name {
|
||||
needMatchKeyword := len(keyword) > 0 && len(keyword[0]) != 0
|
||||
for _, repo := range repos {
|
||||
if needMatchKeyword &&
|
||||
strings.Contains(repo.Name, keyword[0]) {
|
||||
repositories = append(repositories, repo.Name)
|
||||
continue
|
||||
}
|
||||
|
||||
if len(keyword) > 0 && len(keyword[0]) != 0 &&
|
||||
!strings.Contains(rest, keyword[0]) {
|
||||
continue
|
||||
}
|
||||
|
||||
repositories = append(repositories, repository)
|
||||
repositories = append(repositories, repo.Name)
|
||||
}
|
||||
|
||||
return repositories, nil
|
||||
@ -292,3 +493,14 @@ func getReposByProject(name string, keyword ...string) ([]string, error) {
|
||||
func getAllRepos() ([]string, error) {
|
||||
return cache.GetRepoFromCache()
|
||||
}
|
||||
|
||||
func repositoryExist(name string, client *registry.Repository) (bool, error) {
|
||||
tags, err := client.ListTag()
|
||||
if err != nil {
|
||||
if regErr, ok := err.(*registry_error.Error); ok && regErr.StatusCode == http.StatusNotFound {
|
||||
return false, nil
|
||||
}
|
||||
return false, err
|
||||
}
|
||||
return len(tags) != 0, nil
|
||||
}
|
||||
|
184
dao/accesslog.go
184
dao/accesslog.go
@ -38,79 +38,106 @@ func AddAccessLog(accessLog models.AccessLog) error {
|
||||
return err
|
||||
}
|
||||
|
||||
//GetAccessLogs gets access logs according to different conditions
|
||||
func GetAccessLogs(query models.AccessLog, limit, offset int64) ([]models.AccessLog, int64, error) {
|
||||
// GetTotalOfAccessLogs ...
|
||||
func GetTotalOfAccessLogs(query models.AccessLog) (int64, error) {
|
||||
o := GetOrmer()
|
||||
|
||||
condition := ` from access_log a left join user u on a.user_id = u.user_id
|
||||
where a.project_id = ? `
|
||||
queryParam := make([]interface{}, 1)
|
||||
queryParam := []interface{}{}
|
||||
|
||||
sql := `select count(*) from access_log al
|
||||
where al.project_id = ?`
|
||||
queryParam = append(queryParam, query.ProjectID)
|
||||
|
||||
if query.UserID != 0 {
|
||||
condition += ` and a.user_id = ? `
|
||||
queryParam = append(queryParam, query.UserID)
|
||||
}
|
||||
if query.Operation != "" {
|
||||
condition += ` and a.operation = ? `
|
||||
queryParam = append(queryParam, query.Operation)
|
||||
}
|
||||
if query.Username != "" {
|
||||
condition += ` and u.username like ? `
|
||||
queryParam = append(queryParam, query.Username)
|
||||
sql = `select count(*) from access_log al
|
||||
left join user u
|
||||
on al.user_id = u.user_id
|
||||
where al.project_id = ? and u.username like ? `
|
||||
queryParam = append(queryParam, "%"+query.Username+"%")
|
||||
}
|
||||
|
||||
sql += genFilterClauses(query, &queryParam)
|
||||
|
||||
var total int64
|
||||
if err := o.Raw(sql, queryParam).QueryRow(&total); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return total, nil
|
||||
}
|
||||
|
||||
//GetAccessLogs gets access logs according to different conditions
|
||||
func GetAccessLogs(query models.AccessLog, limit, offset int64) ([]models.AccessLog, error) {
|
||||
o := GetOrmer()
|
||||
|
||||
queryParam := []interface{}{}
|
||||
sql := `select al.log_id, u.username, al.repo_name,
|
||||
al.repo_tag, al.operation, al.op_time
|
||||
from access_log al
|
||||
left join user u
|
||||
on al.user_id = u.user_id
|
||||
where al.project_id = ? `
|
||||
queryParam = append(queryParam, query.ProjectID)
|
||||
|
||||
if query.Username != "" {
|
||||
sql += ` and u.username like ? `
|
||||
queryParam = append(queryParam, "%"+query.Username+"%")
|
||||
}
|
||||
|
||||
sql += genFilterClauses(query, &queryParam)
|
||||
|
||||
sql += ` order by al.op_time desc `
|
||||
|
||||
sql = paginateForRawSQL(sql, limit, offset)
|
||||
|
||||
logs := []models.AccessLog{}
|
||||
_, err := o.Raw(sql, queryParam).QueryRows(&logs)
|
||||
if err != nil {
|
||||
return logs, err
|
||||
}
|
||||
|
||||
return logs, nil
|
||||
}
|
||||
|
||||
func genFilterClauses(query models.AccessLog, queryParam *[]interface{}) string {
|
||||
sql := ""
|
||||
|
||||
if query.Operation != "" {
|
||||
sql += ` and al.operation = ? `
|
||||
*queryParam = append(*queryParam, query.Operation)
|
||||
}
|
||||
if query.RepoName != "" {
|
||||
condition += ` and a.repo_name = ? `
|
||||
queryParam = append(queryParam, query.RepoName)
|
||||
sql += ` and al.repo_name = ? `
|
||||
*queryParam = append(*queryParam, query.RepoName)
|
||||
}
|
||||
if query.RepoTag != "" {
|
||||
condition += ` and a.repo_tag = ? `
|
||||
queryParam = append(queryParam, query.RepoTag)
|
||||
sql += ` and al.repo_tag = ? `
|
||||
*queryParam = append(*queryParam, query.RepoTag)
|
||||
}
|
||||
if query.Keywords != "" {
|
||||
condition += ` and a.operation in ( `
|
||||
sql += ` and al.operation in ( `
|
||||
keywordList := strings.Split(query.Keywords, "/")
|
||||
num := len(keywordList)
|
||||
for i := 0; i < num; i++ {
|
||||
if keywordList[i] != "" {
|
||||
if i == num-1 {
|
||||
condition += `?)`
|
||||
sql += `?)`
|
||||
} else {
|
||||
condition += `?,`
|
||||
sql += `?,`
|
||||
}
|
||||
queryParam = append(queryParam, keywordList[i])
|
||||
*queryParam = append(*queryParam, keywordList[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
if query.BeginTimestamp > 0 {
|
||||
condition += ` and a.op_time >= ? `
|
||||
queryParam = append(queryParam, query.BeginTime)
|
||||
sql += ` and al.op_time >= ? `
|
||||
*queryParam = append(*queryParam, query.BeginTime)
|
||||
}
|
||||
if query.EndTimestamp > 0 {
|
||||
condition += ` and a.op_time <= ? `
|
||||
queryParam = append(queryParam, query.EndTime)
|
||||
sql += ` and al.op_time <= ? `
|
||||
*queryParam = append(*queryParam, query.EndTime)
|
||||
}
|
||||
|
||||
condition += ` order by a.op_time desc `
|
||||
|
||||
totalSQL := `select count(*) ` + condition
|
||||
|
||||
logs := []models.AccessLog{}
|
||||
|
||||
var total int64
|
||||
if err := o.Raw(totalSQL, queryParam).QueryRow(&total); err != nil {
|
||||
return logs, 0, err
|
||||
}
|
||||
|
||||
condition = paginateForRawSQL(condition, limit, offset)
|
||||
|
||||
recordsSQL := `select a.log_id, u.username, a.repo_name, a.repo_tag, a.operation, a.op_time ` + condition
|
||||
_, err := o.Raw(recordsSQL, queryParam).QueryRows(&logs)
|
||||
if err != nil {
|
||||
return logs, 0, err
|
||||
}
|
||||
|
||||
return logs, total, nil
|
||||
return sql
|
||||
}
|
||||
|
||||
// AccessLog ...
|
||||
@ -157,48 +184,31 @@ func GetRecentLogs(userID, linesNum int, startTime, endTime string) ([]models.Ac
|
||||
return recentLogList, nil
|
||||
}
|
||||
|
||||
//GetTopRepos return top accessed public repos
|
||||
func GetTopRepos(countNum int) ([]models.TopRepo, error) {
|
||||
|
||||
// GetAccessLogCreator ...
|
||||
func GetAccessLogCreator(repoName string) (string, error) {
|
||||
o := GetOrmer()
|
||||
// hide the where condition: project.public = 1, Can add to the sql when necessary.
|
||||
sql := "select repo_name, COUNT(repo_name) as access_count from access_log left join project on access_log.project_id=project.project_id where access_log.operation = 'pull' group by repo_name order by access_count desc limit ? "
|
||||
queryParam := []interface{}{}
|
||||
queryParam = append(queryParam, countNum)
|
||||
var list []models.TopRepo
|
||||
_, err := o.Raw(sql, queryParam).QueryRows(&list)
|
||||
sql := "select * from user where user_id = (select user_id from access_log where operation = 'push' and repo_name = ? order by op_time desc limit 1)"
|
||||
|
||||
var u []models.User
|
||||
n, err := o.Raw(sql, repoName).QueryRows(&u)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return "", err
|
||||
}
|
||||
if len(list) == 0 {
|
||||
return list, nil
|
||||
if n == 0 {
|
||||
return "", nil
|
||||
}
|
||||
placeHolder := make([]string, len(list))
|
||||
repos := make([]string, len(list))
|
||||
for i, v := range list {
|
||||
repos[i] = v.RepoName
|
||||
placeHolder[i] = "?"
|
||||
}
|
||||
placeHolderStr := strings.Join(placeHolder, ",")
|
||||
queryParam = nil
|
||||
queryParam = append(queryParam, repos)
|
||||
var usrnameList []models.TopRepo
|
||||
sql = `select a.username as creator, a.repo_name from (select access_log.repo_name, user.username,
|
||||
access_log.op_time from user left join access_log on user.user_id = access_log.user_id where
|
||||
access_log.operation = 'push' and access_log.repo_name in (######) order by access_log.repo_name,
|
||||
access_log.op_time ASC) a group by a.repo_name`
|
||||
sql = strings.Replace(sql, "######", placeHolderStr, 1)
|
||||
_, err = o.Raw(sql, queryParam).QueryRows(&usrnameList)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for i := 0; i < len(list); i++ {
|
||||
for _, v := range usrnameList {
|
||||
if v.RepoName == list[i].RepoName {
|
||||
// list[i].Creator = v.Creator
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return list, nil
|
||||
|
||||
return u[0].Username, nil
|
||||
}
|
||||
|
||||
// CountPull ...
|
||||
func CountPull(repoName string) (int64, error) {
|
||||
o := GetOrmer()
|
||||
num, err := o.QueryTable("access_log").Filter("repo_name", repoName).Filter("operation", "pull").Count()
|
||||
if err != nil {
|
||||
log.Errorf("error in CountPull: %v ", err)
|
||||
return 0, err
|
||||
}
|
||||
return num, nil
|
||||
}
|
||||
|
195
dao/dao_test.go
195
dao/dao_test.go
@ -115,6 +115,7 @@ func clearUp(username string) {
|
||||
const username string = "Tester01"
|
||||
const password string = "Abc12345"
|
||||
const projectName string = "test_project"
|
||||
const repositoryName string = "test_repository"
|
||||
const repoTag string = "test1.1"
|
||||
const repoTag2 string = "test1.2"
|
||||
const SysAdmin int = 1
|
||||
@ -457,7 +458,7 @@ func TestGetAccessLog(t *testing.T) {
|
||||
UserID: currentUser.UserID,
|
||||
ProjectID: currentProject.ProjectID,
|
||||
}
|
||||
accessLogs, _, err := GetAccessLogs(queryAccessLog, 1000, 0)
|
||||
accessLogs, err := GetAccessLogs(queryAccessLog, 1000, 0)
|
||||
if err != nil {
|
||||
t.Errorf("Error occurred in GetAccessLog: %v", err)
|
||||
}
|
||||
@ -469,6 +470,21 @@ func TestGetAccessLog(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetTotalOfAccessLogs(t *testing.T) {
|
||||
queryAccessLog := models.AccessLog{
|
||||
UserID: currentUser.UserID,
|
||||
ProjectID: currentProject.ProjectID,
|
||||
}
|
||||
total, err := GetTotalOfAccessLogs(queryAccessLog)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to get total of access log: %v", err)
|
||||
}
|
||||
|
||||
if total != 1 {
|
||||
t.Errorf("unexpected total %d != %d", total, 1)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAddAccessLog(t *testing.T) {
|
||||
var err error
|
||||
var accessLogList []models.AccessLog
|
||||
@ -485,7 +501,7 @@ func TestAddAccessLog(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Errorf("Error occurred in AddAccessLog: %v", err)
|
||||
}
|
||||
accessLogList, _, err = GetAccessLogs(accessLog, 1000, 0)
|
||||
accessLogList, err = GetAccessLogs(accessLog, 1000, 0)
|
||||
if err != nil {
|
||||
t.Errorf("Error occurred in GetAccessLog: %v", err)
|
||||
}
|
||||
@ -514,7 +530,7 @@ func TestAccessLog(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Errorf("Error occurred in AccessLog: %v", err)
|
||||
}
|
||||
accessLogList, _, err = GetAccessLogs(accessLog, 1000, 0)
|
||||
accessLogList, err = GetAccessLogs(accessLog, 1000, 0)
|
||||
if err != nil {
|
||||
t.Errorf("Error occurred in GetAccessLog: %v", err)
|
||||
}
|
||||
@ -529,6 +545,50 @@ func TestAccessLog(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetAccessLogCreator(t *testing.T) {
|
||||
var err error
|
||||
err = AccessLog(currentUser.Username, currentProject.Name, currentProject.Name+"/tomcat", repoTag2, "push")
|
||||
if err != nil {
|
||||
t.Errorf("Error occurred in AccessLog: %v", err)
|
||||
}
|
||||
err = AccessLog(currentUser.Username, currentProject.Name, currentProject.Name+"/tomcat", repoTag2, "push")
|
||||
if err != nil {
|
||||
t.Errorf("Error occurred in AccessLog: %v", err)
|
||||
}
|
||||
|
||||
user, err := GetAccessLogCreator(currentProject.Name + "/tomcat")
|
||||
if err != nil {
|
||||
t.Errorf("Error occurred in GetAccessLogCreator: %v", err)
|
||||
}
|
||||
if user != currentUser.Username {
|
||||
t.Errorf("The access log creator does not match, expected: %s, actual: %s", currentUser.Username, user)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCountPull(t *testing.T) {
|
||||
var err error
|
||||
err = AccessLog(currentUser.Username, currentProject.Name, currentProject.Name+"/tomcat", repoTag2, "pull")
|
||||
if err != nil {
|
||||
t.Errorf("Error occurred in AccessLog: %v", err)
|
||||
}
|
||||
err = AccessLog(currentUser.Username, currentProject.Name, currentProject.Name+"/tomcat", repoTag2, "pull")
|
||||
if err != nil {
|
||||
t.Errorf("Error occurred in AccessLog: %v", err)
|
||||
}
|
||||
err = AccessLog(currentUser.Username, currentProject.Name, currentProject.Name+"/tomcat", repoTag2, "pull")
|
||||
if err != nil {
|
||||
t.Errorf("Error occurred in AccessLog: %v", err)
|
||||
}
|
||||
|
||||
pullCount, err := CountPull(currentProject.Name + "/tomcat")
|
||||
if err != nil {
|
||||
t.Errorf("Error occurred in CountPull: %v", err)
|
||||
}
|
||||
if pullCount != 3 {
|
||||
t.Errorf("The access log pull count does not match, expected: 3, actual: %d", pullCount)
|
||||
}
|
||||
}
|
||||
|
||||
func TestProjectExists(t *testing.T) {
|
||||
var exists bool
|
||||
var err error
|
||||
@ -838,57 +898,9 @@ func TestGetRecentLogs(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestGetTopRepos(t *testing.T) {
|
||||
|
||||
err := ToggleProjectPublicity(currentProject.ProjectID, publicityOn)
|
||||
_, err := GetTopRepos(10)
|
||||
if err != nil {
|
||||
t.Errorf("Error occurred in ToggleProjectPublicity: %v", err)
|
||||
}
|
||||
err = AccessLog(currentUser.Username, currentProject.Name, currentProject.Name+"/ubuntu", repoTag2, "push")
|
||||
if err != nil {
|
||||
t.Errorf("Error occurred in AccessLog: %v", err)
|
||||
}
|
||||
err = AccessLog(currentUser.Username, currentProject.Name, currentProject.Name+"/ubuntu", repoTag2, "pull")
|
||||
if err != nil {
|
||||
t.Errorf("Error occurred in AccessLog: %v", err)
|
||||
}
|
||||
topRepos, err := GetTopRepos(10)
|
||||
if err != nil {
|
||||
t.Errorf("error occured in getting top repos, error: %v", err)
|
||||
}
|
||||
if topRepos[0].RepoName != currentProject.Name+"/ubuntu" {
|
||||
t.Errorf("error occured in get top reop's name, expected: %v, actual: %v", currentProject.Name+"/ubuntu", topRepos[0].RepoName)
|
||||
}
|
||||
if topRepos[0].AccessCount != 1 {
|
||||
t.Errorf("error occured in get top reop's access count, expected: %v, actual: %v", 1, topRepos[0].AccessCount)
|
||||
}
|
||||
/*
|
||||
if topRepos[0].Creator != currentUser.Username {
|
||||
t.Errorf("error occured in get top reop's creator, expected: %v, actual: %v", currentUser.Username, topRepos[0].Creator)
|
||||
}
|
||||
*/
|
||||
err = ToggleProjectPublicity(currentProject.ProjectID, publicityOff)
|
||||
if err != nil {
|
||||
t.Errorf("Error occurred in ToggleProjectPublicity: %v", err)
|
||||
}
|
||||
o := GetOrmer()
|
||||
_, err = o.QueryTable("access_log").Filter("operation__in", "push,pull").Delete()
|
||||
if err != nil {
|
||||
t.Errorf("error occurred in deleting access logs, %v", err)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestDeleteUser(t *testing.T) {
|
||||
err := DeleteUser(currentUser.UserID)
|
||||
if err != nil {
|
||||
t.Errorf("Error occurred in DeleteUser: %v", err)
|
||||
}
|
||||
user, err := GetUser(*currentUser)
|
||||
if err != nil {
|
||||
t.Errorf("Error occurred in GetUser: %v", err)
|
||||
}
|
||||
if user != nil {
|
||||
t.Errorf("user is not nil after deletion, user: %+v", user)
|
||||
t.Fatalf("error occured in getting top repos, error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
@ -1514,3 +1526,80 @@ func TestDeleteProject(t *testing.T) {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestAddRepository(t *testing.T) {
|
||||
repoRecord := models.RepoRecord{
|
||||
Name: currentProject.Name + "/" + repositoryName,
|
||||
OwnerName: currentUser.Username,
|
||||
ProjectName: currentProject.Name,
|
||||
Description: "testing repo",
|
||||
PullCount: 0,
|
||||
StarCount: 0,
|
||||
}
|
||||
|
||||
err := AddRepository(repoRecord)
|
||||
if err != nil {
|
||||
t.Errorf("Error occurred in AddRepository: %v", err)
|
||||
}
|
||||
|
||||
newRepoRecord, err := GetRepositoryByName(currentProject.Name + "/" + repositoryName)
|
||||
if err != nil {
|
||||
t.Errorf("Error occurred in GetRepositoryByName: %v", err)
|
||||
}
|
||||
if newRepoRecord == nil {
|
||||
t.Errorf("No repository found queried by repository name: %v", currentProject.Name+"/"+repositoryName)
|
||||
}
|
||||
}
|
||||
|
||||
var currentRepository *models.RepoRecord
|
||||
|
||||
func TestGetRepositoryByName(t *testing.T) {
|
||||
var err error
|
||||
currentRepository, err = GetRepositoryByName(currentProject.Name + "/" + repositoryName)
|
||||
if err != nil {
|
||||
t.Errorf("Error occurred in GetRepositoryByName: %v", err)
|
||||
}
|
||||
if currentRepository == nil {
|
||||
t.Errorf("No repository found queried by repository name: %v", currentProject.Name+"/"+repositoryName)
|
||||
}
|
||||
if currentRepository.Name != currentProject.Name+"/"+repositoryName {
|
||||
t.Errorf("Repository name does not match, expected: %s, actual: %s", currentProject.Name+"/"+repositoryName, currentProject.Name)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIncreasePullCount(t *testing.T) {
|
||||
if err := IncreasePullCount(currentRepository.Name); err != nil {
|
||||
log.Errorf("Error happens when increasing pull count: %v", currentRepository.Name)
|
||||
}
|
||||
|
||||
repository, err := GetRepositoryByName(currentRepository.Name)
|
||||
if err != nil {
|
||||
t.Errorf("Error occurred in GetRepositoryByName: %v", err)
|
||||
}
|
||||
|
||||
if repository.PullCount != 1 {
|
||||
t.Errorf("repository pull count is not 1 after IncreasePullCount, expected: 1, actual: %d", repository.PullCount)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRepositoryExists(t *testing.T) {
|
||||
var exists bool
|
||||
exists = RepositoryExists(currentRepository.Name)
|
||||
if !exists {
|
||||
t.Errorf("The repository with name: %d, does not exist", currentRepository.Name)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeleteRepository(t *testing.T) {
|
||||
err := DeleteRepository(currentRepository.Name)
|
||||
if err != nil {
|
||||
t.Errorf("Error occurred in DeleteRepository: %v", err)
|
||||
}
|
||||
repository, err := GetRepositoryByName(currentRepository.Name)
|
||||
if err != nil {
|
||||
t.Errorf("Error occurred in GetRepositoryByName: %v", err)
|
||||
}
|
||||
if repository != nil {
|
||||
t.Errorf("repository is not nil after deletion, repository: %+v", repository)
|
||||
}
|
||||
}
|
||||
|
@ -184,8 +184,7 @@ func SearchProjects(userID int) ([]models.Project, error) {
|
||||
|
||||
//GetTotalOfUserRelevantProjects returns the total count of
|
||||
// user relevant projects
|
||||
func GetTotalOfUserRelevantProjects(userID int, projectName string,
|
||||
public ...int) (int64, error) {
|
||||
func GetTotalOfUserRelevantProjects(userID int, projectName string) (int64, error) {
|
||||
o := GetOrmer()
|
||||
sql := `select count(*) from project p
|
||||
left join project_member pm
|
||||
|
167
dao/repository.go
Normal file
167
dao/repository.go
Normal file
@ -0,0 +1,167 @@
|
||||
/*
|
||||
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 dao
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/astaxie/beego/orm"
|
||||
"github.com/vmware/harbor/models"
|
||||
)
|
||||
|
||||
// AddRepository adds a repo to the database.
|
||||
func AddRepository(repo models.RepoRecord) error {
|
||||
o := GetOrmer()
|
||||
sql := "insert into repository (owner_id, project_id, name, description, pull_count, star_count, creation_time, update_time) " +
|
||||
"select (select user_id as owner_id from user where username=?), " +
|
||||
"(select project_id as project_id from project where name=?), ?, ?, ?, ?, NOW(), NULL "
|
||||
|
||||
_, err := o.Raw(sql, repo.OwnerName, repo.ProjectName, repo.Name, repo.Description, repo.PullCount, repo.StarCount).Exec()
|
||||
return err
|
||||
}
|
||||
|
||||
// GetRepositoryByName ...
|
||||
func GetRepositoryByName(name string) (*models.RepoRecord, error) {
|
||||
o := GetOrmer()
|
||||
r := models.RepoRecord{Name: name}
|
||||
err := o.Read(&r, "Name")
|
||||
if err == orm.ErrNoRows {
|
||||
return nil, nil
|
||||
}
|
||||
return &r, err
|
||||
}
|
||||
|
||||
// GetAllRepositories ...
|
||||
func GetAllRepositories() ([]models.RepoRecord, error) {
|
||||
o := GetOrmer()
|
||||
var repos []models.RepoRecord
|
||||
_, err := o.QueryTable("repository").All(&repos)
|
||||
return repos, err
|
||||
}
|
||||
|
||||
// DeleteRepository ...
|
||||
func DeleteRepository(name string) error {
|
||||
o := GetOrmer()
|
||||
_, err := o.QueryTable("repository").Filter("name", name).Delete()
|
||||
return err
|
||||
}
|
||||
|
||||
// UpdateRepository ...
|
||||
func UpdateRepository(repo models.RepoRecord) error {
|
||||
o := GetOrmer()
|
||||
_, err := o.Update(&repo)
|
||||
return err
|
||||
}
|
||||
|
||||
// IncreasePullCount ...
|
||||
func IncreasePullCount(name string) (err error) {
|
||||
o := GetOrmer()
|
||||
num, err := o.QueryTable("repository").Filter("name", name).Update(
|
||||
orm.Params{
|
||||
"pull_count": orm.ColValue(orm.ColAdd, 1),
|
||||
})
|
||||
if num == 0 {
|
||||
err = fmt.Errorf("Failed to increase repository pull count with name: %s %s", name, err.Error())
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
//RepositoryExists returns whether the repository exists according to its name.
|
||||
func RepositoryExists(name string) bool {
|
||||
o := GetOrmer()
|
||||
return o.QueryTable("repository").Filter("name", name).Exist()
|
||||
}
|
||||
|
||||
// GetRepositoryByProjectName ...
|
||||
func GetRepositoryByProjectName(name string) ([]*models.RepoRecord, error) {
|
||||
sql := `select * from repository
|
||||
where project_id = (
|
||||
select project_id from project
|
||||
where name = ?
|
||||
)`
|
||||
repos := []*models.RepoRecord{}
|
||||
_, err := GetOrmer().Raw(sql, name).QueryRows(&repos)
|
||||
return repos, err
|
||||
}
|
||||
|
||||
//GetTopRepos returns the most popular repositories
|
||||
func GetTopRepos(count int) ([]models.TopRepo, error) {
|
||||
topRepos := []models.TopRepo{}
|
||||
|
||||
repositories := []*models.RepoRecord{}
|
||||
if _, err := GetOrmer().QueryTable(&models.RepoRecord{}).
|
||||
OrderBy("-PullCount", "Name").Limit(count).All(&repositories); err != nil {
|
||||
return topRepos, err
|
||||
}
|
||||
|
||||
for _, repository := range repositories {
|
||||
topRepos = append(topRepos, models.TopRepo{
|
||||
RepoName: repository.Name,
|
||||
AccessCount: repository.PullCount,
|
||||
})
|
||||
}
|
||||
|
||||
return topRepos, nil
|
||||
}
|
||||
|
||||
// GetTotalOfRepositories ...
|
||||
func GetTotalOfRepositories(name string) (int64, error) {
|
||||
qs := GetOrmer().QueryTable(&models.RepoRecord{})
|
||||
if len(name) != 0 {
|
||||
qs = qs.Filter("Name__contains", name)
|
||||
}
|
||||
return qs.Count()
|
||||
}
|
||||
|
||||
// GetTotalOfPublicRepositories ...
|
||||
func GetTotalOfPublicRepositories(name string) (int64, error) {
|
||||
params := []interface{}{}
|
||||
sql := `select count(*) from repository r
|
||||
join project p
|
||||
on r.project_id = p.project_id and p.public = 1 `
|
||||
if len(name) != 0 {
|
||||
sql += ` where r.name like ?`
|
||||
params = append(params, "%"+name+"%")
|
||||
}
|
||||
|
||||
var total int64
|
||||
err := GetOrmer().Raw(sql, params).QueryRow(&total)
|
||||
return total, err
|
||||
}
|
||||
|
||||
// GetTotalOfUserRelevantRepositories ...
|
||||
func GetTotalOfUserRelevantRepositories(userID int, name string) (int64, error) {
|
||||
params := []interface{}{}
|
||||
sql := `select count(*)
|
||||
from repository r
|
||||
join (
|
||||
select p.project_id, p.public
|
||||
from project p
|
||||
join project_member pm
|
||||
on p.project_id = pm.project_id
|
||||
where pm.user_id = ?
|
||||
) as pp
|
||||
on r.project_id = pp.project_id `
|
||||
params = append(params, userID)
|
||||
if len(name) != 0 {
|
||||
sql += ` where r.name like ?`
|
||||
params = append(params, "%"+name+"%")
|
||||
}
|
||||
|
||||
var total int64
|
||||
err := GetOrmer().Raw(sql, params).QueryRow(&total)
|
||||
return total, err
|
||||
}
|
169
dao/repository_test.go
Normal file
169
dao/repository_test.go
Normal file
@ -0,0 +1,169 @@
|
||||
/*
|
||||
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 dao
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/vmware/harbor/models"
|
||||
)
|
||||
|
||||
var (
|
||||
project = "library"
|
||||
name = "library/repository-test"
|
||||
repository = &models.RepoRecord{
|
||||
Name: name,
|
||||
OwnerName: "admin",
|
||||
ProjectName: project,
|
||||
}
|
||||
)
|
||||
|
||||
func TestGetRepositoryByProjectName(t *testing.T) {
|
||||
if err := addRepository(repository); err != nil {
|
||||
t.Fatalf("failed to add repository %s: %v", name, err)
|
||||
}
|
||||
defer func() {
|
||||
if err := deleteRepository(name); err != nil {
|
||||
t.Fatalf("failed to delete repository %s: %v", name, err)
|
||||
}
|
||||
}()
|
||||
|
||||
repositories, err := GetRepositoryByProjectName(project)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to get repositories of project %s: %v",
|
||||
project, err)
|
||||
}
|
||||
|
||||
if len(repositories) == 0 {
|
||||
t.Fatal("unexpected length of repositories: 0, at least 1")
|
||||
}
|
||||
|
||||
exist := false
|
||||
for _, repo := range repositories {
|
||||
if repo.Name == name {
|
||||
exist = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !exist {
|
||||
t.Errorf("there is no repository whose name is %s", name)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetTotalOfRepositories(t *testing.T) {
|
||||
total, err := GetTotalOfRepositories("")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to get total of repositoreis: %v", err)
|
||||
}
|
||||
|
||||
if err := addRepository(repository); err != nil {
|
||||
t.Fatalf("failed to add repository %s: %v", name, err)
|
||||
}
|
||||
defer func() {
|
||||
if err := deleteRepository(name); err != nil {
|
||||
t.Fatalf("failed to delete repository %s: %v", name, err)
|
||||
}
|
||||
}()
|
||||
|
||||
n, err := GetTotalOfRepositories("")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to get total of repositoreis: %v", err)
|
||||
}
|
||||
|
||||
if n != total+1 {
|
||||
t.Errorf("unexpected total: %d != %d", n, total+1)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetTotalOfPublicRepositories(t *testing.T) {
|
||||
total, err := GetTotalOfPublicRepositories("")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to get total of public repositoreis: %v", err)
|
||||
}
|
||||
|
||||
if err := addRepository(repository); err != nil {
|
||||
t.Fatalf("failed to add repository %s: %v", name, err)
|
||||
}
|
||||
defer func() {
|
||||
if err := deleteRepository(name); err != nil {
|
||||
t.Fatalf("failed to delete repository %s: %v", name, err)
|
||||
}
|
||||
}()
|
||||
|
||||
n, err := GetTotalOfPublicRepositories("")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to get total of public repositoreis: %v", err)
|
||||
}
|
||||
|
||||
if n != total+1 {
|
||||
t.Errorf("unexpected total: %d != %d", n, total+1)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetTotalOfUserRelevantRepositories(t *testing.T) {
|
||||
total, err := GetTotalOfUserRelevantRepositories(1, "")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to get total of repositoreis for user %d: %v", 1, err)
|
||||
}
|
||||
|
||||
if err := addRepository(repository); err != nil {
|
||||
t.Fatalf("failed to add repository %s: %v", name, err)
|
||||
}
|
||||
defer func() {
|
||||
if err := deleteRepository(name); err != nil {
|
||||
t.Fatalf("failed to delete repository %s: %v", name, err)
|
||||
}
|
||||
}()
|
||||
|
||||
users, err := GetUserByProject(1, models.User{})
|
||||
if err != nil {
|
||||
t.Fatalf("failed to list members of project %d: %v", 1, err)
|
||||
}
|
||||
exist := false
|
||||
for _, user := range users {
|
||||
if user.UserID == 1 {
|
||||
exist = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !exist {
|
||||
if err = AddProjectMember(1, 1, models.DEVELOPER); err != nil {
|
||||
t.Fatalf("failed to add user %d to be member of project %d: %v", 1, 1, err)
|
||||
}
|
||||
defer func() {
|
||||
if err = DeleteProjectMember(1, 1); err != nil {
|
||||
t.Fatalf("failed to delete user %d from member of project %d: %v", 1, 1, err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
n, err := GetTotalOfUserRelevantRepositories(1, "")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to get total of public repositoreis for user %d: %v", 1, err)
|
||||
}
|
||||
|
||||
if n != total+1 {
|
||||
t.Errorf("unexpected total: %d != %d", n, total+1)
|
||||
}
|
||||
}
|
||||
|
||||
func addRepository(repository *models.RepoRecord) error {
|
||||
return AddRepository(*repository)
|
||||
}
|
||||
|
||||
func deleteRepository(name string) error {
|
||||
return DeleteRepository(name)
|
||||
}
|
@ -214,7 +214,10 @@ func CheckUserPassword(query models.User) (*models.User, error) {
|
||||
// DeleteUser ...
|
||||
func DeleteUser(userID int) error {
|
||||
o := GetOrmer()
|
||||
_, err := o.Raw(`update user set deleted = 1 where user_id = ?`, userID).Exec()
|
||||
_, err := o.Raw(`update user
|
||||
set deleted = 1, username = concat(username, "#", user_id),
|
||||
email = concat(email, "#", user_id)
|
||||
where user_id = ?`, userID).Exec()
|
||||
return err
|
||||
}
|
||||
|
||||
|
69
dao/user_test.go
Normal file
69
dao/user_test.go
Normal file
@ -0,0 +1,69 @@
|
||||
/*
|
||||
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 dao
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/vmware/harbor/models"
|
||||
)
|
||||
|
||||
func TestDeleteUser(t *testing.T) {
|
||||
username := "user_for_test"
|
||||
email := "user_for_test@vmware.com"
|
||||
password := "P@ssword"
|
||||
realname := "user_for_test"
|
||||
|
||||
u := models.User{
|
||||
Username: username,
|
||||
Email: email,
|
||||
Password: password,
|
||||
Realname: realname,
|
||||
}
|
||||
id, err := Register(u)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to register user: %v", err)
|
||||
}
|
||||
|
||||
err = DeleteUser(int(id))
|
||||
if err != nil {
|
||||
t.Fatalf("Error occurred in DeleteUser: %v", err)
|
||||
}
|
||||
|
||||
user := &models.User{}
|
||||
sql := "select * from user where user_id = ?"
|
||||
if err = GetOrmer().Raw(sql, id).
|
||||
QueryRow(user); err != nil {
|
||||
t.Fatalf("failed to query user: %v", err)
|
||||
}
|
||||
|
||||
if user.Deleted != 1 {
|
||||
t.Error("user is not deleted")
|
||||
}
|
||||
|
||||
expected := fmt.Sprintf("%s#%d", u.Username, id)
|
||||
if user.Username != expected {
|
||||
t.Errorf("unexpected username: %s != %s", user.Username,
|
||||
expected)
|
||||
}
|
||||
|
||||
expected = fmt.Sprintf("%s#%d", u.Email, id)
|
||||
if user.Email != expected {
|
||||
t.Errorf("unexpected email: %s != %s", user.Email,
|
||||
expected)
|
||||
}
|
||||
}
|
@ -123,7 +123,7 @@ paths:
|
||||
description: New created project.
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/Project'
|
||||
$ref: '#/definitions/ProjectReq'
|
||||
tags:
|
||||
- Products
|
||||
responses:
|
||||
@ -155,13 +155,37 @@ paths:
|
||||
200:
|
||||
description: Return matched project information.
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/definitions/Project'
|
||||
$ref: '#/definitions/Project'
|
||||
401:
|
||||
description: User need to log in first.
|
||||
500:
|
||||
description: Internal errors.
|
||||
delete:
|
||||
summary: Delete project by projectID
|
||||
description: |
|
||||
This endpoint is aimed to delete project by project ID.
|
||||
parameters:
|
||||
- name: project_id
|
||||
in: path
|
||||
description: Project ID of project which will be deleted.
|
||||
required: true
|
||||
type: integer
|
||||
format: int64
|
||||
tags:
|
||||
- Products
|
||||
responses:
|
||||
200:
|
||||
description: Project is deleted successfully.
|
||||
400:
|
||||
description: Invalid project id.
|
||||
403:
|
||||
description: User need to log in first.
|
||||
404:
|
||||
description: Project does not exist.
|
||||
412:
|
||||
description: Project contains policies, can not be deleted.
|
||||
500:
|
||||
description: Internal errors.
|
||||
/projects/{project_id}/publicity:
|
||||
put:
|
||||
summary: Update properties for a selected project.
|
||||
@ -682,7 +706,7 @@ paths:
|
||||
404:
|
||||
description: Repository or tag not found.
|
||||
403:
|
||||
description: Forbidden.
|
||||
description: Forbidden.
|
||||
/repositories/tags:
|
||||
get:
|
||||
summary: Get tags of a relevant repository.
|
||||
@ -721,7 +745,7 @@ paths:
|
||||
in: query
|
||||
type: string
|
||||
required: false
|
||||
description: The version of manifest, valid value are "v1" and "v2", default is "v2"
|
||||
description: The version of manifest, valid value are "v1" and "v2", default is "v2"
|
||||
tags:
|
||||
- Products
|
||||
responses:
|
||||
@ -1290,6 +1314,16 @@ definitions:
|
||||
project_public:
|
||||
type: integer
|
||||
description: The flag to indicate the publicity of the project that the repository belongs to (1 is public, 0 is not)
|
||||
ProjectReq:
|
||||
type: object
|
||||
properties:
|
||||
project_name:
|
||||
type: string
|
||||
description: The name of the project.
|
||||
public:
|
||||
type: integer
|
||||
format: int
|
||||
description: The public status of the project.
|
||||
Project:
|
||||
type: object
|
||||
properties:
|
||||
@ -1301,7 +1335,7 @@ definitions:
|
||||
type: integer
|
||||
format: int32
|
||||
description: The owner ID of the project always means the creator of the project.
|
||||
project_name:
|
||||
name:
|
||||
type: string
|
||||
description: The name of the project.
|
||||
creation_time:
|
||||
@ -1320,10 +1354,10 @@ definitions:
|
||||
description: A relation field to the user table.
|
||||
owner_name:
|
||||
type: string
|
||||
description: The owner name of the project.
|
||||
description: The owner name of the project.
|
||||
public:
|
||||
type: boolean
|
||||
format: boolean
|
||||
type: integer
|
||||
format: int
|
||||
description: The public status of the project.
|
||||
togglable:
|
||||
type: boolean
|
||||
@ -1348,21 +1382,37 @@ definitions:
|
||||
properties:
|
||||
user_id:
|
||||
type: integer
|
||||
format: int32
|
||||
format: int
|
||||
description: The ID of the user.
|
||||
username:
|
||||
type: string
|
||||
email:
|
||||
type: string
|
||||
password:
|
||||
password:
|
||||
type: string
|
||||
realname:
|
||||
realname:
|
||||
type: string
|
||||
comment:
|
||||
type: string
|
||||
deleted:
|
||||
type: integer
|
||||
format: int32
|
||||
role_name:
|
||||
type: string
|
||||
role_id:
|
||||
type: integer
|
||||
format: int
|
||||
has_admin_role:
|
||||
type: integer
|
||||
format: int
|
||||
reset_uuid:
|
||||
type: string
|
||||
Salt:
|
||||
type: string
|
||||
creation_time:
|
||||
type: string
|
||||
update_time:
|
||||
type: string
|
||||
Password:
|
||||
type: object
|
||||
properties:
|
||||
@ -1371,7 +1421,7 @@ definitions:
|
||||
description: The user's existing password.
|
||||
new_password:
|
||||
type: string
|
||||
description: New password for marking as to be updated.
|
||||
description: New password for marking as to be updated.
|
||||
AccessLogFilter:
|
||||
type: object
|
||||
properties:
|
||||
@ -1637,3 +1687,4 @@ definitions:
|
||||
password:
|
||||
type: string
|
||||
description: The target server password.
|
||||
|
||||
|
@ -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 models
|
||||
@ -23,8 +23,9 @@ func init() {
|
||||
orm.RegisterModel(new(RepTarget),
|
||||
new(RepPolicy),
|
||||
new(RepJob),
|
||||
new(User),
|
||||
new(User),
|
||||
new(Project),
|
||||
new(Role),
|
||||
new(AccessLog))
|
||||
new(AccessLog),
|
||||
new(RepoRecord))
|
||||
}
|
||||
|
38
models/repo.go
Normal file
38
models/repo.go
Normal file
@ -0,0 +1,38 @@
|
||||
/*
|
||||
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 models
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// RepoRecord holds the record of an repository in DB, all the infors are from the registry notification event.
|
||||
type RepoRecord struct {
|
||||
RepositoryID string `orm:"column(repository_id);pk" json:"repository_id"`
|
||||
Name string `orm:"column(name)" json:"name"`
|
||||
OwnerName string `orm:"-"`
|
||||
OwnerID int64 `orm:"column(owner_id)" json:"owner_id"`
|
||||
ProjectName string `orm:"-"`
|
||||
ProjectID int64 `orm:"column(project_id)" json:"project_id"`
|
||||
Description string `orm:"column(description)" json:"description"`
|
||||
PullCount int64 `orm:"column(pull_count)" json:"pull_count"`
|
||||
StarCount int64 `orm:"column(star_count)" json:"star_count"`
|
||||
CreationTime time.Time `orm:"column(creation_time);auto_now_add" json:"creation_time"`
|
||||
UpdateTime time.Time `orm:"column(update_time);auto_now" json:"update_time"`
|
||||
}
|
||||
|
||||
//TableName is required by by beego orm to map RepoRecord to table repository
|
||||
func (rp *RepoRecord) TableName() string {
|
||||
return "repository"
|
||||
}
|
@ -24,6 +24,7 @@ import (
|
||||
"github.com/vmware/harbor/dao"
|
||||
"github.com/vmware/harbor/models"
|
||||
"github.com/vmware/harbor/service/cache"
|
||||
"github.com/vmware/harbor/utils"
|
||||
"github.com/vmware/harbor/utils/log"
|
||||
|
||||
"github.com/astaxie/beego"
|
||||
@ -55,11 +56,7 @@ func (n *NotificationHandler) Post() {
|
||||
for _, event := range events {
|
||||
repository := event.Target.Repository
|
||||
|
||||
project := ""
|
||||
if strings.Contains(repository, "/") {
|
||||
project = repository[0:strings.LastIndex(repository, "/")]
|
||||
}
|
||||
|
||||
project, _ := utils.ParseRepository(repository)
|
||||
tag := event.Target.Tag
|
||||
action := event.Action
|
||||
|
||||
@ -80,6 +77,18 @@ func (n *NotificationHandler) Post() {
|
||||
}
|
||||
}()
|
||||
|
||||
go func() {
|
||||
exist := dao.RepositoryExists(repository)
|
||||
if exist {
|
||||
return
|
||||
}
|
||||
log.Debugf("Add repository %s into DB.", repository)
|
||||
repoRecord := models.RepoRecord{Name: repository, OwnerName: user, ProjectName: project}
|
||||
if err := dao.AddRepository(repoRecord); err != nil {
|
||||
log.Errorf("Error happens when adding repository: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
operation := ""
|
||||
if action == "push" {
|
||||
operation = models.RepOpTransfer
|
||||
@ -87,6 +96,14 @@ func (n *NotificationHandler) Post() {
|
||||
|
||||
go api.TriggerReplicationByRepository(repository, []string{tag}, operation)
|
||||
}
|
||||
if action == "pull" {
|
||||
go func() {
|
||||
log.Debugf("Increase the repository %s pull count.", repository)
|
||||
if err := dao.IncreasePullCount(repository); err != nil {
|
||||
log.Errorf("Error happens when increasing pull count: %v", repository)
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -27,12 +27,12 @@
|
||||
|
||||
vm.projectName = '';
|
||||
vm.isOpen = false;
|
||||
vm.isProjectMember = false;
|
||||
vm.target = 'repositories';
|
||||
|
||||
vm.isPublic = Number(getParameterByName('is_public', $location.absUrl()));
|
||||
vm.publicity = (vm.isPublic === 1) ? true : false;
|
||||
|
||||
if(getParameterByName('is_public', $location.absUrl())) {
|
||||
vm.isPublic = getParameterByName('is_public', $location.absUrl()) === 'true' ? 1 : 0;
|
||||
vm.publicity = (vm.isPublic === 1) ? true : false;
|
||||
}
|
||||
|
||||
vm.retrieve = retrieve;
|
||||
vm.filterInput = '';
|
||||
vm.selectItem = selectItem;
|
||||
@ -58,20 +58,20 @@
|
||||
}
|
||||
|
||||
function getProjectSuccess(data, status) {
|
||||
vm.projects = data;
|
||||
|
||||
if(vm.projects == null) {
|
||||
vm.isPublic = 1;
|
||||
vm.publicity = true;
|
||||
vm.projectType = 'public_projects';
|
||||
console.log('vm.projects is null, load public projects.');
|
||||
return;
|
||||
vm.projects = data || [];
|
||||
|
||||
if(vm.projects.length == 0){
|
||||
if(vm.isPublic === 0) {
|
||||
$window.location.href = '/project';
|
||||
}else{
|
||||
vm.publicity = true;
|
||||
vm.projectType = 'public_projects';
|
||||
vm.target = 'repositories';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
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())){
|
||||
@ -81,10 +81,10 @@
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
$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) {
|
||||
@ -102,11 +102,12 @@
|
||||
function selectItem(item) {
|
||||
vm.selectedProject = item;
|
||||
$location.search('project_id', vm.selectedProject.project_id);
|
||||
vm.checkProjectMember(vm.selectedProject.project_id);
|
||||
}
|
||||
|
||||
$scope.$on('$locationChangeSuccess', function(e) {
|
||||
var projectId = getParameterByName('project_id', $location.absUrl());
|
||||
vm.isOpen = false;
|
||||
vm.projectId = getParameterByName('project_id', $location.absUrl());
|
||||
vm.isOpen = false;
|
||||
});
|
||||
|
||||
function checkProjectMember(projectId) {
|
||||
@ -121,7 +122,7 @@
|
||||
}
|
||||
|
||||
function getCurrentProjectMemberFailed(data, status) {
|
||||
vm.isProjectMember = false;
|
||||
vm.isProjectMember = false;
|
||||
console.log('Current user has no member for the project:' + status + ', location.url:' + $location.url());
|
||||
}
|
||||
|
||||
@ -132,6 +133,7 @@
|
||||
restrict: 'E',
|
||||
templateUrl: '/static/resources/js/components/details/retrieve-projects.directive.html',
|
||||
scope: {
|
||||
'target': '=',
|
||||
'isOpen': '=',
|
||||
'selectedProject': '=',
|
||||
'publicity': '=',
|
||||
@ -147,7 +149,7 @@
|
||||
|
||||
function link(scope, element, attrs, ctrl) {
|
||||
$(document).on('click', clickHandler);
|
||||
|
||||
|
||||
function clickHandler(e) {
|
||||
$('[data-toggle="popover"]').each(function () {
|
||||
if (!$(this).is(e.target) &&
|
||||
|
@ -56,20 +56,14 @@
|
||||
vm.pageSize = 20;
|
||||
|
||||
$scope.$watch('vm.page', function(current, origin) {
|
||||
if(current !== 1) {
|
||||
if(current) {
|
||||
vm.page = current;
|
||||
retrieve(vm.queryParams, vm.page, vm.pageSize);
|
||||
}
|
||||
});
|
||||
|
||||
retrieve(vm.queryParams, vm.page, vm.pageSize);
|
||||
|
||||
|
||||
$scope.$on('$locationChangeSuccess', function() {
|
||||
|
||||
if(vm.publicity) {
|
||||
vm.target = 'repositories';
|
||||
}
|
||||
|
||||
|
||||
vm.projectId = getParameterByName('project_id', $location.absUrl());
|
||||
vm.queryParams = {
|
||||
'beginTimestamp' : vm.beginTimestamp,
|
||||
|
@ -28,7 +28,7 @@
|
||||
$scope.p = {};
|
||||
var vm0 = $scope.p;
|
||||
vm0.projectName = '';
|
||||
vm.isPublic = false;
|
||||
vm.isPublic = 0;
|
||||
|
||||
vm.addProject = addProject;
|
||||
vm.cancel = cancel;
|
||||
@ -37,9 +37,20 @@
|
||||
|
||||
vm.hasError = false;
|
||||
vm.errorMessage = '';
|
||||
|
||||
$scope.$watch('vm.isOpen', function(current) {
|
||||
if(current) {
|
||||
$scope.form.$setPristine();
|
||||
$scope.form.$setUntouched();
|
||||
vm0.projectName = '';
|
||||
vm.isPublic = 0;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
function addProject(p) {
|
||||
if(p && angular.isDefined(p.projectName)) {
|
||||
vm.isPublic = vm.isPublic ? 1 : 0;
|
||||
AddProjectService(p.projectName, vm.isPublic)
|
||||
.success(addProjectSuccess)
|
||||
.error(addProjectFailed);
|
||||
@ -74,9 +85,9 @@
|
||||
}
|
||||
vm.isOpen = false;
|
||||
vm0.projectName = '';
|
||||
vm.isPublic = false;
|
||||
vm.isPublic = 0;
|
||||
|
||||
vm.hasError = false; vm.close = close;
|
||||
vm.hasError = false;
|
||||
vm.errorMessage = '';
|
||||
}
|
||||
|
||||
@ -94,16 +105,10 @@
|
||||
'scope' : {
|
||||
'isOpen': '='
|
||||
},
|
||||
'link': link,
|
||||
'controllerAs': 'vm',
|
||||
'bindToController': true
|
||||
};
|
||||
return directive;
|
||||
|
||||
function link(scope, element, attrs, ctrl) {
|
||||
scope.form.$setPristine();
|
||||
scope.form.$setUntouched();
|
||||
}
|
||||
return directive;
|
||||
}
|
||||
|
||||
})();
|
||||
|
@ -27,11 +27,7 @@
|
||||
vm.toggle = toggle;
|
||||
|
||||
function toggle() {
|
||||
if(vm.isPublic) {
|
||||
vm.isPublic = false;
|
||||
}else{
|
||||
vm.isPublic = true;
|
||||
}
|
||||
vm.isPublic = vm.isPublic ? 0 : 1;
|
||||
ToggleProjectPublicityService(vm.projectId, vm.isPublic)
|
||||
.success(toggleProjectPublicitySuccess)
|
||||
.error(toggleProjectPublicityFailed);
|
||||
@ -53,12 +49,7 @@
|
||||
$scope.$emit('modalMessage', message);
|
||||
$scope.$emit('raiseError', true);
|
||||
|
||||
if(vm.isPublic) {
|
||||
vm.isPublic = false;
|
||||
}else{
|
||||
vm.isPublic = true;
|
||||
}
|
||||
|
||||
vm.isPublic = vm.isPublic ? 0 : 1;
|
||||
console.log('Failed to toggle project publicity:' + e);
|
||||
}
|
||||
}
|
||||
@ -69,7 +60,6 @@
|
||||
'templateUrl': '/static/resources/js/components/project/publicity-button.directive.html',
|
||||
'scope': {
|
||||
'isPublic': '=',
|
||||
'owned': '=',
|
||||
'projectId': '='
|
||||
},
|
||||
'link': link,
|
||||
|
@ -62,7 +62,7 @@
|
||||
|
||||
<a href="javascript:void(0);" data-toggle="modal" data-target="#createPolicyModal" ng-click="vm.editReplication(r.id)" title="// 'edit_policy' | tr //"><span class="glyphicon glyphicon-pencil"></span></a>
|
||||
|
||||
<!--a href="javascript:void(0);"><span class="glyphicon glyphicon-trash"></span></a -->
|
||||
<a href="javascript:void(0);" ng-click="vm.confirmToDelete(r.id, r.name)" title="// 'delete_policy' | tr //"><span class="glyphicon glyphicon-trash"></span></a>
|
||||
</td>
|
||||
<td width="5%">
|
||||
<a ng-if= "r.error_job_count > 0" title = "// 'found_error_in_replication_job' | tr: [r.error_job_count] //"><span class="glyphicon glyphicon-exclamation-sign color-danger" ></span></a>
|
||||
|
@ -37,9 +37,9 @@
|
||||
};
|
||||
}
|
||||
|
||||
ListReplicationController.$inject = ['$scope', 'getParameterByName', '$location', 'ListReplicationPolicyService', 'ToggleReplicationPolicyService', 'ListReplicationJobService', '$window', '$filter', 'trFilter', 'jobStatus'];
|
||||
ListReplicationController.$inject = ['$scope', 'getParameterByName', '$location', 'ListReplicationPolicyService', 'ToggleReplicationPolicyService', 'DeleteReplicationPolicyService', 'ListReplicationJobService', '$window', '$filter', 'trFilter', 'jobStatus'];
|
||||
|
||||
function ListReplicationController($scope, getParameterByName, $location, ListReplicationPolicyService, ToggleReplicationPolicyService, ListReplicationJobService, $window, $filter, trFilter, jobStatus) {
|
||||
function ListReplicationController($scope, getParameterByName, $location, ListReplicationPolicyService, ToggleReplicationPolicyService, DeleteReplicationPolicyService, ListReplicationJobService, $window, $filter, trFilter, jobStatus) {
|
||||
var vm = this;
|
||||
|
||||
vm.sectionHeight = {'min-height': '1260px'};
|
||||
@ -51,6 +51,9 @@
|
||||
|
||||
vm.addReplication = addReplication;
|
||||
vm.editReplication = editReplication;
|
||||
vm.deleteReplicationPolicy = deleteReplicationPolicy;
|
||||
|
||||
vm.confirmToDelete = confirmToDelete;
|
||||
|
||||
vm.searchReplicationPolicy = searchReplicationPolicy;
|
||||
vm.searchReplicationJob = searchReplicationJob;
|
||||
@ -176,6 +179,40 @@
|
||||
console.log('Selected policy ID:' + vm.policyId);
|
||||
}
|
||||
|
||||
function deleteReplicationPolicy() {
|
||||
DeleteReplicationPolicyService(vm.policyId)
|
||||
.success(deleteReplicationPolicySuccess)
|
||||
.error(deleteReplicationPolicyFailed);
|
||||
}
|
||||
|
||||
function deleteReplicationPolicySuccess(data, status) {
|
||||
console.log('Successful delete replication policy.');
|
||||
vm.retrievePolicy();
|
||||
}
|
||||
|
||||
function deleteReplicationPolicyFailed(data, status) {
|
||||
$scope.$emit('modalTitle', $filter('tr')('error'));
|
||||
$scope.$emit('modalMessage', $filter('tr')('failed_to_delete_replication_policy'));
|
||||
$scope.$emit('raiseError', true);
|
||||
console.log('Failed to delete replication policy.');
|
||||
}
|
||||
|
||||
function confirmToDelete(policyId, policyName) {
|
||||
vm.policyId = policyId;
|
||||
|
||||
$scope.$emit('modalTitle', $filter('tr')('confirm_delete_policy_title'));
|
||||
$scope.$emit('modalMessage', $filter('tr')('confirm_delete_policy', [policyName]));
|
||||
|
||||
var emitInfo = {
|
||||
'confirmOnly': false,
|
||||
'contentType': 'text/plain',
|
||||
'action': vm.deleteReplicationPolicy
|
||||
};
|
||||
|
||||
$scope.$emit('raiseInfo', emitInfo);
|
||||
}
|
||||
|
||||
|
||||
function confirmToTogglePolicy(policyId, enabled, name) {
|
||||
vm.policyId = policyId;
|
||||
vm.enabled = enabled;
|
||||
|
@ -48,7 +48,6 @@
|
||||
vm.tagCount = {};
|
||||
|
||||
vm.projectId = getParameterByName('project_id', $location.absUrl());
|
||||
vm.retrieve();
|
||||
|
||||
$scope.$on('$locationChangeSuccess', function() {
|
||||
vm.projectId = getParameterByName('project_id', $location.absUrl());
|
||||
@ -62,11 +61,9 @@
|
||||
vm.repositories = current || [];
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
$scope.$watch('vm.page', function(current) {
|
||||
if(current !== 1) {
|
||||
if(current) {
|
||||
vm.page = current;
|
||||
vm.retrieve();
|
||||
}
|
||||
|
@ -24,13 +24,14 @@
|
||||
|
||||
function NavigationDetailsController($window, $location, $scope, getParameterByName) {
|
||||
var vm = this;
|
||||
|
||||
|
||||
vm.projectId = getParameterByName('project_id', $location.absUrl());
|
||||
|
||||
$scope.$on('$locationChangeSuccess', function() {
|
||||
vm.projectId = getParameterByName('project_id', $location.absUrl());
|
||||
});
|
||||
|
||||
|
||||
vm.path = $location.path();
|
||||
}
|
||||
|
||||
@ -51,19 +52,23 @@
|
||||
return directive;
|
||||
|
||||
function link(scope, element, attrs, ctrl) {
|
||||
|
||||
|
||||
var visited = ctrl.path.substring(1);
|
||||
if(visited.indexOf('?') >= 0) {
|
||||
visited = ctrl.url.substring(1, ctrl.url.indexOf('?'));
|
||||
}
|
||||
|
||||
if(visited) {
|
||||
element.find('a[tag="' + visited + '"]').addClass('active');
|
||||
}else{
|
||||
element.find('a:first').addClass('active');
|
||||
}
|
||||
|
||||
ctrl.target = visited;
|
||||
|
||||
scope.$watch('vm.target', function(current) {
|
||||
if(current) {
|
||||
ctrl.target = current;
|
||||
element.find('a').removeClass('active');
|
||||
element.find('a[tag="' + ctrl.target + '"]').addClass('active');
|
||||
}
|
||||
});
|
||||
|
||||
element.find('a').on('click', click);
|
||||
|
||||
function click(event) {
|
||||
|
@ -20,14 +20,17 @@
|
||||
.module('harbor.layout.project')
|
||||
.controller('ProjectController', ProjectController);
|
||||
|
||||
ProjectController.$inject = ['$scope', 'ListProjectService', '$timeout', 'currentUser', 'getRole', '$filter', 'trFilter'];
|
||||
ProjectController.$inject = ['$scope', 'ListProjectService', 'DeleteProjectService', '$timeout', 'currentUser', 'getRole', '$filter', 'trFilter'];
|
||||
|
||||
function ProjectController($scope, ListProjectService, $timeout, currentUser, getRole, $filter, trFilter) {
|
||||
function ProjectController($scope, ListProjectService, DeleteProjectService, $timeout, currentUser, getRole, $filter, trFilter) {
|
||||
var vm = this;
|
||||
|
||||
vm.isOpen = false;
|
||||
vm.projectName = '';
|
||||
vm.publicity = 0;
|
||||
|
||||
vm.page = 1;
|
||||
vm.pageSize = 10;
|
||||
|
||||
vm.retrieve = retrieve;
|
||||
vm.showAddProject = showAddProject;
|
||||
@ -35,10 +38,11 @@
|
||||
vm.showAddButton = showAddButton;
|
||||
vm.togglePublicity = togglePublicity;
|
||||
vm.user = currentUser.get();
|
||||
vm.retrieve();
|
||||
vm.getProjectRole = getProjectRole;
|
||||
|
||||
vm.searchProjectByKeyPress = searchProjectByKeyPress;
|
||||
vm.confirmToDelete = confirmToDelete;
|
||||
vm.deleteProject = deleteProject;
|
||||
|
||||
|
||||
//Error message dialog handler for project.
|
||||
@ -57,19 +61,42 @@
|
||||
};
|
||||
vm.contentType = 'text/plain';
|
||||
vm.confirmOnly = true;
|
||||
$timeout(function() {
|
||||
$scope.$broadcast('showDialog', true);
|
||||
}, 350);
|
||||
}
|
||||
});
|
||||
|
||||
$scope.$on('raiseInfo', function(e, val) {
|
||||
if(val) {
|
||||
vm.action = function() {
|
||||
val.action();
|
||||
$scope.$broadcast('showDialog', false);
|
||||
};
|
||||
vm.contentType = val.contentType;
|
||||
vm.confirmOnly = val.confirmOnly;
|
||||
|
||||
$scope.$broadcast('showDialog', true);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
$scope.$watch('vm.page', function(current) {
|
||||
if(current) {
|
||||
vm.page = current;
|
||||
vm.retrieve();
|
||||
}
|
||||
});
|
||||
|
||||
function retrieve() {
|
||||
ListProjectService(vm.projectName, vm.publicity)
|
||||
.success(listProjectSuccess)
|
||||
.error(listProjectFailed);
|
||||
ListProjectService(vm.projectName, vm.publicity, vm.page, vm.pageSize)
|
||||
.then(listProjectSuccess)
|
||||
.catch(listProjectFailed);
|
||||
}
|
||||
|
||||
function listProjectSuccess(data, status) {
|
||||
vm.projects = data || [];
|
||||
function listProjectSuccess(response) {
|
||||
vm.totalCount = response.headers('X-Total-Count');
|
||||
vm.projects = response.data || [];
|
||||
}
|
||||
|
||||
function getProjectRole(roleId) {
|
||||
@ -80,7 +107,7 @@
|
||||
return '';
|
||||
}
|
||||
|
||||
function listProjectFailed(data, status) {
|
||||
function listProjectFailed(response) {
|
||||
$scope.$emit('modalTitle', $filter('tr')('error'));
|
||||
$scope.$emit('modalMessage', $filter('tr')('failed_to_get_project'));
|
||||
$scope.$emit('raiseError', true);
|
||||
@ -92,11 +119,7 @@
|
||||
});
|
||||
|
||||
function showAddProject() {
|
||||
if(vm.isOpen){
|
||||
vm.isOpen = false;
|
||||
}else{
|
||||
vm.isOpen = true;
|
||||
}
|
||||
vm.isOpen = vm.isOpen ? false : true;
|
||||
}
|
||||
|
||||
function searchProject() {
|
||||
@ -104,16 +127,13 @@
|
||||
}
|
||||
|
||||
function showAddButton() {
|
||||
if(vm.publicity === 0) {
|
||||
return true;
|
||||
}else{
|
||||
return false;
|
||||
}
|
||||
return (vm.publicity === 0);
|
||||
}
|
||||
|
||||
function togglePublicity(e) {
|
||||
vm.publicity = e.publicity;
|
||||
vm.isOpen = false;
|
||||
vm.page = 1;
|
||||
vm.retrieve();
|
||||
console.log('vm.publicity:' + vm.publicity);
|
||||
}
|
||||
@ -125,6 +145,39 @@
|
||||
}
|
||||
}
|
||||
|
||||
function confirmToDelete(projectId, projectName) {
|
||||
vm.selectedProjectId = projectId;
|
||||
|
||||
$scope.$emit('modalTitle', $filter('tr')('confirm_delete_project_title'));
|
||||
$scope.$emit('modalMessage', $filter('tr')('confirm_delete_project', [projectName]));
|
||||
|
||||
var emitInfo = {
|
||||
'confirmOnly': false,
|
||||
'contentType': 'text/plain',
|
||||
'action': vm.deleteProject
|
||||
};
|
||||
|
||||
$scope.$emit('raiseInfo', emitInfo);
|
||||
}
|
||||
|
||||
function deleteProject() {
|
||||
DeleteProjectService(vm.selectedProjectId)
|
||||
.success(deleteProjectSuccess)
|
||||
.error(deleteProjectFailed);
|
||||
}
|
||||
|
||||
function deleteProjectSuccess(data, status) {
|
||||
console.log('Successful delete project.');
|
||||
vm.retrieve();
|
||||
}
|
||||
|
||||
function deleteProjectFailed(data, status) {
|
||||
$scope.$emit('modalTitle', $filter('tr')('error'));
|
||||
$scope.$emit('modalMessage', $filter('tr')('failed_to_delete_project'));
|
||||
$scope.$emit('raiseError', true);
|
||||
console.log('Failed to delete project.');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
})();
|
@ -149,8 +149,13 @@ var locale_messages = {
|
||||
'failed_to_add_member': 'Project member can not be added, insuffient permissions.',
|
||||
'failed_to_change_member': 'Project member can not be changed, insuffient permissions.',
|
||||
'failed_to_delete_member': 'Project member can not be deleted, insuffient permissions.',
|
||||
'failed_to_delete_project': 'Project can not be deleted, insuffient permissions.',
|
||||
'confirm_delete_project_title': 'Project Deletion',
|
||||
'confirm_delete_project': 'Are you sure to delete the project "$0" ?',
|
||||
'confirm_delete_user_title': 'User Deletion',
|
||||
'confirm_delete_user': 'Are you sure to delete the user "$0" ?',
|
||||
'confirm_delete_policy_title': 'Replication Policy Deletion',
|
||||
'confirm_delete_policy': 'Are you sure to delete the replication policy "$0" ?',
|
||||
'confirm_delete_destination_title': 'Destination Deletion',
|
||||
'confirm_delete_destination': 'Are you sure to delete the destination "$0" ?',
|
||||
'replication': 'Replication',
|
||||
@ -194,6 +199,7 @@ var locale_messages = {
|
||||
'successful_signed_up': 'Signed up successfully.',
|
||||
'add_new_policy': 'Add New Policy',
|
||||
'edit_policy': 'Edit Policy',
|
||||
'delete_policy': 'Delete Policy',
|
||||
'add_new_title': 'Add User',
|
||||
'add_new': 'Add',
|
||||
'successful_added': 'New user added successfully.',
|
||||
@ -246,6 +252,7 @@ var locale_messages = {
|
||||
'failed_to_get_destination_policies': 'Failed to get destination policies.',
|
||||
'failed_to_get_replication_policy': 'Failed to get replication policy.',
|
||||
'failed_to_update_replication_policy': 'Failed to update replication policy.',
|
||||
'failed_to_delete_replication_policy': 'Failed to delete 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.',
|
||||
|
@ -148,8 +148,13 @@ var locale_messages = {
|
||||
'failed_to_add_member': '无法添加项目成员,权限不足。',
|
||||
'failed_to_change_member': '无法修改项目成员,权限不足。',
|
||||
'failed_to_delete_member': '无法删除项目成员,权限不足。',
|
||||
'failed_to_delete_project' : '无法删除项目,权限不足。',
|
||||
'confirm_delete_project_title': '删除项目',
|
||||
'confirm_delete_project': '确认删除项目 "$0" ?',
|
||||
'confirm_delete_user_title': '删除用户',
|
||||
'confirm_delete_user': '确认删除用户 "$0" ?',
|
||||
'confirm_delete_policy_title': '删除复制策略',
|
||||
'confirm_delete_policy': '确认删除复制策略 "$0" ?',
|
||||
'confirm_delete_destination_title': '删除目标',
|
||||
'confirm_delete_destination': '确认删除目标 "$0"?',
|
||||
'replication': '复制',
|
||||
@ -194,6 +199,7 @@ var locale_messages = {
|
||||
'successful_signed_up': '注册成功。',
|
||||
'add_new_policy': '新增策略',
|
||||
'edit_policy': '修改策略',
|
||||
'delete_policy': '删除策略',
|
||||
'add_new_title': '新增用户',
|
||||
'add_new': '新增',
|
||||
'successful_added': '新增用户成功。',
|
||||
@ -246,6 +252,7 @@ var locale_messages = {
|
||||
'failed_to_get_destination_policies': '获取目标关联策略数据失败。',
|
||||
'failed_to_get_replication_policy': '获取复制策略失败。',
|
||||
'failed_to_update_replication_policy': '修改复制策略失败。',
|
||||
'failed_to_delete_replication_policy': '删除复制策略失败。',
|
||||
'failed_to_delete_destination': '删除目标失败。',
|
||||
'failed_to_create_destination': '创建目标失败。',
|
||||
'failed_to_update_destination': '修改目标失败。',
|
||||
|
@ -0,0 +1,34 @@
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
angular
|
||||
.module('harbor.services.project')
|
||||
.factory('DeleteProjectService', DeleteProjectService);
|
||||
|
||||
DeleteProjectService.$inject = ['$http', '$log'];
|
||||
|
||||
function DeleteProjectService($http, $log) {
|
||||
|
||||
return DeleteProject;
|
||||
|
||||
function DeleteProject(projectId) {
|
||||
return $http
|
||||
.delete('/api/projects/' + projectId);
|
||||
}
|
||||
}
|
||||
|
||||
})();
|
@ -25,10 +25,14 @@
|
||||
|
||||
return ListProject;
|
||||
|
||||
function ListProject(projectName, isPublic) {
|
||||
function ListProject(projectName, isPublic, page, pageSize) {
|
||||
$log.info('list project projectName:' + projectName, ', isPublic:' + isPublic);
|
||||
var urlParams = '';
|
||||
if(angular.isDefined(page, pageSize)) {
|
||||
urlParams = '?page=' + page + '&page_size=' + pageSize;
|
||||
}
|
||||
return $http
|
||||
.get('/api/projects', {
|
||||
.get('/api/projects' + urlParams, {
|
||||
'params' : {
|
||||
'is_public': isPublic,
|
||||
'project_name': projectName
|
||||
|
@ -0,0 +1,34 @@
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
(function() {
|
||||
|
||||
'use strict';
|
||||
|
||||
angular
|
||||
.module('harbor.services.replication.policy')
|
||||
.factory('DeleteReplicationPolicyService', DeleteReplicationPolicyService);
|
||||
|
||||
DeleteReplicationPolicyService.$inject = ['$http'];
|
||||
|
||||
function DeleteReplicationPolicyService($http) {
|
||||
return deleteReplicationPolicy;
|
||||
|
||||
function deleteReplicationPolicy(policyId) {
|
||||
return $http
|
||||
.delete('/api/policies/replication/' + policyId);
|
||||
}
|
||||
}
|
||||
|
||||
})();
|
@ -31,7 +31,7 @@ type Project struct {
|
||||
OwnerId int32 `json:"owner_id,omitempty"`
|
||||
|
||||
// The name of the project.
|
||||
ProjectName string `json:"project_name,omitempty"`
|
||||
ProjectName string `json:"name,omitempty"`
|
||||
|
||||
// The creation time of the project.
|
||||
CreationTime string `json:"creation_time,omitempty"`
|
||||
|
32
tests/apitests/apilib/project_req.go
Normal file
32
tests/apitests/apilib/project_req.go
Normal file
@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Harbor API
|
||||
*
|
||||
* These APIs provide services for manipulating Harbor project.
|
||||
*
|
||||
* OpenAPI spec version: 0.3.0
|
||||
*
|
||||
* Generated by: https://github.com/swagger-api/swagger-codegen.git
|
||||
*
|
||||
* 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 apilib
|
||||
|
||||
type ProjectReq struct {
|
||||
|
||||
// The name of the project.
|
||||
ProjectName string `json:"project_name,omitempty"`
|
||||
|
||||
// The public status of the project.
|
||||
Public int32 `json:"public,omitempty"`
|
||||
}
|
@ -1,10 +1,10 @@
|
||||
/*
|
||||
/*
|
||||
* Harbor API
|
||||
*
|
||||
* These APIs provide services for manipulating Harbor project.
|
||||
*
|
||||
* OpenAPI spec version: 0.3.0
|
||||
*
|
||||
*
|
||||
* Generated by: https://github.com/swagger-api/swagger-codegen.git
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@ -25,7 +25,7 @@ package apilib
|
||||
type User struct {
|
||||
|
||||
// The ID of the user.
|
||||
UserId int32 `json:"user_id,omitempty"`
|
||||
UserId int `json:"user_id,omitempty"`
|
||||
|
||||
Username string `json:"username,omitempty"`
|
||||
|
||||
@ -38,4 +38,18 @@ type User struct {
|
||||
Comment string `json:"comment,omitempty"`
|
||||
|
||||
Deleted int32 `json:"deleted,omitempty"`
|
||||
|
||||
RoleName string `json:"role_name,omitempty"`
|
||||
|
||||
RoleId int32 `json:"role_id,omitempty"`
|
||||
|
||||
HasAdminRole int32 `json:"has_admin_role,omitempty"`
|
||||
|
||||
ResetUuid string `json:"reset_uuid,omitempty"`
|
||||
|
||||
Salt string `json:"Salt,omitempty"`
|
||||
|
||||
CreationTime string `json:"creation_time,omitempty"`
|
||||
|
||||
UpdateTime string `json:"update_time,omitempty"`
|
||||
}
|
||||
|
@ -11,7 +11,7 @@ services:
|
||||
ports:
|
||||
- 5000:5000
|
||||
command:
|
||||
["serve", "/etc/registry/config.yml"]
|
||||
["serve", "/etc/docker/registry/config.yml"]
|
||||
mysql:
|
||||
build: ./db/
|
||||
restart: always
|
||||
|
12
tests/pushimage.sh
Executable file
12
tests/pushimage.sh
Executable file
@ -0,0 +1,12 @@
|
||||
#!/bin/bash
|
||||
|
||||
IP=`ip addr s eth0 |grep "inet "|awk '{print $2}' |awk -F "/" '{print $1}'`
|
||||
docker pull hello-world
|
||||
docker pull docker
|
||||
docker login -u admin -p Harbor12345 $IP:5000
|
||||
|
||||
docker tag hello-world $IP:5000/library/hello-world:latest
|
||||
docker push $IP:5000/library/hello-world:latest
|
||||
|
||||
docker tag docker $IP:5000/library/docker:latest
|
||||
docker push $IP:5000/library/docker:latest
|
@ -17,11 +17,11 @@ package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
log "github.com/vmware/harbor/utils/log"
|
||||
|
||||
"os"
|
||||
|
||||
"github.com/vmware/harbor/api"
|
||||
_ "github.com/vmware/harbor/auth/db"
|
||||
_ "github.com/vmware/harbor/auth/ldap"
|
||||
"github.com/vmware/harbor/dao"
|
||||
@ -80,5 +80,8 @@ func main() {
|
||||
log.Error(err)
|
||||
}
|
||||
initRouters()
|
||||
if err := api.SyncRegistry(); err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
beego.Run()
|
||||
}
|
||||
|
@ -65,6 +65,7 @@ func initRouters() {
|
||||
beego.Router("/api/projects/:id([0-9]+)/logs/filter", &api.ProjectAPI{}, "post:FilterAccessLog")
|
||||
beego.Router("/api/users/?:id", &api.UserAPI{})
|
||||
beego.Router("/api/users/:id([0-9]+)/password", &api.UserAPI{}, "put:ChangePassword")
|
||||
beego.Router("/api/internal/syncregistry", &api.InternalAPI{}, "post:SyncRegistry")
|
||||
beego.Router("/api/repositories", &api.RepositoryAPI{})
|
||||
beego.Router("/api/repositories/tags", &api.RepositoryAPI{}, "get:GetTags")
|
||||
beego.Router("/api/repositories/manifests", &api.RepositoryAPI{}, "get:GetManifests")
|
||||
|
@ -37,16 +37,19 @@
|
||||
<button ng-if="!vm.isOpen" class="btn btn-success" type="button" ng-show="vm.showAddButton()" ng-click="vm.showAddProject()"><span class="glyphicon glyphicon-plus"></span> // 'new_project' | tr //</button>
|
||||
</div>
|
||||
</div>
|
||||
<add-project ng-show="vm.isOpen" is-open="vm.isOpen"></add-project>
|
||||
|
||||
<add-project ng-show="vm.isOpen" is-open="vm.isOpen"></add-project>
|
||||
<div class="each-tab-pane">
|
||||
<div class="sub-pane">
|
||||
<div class="table-head-container">
|
||||
<table class="table table-pane table-header">
|
||||
<thead>
|
||||
<th width="15%">// 'project_name' | tr //</th>
|
||||
<th width="20%">// 'repositories' | tr //</th>
|
||||
<th width="20%">// 'project_name' | tr //</th>
|
||||
<th width="15%">// 'repositories' | tr //</th>
|
||||
<th width="15%" ng-if="!vm.publicity">// 'role' | tr //</th>
|
||||
<th width="30%">// 'creation_time' | tr //</th>
|
||||
<th width="20%">// 'creation_time' | tr //</th>
|
||||
<th width="15%">// 'publicity' | tr //</th>
|
||||
<th width="10%">// 'operation' | tr //</th>
|
||||
</thead>
|
||||
</table>
|
||||
</div>
|
||||
@ -57,17 +60,21 @@
|
||||
<td colspan="5" height="320px" class="empty-hint" ng-if="vm.projects.length === 0"><h4 class="text-muted">// 'no_projects_add_new_project' | tr //</h4></td>
|
||||
</tr>
|
||||
<tr ng-if="vm.projects.length > 0" ng-repeat="p in vm.projects">
|
||||
<td width="15%"><a href="/repository#/repositories?project_id=//p.project_id//&is_public=//p.public//">//p.name//</a></td>
|
||||
<td width="20%">//p.repo_count//</td>
|
||||
<td width="20%"><a href="/repository#/repositories?project_id=//p.project_id//&is_public=//p.public//">//p.name//</a></td>
|
||||
<td width="15%">//p.repo_count//</td>
|
||||
<td width="15%" ng-if="!vm.publicity">//vm.getProjectRole(p.current_user_role_id) | tr//</td>
|
||||
<td width="30%">//p.creation_time | dateL : 'YYYY-MM-DD HH:mm:ss'//</td>
|
||||
<td width="15%"><publicity-button is-public="p.public" owned="p.owner_id == vm.user.user_id" project-id="p.project_id"></publicity-button></td>
|
||||
<td width="20%">//p.creation_time | dateL : 'YYYY-MM-DD HH:mm:ss'//</td>
|
||||
<td width="15%"><publicity-button is-public="p.public" project-id="p.project_id"></publicity-button></td>
|
||||
<td width="10%">
|
||||
<a href="javascript:void(0)" ng-click="vm.confirmToDelete(p.project_id, p.name)"><span class="glyphicon glyphicon-trash"></span></a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xs-4 col-md-12 well well-sm well-custom"><div class="col-md-offset-10">//vm.projects ? vm.projects.length : 0// // 'items' | tr //</div></div>
|
||||
</div>
|
||||
<paginator ng-if="vm.totalCount > 0" total-count="//vm.totalCount//" page-size="//vm.pageSize//" page="vm.page" display-count="5"></paginator>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -30,7 +30,7 @@
|
||||
<navigation-details target="vm.target" ng-show="vm.isProjectMember"></navigation-details>
|
||||
</span>
|
||||
</div>
|
||||
<retrieve-projects is-open="vm.isOpen" selected-project="vm.selectedProject" is-project-member="vm.isProjectMember" publicity="vm.publicity"></retrieve-projects>
|
||||
<retrieve-projects target="vm.target" is-open="vm.isOpen" selected-project="vm.selectedProject" is-project-member="vm.isProjectMember" publicity="vm.publicity"></retrieve-projects>
|
||||
<!-- Tab panes -->
|
||||
<div class="tab-content" ng-click="vm.closeRetrievePane()">
|
||||
<input type="hidden" id="HarborRegUrl" value="{{.HarborRegUrl}}">
|
||||
|
@ -58,6 +58,7 @@
|
||||
<script src="/static/resources/js/services/project/services.add-project.js"></script>
|
||||
<script src="/static/resources/js/services/project/services.toggle-project-publicity.js"></script>
|
||||
<script src="/static/resources/js/services/project/services.stat-project.js"></script>
|
||||
<script src="/static/resources/js/services/project/services.delete-project.js"></script>
|
||||
|
||||
<script src="/static/resources/js/services/user/services.user.module.js"></script>
|
||||
<script src="/static/resources/js/services/user/services.current-user.js"></script>
|
||||
@ -96,6 +97,7 @@
|
||||
<script src="/static/resources/js/services/replication-policy/services.create-replication-policy.js"></script>
|
||||
<script src="/static/resources/js/services/replication-policy/services.toggle-replication-policy.js"></script>
|
||||
<script src="/static/resources/js/services/replication-policy/services.update-replication-policy.js"></script>
|
||||
<script src="/static/resources/js/services/replication-policy/services.delete-replication-policy.js"></script>
|
||||
|
||||
<script src="/static/resources/js/services/replication-job/services.replication-job.module.js"></script>
|
||||
<script src="/static/resources/js/services/replication-job/services.list-replication-job.js"></script>
|
||||
|
Loading…
Reference in New Issue
Block a user