mirror of
https://github.com/goharbor/harbor.git
synced 2025-02-02 13:01:23 +01:00
Fix conflicts of list-repository.component.spec.ts
This commit is contained in:
commit
3e02f756a3
@ -861,24 +861,18 @@ paths:
|
||||
description: |
|
||||
This endpoint let user see the recent operation logs of the projects which he is member of
|
||||
parameters:
|
||||
- name: lines
|
||||
- name: page
|
||||
in: query
|
||||
type: integer
|
||||
format: int32
|
||||
required: false
|
||||
description: The number of logs to be shown, default is 10 if lines, start_time, end_time are not provided.
|
||||
- name: start_time
|
||||
description: The page nubmer, default is 1.
|
||||
- name: page_size
|
||||
in: query
|
||||
type: integer
|
||||
format: int64
|
||||
format: int32
|
||||
required: false
|
||||
description: The start time of logs to be shown in unix timestap
|
||||
- name: end_time
|
||||
in: query
|
||||
type: integer
|
||||
format: int64
|
||||
required: false
|
||||
description: The end time of logs to be shown in unix timestap
|
||||
description: The size of per page, default is 10, maximum is 100.
|
||||
tags:
|
||||
- Products
|
||||
responses:
|
||||
@ -889,7 +883,7 @@ paths:
|
||||
items:
|
||||
$ref: '#/definitions/AccessLog'
|
||||
400:
|
||||
description: Bad request because of invalid parameter of lines or start_time or end_time.
|
||||
description: Bad request because of invalid parameters.
|
||||
401:
|
||||
description: User need to login first.
|
||||
500:
|
||||
|
@ -15,8 +15,7 @@
|
||||
package dao
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/astaxie/beego/orm"
|
||||
"github.com/vmware/harbor/src/common/models"
|
||||
"github.com/vmware/harbor/src/common/utils/log"
|
||||
)
|
||||
@ -29,155 +28,61 @@ func AddAccessLog(accessLog models.AccessLog) error {
|
||||
}
|
||||
|
||||
// GetTotalOfAccessLogs ...
|
||||
func GetTotalOfAccessLogs(query models.AccessLog) (int64, error) {
|
||||
o := GetOrmer()
|
||||
|
||||
queryParam := []interface{}{}
|
||||
|
||||
sql := `select count(*) from access_log al
|
||||
where al.project_id = ?`
|
||||
queryParam = append(queryParam, query.ProjectID)
|
||||
|
||||
sql += genFilterClauses(query, &queryParam)
|
||||
|
||||
var total int64
|
||||
if err := o.Raw(sql, queryParam).QueryRow(&total); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return total, nil
|
||||
func GetTotalOfAccessLogs(query *models.LogQueryParam) (int64, error) {
|
||||
return logQueryConditions(query).Count()
|
||||
}
|
||||
|
||||
//GetAccessLogs gets access logs according to different conditions
|
||||
func GetAccessLogs(query models.AccessLog, limit, offset int64) ([]models.AccessLog, error) {
|
||||
o := GetOrmer()
|
||||
func GetAccessLogs(query *models.LogQueryParam) ([]models.AccessLog, error) {
|
||||
qs := logQueryConditions(query).OrderBy("-op_time")
|
||||
|
||||
queryParam := []interface{}{}
|
||||
sql := `select al.log_id, al.username, al.repo_name,
|
||||
al.repo_tag, al.operation, al.op_time
|
||||
from access_log al
|
||||
where al.project_id = ? `
|
||||
queryParam = append(queryParam, query.ProjectID)
|
||||
if query != nil && query.Pagination != nil {
|
||||
size := query.Pagination.Size
|
||||
if size > 0 {
|
||||
qs = qs.Limit(size)
|
||||
|
||||
sql += genFilterClauses(query, &queryParam)
|
||||
|
||||
sql += ` order by al.op_time desc `
|
||||
|
||||
sql = paginateForRawSQL(sql, limit, offset)
|
||||
page := query.Pagination.Page
|
||||
if page > 0 {
|
||||
qs = qs.Offset((page - 1) * size)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
logs := []models.AccessLog{}
|
||||
_, err := o.Raw(sql, queryParam).QueryRows(&logs)
|
||||
if err != nil {
|
||||
_, err := qs.All(&logs)
|
||||
return logs, err
|
||||
}
|
||||
|
||||
return logs, nil
|
||||
}
|
||||
|
||||
func genFilterClauses(query models.AccessLog, queryParam *[]interface{}) string {
|
||||
sql := ""
|
||||
func logQueryConditions(query *models.LogQueryParam) orm.QuerySeter {
|
||||
qs := GetOrmer().QueryTable(&models.AccessLog{})
|
||||
|
||||
if query.Username != "" {
|
||||
sql += ` and al.username like ? `
|
||||
*queryParam = append(*queryParam, "%"+escape(query.Username)+"%")
|
||||
if query == nil {
|
||||
return qs
|
||||
}
|
||||
|
||||
if query.Operation != "" {
|
||||
sql += ` and al.operation = ? `
|
||||
*queryParam = append(*queryParam, query.Operation)
|
||||
if len(query.ProjectIDs) > 0 {
|
||||
qs = qs.Filter("project_id__in", query.ProjectIDs)
|
||||
}
|
||||
if query.RepoName != "" {
|
||||
sql += ` and al.repo_name = ? `
|
||||
*queryParam = append(*queryParam, query.RepoName)
|
||||
if len(query.Username) != 0 {
|
||||
qs = qs.Filter("username__contains", query.Username)
|
||||
}
|
||||
if query.RepoTag != "" {
|
||||
sql += ` and al.repo_tag = ? `
|
||||
*queryParam = append(*queryParam, query.RepoTag)
|
||||
if len(query.Repository) != 0 {
|
||||
qs = qs.Filter("repo_name", query.Repository)
|
||||
}
|
||||
if query.Keywords != "" {
|
||||
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 {
|
||||
sql += `?)`
|
||||
} else {
|
||||
sql += `?,`
|
||||
if len(query.Tag) != 0 {
|
||||
qs = qs.Filter("repo_tag", query.Tag)
|
||||
}
|
||||
*queryParam = append(*queryParam, keywordList[i])
|
||||
if len(query.Operations) > 0 {
|
||||
qs = qs.Filter("operation__in", query.Operations)
|
||||
}
|
||||
if query.BeginTime != nil {
|
||||
qs = qs.Filter("op_time__gte", query.BeginTime)
|
||||
}
|
||||
}
|
||||
if query.BeginTimestamp > 0 {
|
||||
sql += ` and al.op_time >= ? `
|
||||
*queryParam = append(*queryParam, query.BeginTime)
|
||||
}
|
||||
if query.EndTimestamp > 0 {
|
||||
sql += ` and al.op_time <= ? `
|
||||
*queryParam = append(*queryParam, query.EndTime)
|
||||
if query.EndTime != nil {
|
||||
qs = qs.Filter("op_time__lte", query.EndTime)
|
||||
}
|
||||
|
||||
return sql
|
||||
}
|
||||
|
||||
//GetRecentLogs returns recent logs according to parameters
|
||||
func GetRecentLogs(username string, linesNum int, startTime, endTime string) ([]models.AccessLog, error) {
|
||||
logs := []models.AccessLog{}
|
||||
|
||||
isAdmin, err := IsAdminRole(username)
|
||||
if err != nil {
|
||||
return logs, err
|
||||
}
|
||||
|
||||
queryParam := []interface{}{}
|
||||
sql := `select log_id, username, project_id, repo_name, repo_tag, GUID, operation, op_time
|
||||
from access_log `
|
||||
|
||||
hasWhere := false
|
||||
if !isAdmin {
|
||||
sql += ` where project_id in
|
||||
(select distinct project_id
|
||||
from project_member pm
|
||||
join user u
|
||||
on pm.user_id = u.user_id
|
||||
where u.username = ?) `
|
||||
queryParam = append(queryParam, username)
|
||||
hasWhere = true
|
||||
}
|
||||
|
||||
if startTime != "" {
|
||||
if hasWhere {
|
||||
sql += " and op_time >= ?"
|
||||
} else {
|
||||
sql += " where op_time >= ?"
|
||||
hasWhere = true
|
||||
}
|
||||
|
||||
queryParam = append(queryParam, startTime)
|
||||
}
|
||||
|
||||
if endTime != "" {
|
||||
if hasWhere {
|
||||
sql += " and op_time <= ?"
|
||||
} else {
|
||||
sql += " where op_time <= ?"
|
||||
hasWhere = true
|
||||
}
|
||||
|
||||
queryParam = append(queryParam, endTime)
|
||||
}
|
||||
|
||||
sql += " order by op_time desc"
|
||||
if linesNum != 0 {
|
||||
sql += " limit ?"
|
||||
queryParam = append(queryParam, linesNum)
|
||||
}
|
||||
|
||||
_, err = GetOrmer().Raw(sql, queryParam).QueryRows(&logs)
|
||||
if err != nil {
|
||||
return logs, err
|
||||
}
|
||||
return logs, nil
|
||||
return qs
|
||||
}
|
||||
|
||||
// CountPull ...
|
||||
|
@ -504,7 +504,7 @@ func TestChangeUserPasswordWithIncorrectOldPassword(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestQueryRelevantProjectsWhenNoProjectAdded(t *testing.T) {
|
||||
projects, err := SearchProjects(currentUser.UserID)
|
||||
projects, err := GetHasReadPermProjects(currentUser.Username)
|
||||
if err != nil {
|
||||
t.Errorf("Error occurred in QueryRelevantProjects: %v", err)
|
||||
}
|
||||
@ -570,11 +570,11 @@ func TestGetAccessLog(t *testing.T) {
|
||||
t.Errorf("failed to add access log: %v", err)
|
||||
}
|
||||
|
||||
queryAccessLog := models.AccessLog{
|
||||
query := &models.LogQueryParam{
|
||||
Username: currentUser.Username,
|
||||
ProjectID: currentProject.ProjectID,
|
||||
ProjectIDs: []int64{currentProject.ProjectID},
|
||||
}
|
||||
accessLogs, err := GetAccessLogs(queryAccessLog, 1000, 0)
|
||||
accessLogs, err := GetAccessLogs(query)
|
||||
if err != nil {
|
||||
t.Errorf("Error occurred in GetAccessLog: %v", err)
|
||||
}
|
||||
@ -587,11 +587,11 @@ func TestGetAccessLog(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestGetTotalOfAccessLogs(t *testing.T) {
|
||||
queryAccessLog := models.AccessLog{
|
||||
query := &models.LogQueryParam{
|
||||
Username: currentUser.Username,
|
||||
ProjectID: currentProject.ProjectID,
|
||||
ProjectIDs: []int64{currentProject.ProjectID},
|
||||
}
|
||||
total, err := GetTotalOfAccessLogs(queryAccessLog)
|
||||
total, err := GetTotalOfAccessLogs(query)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to get total of access log: %v", err)
|
||||
}
|
||||
@ -617,7 +617,15 @@ func TestAddAccessLog(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Errorf("Error occurred in AddAccessLog: %v", err)
|
||||
}
|
||||
accessLogList, err = GetAccessLogs(accessLog, 1000, 0)
|
||||
|
||||
query := &models.LogQueryParam{
|
||||
Username: accessLog.Username,
|
||||
ProjectIDs: []int64{accessLog.ProjectID},
|
||||
Repository: accessLog.RepoName,
|
||||
Tag: accessLog.RepoTag,
|
||||
Operations: []string{accessLog.Operation},
|
||||
}
|
||||
accessLogList, err = GetAccessLogs(query)
|
||||
if err != nil {
|
||||
t.Errorf("Error occurred in GetAccessLog: %v", err)
|
||||
}
|
||||
@ -819,7 +827,7 @@ func TestGetProjects(t *testing.T) {
|
||||
|
||||
func TestGetPublicProjects(t *testing.T) {
|
||||
value := true
|
||||
projects, err := GetProjects(&models.QueryParam{
|
||||
projects, err := GetProjects(&models.ProjectQueryParam{
|
||||
Public: &value,
|
||||
})
|
||||
if err != nil {
|
||||
@ -953,16 +961,6 @@ func TestChangeUserProfile(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetRecentLogs(t *testing.T) {
|
||||
logs, err := GetRecentLogs(currentUser.Username, 10, "2016-05-13 00:00:00", time.Now().String())
|
||||
if err != nil {
|
||||
t.Errorf("error occured in getting recent logs, error: %v", err)
|
||||
}
|
||||
if len(logs) <= 0 {
|
||||
t.Errorf("get logs error, expected: %d, actual: %d", 1, len(logs))
|
||||
}
|
||||
}
|
||||
|
||||
var targetID, policyID, policyID2, policyID3, jobID, jobID2, jobID3 int64
|
||||
|
||||
func TestAddRepTarget(t *testing.T) {
|
||||
|
@ -156,11 +156,18 @@ func ToggleProjectPublicity(projectID int64, publicity int) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// SearchProjects returns a project list,
|
||||
// GetHasReadPermProjects returns a project list,
|
||||
// which satisfies the following conditions:
|
||||
// 1. the project is not deleted
|
||||
// 2. the prject is public or the user is a member of the project
|
||||
func SearchProjects(userID int) ([]*models.Project, error) {
|
||||
func GetHasReadPermProjects(username string) ([]*models.Project, error) {
|
||||
user, err := GetUser(models.User{
|
||||
Username: username,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
o := GetOrmer()
|
||||
|
||||
sql :=
|
||||
@ -174,7 +181,7 @@ func SearchProjects(userID int) ([]*models.Project, error) {
|
||||
|
||||
var projects []*models.Project
|
||||
|
||||
if _, err := o.Raw(sql, userID).QueryRows(&projects); err != nil {
|
||||
if _, err := o.Raw(sql, user.UserID).QueryRows(&projects); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@ -183,7 +190,7 @@ func SearchProjects(userID int) ([]*models.Project, error) {
|
||||
|
||||
// GetTotalOfProjects returns the total count of projects
|
||||
// according to the query conditions
|
||||
func GetTotalOfProjects(query *models.QueryParam) (int64, error) {
|
||||
func GetTotalOfProjects(query *models.ProjectQueryParam) (int64, error) {
|
||||
|
||||
var (
|
||||
owner string
|
||||
@ -203,7 +210,7 @@ func GetTotalOfProjects(query *models.QueryParam) (int64, error) {
|
||||
}
|
||||
}
|
||||
|
||||
sql, params := queryConditions(owner, name, public, member, role)
|
||||
sql, params := projectQueryConditions(owner, name, public, member, role)
|
||||
|
||||
sql = `select count(*) ` + sql
|
||||
|
||||
@ -213,7 +220,7 @@ func GetTotalOfProjects(query *models.QueryParam) (int64, error) {
|
||||
}
|
||||
|
||||
// GetProjects returns a project list according to the query conditions
|
||||
func GetProjects(query *models.QueryParam) ([]*models.Project, error) {
|
||||
func GetProjects(query *models.ProjectQueryParam) ([]*models.Project, error) {
|
||||
|
||||
var (
|
||||
owner string
|
||||
@ -239,7 +246,7 @@ func GetProjects(query *models.QueryParam) ([]*models.Project, error) {
|
||||
}
|
||||
}
|
||||
|
||||
sql, params := queryConditions(owner, name, public, member, role)
|
||||
sql, params := projectQueryConditions(owner, name, public, member, role)
|
||||
|
||||
sql = `select distinct p.project_id, p.name, p.public, p.owner_id,
|
||||
p.creation_time, p.update_time ` + sql
|
||||
@ -258,7 +265,7 @@ func GetProjects(query *models.QueryParam) ([]*models.Project, error) {
|
||||
return projects, err
|
||||
}
|
||||
|
||||
func queryConditions(owner, name string, public *bool, member string,
|
||||
func projectQueryConditions(owner, name string, public *bool, member string,
|
||||
role int) (string, []interface{}) {
|
||||
params := []interface{}{}
|
||||
|
||||
|
@ -152,12 +152,11 @@ func FilterRepPolicies(name string, projectID int64) ([]*models.RepPolicy, error
|
||||
|
||||
var args []interface{}
|
||||
|
||||
sql := `select rp.id, rp.project_id, p.name as project_name, rp.target_id,
|
||||
sql := `select rp.id, rp.project_id, rp.target_id,
|
||||
rt.name as target_name, rp.name, rp.enabled, rp.description,
|
||||
rp.cron_str, rp.start_time, rp.creation_time, rp.update_time,
|
||||
count(rj.status) as error_job_count
|
||||
from replication_policy rp
|
||||
left join project p on rp.project_id=p.project_id
|
||||
left join replication_target rt on rp.target_id=rt.id
|
||||
left join replication_job rj on rp.id=rj.policy_id and (rj.status="error"
|
||||
or rj.status="retrying")
|
||||
|
@ -125,50 +125,10 @@ func GetTotalOfRepositories(name string) (int64, error) {
|
||||
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, "%"+escape(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, "%"+escape(name)+"%")
|
||||
}
|
||||
|
||||
var total int64
|
||||
err := GetOrmer().Raw(sql, params).QueryRow(&total)
|
||||
return total, err
|
||||
}
|
||||
|
||||
// GetTotalOfRepositoriesByProject ...
|
||||
func GetTotalOfRepositoriesByProject(projectID int64, name string) (int64, error) {
|
||||
func GetTotalOfRepositoriesByProject(projectIDs []int64, name string) (int64, error) {
|
||||
qs := GetOrmer().QueryTable(&models.RepoRecord{}).
|
||||
Filter("ProjectID", projectID)
|
||||
Filter("project_id__in", projectIDs)
|
||||
|
||||
if len(name) != 0 {
|
||||
qs = qs.Filter("Name__contains", name)
|
||||
|
@ -88,78 +88,6 @@ func TestGetTotalOfRepositories(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
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 TestGetTopRepos(t *testing.T) {
|
||||
var err error
|
||||
require := require.New(t)
|
||||
@ -226,7 +154,7 @@ func TestGetTotalOfRepositoriesByProject(t *testing.T) {
|
||||
var projectID int64 = 1
|
||||
repoName := "library/total_count"
|
||||
|
||||
total, err := GetTotalOfRepositoriesByProject(projectID, repoName)
|
||||
total, err := GetTotalOfRepositoriesByProject([]int64{projectID}, repoName)
|
||||
if err != nil {
|
||||
t.Errorf("failed to get total of repositoreis of project %d: %v", projectID, err)
|
||||
return
|
||||
@ -246,7 +174,7 @@ func TestGetTotalOfRepositoriesByProject(t *testing.T) {
|
||||
}
|
||||
}()
|
||||
|
||||
n, err := GetTotalOfRepositoriesByProject(projectID, repoName)
|
||||
n, err := GetTotalOfRepositoriesByProject([]int64{projectID}, repoName)
|
||||
if err != nil {
|
||||
t.Errorf("failed to get total of repositoreis of project %d: %v", projectID, err)
|
||||
return
|
||||
|
@ -30,8 +30,19 @@ type AccessLog struct {
|
||||
Operation string `orm:"column(operation)" json:"operation"`
|
||||
OpTime time.Time `orm:"column(op_time)" json:"op_time"`
|
||||
Keywords string `orm:"-" json:"keywords"`
|
||||
BeginTime time.Time `orm:"-"`
|
||||
BeginTimestamp int64 `orm:"-" json:"begin_timestamp"`
|
||||
EndTime time.Time `orm:"-"`
|
||||
EndTimestamp int64 `orm:"-" json:"end_timestamp"`
|
||||
}
|
||||
|
||||
// LogQueryParam is used to set query conditions when listing
|
||||
// access logs.
|
||||
type LogQueryParam struct {
|
||||
ProjectIDs []int64 // the IDs of projects to which the operation is done
|
||||
Username string // the operator's username of the log
|
||||
Repository string // repository name
|
||||
Tag string // tag name
|
||||
Operations []string // operations
|
||||
BeginTime *time.Time // the time after which the operation is done
|
||||
EndTime *time.Time // the time before which the operation is doen
|
||||
Pagination *Pagination // pagination information
|
||||
}
|
||||
|
@ -58,7 +58,7 @@ func (ps *ProjectSorter) Swap(i, j int) {
|
||||
ps.Projects[i], ps.Projects[j] = ps.Projects[j], ps.Projects[i]
|
||||
}
|
||||
|
||||
// QueryParam can be used to set query parameters when listing projects.
|
||||
// ProjectQueryParam can be used to set query parameters when listing projects.
|
||||
// The query condition will be set in the query if its corresponding field
|
||||
// is not nil. Leave it empty if you don't want to apply this condition.
|
||||
//
|
||||
@ -69,7 +69,7 @@ func (ps *ProjectSorter) Swap(i, j int) {
|
||||
// List all public projects the owner of which is user1: query := &QueryParam{Owner:"user1",Public:true}
|
||||
// List projects which user1 is member of: query := &QueryParam{Member:&Member{Name:"user1"}}
|
||||
// List projects which user1 is the project admin : query := &QueryParam{Memeber:&Member{Name:"user1",Role:1}}
|
||||
type QueryParam struct {
|
||||
type ProjectQueryParam struct {
|
||||
Name string // the name of project
|
||||
Owner string // the username of project owner
|
||||
Public *bool // the project is public or not, can be ture, false and nil
|
||||
|
@ -106,12 +106,17 @@ func (f *fakePM) Update(projectIDOrName interface{}, project *models.Project) er
|
||||
}
|
||||
|
||||
// nil implement
|
||||
func (f *fakePM) GetAll(*models.QueryParam) ([]*models.Project, error) {
|
||||
func (f *fakePM) GetAll(*models.ProjectQueryParam) ([]*models.Project, error) {
|
||||
return []*models.Project{}, nil
|
||||
}
|
||||
|
||||
// nil implement
|
||||
func (f *fakePM) GetTotal(*models.QueryParam) (int64, error) {
|
||||
func (f *fakePM) GetHasReadPerm(username ...string) ([]*models.Project, error) {
|
||||
return []*models.Project{}, nil
|
||||
}
|
||||
|
||||
// nil implement
|
||||
func (f *fakePM) GetTotal(*models.ProjectQueryParam) (int64, error) {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
|
@ -54,7 +54,7 @@ func (sm *SM) EnterState(s string) (string, error) {
|
||||
return "", err
|
||||
}
|
||||
} else {
|
||||
log.Debugf("Job: %d, no handler found for state:%s, skip", sm.CurrentJob, sm.CurrentState)
|
||||
log.Debugf("Job: %v, no exit handler found for state:%s, skip", sm.CurrentJob, sm.CurrentState)
|
||||
}
|
||||
enterHandler, ok := sm.Handlers[s]
|
||||
var next = models.JobContinue
|
||||
@ -101,7 +101,7 @@ func (sm *SM) Start(s string) {
|
||||
log.Debugf("Job: %v, next state from handler: %s", sm.CurrentJob, n)
|
||||
}
|
||||
if err != nil {
|
||||
log.Warningf("Job: %v, the statemachin will enter error state due to error: %v", sm.CurrentJob, err)
|
||||
log.Warningf("Job: %v, the statemachine will enter error state due to error: %v", sm.CurrentJob, err)
|
||||
sm.EnterState(models.JobError)
|
||||
}
|
||||
}
|
||||
@ -187,6 +187,7 @@ func (sm *SM) Reset(j Job) error {
|
||||
sm.AddTransition(models.JobRetrying, models.JobRunning, StatusUpdater{sm.CurrentJob, models.JobRunning})
|
||||
sm.Handlers[models.JobError] = StatusUpdater{sm.CurrentJob, models.JobError}
|
||||
sm.Handlers[models.JobStopped] = StatusUpdater{sm.CurrentJob, models.JobStopped}
|
||||
sm.Handlers[models.JobCanceled] = StatusUpdater{sm.CurrentJob, models.JobCanceled}
|
||||
sm.Handlers[models.JobRetrying] = Retry{sm.CurrentJob}
|
||||
if err := sm.CurrentJob.Init(); err != nil {
|
||||
return err
|
||||
@ -201,12 +202,14 @@ func (sm *SM) kickOff() error {
|
||||
if repJob, ok := sm.CurrentJob.(*RepJob); ok {
|
||||
if repJob.parm.Enabled == 0 {
|
||||
log.Debugf("The policy of job:%v is disabled, will cancel the job", repJob)
|
||||
if err := repJob.UpdateStatus(models.JobCanceled); err != nil {
|
||||
log.Warningf("Failed to update status of job: %v to 'canceled', error: %v", repJob, err)
|
||||
|
||||
}
|
||||
_, err := sm.EnterState(models.JobCanceled)
|
||||
if err != nil {
|
||||
log.Warningf("For job: %v, failed to update state to 'canceled', error: %v", repJob, err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
}
|
||||
log.Debugf("In kickOff: will start job: %v", sm.CurrentJob)
|
||||
sm.Start(models.JobRunning)
|
||||
return nil
|
||||
}
|
||||
|
@ -103,9 +103,10 @@ func (w *Worker) handle(job Job) {
|
||||
}
|
||||
|
||||
// NewWorker returns a pointer to new instance of worker
|
||||
func NewWorker(id int, wp *workerPool) *Worker {
|
||||
func NewWorker(id int, t Type, wp *workerPool) *Worker {
|
||||
w := &Worker{
|
||||
ID: id,
|
||||
Type: t,
|
||||
Jobs: make(chan Job),
|
||||
quit: make(chan bool),
|
||||
queue: wp.workerChan,
|
||||
@ -125,19 +126,19 @@ func InitWorkerPools() error {
|
||||
return err
|
||||
}
|
||||
WorkerPools = make(map[Type]*workerPool)
|
||||
WorkerPools[ReplicationType] = createWorkerPool(maxRepWorker)
|
||||
WorkerPools[ScanType] = createWorkerPool(maxScanWorker)
|
||||
WorkerPools[ReplicationType] = createWorkerPool(maxRepWorker, ReplicationType)
|
||||
WorkerPools[ScanType] = createWorkerPool(maxScanWorker, ScanType)
|
||||
return nil
|
||||
}
|
||||
|
||||
//createWorkerPool create workers according to parm
|
||||
func createWorkerPool(n int) *workerPool {
|
||||
func createWorkerPool(n int, t Type) *workerPool {
|
||||
wp := &workerPool{
|
||||
workerChan: make(chan *Worker, n),
|
||||
workerList: make([]*Worker, 0, n),
|
||||
}
|
||||
for i := 0; i < n; i++ {
|
||||
worker := NewWorker(i, wp)
|
||||
worker := NewWorker(i, t, wp)
|
||||
wp.workerList = append(wp.workerList, worker)
|
||||
worker.Start()
|
||||
log.Debugf("worker %v started", worker)
|
||||
|
@ -19,8 +19,6 @@ import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/vmware/harbor/src/common/api"
|
||||
"github.com/vmware/harbor/src/common/dao"
|
||||
"github.com/vmware/harbor/src/common/utils/email"
|
||||
"github.com/vmware/harbor/src/common/utils/log"
|
||||
"github.com/vmware/harbor/src/ui/config"
|
||||
@ -32,20 +30,20 @@ const (
|
||||
|
||||
// EmailAPI ...
|
||||
type EmailAPI struct {
|
||||
api.BaseAPI
|
||||
BaseController
|
||||
}
|
||||
|
||||
// Prepare ...
|
||||
func (e *EmailAPI) Prepare() {
|
||||
userID := e.ValidateUser()
|
||||
isSysAdmin, err := dao.IsAdminRole(userID)
|
||||
if err != nil {
|
||||
log.Errorf("failed to check the role of user: %v", err)
|
||||
e.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
|
||||
e.BaseController.Prepare()
|
||||
if !e.SecurityCtx.IsAuthenticated() {
|
||||
e.HandleUnauthorized()
|
||||
return
|
||||
}
|
||||
|
||||
if !isSysAdmin {
|
||||
e.CustomAbort(http.StatusForbidden, http.StatusText(http.StatusForbidden))
|
||||
if !e.SecurityCtx.IsSysAdmin() {
|
||||
e.HandleForbidden(e.SecurityCtx.GetUsername())
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -128,7 +128,7 @@ func init() {
|
||||
_ = updateInitPassword(1, "Harbor12345")
|
||||
|
||||
//syncRegistry
|
||||
if err := SyncRegistry(); err != nil {
|
||||
if err := SyncRegistry(config.GlobalProjectMgr); err != nil {
|
||||
log.Fatalf("failed to sync repositories from registry: %v", err)
|
||||
}
|
||||
|
||||
@ -226,20 +226,14 @@ func (a testapi) StatisticGet(user usrInfo) (int, apilib.StatisticMap, error) {
|
||||
return httpStatusCode, successPayload, err
|
||||
}
|
||||
|
||||
func (a testapi) LogGet(user usrInfo, startTime, endTime, lines string) (int, []apilib.AccessLog, error) {
|
||||
func (a testapi) LogGet(user usrInfo) (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 {
|
||||
|
@ -16,35 +16,29 @@ package api
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/vmware/harbor/src/common/dao"
|
||||
"github.com/vmware/harbor/src/common/utils/log"
|
||||
"github.com/vmware/harbor/src/common/api"
|
||||
)
|
||||
|
||||
// InternalAPI handles request of harbor admin...
|
||||
type InternalAPI struct {
|
||||
api.BaseAPI
|
||||
BaseController
|
||||
}
|
||||
|
||||
// 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.")
|
||||
ia.BaseController.Prepare()
|
||||
if !ia.SecurityCtx.IsAuthenticated() {
|
||||
ia.HandleUnauthorized()
|
||||
return
|
||||
}
|
||||
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.")
|
||||
if !ia.SecurityCtx.IsSysAdmin() {
|
||||
ia.HandleForbidden(ia.SecurityCtx.GetUsername())
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// SyncRegistry ...
|
||||
func (ia *InternalAPI) SyncRegistry() {
|
||||
err := SyncRegistry()
|
||||
err := SyncRegistry(ia.ProjectMgr)
|
||||
if err != nil {
|
||||
ia.CustomAbort(http.StatusInternalServerError, "internal error")
|
||||
}
|
||||
|
@ -15,18 +15,17 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
"fmt"
|
||||
|
||||
"github.com/vmware/harbor/src/common/dao"
|
||||
"github.com/vmware/harbor/src/common/models"
|
||||
"github.com/vmware/harbor/src/common/utils/log"
|
||||
)
|
||||
|
||||
//LogAPI handles request api/logs
|
||||
type LogAPI struct {
|
||||
BaseController
|
||||
username string
|
||||
isSysAdmin bool
|
||||
}
|
||||
|
||||
//Prepare validates the URL and the user
|
||||
@ -36,53 +35,58 @@ func (l *LogAPI) Prepare() {
|
||||
l.HandleUnauthorized()
|
||||
return
|
||||
}
|
||||
l.username = l.SecurityCtx.GetUsername()
|
||||
l.isSysAdmin = l.SecurityCtx.IsSysAdmin()
|
||||
}
|
||||
|
||||
//Get returns the recent logs according to parameters
|
||||
func (l *LogAPI) Get() {
|
||||
var err error
|
||||
startTime := l.GetString("start_time")
|
||||
if len(startTime) != 0 {
|
||||
i, err := strconv.ParseInt(startTime, 10, 64)
|
||||
if err != nil {
|
||||
log.Errorf("Parse startTime to int error, err: %v", err)
|
||||
l.CustomAbort(http.StatusBadRequest, "startTime is not a valid integer")
|
||||
}
|
||||
startTime = time.Unix(i, 0).String()
|
||||
page, size := l.GetPaginationParams()
|
||||
query := &models.LogQueryParam{
|
||||
Pagination: &models.Pagination{
|
||||
Page: page,
|
||||
Size: size,
|
||||
},
|
||||
}
|
||||
|
||||
endTime := l.GetString("end_time")
|
||||
if len(endTime) != 0 {
|
||||
j, err := strconv.ParseInt(endTime, 10, 64)
|
||||
if !l.isSysAdmin {
|
||||
projects, err := l.ProjectMgr.GetByMember(l.username)
|
||||
if err != nil {
|
||||
log.Errorf("Parse endTime to int error, err: %v", err)
|
||||
l.CustomAbort(http.StatusBadRequest, "endTime is not a valid integer")
|
||||
}
|
||||
endTime = time.Unix(j, 0).String()
|
||||
l.HandleInternalServerError(fmt.Sprintf(
|
||||
"failed to get projects of user %s: %v", l.username, err))
|
||||
return
|
||||
}
|
||||
|
||||
var linesNum int
|
||||
lines := l.GetString("lines")
|
||||
if len(lines) != 0 {
|
||||
linesNum, err = strconv.Atoi(lines)
|
||||
if err != nil {
|
||||
log.Errorf("Get parameters error--lines, err: %v", err)
|
||||
l.CustomAbort(http.StatusBadRequest, "bad request of lines")
|
||||
}
|
||||
if linesNum <= 0 {
|
||||
log.Warning("lines must be a positive integer")
|
||||
l.CustomAbort(http.StatusBadRequest, "lines is 0 or negative")
|
||||
}
|
||||
} else if len(startTime) == 0 && len(endTime) == 0 {
|
||||
linesNum = 10
|
||||
if len(projects) == 0 {
|
||||
l.SetPaginationHeader(0, page, size)
|
||||
l.Data["json"] = nil
|
||||
l.ServeJSON()
|
||||
return
|
||||
}
|
||||
|
||||
var logList []models.AccessLog
|
||||
logList, err = dao.GetRecentLogs(l.SecurityCtx.GetUsername(), linesNum, startTime, endTime)
|
||||
if err != nil {
|
||||
log.Errorf("Get recent logs error, err: %v", err)
|
||||
l.CustomAbort(http.StatusInternalServerError, "Internal error")
|
||||
ids := []int64{}
|
||||
for _, project := range projects {
|
||||
ids = append(ids, project.ProjectID)
|
||||
}
|
||||
l.Data["json"] = logList
|
||||
query.ProjectIDs = ids
|
||||
}
|
||||
|
||||
total, err := dao.GetTotalOfAccessLogs(query)
|
||||
if err != nil {
|
||||
l.HandleInternalServerError(fmt.Sprintf(
|
||||
"failed to get total of access logs: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
logs, err := dao.GetAccessLogs(query)
|
||||
if err != nil {
|
||||
l.HandleInternalServerError(fmt.Sprintf(
|
||||
"failed to get access logs: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
l.SetPaginationHeader(total, page, size)
|
||||
|
||||
l.Data["json"] = logs
|
||||
l.ServeJSON()
|
||||
}
|
||||
|
@ -15,11 +15,11 @@ package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/vmware/harbor/tests/apitests/apilib"
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/vmware/harbor/tests/apitests/apilib"
|
||||
)
|
||||
|
||||
func TestLogGet(t *testing.T) {
|
||||
@ -29,12 +29,11 @@ func TestLogGet(t *testing.T) {
|
||||
apiTest := newHarborAPI()
|
||||
|
||||
//prepare for test
|
||||
|
||||
CommonAddUser()
|
||||
var project apilib.ProjectReq
|
||||
project.ProjectName = "my_project"
|
||||
project.Public = 1
|
||||
now := fmt.Sprintf("%v", time.Now().Unix())
|
||||
statusCode, result, err := apiTest.LogGet(*admin, "0", now, "1000")
|
||||
statusCode, result, err := apiTest.LogGet(*testUser)
|
||||
if err != nil {
|
||||
t.Error("Error while get log information", err.Error())
|
||||
t.Log(err)
|
||||
@ -46,7 +45,7 @@ func TestLogGet(t *testing.T) {
|
||||
fmt.Println("result", result)
|
||||
//add the project first.
|
||||
fmt.Println("add the project first.")
|
||||
reply, err := apiTest.ProjectsPost(*admin, project)
|
||||
reply, err := apiTest.ProjectsPost(*testUser, project)
|
||||
if err != nil {
|
||||
t.Error("Error while creat project", err.Error())
|
||||
t.Log(err)
|
||||
@ -54,8 +53,7 @@ func TestLogGet(t *testing.T) {
|
||||
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, "1000")
|
||||
statusCode, result, err = apiTest.LogGet(*testUser)
|
||||
if err != nil {
|
||||
t.Error("Error while get log information", err.Error())
|
||||
t.Log(err)
|
||||
@ -71,64 +69,6 @@ func TestLogGet(t *testing.T) {
|
||||
}
|
||||
}
|
||||
fmt.Println("log ", result)
|
||||
//case 2: wrong format of start_time parameter, expect the wrong output
|
||||
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
|
||||
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
|
||||
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 {
|
||||
//default get 10 logs
|
||||
if logNum+1 >= 10 {
|
||||
logNum = 10
|
||||
} else {
|
||||
logNum++
|
||||
}
|
||||
assert.Equal(logNum, len(result), "lines of logs should be equal")
|
||||
num, index := getLog(result)
|
||||
if num != 1 {
|
||||
assert.Equal(1, num, "add my_project log number should be 1")
|
||||
} else {
|
||||
assert.Equal("my_project/", result[index].RepoName, "RepoName should be equal")
|
||||
assert.Equal("N/A", result[index].RepoTag, "RepoTag should be equal")
|
||||
assert.Equal("create", result[index].Operation, "Operation should be equal")
|
||||
}
|
||||
}
|
||||
|
||||
//get the project
|
||||
var projects []apilib.Project
|
||||
@ -144,7 +84,7 @@ func TestLogGet(t *testing.T) {
|
||||
|
||||
//delete the project
|
||||
projectID := strconv.Itoa(int(addProjectID))
|
||||
httpStatusCode, err = apiTest.ProjectsDelete(*admin, projectID)
|
||||
httpStatusCode, err = apiTest.ProjectsDelete(*testUser, projectID)
|
||||
if err != nil {
|
||||
t.Error("Error while delete project", err.Error())
|
||||
t.Log(err)
|
||||
@ -152,7 +92,7 @@ func TestLogGet(t *testing.T) {
|
||||
assert.Equal(int(200), httpStatusCode, "Case 1: Project creation status should be 200")
|
||||
//t.Log(result)
|
||||
}
|
||||
|
||||
CommonDelUser()
|
||||
fmt.Printf("\n")
|
||||
}
|
||||
|
||||
|
@ -18,6 +18,7 @@ import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/vmware/harbor/src/common"
|
||||
"github.com/vmware/harbor/src/common/dao"
|
||||
@ -258,7 +259,7 @@ func projectContainsPolicy(id int64) (bool, error) {
|
||||
// TODO refacter pattern to:
|
||||
// /api/repositories?owner=xxx&name=xxx&public=true&member=xxx&role=1&page=1&size=3
|
||||
func (p *ProjectAPI) List() {
|
||||
query := &models.QueryParam{}
|
||||
query := &models.ProjectQueryParam{}
|
||||
|
||||
query.Name = p.GetString("project_name")
|
||||
public := p.GetString("is_public")
|
||||
@ -382,28 +383,49 @@ func (p *ProjectAPI) FilterAccessLog() {
|
||||
var query models.AccessLog
|
||||
p.DecodeJSONReq(&query)
|
||||
|
||||
query.ProjectID = p.project.ProjectID
|
||||
query.BeginTime = time.Unix(query.BeginTimestamp, 0)
|
||||
query.EndTime = time.Unix(query.EndTimestamp, 0)
|
||||
|
||||
page, pageSize := p.GetPaginationParams()
|
||||
|
||||
total, err := dao.GetTotalOfAccessLogs(query)
|
||||
if err != nil {
|
||||
log.Errorf("failed to get total of access log: %v", err)
|
||||
p.CustomAbort(http.StatusInternalServerError, "")
|
||||
queryParm := &models.LogQueryParam{
|
||||
ProjectIDs: []int64{p.project.ProjectID},
|
||||
Username: query.Username,
|
||||
Repository: query.RepoName,
|
||||
Tag: query.RepoTag,
|
||||
}
|
||||
|
||||
logs, err := dao.GetAccessLogs(query, pageSize, pageSize*(page-1))
|
||||
if len(query.Keywords) > 0 {
|
||||
queryParm.Operations = strings.Split(query.Keywords, "/")
|
||||
}
|
||||
|
||||
if query.BeginTimestamp > 0 {
|
||||
beginTime := time.Unix(query.BeginTimestamp, 0)
|
||||
queryParm.BeginTime = &beginTime
|
||||
}
|
||||
|
||||
if query.EndTimestamp > 0 {
|
||||
endTime := time.Unix(query.EndTimestamp, 0)
|
||||
queryParm.EndTime = &endTime
|
||||
}
|
||||
|
||||
page, pageSize := p.GetPaginationParams()
|
||||
queryParm.Pagination = &models.Pagination{
|
||||
Page: page,
|
||||
Size: pageSize,
|
||||
}
|
||||
|
||||
total, err := dao.GetTotalOfAccessLogs(queryParm)
|
||||
if err != nil {
|
||||
log.Errorf("failed to get access log: %v", err)
|
||||
p.CustomAbort(http.StatusInternalServerError, "")
|
||||
p.HandleInternalServerError(fmt.Sprintf(
|
||||
"failed to get total of access log: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
logs, err := dao.GetAccessLogs(queryParm)
|
||||
if err != nil {
|
||||
p.HandleInternalServerError(fmt.Sprintf(
|
||||
"failed to get access log: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
p.SetPaginationHeader(total, page, pageSize)
|
||||
|
||||
p.Data["json"] = logs
|
||||
|
||||
p.ServeJSON()
|
||||
}
|
||||
|
||||
|
@ -25,29 +25,29 @@ import (
|
||||
"github.com/vmware/harbor/src/common/dao"
|
||||
"github.com/vmware/harbor/src/common/models"
|
||||
"github.com/vmware/harbor/src/common/utils/log"
|
||||
"github.com/vmware/harbor/src/common/api"
|
||||
)
|
||||
|
||||
// RepJobAPI handles request to /api/replicationJobs /api/replicationJobs/:id/log
|
||||
type RepJobAPI struct {
|
||||
api.BaseAPI
|
||||
BaseController
|
||||
jobID int64
|
||||
}
|
||||
|
||||
// Prepare validates that whether user has system admin role
|
||||
func (ra *RepJobAPI) Prepare() {
|
||||
uid := ra.ValidateUser()
|
||||
isAdmin, err := dao.IsAdminRole(uid)
|
||||
if err != nil {
|
||||
log.Errorf("Failed to Check if the user is admin, error: %v, uid: %d", err, uid)
|
||||
}
|
||||
if !isAdmin {
|
||||
ra.CustomAbort(http.StatusForbidden, "")
|
||||
ra.BaseController.Prepare()
|
||||
if !ra.SecurityCtx.IsAuthenticated() {
|
||||
ra.HandleUnauthorized()
|
||||
return
|
||||
}
|
||||
|
||||
idStr := ra.Ctx.Input.Param(":id")
|
||||
if len(idStr) != 0 {
|
||||
id, err := strconv.ParseInt(idStr, 10, 64)
|
||||
if !ra.SecurityCtx.IsSysAdmin() {
|
||||
ra.HandleForbidden(ra.SecurityCtx.GetUsername())
|
||||
return
|
||||
}
|
||||
|
||||
if len(ra.GetStringFromPath(":id")) != 0 {
|
||||
id, err := ra.GetInt64FromPath(":id")
|
||||
if err != nil {
|
||||
ra.CustomAbort(http.StatusBadRequest, "ID is invalid")
|
||||
}
|
||||
|
@ -23,24 +23,24 @@ import (
|
||||
"github.com/vmware/harbor/src/common/dao"
|
||||
"github.com/vmware/harbor/src/common/models"
|
||||
"github.com/vmware/harbor/src/common/utils/log"
|
||||
"github.com/vmware/harbor/src/common/api"
|
||||
)
|
||||
|
||||
// RepPolicyAPI handles /api/replicationPolicies /api/replicationPolicies/:id/enablement
|
||||
type RepPolicyAPI struct {
|
||||
api.BaseAPI
|
||||
BaseController
|
||||
}
|
||||
|
||||
// Prepare validates whether the user has system admin role
|
||||
func (pa *RepPolicyAPI) Prepare() {
|
||||
uid := pa.ValidateUser()
|
||||
var err error
|
||||
isAdmin, err := dao.IsAdminRole(uid)
|
||||
if err != nil {
|
||||
log.Errorf("Failed to Check if the user is admin, error: %v, uid: %d", err, uid)
|
||||
pa.BaseController.Prepare()
|
||||
if !pa.SecurityCtx.IsAuthenticated() {
|
||||
pa.HandleUnauthorized()
|
||||
return
|
||||
}
|
||||
if !isAdmin {
|
||||
pa.CustomAbort(http.StatusForbidden, "")
|
||||
|
||||
if !pa.SecurityCtx.IsSysAdmin() {
|
||||
pa.HandleForbidden(pa.SecurityCtx.GetUsername())
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
@ -82,6 +82,19 @@ func (pa *RepPolicyAPI) List() {
|
||||
log.Errorf("failed to filter policies %s project ID %d: %v", name, projectID, err)
|
||||
pa.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
|
||||
}
|
||||
|
||||
for _, policy := range policies {
|
||||
project, err := pa.ProjectMgr.Get(policy.ProjectID)
|
||||
if err != nil {
|
||||
pa.HandleInternalServerError(fmt.Sprintf(
|
||||
"failed to get project %d: %v", policy.ProjectID, err))
|
||||
return
|
||||
}
|
||||
if project != nil {
|
||||
policy.ProjectName = project.Name
|
||||
}
|
||||
}
|
||||
|
||||
pa.Data["json"] = policies
|
||||
pa.ServeJSON()
|
||||
}
|
||||
@ -103,7 +116,7 @@ func (pa *RepPolicyAPI) Post() {
|
||||
}
|
||||
*/
|
||||
|
||||
project, err := dao.GetProjectByID(policy.ProjectID)
|
||||
project, err := pa.ProjectMgr.Get(policy.ProjectID)
|
||||
if err != nil {
|
||||
log.Errorf("failed to get project %d: %v", policy.ProjectID, err)
|
||||
pa.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
|
||||
|
@ -103,7 +103,8 @@ func (ra *RepositoryAPI) Get() {
|
||||
|
||||
keyword := ra.GetString("q")
|
||||
|
||||
total, err := dao.GetTotalOfRepositoriesByProject(projectID, keyword)
|
||||
total, err := dao.GetTotalOfRepositoriesByProject(
|
||||
[]int64{projectID}, keyword)
|
||||
if err != nil {
|
||||
ra.HandleInternalServerError(fmt.Sprintf("failed to get total of repositories of project %d: %v",
|
||||
projectID, err))
|
||||
|
@ -15,11 +15,12 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/vmware/harbor/src/common/api"
|
||||
"github.com/vmware/harbor/src/common"
|
||||
"github.com/vmware/harbor/src/common/dao"
|
||||
"github.com/vmware/harbor/src/common/models"
|
||||
"github.com/vmware/harbor/src/common/utils"
|
||||
@ -29,7 +30,7 @@ import (
|
||||
|
||||
// SearchAPI handles requesst to /api/search
|
||||
type SearchAPI struct {
|
||||
api.BaseAPI
|
||||
BaseController
|
||||
}
|
||||
|
||||
type searchResult struct {
|
||||
@ -39,33 +40,25 @@ type searchResult struct {
|
||||
|
||||
// Get ...
|
||||
func (s *SearchAPI) Get() {
|
||||
userID, _, ok := s.GetUserIDForRequest()
|
||||
if !ok {
|
||||
userID = dao.NonExistUserID
|
||||
}
|
||||
|
||||
keyword := s.GetString("q")
|
||||
|
||||
isSysAdmin, err := dao.IsAdminRole(userID)
|
||||
if err != nil {
|
||||
log.Errorf("failed to check whether the user %d is system admin: %v", userID, err)
|
||||
s.CustomAbort(http.StatusInternalServerError, "internal error")
|
||||
}
|
||||
isAuthenticated := s.SecurityCtx.IsAuthenticated()
|
||||
username := s.SecurityCtx.GetUsername()
|
||||
isSysAdmin := s.SecurityCtx.IsSysAdmin()
|
||||
|
||||
var projects []*models.Project
|
||||
var err error
|
||||
|
||||
if isSysAdmin {
|
||||
projects, err = dao.GetProjects(nil)
|
||||
if err != nil {
|
||||
log.Errorf("failed to get all projects: %v", err)
|
||||
s.CustomAbort(http.StatusInternalServerError, "internal error")
|
||||
}
|
||||
if !isAuthenticated {
|
||||
projects, err = s.ProjectMgr.GetPublic()
|
||||
} else if isSysAdmin {
|
||||
projects, err = s.ProjectMgr.GetAll(nil)
|
||||
} else {
|
||||
projects, err = dao.SearchProjects(userID)
|
||||
if err != nil {
|
||||
log.Errorf("failed to get user %d 's relevant projects: %v", userID, err)
|
||||
s.CustomAbort(http.StatusInternalServerError, "internal error")
|
||||
projects, err = s.ProjectMgr.GetHasReadPerm(username)
|
||||
}
|
||||
if err != nil {
|
||||
s.HandleInternalServerError(fmt.Sprintf(
|
||||
"failed to get projects: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
projectSorter := &models.ProjectSorter{Projects: projects}
|
||||
@ -76,17 +69,19 @@ func (s *SearchAPI) Get() {
|
||||
continue
|
||||
}
|
||||
|
||||
if userID != dao.NonExistUserID {
|
||||
roles, err := dao.GetUserProjectRoles(userID, p.ProjectID)
|
||||
if isAuthenticated {
|
||||
roles, err := s.ProjectMgr.GetRoles(username, p.ProjectID)
|
||||
if err != nil {
|
||||
log.Errorf("failed to get user's project role: %v", err)
|
||||
s.CustomAbort(http.StatusInternalServerError, "")
|
||||
}
|
||||
if len(roles) != 0 {
|
||||
p.Role = roles[0].RoleID
|
||||
s.HandleInternalServerError(fmt.Sprintf("failed to get roles of user %s to project %d: %v",
|
||||
username, p.ProjectID, err))
|
||||
return
|
||||
}
|
||||
|
||||
if p.Role == models.PROJECTADMIN || isSysAdmin {
|
||||
if len(roles) != 0 {
|
||||
p.Role = roles[0]
|
||||
}
|
||||
|
||||
if p.Role == common.RoleProjectAdmin || isSysAdmin {
|
||||
p.Togglable = true
|
||||
}
|
||||
}
|
||||
|
@ -15,9 +15,9 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/vmware/harbor/src/common/api"
|
||||
"github.com/vmware/harbor/src/common/dao"
|
||||
"github.com/vmware/harbor/src/common/models"
|
||||
"github.com/vmware/harbor/src/common/utils/log"
|
||||
@ -40,42 +40,44 @@ const (
|
||||
|
||||
// StatisticAPI handles request to /api/statistics/
|
||||
type StatisticAPI struct {
|
||||
api.BaseAPI
|
||||
userID int
|
||||
BaseController
|
||||
username string
|
||||
}
|
||||
|
||||
//Prepare validates the URL and the user
|
||||
func (s *StatisticAPI) Prepare() {
|
||||
s.userID = s.ValidateUser()
|
||||
s.BaseController.Prepare()
|
||||
if !s.SecurityCtx.IsAuthenticated() {
|
||||
s.HandleUnauthorized()
|
||||
return
|
||||
}
|
||||
s.username = s.SecurityCtx.GetUsername()
|
||||
}
|
||||
|
||||
// Get total projects and repos of the user
|
||||
func (s *StatisticAPI) Get() {
|
||||
statistic := map[string]int64{}
|
||||
t := true
|
||||
n, err := dao.GetTotalOfProjects(&models.QueryParam{
|
||||
Public: &t,
|
||||
})
|
||||
projects, err := s.ProjectMgr.GetPublic()
|
||||
if err != nil {
|
||||
log.Errorf("failed to get total of public projects: %v", err)
|
||||
s.CustomAbort(http.StatusInternalServerError, "")
|
||||
s.HandleInternalServerError(fmt.Sprintf(
|
||||
"failed to get public projects: %v", err))
|
||||
return
|
||||
}
|
||||
statistic[PPC] = n
|
||||
|
||||
n, err = dao.GetTotalOfPublicRepositories("")
|
||||
statistic[PPC] = (int64)(len(projects))
|
||||
|
||||
ids := []int64{}
|
||||
for _, p := range projects {
|
||||
ids = append(ids, p.ProjectID)
|
||||
}
|
||||
n, err := dao.GetTotalOfRepositoriesByProject(ids, "")
|
||||
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.")
|
||||
}
|
||||
|
||||
if isAdmin {
|
||||
if s.SecurityCtx.IsSysAdmin() {
|
||||
n, err := dao.GetTotalOfProjects(nil)
|
||||
if err != nil {
|
||||
log.Errorf("failed to get total of projects: %v", err)
|
||||
@ -92,28 +94,29 @@ func (s *StatisticAPI) Get() {
|
||||
statistic[MRC] = n
|
||||
statistic[TRC] = n
|
||||
} else {
|
||||
user, err := dao.GetUser(models.User{
|
||||
UserID: s.userID,
|
||||
})
|
||||
if err != nil {
|
||||
log.Errorf("failed to get user %d: %v", s.userID, err)
|
||||
s.CustomAbort(http.StatusInternalServerError, "")
|
||||
}
|
||||
n, err := dao.GetTotalOfProjects(&models.QueryParam{
|
||||
projects, err := s.ProjectMgr.GetAll(&models.ProjectQueryParam{
|
||||
Member: &models.Member{
|
||||
Name: user.Username,
|
||||
Name: s.username,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
log.Errorf("failed to get total of projects for user %d: %v", s.userID, err)
|
||||
s.CustomAbort(http.StatusInternalServerError, "")
|
||||
s.HandleInternalServerError(fmt.Sprintf(
|
||||
"failed to get projects of user %s: %v", s.username, err))
|
||||
return
|
||||
}
|
||||
statistic[MPC] = n
|
||||
statistic[MPC] = (int64)(len(projects))
|
||||
|
||||
n, err = dao.GetTotalOfUserRelevantRepositories(s.userID, "")
|
||||
ids := []int64{}
|
||||
for _, p := range projects {
|
||||
ids = append(ids, p.ProjectID)
|
||||
}
|
||||
|
||||
n, err = dao.GetTotalOfRepositoriesByProject(ids, "")
|
||||
if err != nil {
|
||||
log.Errorf("failed to get total of repositories for user %d: %v", s.userID, err)
|
||||
s.CustomAbort(http.StatusInternalServerError, "")
|
||||
s.HandleInternalServerError(fmt.Sprintf(
|
||||
"failed to get total of repositories for user %s: %v",
|
||||
s.username, err))
|
||||
return
|
||||
}
|
||||
statistic[MRC] = n
|
||||
}
|
||||
|
@ -21,7 +21,6 @@ import (
|
||||
"net/url"
|
||||
"strconv"
|
||||
|
||||
"github.com/vmware/harbor/src/common/api"
|
||||
"github.com/vmware/harbor/src/common/dao"
|
||||
"github.com/vmware/harbor/src/common/models"
|
||||
"github.com/vmware/harbor/src/common/utils"
|
||||
@ -34,29 +33,29 @@ import (
|
||||
|
||||
// TargetAPI handles request to /api/targets/ping /api/targets/{}
|
||||
type TargetAPI struct {
|
||||
api.BaseAPI
|
||||
BaseController
|
||||
secretKey string
|
||||
}
|
||||
|
||||
// Prepare validates the user
|
||||
func (t *TargetAPI) Prepare() {
|
||||
t.BaseController.Prepare()
|
||||
if !t.SecurityCtx.IsAuthenticated() {
|
||||
t.HandleUnauthorized()
|
||||
return
|
||||
}
|
||||
|
||||
if !t.SecurityCtx.IsSysAdmin() {
|
||||
t.HandleForbidden(t.SecurityCtx.GetUsername())
|
||||
return
|
||||
}
|
||||
|
||||
var err error
|
||||
t.secretKey, err = config.SecretKey()
|
||||
if err != nil {
|
||||
log.Errorf("failed to get secret key: %v", err)
|
||||
t.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
|
||||
}
|
||||
|
||||
userID := t.ValidateUser()
|
||||
isSysAdmin, err := dao.IsAdminRole(userID)
|
||||
if err != nil {
|
||||
log.Errorf("error occurred in IsAdminRole: %v", err)
|
||||
t.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
|
||||
}
|
||||
|
||||
if !isSysAdmin {
|
||||
t.CustomAbort(http.StatusForbidden, http.StatusText(http.StatusForbidden))
|
||||
}
|
||||
}
|
||||
|
||||
func (t *TargetAPI) ping(endpoint, username, password string) {
|
||||
|
@ -31,6 +31,7 @@ import (
|
||||
"github.com/vmware/harbor/src/common/utils/registry/auth"
|
||||
registry_error "github.com/vmware/harbor/src/common/utils/registry/error"
|
||||
"github.com/vmware/harbor/src/ui/config"
|
||||
"github.com/vmware/harbor/src/ui/projectmanager"
|
||||
)
|
||||
|
||||
//sysadmin has all privileges to all projects
|
||||
@ -212,7 +213,7 @@ func addAuthentication(req *http.Request) {
|
||||
}
|
||||
|
||||
// SyncRegistry syncs the repositories of registry with database.
|
||||
func SyncRegistry() error {
|
||||
func SyncRegistry(pm projectmanager.ProjectManager) error {
|
||||
|
||||
log.Infof("Start syncing repositories from registry to DB... ")
|
||||
|
||||
@ -236,7 +237,7 @@ func SyncRegistry() error {
|
||||
|
||||
var reposToAdd []string
|
||||
var reposToDel []string
|
||||
reposToAdd, reposToDel, err = diffRepos(reposInRegistry, reposInDB)
|
||||
reposToAdd, reposToDel, err = diffRepos(reposInRegistry, reposInDB, pm)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -249,7 +250,7 @@ func SyncRegistry() error {
|
||||
if err != nil {
|
||||
log.Errorf("Error happens when counting pull count from access log: %v", err)
|
||||
}
|
||||
pro, err := dao.GetProjectByName(project)
|
||||
pro, err := pm.Get(project)
|
||||
if err != nil {
|
||||
log.Errorf("failed to get project %s: %v", project, err)
|
||||
continue
|
||||
@ -299,7 +300,8 @@ func catalog() ([]string, error) {
|
||||
return repositories, nil
|
||||
}
|
||||
|
||||
func diffRepos(reposInRegistry []string, reposInDB []string) ([]string, []string, error) {
|
||||
func diffRepos(reposInRegistry []string, reposInDB []string,
|
||||
pm projectmanager.ProjectManager) ([]string, []string, error) {
|
||||
var needsAdd []string
|
||||
var needsDel []string
|
||||
|
||||
@ -314,7 +316,7 @@ func diffRepos(reposInRegistry []string, reposInDB []string) ([]string, []string
|
||||
d := strings.Compare(repoInR, repoInD)
|
||||
if d < 0 {
|
||||
i++
|
||||
exist, err := projectExists(repoInR)
|
||||
exist, err := projectExists(pm, repoInR)
|
||||
if err != nil {
|
||||
log.Errorf("failed to check the existence of project %s: %v", repoInR, err)
|
||||
continue
|
||||
@ -377,7 +379,7 @@ func diffRepos(reposInRegistry []string, reposInDB []string) ([]string, []string
|
||||
for i < len(reposInRegistry) {
|
||||
repoInR = reposInRegistry[i]
|
||||
i++
|
||||
exist, err := projectExists(repoInR)
|
||||
exist, err := projectExists(pm, repoInR)
|
||||
if err != nil {
|
||||
log.Errorf("failed to check whether project of %s exists: %v", repoInR, err)
|
||||
continue
|
||||
@ -397,9 +399,9 @@ func diffRepos(reposInRegistry []string, reposInDB []string) ([]string, []string
|
||||
return needsAdd, needsDel, nil
|
||||
}
|
||||
|
||||
func projectExists(repository string) (bool, error) {
|
||||
func projectExists(pm projectmanager.ProjectManager, repository string) (bool, error) {
|
||||
project, _ := utils.ParseRepository(repository)
|
||||
return dao.ProjectExists(project)
|
||||
return pm.Exist(project)
|
||||
}
|
||||
|
||||
// TODO need a registry client which accept a raw token as param
|
||||
|
@ -40,9 +40,8 @@ var (
|
||||
SecretStore *secret.Store
|
||||
// AdminserverClient is a client for adminserver
|
||||
AdminserverClient client.Client
|
||||
// DBProjectManager is the project manager based on database,
|
||||
// it is initialized only the deploy mode is standalone
|
||||
DBProjectManager projectmanager.ProjectManager
|
||||
// GlobalProjectMgr is initialized based on the deploy mode
|
||||
GlobalProjectMgr projectmanager.ProjectManager
|
||||
mg *comcfg.Manager
|
||||
keyProvider comcfg.KeyProvider
|
||||
)
|
||||
@ -73,8 +72,8 @@ func Init() error {
|
||||
// init secret store
|
||||
initSecretStore()
|
||||
|
||||
// init project manager based on database
|
||||
initDBProjectManager()
|
||||
// init project manager based on deploy mode
|
||||
initProjectManager()
|
||||
|
||||
return nil
|
||||
}
|
||||
@ -95,12 +94,13 @@ func initSecretStore() {
|
||||
SecretStore = secret.NewStore(m)
|
||||
}
|
||||
|
||||
func initDBProjectManager() {
|
||||
func initProjectManager() {
|
||||
if len(DeployMode()) == 0 ||
|
||||
DeployMode() == common.DeployModeStandAlone {
|
||||
log.Info("initializing the project manager based on database...")
|
||||
DBProjectManager = &db.ProjectManager{}
|
||||
GlobalProjectMgr = &db.ProjectManager{}
|
||||
}
|
||||
// TODO create project manager based on pms
|
||||
}
|
||||
|
||||
// Load configurations
|
||||
|
@ -136,7 +136,7 @@ func getProjectManager(ctx *beegoctx.Context) projectmanager.ProjectManager {
|
||||
if len(config.DeployMode()) == 0 ||
|
||||
config.DeployMode() == common.DeployModeStandAlone {
|
||||
log.Info("filling a project manager based on database...")
|
||||
return config.DBProjectManager
|
||||
return config.GlobalProjectMgr
|
||||
}
|
||||
|
||||
// TODO create project manager based on pms
|
||||
|
@ -101,7 +101,7 @@ func main() {
|
||||
beego.InsertFilter("/*", beego.BeforeRouter, filter.SecurityFilter)
|
||||
|
||||
initRouters()
|
||||
if err := api.SyncRegistry(); err != nil {
|
||||
if err := api.SyncRegistry(config.GlobalProjectMgr); err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
log.Info("Init proxy")
|
||||
|
@ -107,7 +107,7 @@ func (p *ProjectManager) GetRoles(username string, projectIDOrName interface{})
|
||||
// GetPublic returns all public projects
|
||||
func (p *ProjectManager) GetPublic() ([]*models.Project, error) {
|
||||
t := true
|
||||
return p.GetAll(&models.QueryParam{
|
||||
return p.GetAll(&models.ProjectQueryParam{
|
||||
Public: &t,
|
||||
})
|
||||
}
|
||||
@ -115,7 +115,7 @@ func (p *ProjectManager) GetPublic() ([]*models.Project, error) {
|
||||
// GetByMember returns all projects which the user is a member of
|
||||
func (p *ProjectManager) GetByMember(username string) (
|
||||
[]*models.Project, error) {
|
||||
return p.GetAll(&models.QueryParam{
|
||||
return p.GetAll(&models.ProjectQueryParam{
|
||||
Member: &models.Member{
|
||||
Name: username,
|
||||
},
|
||||
@ -190,13 +190,23 @@ func (p *ProjectManager) Update(projectIDOrName interface{},
|
||||
}
|
||||
|
||||
// GetAll returns a project list according to the query parameters
|
||||
func (p *ProjectManager) GetAll(query *models.QueryParam) (
|
||||
func (p *ProjectManager) GetAll(query *models.ProjectQueryParam) (
|
||||
[]*models.Project, error) {
|
||||
return dao.GetProjects(query)
|
||||
}
|
||||
|
||||
// GetTotal returns the total count according to the query parameters
|
||||
func (p *ProjectManager) GetTotal(query *models.QueryParam) (
|
||||
func (p *ProjectManager) GetTotal(query *models.ProjectQueryParam) (
|
||||
int64, error) {
|
||||
return dao.GetTotalOfProjects(query)
|
||||
}
|
||||
|
||||
// GetHasReadPerm returns projects which are public or the user is a member of
|
||||
func (p *ProjectManager) GetHasReadPerm(username ...string) (
|
||||
[]*models.Project, error) {
|
||||
if len(username) == 0 || len(username[0]) == 0 {
|
||||
return p.GetPublic()
|
||||
}
|
||||
|
||||
return dao.GetHasReadPermProjects(username[0])
|
||||
}
|
||||
|
@ -229,14 +229,14 @@ func TestGetTotal(t *testing.T) {
|
||||
defer pm.Delete(id)
|
||||
|
||||
// get by name
|
||||
total, err := pm.GetTotal(&models.QueryParam{
|
||||
total, err := pm.GetTotal(&models.ProjectQueryParam{
|
||||
Name: "get_total_test",
|
||||
})
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, int64(1), total)
|
||||
|
||||
// get by owner
|
||||
total, err = pm.GetTotal(&models.QueryParam{
|
||||
total, err = pm.GetTotal(&models.ProjectQueryParam{
|
||||
Owner: "admin",
|
||||
})
|
||||
assert.Nil(t, err)
|
||||
@ -244,7 +244,7 @@ func TestGetTotal(t *testing.T) {
|
||||
|
||||
// get by public
|
||||
value := true
|
||||
total, err = pm.GetTotal(&models.QueryParam{
|
||||
total, err = pm.GetTotal(&models.ProjectQueryParam{
|
||||
Public: &value,
|
||||
})
|
||||
assert.Nil(t, err)
|
||||
@ -263,14 +263,14 @@ func TestGetAll(t *testing.T) {
|
||||
defer pm.Delete(id)
|
||||
|
||||
// get by name
|
||||
projects, err := pm.GetAll(&models.QueryParam{
|
||||
projects, err := pm.GetAll(&models.ProjectQueryParam{
|
||||
Name: "get_all_test",
|
||||
})
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, id, projects[0].ProjectID)
|
||||
|
||||
// get by owner
|
||||
projects, err = pm.GetAll(&models.QueryParam{
|
||||
projects, err = pm.GetAll(&models.ProjectQueryParam{
|
||||
Owner: "admin",
|
||||
})
|
||||
assert.Nil(t, err)
|
||||
@ -285,7 +285,7 @@ func TestGetAll(t *testing.T) {
|
||||
|
||||
// get by public
|
||||
value := true
|
||||
projects, err = pm.GetAll(&models.QueryParam{
|
||||
projects, err = pm.GetAll(&models.ProjectQueryParam{
|
||||
Public: &value,
|
||||
})
|
||||
assert.Nil(t, err)
|
||||
@ -298,3 +298,58 @@ func TestGetAll(t *testing.T) {
|
||||
}
|
||||
assert.True(t, exist)
|
||||
}
|
||||
|
||||
func TestGetHasReadPerm(t *testing.T) {
|
||||
pm := &ProjectManager{}
|
||||
|
||||
// do not pass username
|
||||
projects, err := pm.GetHasReadPerm()
|
||||
assert.Nil(t, err)
|
||||
assert.NotEqual(t, 0, len(projects))
|
||||
exist := false
|
||||
for _, project := range projects {
|
||||
if project.ProjectID == 1 {
|
||||
exist = true
|
||||
break
|
||||
}
|
||||
}
|
||||
assert.True(t, exist)
|
||||
|
||||
// username is nil
|
||||
projects, err = pm.GetHasReadPerm("")
|
||||
assert.Nil(t, err)
|
||||
assert.NotEqual(t, 0, len(projects))
|
||||
exist = false
|
||||
for _, project := range projects {
|
||||
if project.ProjectID == 1 {
|
||||
exist = true
|
||||
break
|
||||
}
|
||||
}
|
||||
assert.True(t, exist)
|
||||
|
||||
// valid username
|
||||
id, err := pm.Create(&models.Project{
|
||||
Name: "get_has_read_perm_test",
|
||||
OwnerID: 1,
|
||||
Public: 0,
|
||||
})
|
||||
assert.Nil(t, err)
|
||||
defer pm.Delete(id)
|
||||
|
||||
projects, err = pm.GetHasReadPerm("admin")
|
||||
assert.Nil(t, err)
|
||||
assert.NotEqual(t, 0, len(projects))
|
||||
exist1 := false
|
||||
exist2 := false
|
||||
for _, project := range projects {
|
||||
if project.ProjectID == 1 {
|
||||
exist1 = true
|
||||
}
|
||||
if project.ProjectID == id {
|
||||
exist2 = true
|
||||
}
|
||||
}
|
||||
assert.True(t, exist1)
|
||||
assert.True(t, exist2)
|
||||
}
|
||||
|
@ -33,7 +33,11 @@ type ProjectManager interface {
|
||||
Delete(projectIDOrName interface{}) error
|
||||
Update(projectIDOrName interface{}, project *models.Project) error
|
||||
// GetAll returns a project list according to the query parameters
|
||||
GetAll(query *models.QueryParam) ([]*models.Project, error)
|
||||
GetAll(query *models.ProjectQueryParam) ([]*models.Project, error)
|
||||
// GetTotal returns the total count according to the query parameters
|
||||
GetTotal(query *models.QueryParam) (int64, error)
|
||||
GetTotal(query *models.ProjectQueryParam) (int64, error)
|
||||
// GetHasReadPerm returns a project list which the user has read
|
||||
// permission of. The list should contains all public projects and
|
||||
// projects which the user is a member of if the username is not nil
|
||||
GetHasReadPerm(username ...string) ([]*models.Project, error)
|
||||
}
|
||||
|
@ -4,6 +4,8 @@ import { LOG_DIRECTIVES } from './log/index';
|
||||
import { FILTER_DIRECTIVES } from './filter/index';
|
||||
import { ENDPOINT_DIRECTIVES } from './endpoint/index';
|
||||
import { REPOSITORY_DIRECTIVES } from './repository/index';
|
||||
import { REPOSITORY_STACKVIEW_DIRECTIVES } from './repository-stackview/index';
|
||||
|
||||
import { LIST_REPOSITORY_DIRECTIVES } from './list-repository/index';
|
||||
import { TAG_DIRECTIVES } from './tag/index';
|
||||
|
||||
@ -125,6 +127,7 @@ export function initConfig(translateInitializer: TranslateServiceInitializer, co
|
||||
FILTER_DIRECTIVES,
|
||||
ENDPOINT_DIRECTIVES,
|
||||
REPOSITORY_DIRECTIVES,
|
||||
REPOSITORY_STACKVIEW_DIRECTIVES,
|
||||
LIST_REPOSITORY_DIRECTIVES,
|
||||
TAG_DIRECTIVES,
|
||||
CREATE_EDIT_ENDPOINT_DIRECTIVES,
|
||||
@ -141,6 +144,7 @@ export function initConfig(translateInitializer: TranslateServiceInitializer, co
|
||||
FILTER_DIRECTIVES,
|
||||
ENDPOINT_DIRECTIVES,
|
||||
REPOSITORY_DIRECTIVES,
|
||||
REPOSITORY_STACKVIEW_DIRECTIVES,
|
||||
LIST_REPOSITORY_DIRECTIVES,
|
||||
TAG_DIRECTIVES,
|
||||
CREATE_EDIT_ENDPOINT_DIRECTIVES,
|
||||
|
@ -7,7 +7,9 @@ export * from './log/index';
|
||||
export * from './filter/index';
|
||||
export * from './endpoint/index';
|
||||
export * from './repository/index';
|
||||
export * from './repository-stackview/index';
|
||||
export * from './tag/index';
|
||||
export * from './list-replication-rule/index';
|
||||
export * from './replication/index';
|
||||
export * from './vulnerability-scanning/index';
|
||||
export * from './i18n/index';
|
@ -3,26 +3,26 @@ export const LIST_REPLICATION_RULE_TEMPLATE: string = `
|
||||
<confirmation-dialog #deletionConfirmDialog (confirmAction)="deletionConfirm($event)"></confirmation-dialog>
|
||||
<clr-datagrid [clrDgLoading]="loading">
|
||||
<clr-dg-column [clrDgField]="'name'">{{'REPLICATION.NAME' | translate}}</clr-dg-column>
|
||||
<clr-dg-column [clrDgField]="'project_name'" *ngIf="projectless">{{'REPLICATION.PROJECT' | translate}}</clr-dg-column>
|
||||
<clr-dg-column [clrDgField]="'project_name'" *ngIf="projectScope">{{'REPLICATION.PROJECT' | translate}}</clr-dg-column>
|
||||
<clr-dg-column [clrDgField]="'description'">{{'REPLICATION.DESCRIPTION' | translate}}</clr-dg-column>
|
||||
<clr-dg-column [clrDgField]="'target_name'">{{'REPLICATION.DESTINATION_NAME' | translate}}</clr-dg-column>
|
||||
<clr-dg-column [clrDgSortBy]="startTimeComparator">{{'REPLICATION.LAST_START_TIME' | translate}}</clr-dg-column>
|
||||
<clr-dg-column [clrDgSortBy]="enabledComparator">{{'REPLICATION.ACTIVATION' | translate}}</clr-dg-column>
|
||||
<clr-dg-row *clrDgItems="let p of rules" [clrDgItem]="p" (click)="selectRule(p)" [style.backgroundColor]="(!projectless && selectedId === p.id) ? '#eee' : ''">
|
||||
<clr-dg-row *clrDgItems="let p of changedRules" [clrDgItem]="p" (click)="selectRule(p)" [style.backgroundColor]="(!projectScope && withReplicationJob && selectedId === p.id) ? '#eee' : ''">
|
||||
<clr-dg-action-overflow>
|
||||
<button class="action-item" (click)="editRule(p)">{{'REPLICATION.EDIT_POLICY' | translate}}</button>
|
||||
<button class="action-item" (click)="toggleRule(p)">{{ (p.enabled === 0 ? 'REPLICATION.ENABLE' : 'REPLICATION.DISABLE') | translate}}</button>
|
||||
<button class="action-item" (click)="deleteRule(p)">{{'REPLICATION.DELETE_POLICY' | translate}}</button>
|
||||
</clr-dg-action-overflow>
|
||||
<clr-dg-cell>
|
||||
<ng-template [ngIf]="projectless">
|
||||
<a href="javascript:void(0)">{{p.name}}</a>
|
||||
<ng-template [ngIf]="projectScope">
|
||||
<a href="javascript:void(0)" (click)="redirectTo(p)">{{p.name}}</a>
|
||||
</ng-template>
|
||||
<ng-template [ngIf]="!projectless">
|
||||
<ng-template [ngIf]="!projectScope">
|
||||
{{p.name}}
|
||||
</ng-template>
|
||||
</clr-dg-cell>
|
||||
<clr-dg-cell *ngIf="projectless">{{p.project_name}}</clr-dg-cell>
|
||||
<clr-dg-cell *ngIf="projectScope">{{p.project_name}}</clr-dg-cell>
|
||||
<clr-dg-cell>{{p.description ? p.description : '-'}}</clr-dg-cell>
|
||||
<clr-dg-cell>{{p.target_name}}</clr-dg-cell>
|
||||
<clr-dg-cell>
|
||||
|
@ -11,7 +11,7 @@
|
||||
// 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.
|
||||
import { Component, Input, Output, EventEmitter, ViewChild, ChangeDetectionStrategy, ChangeDetectorRef } from '@angular/core';
|
||||
import { Component, Input, Output, OnInit, EventEmitter, ViewChild, ChangeDetectionStrategy, ChangeDetectorRef } from '@angular/core';
|
||||
|
||||
import { ReplicationService } from '../service/replication.service';
|
||||
import { ReplicationRule } from '../service/interface';
|
||||
@ -32,17 +32,17 @@ import { State, Comparator } from 'clarity-angular';
|
||||
import { LIST_REPLICATION_RULE_TEMPLATE } from './list-replication-rule.component.html';
|
||||
|
||||
@Component({
|
||||
selector: 'list-replication-rule',
|
||||
selector: 'hbr-list-replication-rule',
|
||||
template: LIST_REPLICATION_RULE_TEMPLATE,
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class ListReplicationRuleComponent {
|
||||
export class ListReplicationRuleComponent implements OnInit {
|
||||
|
||||
nullTime: string = '0001-01-01T00:00:00Z';
|
||||
|
||||
@Input() rules: ReplicationRule[];
|
||||
@Input() projectless: boolean;
|
||||
@Input() projectId: number;
|
||||
@Input() selectedId: number | string;
|
||||
@Input() withReplicationJob: boolean;
|
||||
|
||||
@Input() loading: boolean = false;
|
||||
|
||||
@ -50,6 +50,13 @@ export class ListReplicationRuleComponent {
|
||||
@Output() selectOne = new EventEmitter<ReplicationRule>();
|
||||
@Output() editOne = new EventEmitter<ReplicationRule>();
|
||||
@Output() toggleOne = new EventEmitter<ReplicationRule>();
|
||||
@Output() redirect = new EventEmitter<ReplicationRule>();
|
||||
|
||||
projectScope: boolean;
|
||||
|
||||
rules: ReplicationRule[];
|
||||
changedRules: ReplicationRule[];
|
||||
ruleName: string;
|
||||
|
||||
@ViewChild('toggleConfirmDialog')
|
||||
toggleConfirmDialog: ConfirmationDialogComponent;
|
||||
@ -68,6 +75,38 @@ export class ListReplicationRuleComponent {
|
||||
setInterval(()=>ref.markForCheck(), 500);
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.projectScope = (!this.projectId);
|
||||
this.retrieveRules();
|
||||
}
|
||||
|
||||
retrieveRules(ruleName: string = ''): void {
|
||||
this.loading = true;
|
||||
toPromise<ReplicationRule[]>(this.replicationService
|
||||
.getReplicationRules(this.projectId, ruleName))
|
||||
.then(rules=>{
|
||||
this.rules = rules || [];
|
||||
if(this.rules && this.rules.length > 0) {
|
||||
this.selectedId = this.rules[0].id || '';
|
||||
this.selectOne.emit(this.rules[0]);
|
||||
}
|
||||
this.changedRules = this.rules;
|
||||
this.loading = false;
|
||||
}
|
||||
).catch(error=>{
|
||||
this.errorHandler.error(error);
|
||||
this.loading = false;
|
||||
});
|
||||
}
|
||||
|
||||
filterRuleStatus(status: string) {
|
||||
if(status === 'all') {
|
||||
this.changedRules = this.rules;
|
||||
} else {
|
||||
this.changedRules = this.rules.filter(policy=>policy.enabled === +status);
|
||||
}
|
||||
}
|
||||
|
||||
toggleConfirm(message: ConfirmationAcknowledgement) {
|
||||
if(message &&
|
||||
message.source === ConfirmationTargets.TOGGLE_CONFIRM &&
|
||||
@ -112,6 +151,10 @@ export class ListReplicationRuleComponent {
|
||||
this.selectOne.emit(rule);
|
||||
}
|
||||
|
||||
redirectTo(rule: ReplicationRule): void {
|
||||
this.redirect.emit(rule);
|
||||
}
|
||||
|
||||
editRule(rule: ReplicationRule) {
|
||||
this.editOne.emit(rule);
|
||||
}
|
||||
|
@ -7,7 +7,7 @@ export const LIST_REPOSITORY_TEMPLATE = `
|
||||
<clr-dg-action-overflow [hidden]="!hasProjectAdminRole">
|
||||
<button class="action-item" (click)="deleteRepo(r.name)">{{'REPOSITORY.DELETE' | translate}}</button>
|
||||
</clr-dg-action-overflow>
|
||||
<clr-dg-cell>{{r.name}}</clr-dg-cell>
|
||||
<clr-dg-cell><a href="javascript:void(0)" (click)="gotoLink(projectId || r.project_id, r.name || r.repository_name)">{{r.name}}</a></clr-dg-cell>
|
||||
<clr-dg-cell>{{r.tags_count}}</clr-dg-cell>
|
||||
<clr-dg-cell>{{r.pull_count}}</clr-dg-cell>
|
||||
</clr-dg-row>
|
||||
|
@ -2,6 +2,7 @@
|
||||
import { ComponentFixture, TestBed, async } from '@angular/core/testing';
|
||||
import { By } from '@angular/platform-browser';
|
||||
import { DebugElement } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
|
||||
import { SharedModule } from '../shared/shared.module';
|
||||
import { ConfirmationDialogComponent } from '../confirmation-dialog/confirmation-dialog.component';
|
||||
@ -10,8 +11,11 @@ import { Repository } from '../service/interface';
|
||||
|
||||
import { SERVICE_CONFIG, IServiceConfig } from '../service.config';
|
||||
|
||||
describe('ListRepositoryComponent (inline template)', () => {
|
||||
class RouterStub {
|
||||
navigateByUrl(url: string) { return url; }
|
||||
}
|
||||
|
||||
describe('ListRepositoryComponent (inline template)', () => {
|
||||
let comp: ListRepositoryComponent;
|
||||
let fixture: ComponentFixture<ListRepositoryComponent>;
|
||||
|
||||
@ -45,7 +49,10 @@ describe('ListRepositoryComponent (inline template)', () => {
|
||||
ListRepositoryComponent,
|
||||
ConfirmationDialogComponent
|
||||
],
|
||||
providers: [{ provide: SERVICE_CONFIG, useValue: {} }]
|
||||
providers: [
|
||||
{ provide: Router, useClass: RouterStub },
|
||||
{ provide: SERVICE_CONFIG, useValue: {} }
|
||||
]
|
||||
});
|
||||
}));
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { Component, Input, Output, EventEmitter, ChangeDetectorRef, ChangeDetectionStrategy } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
|
||||
import { State, Comparator } from 'clarity-angular';
|
||||
|
||||
@ -13,6 +14,7 @@ import { CustomComparator } from '../utils';
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class ListRepositoryComponent {
|
||||
@Input() urlPrefix: string;
|
||||
@Input() projectId: number;
|
||||
@Input() repositories: Repository[];
|
||||
|
||||
@ -28,6 +30,7 @@ export class ListRepositoryComponent {
|
||||
tagsCountComparator: Comparator<Repository> = new CustomComparator<Repository>('tags_count', 'number');
|
||||
|
||||
constructor(
|
||||
private router: Router,
|
||||
private ref: ChangeDetectorRef) {
|
||||
let hnd = setInterval(()=>ref.markForCheck(), 100);
|
||||
setTimeout(()=>clearInterval(hnd), 1000);
|
||||
@ -44,4 +47,9 @@ export class ListRepositoryComponent {
|
||||
this.paginate.emit(state);
|
||||
}
|
||||
}
|
||||
|
||||
public gotoLink(projectId: number, repoName: string): void {
|
||||
let linkUrl = [this.urlPrefix, 'tags', projectId, repoName];
|
||||
this.router.navigate(linkUrl);
|
||||
}
|
||||
}
|
@ -3,7 +3,7 @@ export const REPLICATION_TEMPLATE: string = `
|
||||
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
|
||||
<div class="row flex-items-xs-between">
|
||||
<div class="flex-xs-middle option-left">
|
||||
<button class="btn btn-link" (click)="openModal()"><clr-icon shape="add"></clr-icon> {{'REPLICATION.REPLICATION_RULE' | translate}}</button>
|
||||
<button *ngIf="withReplicationJob" class="btn btn-link" (click)="openModal()"><clr-icon shape="add"></clr-icon> {{'REPLICATION.REPLICATION_RULE' | translate}}</button>
|
||||
<create-edit-rule [projectId]="projectId" (reload)="reloadRules($event)"></create-edit-rule>
|
||||
</div>
|
||||
<div class="flex-xs-middle option-right">
|
||||
@ -20,9 +20,9 @@ export const REPLICATION_TEMPLATE: string = `
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
|
||||
<list-replication-rule [rules]="changedRules" [projectless]="false" [selectedId]="initSelectedId" (selectOne)="selectOneRule($event)" (editOne)="openEditRule($event)" (reload)="reloadRules($event)" [loading]="loading"></list-replication-rule>
|
||||
<hbr-list-replication-rule #listReplicationRule [projectId]="projectId" (selectOne)="selectOneRule($event)" (editOne)="openEditRule($event)" (reload)="reloadRules($event)" [loading]="loading" [withReplicationJob]="withReplicationJob" (redirect)="customRedirect($event)"></hbr-list-replication-rule>
|
||||
</div>
|
||||
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
|
||||
<div *ngIf="withReplicationJob" class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
|
||||
<div class="row flex-items-xs-between">
|
||||
<h5 class="flex-items-xs-bottom option-left-down" style="margin-left: 14px;">{{'REPLICATION.REPLICATION_JOBS' | translate}}</h5>
|
||||
<div class="flex-items-xs-bottom option-right-down">
|
||||
@ -45,7 +45,7 @@ export const REPLICATION_TEMPLATE: string = `
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
|
||||
<div *ngIf="withReplicationJob" class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
|
||||
<clr-datagrid [clrDgLoading]="loading">
|
||||
<clr-dg-column [clrDgField]="'repository'">{{'REPLICATION.NAME' | translate}}</clr-dg-column>
|
||||
<clr-dg-column [clrDgField]="'status'">{{'REPLICATION.STATUS' | translate}}</clr-dg-column>
|
||||
|
@ -11,12 +11,13 @@
|
||||
// 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.
|
||||
import { Component, OnInit, ViewChild, Input } from '@angular/core';
|
||||
import { Component, OnInit, ViewChild, Input, Output, EventEmitter } from '@angular/core';
|
||||
import { ResponseOptions, RequestOptions } from '@angular/http';
|
||||
import { NgModel } from '@angular/forms';
|
||||
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
|
||||
import { ListReplicationRuleComponent} from '../list-replication-rule/list-replication-rule.component';
|
||||
import { CreateEditRuleComponent } from '../create-edit-rule/create-edit-rule.component';
|
||||
import { ErrorHandler } from '../error-handler/error-handler';
|
||||
|
||||
@ -71,6 +72,9 @@ export class SearchOption {
|
||||
export class ReplicationComponent implements OnInit {
|
||||
|
||||
@Input() projectId: number | string;
|
||||
@Input() withReplicationJob: boolean;
|
||||
|
||||
@Output() redirect = new EventEmitter<ReplicationRule>();
|
||||
|
||||
search: SearchOption = new SearchOption();
|
||||
|
||||
@ -94,6 +98,9 @@ export class ReplicationComponent implements OnInit {
|
||||
toggleJobSearchOption = optionalSearch;
|
||||
currentJobSearchOption: number;
|
||||
|
||||
@ViewChild(ListReplicationRuleComponent)
|
||||
listReplicationRule: ListReplicationRuleComponent;
|
||||
|
||||
@ViewChild(CreateEditRuleComponent)
|
||||
createEditPolicyComponent: CreateEditRuleComponent;
|
||||
|
||||
@ -113,30 +120,6 @@ export class ReplicationComponent implements OnInit {
|
||||
this.currentRuleStatus = this.ruleStatus[0];
|
||||
this.currentJobStatus = this.jobStatus[0];
|
||||
this.currentJobSearchOption = 0;
|
||||
|
||||
this.retrieveRules();
|
||||
}
|
||||
|
||||
retrieveRules(): void {
|
||||
this.loading = true;
|
||||
toPromise<ReplicationRule[]>(this.replicationService
|
||||
.getReplicationRules(this.projectId, this.search.ruleName))
|
||||
.then(response=>{
|
||||
this.changedRules = response || [];
|
||||
if(this.changedRules && this.changedRules.length > 0) {
|
||||
this.initSelectedId = this.changedRules[0].id || '';
|
||||
}
|
||||
this.rules = this.changedRules;
|
||||
if(this.changedRules && this.changedRules.length > 0) {
|
||||
this.search.ruleId = this.changedRules[0].id || '';
|
||||
this.fetchReplicationJobs();
|
||||
}
|
||||
this.loading = false;
|
||||
}
|
||||
).catch(error=>{
|
||||
this.errorHandler.error(error);
|
||||
this.loading = false;
|
||||
});
|
||||
}
|
||||
|
||||
openModal(): void {
|
||||
@ -182,32 +165,34 @@ export class ReplicationComponent implements OnInit {
|
||||
}
|
||||
}
|
||||
|
||||
customRedirect(rule: ReplicationRule) {
|
||||
this.redirect.emit(rule);
|
||||
}
|
||||
|
||||
doSearchRules(ruleName: string) {
|
||||
this.search.ruleName = ruleName;
|
||||
this.retrieveRules();
|
||||
this.listReplicationRule.retrieveRules(ruleName);
|
||||
}
|
||||
|
||||
doFilterRuleStatus($event: any) {
|
||||
if ($event && $event.target && $event.target["value"]) {
|
||||
let status = $event.target["value"];
|
||||
this.currentRuleStatus = this.ruleStatus.find((r: any)=>r.key === status);
|
||||
if(this.currentRuleStatus.key === 'all') {
|
||||
this.changedRules = this.rules;
|
||||
} else {
|
||||
this.changedRules = this.rules.filter(policy=>policy.enabled === +this.currentRuleStatus.key);
|
||||
}
|
||||
this.listReplicationRule.filterRuleStatus(this.currentRuleStatus.key);
|
||||
}
|
||||
}
|
||||
|
||||
doFilterJobStatus($event: any) {
|
||||
if ($event && $event.target && $event.target["value"]) {
|
||||
let status = $event.target["value"];
|
||||
|
||||
this.currentJobStatus = this.jobStatus.find((r: any)=>r.key === status);
|
||||
if(this.currentJobStatus.key === 'all') {
|
||||
status = '';
|
||||
}
|
||||
this.search.status = status;
|
||||
this.doSearchJobs(this.search.repoName);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -219,12 +204,12 @@ export class ReplicationComponent implements OnInit {
|
||||
reloadRules(isReady: boolean) {
|
||||
if(isReady) {
|
||||
this.search.ruleName = '';
|
||||
this.retrieveRules();
|
||||
this.listReplicationRule.retrieveRules(this.search.ruleName);
|
||||
}
|
||||
}
|
||||
|
||||
refreshRules() {
|
||||
this.retrieveRules();
|
||||
this.listReplicationRule.retrieveRules();
|
||||
}
|
||||
|
||||
refreshJobs() {
|
||||
|
6
src/ui_ng/lib/src/repository-stackview/index.ts
Normal file
6
src/ui_ng/lib/src/repository-stackview/index.ts
Normal file
@ -0,0 +1,6 @@
|
||||
import { Type } from '@angular/core';
|
||||
import { RepositoryStackviewComponent } from './repository-stackview.component';
|
||||
|
||||
export const REPOSITORY_STACKVIEW_DIRECTIVES: Type<any>[] = [
|
||||
RepositoryStackviewComponent
|
||||
];
|
@ -0,0 +1,26 @@
|
||||
export const REPOSITORY_STACKVIEW_STYLES: string = `
|
||||
.option-right {
|
||||
padding-right: 16px;
|
||||
margin-top: 32px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.sub-grid-custom {
|
||||
position: relative;
|
||||
left: 40px;
|
||||
}
|
||||
|
||||
:host >>> .datagrid .datagrid-body .datagrid-row {
|
||||
overflow-x: hidden;
|
||||
overflow-y: hidden;
|
||||
background-color: #ccc;
|
||||
}
|
||||
|
||||
:host >>> .datagrid-body .datagrid-row .datagrid-row-master{
|
||||
background-color: #FFFFFF;
|
||||
}
|
||||
|
||||
:host >>> .datagrid .datagrid-placeholder-container {
|
||||
display: none;
|
||||
}
|
||||
`;
|
@ -0,0 +1,34 @@
|
||||
export const REPOSITORY_STACKVIEW_TEMPLATE: string = `
|
||||
<confirmation-dialog #confirmationDialog (confirmAction)="confirmDeletion($event)"></confirmation-dialog>
|
||||
<div class="row">
|
||||
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
|
||||
<div class="row flex-items-xs-right option-right">
|
||||
<div class="flex-xs-middle">
|
||||
<hbr-filter filterPlaceholder="{{'REPOSITORY.FILTER_FOR_REPOSITORIES' | translate}}" (filter)="doSearchRepoNames($event)"></hbr-filter>
|
||||
<a href="javascript:void(0)" (click)="refresh()"><clr-icon shape="refresh"></clr-icon></a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
|
||||
<clr-datagrid>
|
||||
<clr-dg-column [clrDgField]="'name'">{{'REPOSITORY.NAME' | translate}}</clr-dg-column>
|
||||
<clr-dg-column [clrDgSortBy]="tagsCountComparator">{{'REPOSITORY.TAGS_COUNT' | translate}}</clr-dg-column>
|
||||
<clr-dg-column [clrDgSortBy]="pullCountComparator">{{'REPOSITORY.PULL_COUNT' | translate}}</clr-dg-column>
|
||||
<clr-dg-row *clrDgItems="let r of repositories">
|
||||
<clr-dg-action-overflow [hidden]="!hasProjectAdminRole">
|
||||
<button class="action-item" (click)="deleteRepo(r.name)">{{'REPOSITORY.DELETE' | translate}}</button>
|
||||
</clr-dg-action-overflow>
|
||||
<clr-dg-cell>{{r.name}}</clr-dg-cell>
|
||||
<clr-dg-cell>{{r.tags_count}}</clr-dg-cell>
|
||||
<clr-dg-cell>{{r.pull_count}}</clr-dg-cell>
|
||||
<hbr-tag *clrIfExpanded ngProjectAs="clr-dg-row-detail" class="sub-grid-custom" [repoName]="r.name" [sessionInfo]="sessionInfo" [projectId]="projectId" [isEmbedded]="true" (refreshRepo)="refresh($event)"></hbr-tag>
|
||||
</clr-dg-row>
|
||||
<clr-dg-footer>
|
||||
{{pagination.firstItem + 1}} - {{pagination.lastItem + 1}} {{'REPOSITORY.OF' | translate}}
|
||||
{{pagination.totalItems}} {{'REPOSITORY.ITEMS' | translate}}
|
||||
<clr-dg-pagination #pagination [clrDgPageSize]="15"></clr-dg-pagination>
|
||||
</clr-dg-footer>
|
||||
</clr-datagrid>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
@ -0,0 +1,152 @@
|
||||
import { ComponentFixture, TestBed, async } from '@angular/core/testing';
|
||||
import { By } from '@angular/platform-browser';
|
||||
import { DebugElement } from '@angular/core';
|
||||
|
||||
import { SharedModule } from '../shared/shared.module';
|
||||
import { ConfirmationDialogComponent } from '../confirmation-dialog/confirmation-dialog.component';
|
||||
import { RepositoryStackviewComponent } from './repository-stackview.component';
|
||||
import { TagComponent } from '../tag/tag.component';
|
||||
import { FilterComponent } from '../filter/filter.component';
|
||||
|
||||
import { ErrorHandler } from '../error-handler/error-handler';
|
||||
import { Repository, Tag } from '../service/interface';
|
||||
import { SERVICE_CONFIG, IServiceConfig } from '../service.config';
|
||||
import { RepositoryService, RepositoryDefaultService } from '../service/repository.service';
|
||||
import { TagService, TagDefaultService } from '../service/tag.service';
|
||||
|
||||
import { click } from '../utils';
|
||||
|
||||
describe('RepositoryComponentStackview (inline template)', ()=> {
|
||||
|
||||
let compRepo: RepositoryStackviewComponent;
|
||||
let fixtureRepo: ComponentFixture<RepositoryStackviewComponent>;
|
||||
let repositoryService: RepositoryService;
|
||||
let spyRepos: jasmine.Spy;
|
||||
|
||||
let compTag: TagComponent;
|
||||
let fixtureTag: ComponentFixture<TagComponent>;
|
||||
let tagService: TagService;
|
||||
let spyTags: jasmine.Spy;
|
||||
|
||||
let mockRepoData: Repository[] = [
|
||||
{
|
||||
"id": 1,
|
||||
"name": "library/busybox",
|
||||
"project_id": 1,
|
||||
"description": "",
|
||||
"pull_count": 0,
|
||||
"star_count": 0,
|
||||
"tags_count": 1
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"name": "library/nginx",
|
||||
"project_id": 1,
|
||||
"description": "",
|
||||
"pull_count": 0,
|
||||
"star_count": 0,
|
||||
"tags_count": 1
|
||||
}
|
||||
];
|
||||
|
||||
let mockTagData: Tag[] = [
|
||||
{
|
||||
"digest": "sha256:e5c82328a509aeb7c18c1d7fb36633dc638fcf433f651bdcda59c1cc04d3ee55",
|
||||
"name": "1.11.5",
|
||||
"architecture": "amd64",
|
||||
"os": "linux",
|
||||
"docker_version": "1.12.3",
|
||||
"author": "NGINX Docker Maintainers \"docker-maint@nginx.com\"",
|
||||
"created": new Date("2016-11-08T22:41:15.912313785Z"),
|
||||
"signature": null
|
||||
}
|
||||
];
|
||||
|
||||
let config: IServiceConfig = {
|
||||
repositoryBaseEndpoint: '/api/repository/testing'
|
||||
};
|
||||
|
||||
beforeEach(async(()=>{
|
||||
TestBed.configureTestingModule({
|
||||
imports: [
|
||||
SharedModule
|
||||
],
|
||||
declarations: [
|
||||
RepositoryStackviewComponent,
|
||||
TagComponent,
|
||||
ConfirmationDialogComponent,
|
||||
FilterComponent
|
||||
],
|
||||
providers: [
|
||||
ErrorHandler,
|
||||
{ provide: SERVICE_CONFIG, useValue : config },
|
||||
{ provide: RepositoryService, useClass: RepositoryDefaultService },
|
||||
{ provide: TagService, useClass: TagDefaultService }
|
||||
]
|
||||
});
|
||||
}));
|
||||
|
||||
beforeEach(()=>{
|
||||
fixtureRepo = TestBed.createComponent(RepositoryStackviewComponent);
|
||||
compRepo = fixtureRepo.componentInstance;
|
||||
compRepo.projectId = 1;
|
||||
compRepo.sessionInfo = {
|
||||
hasProjectAdminRole: true
|
||||
};
|
||||
repositoryService = fixtureRepo.debugElement.injector.get(RepositoryService);
|
||||
|
||||
spyRepos = spyOn(repositoryService, 'getRepositories').and.returnValues(Promise.resolve(mockRepoData));
|
||||
fixtureRepo.detectChanges();
|
||||
});
|
||||
|
||||
beforeEach(()=>{
|
||||
fixtureTag = TestBed.createComponent(TagComponent);
|
||||
compTag = fixtureTag.componentInstance;
|
||||
compTag.projectId = compRepo.projectId;
|
||||
compTag.repoName = 'library/busybox';
|
||||
compTag.sessionInfo = compRepo.sessionInfo;
|
||||
tagService = fixtureTag.debugElement.injector.get(TagService);
|
||||
spyTags = spyOn(tagService, 'getTags').and.returnValues(Promise.resolve(mockTagData));
|
||||
fixtureTag.detectChanges();
|
||||
});
|
||||
|
||||
it('should load and render data', async(()=>{
|
||||
fixtureRepo.detectChanges();
|
||||
fixtureRepo.whenStable().then(()=>{
|
||||
fixtureRepo.detectChanges();
|
||||
let deRepo: DebugElement = fixtureRepo.debugElement.query(By.css('datagrid-cell'));
|
||||
fixtureRepo.detectChanges();
|
||||
expect(deRepo).toBeTruthy();
|
||||
let elRepo: HTMLElement = deRepo.nativeElement;
|
||||
fixtureRepo.detectChanges();
|
||||
expect(elRepo).toBeTruthy();
|
||||
fixtureRepo.detectChanges();
|
||||
expect(elRepo.textContent).toEqual('library/busybox');
|
||||
click(deRepo);
|
||||
fixtureTag.detectChanges();
|
||||
let deTag: DebugElement = fixtureTag.debugElement.query(By.css('datagrid-cell'));
|
||||
expect(deTag).toBeTruthy();
|
||||
let elTag: HTMLElement = deTag.nativeElement;
|
||||
expect(elTag).toBeTruthy();
|
||||
expect(elTag.textContent).toEqual('1.12.5');
|
||||
});
|
||||
}));
|
||||
|
||||
it('should filter data by keyword', async(()=>{
|
||||
fixtureRepo.detectChanges();
|
||||
fixtureRepo.whenStable().then(()=>{
|
||||
fixtureRepo.detectChanges();
|
||||
compRepo.doSearchRepoNames('nginx');
|
||||
fixtureRepo.detectChanges();
|
||||
let de: DebugElement[] = fixtureRepo.debugElement.queryAll(By.css('datagrid-cell'));
|
||||
fixtureRepo.detectChanges();
|
||||
expect(de).toBeTruthy();
|
||||
expect(de.length).toEqual(1);
|
||||
let el: HTMLElement = de[0].nativeElement;
|
||||
fixtureRepo.detectChanges();
|
||||
expect(el).toBeTruthy();
|
||||
expect(el.textContent).toEqual('library/nginx');
|
||||
});
|
||||
}));
|
||||
|
||||
});
|
@ -0,0 +1,111 @@
|
||||
import { Component, Input, OnInit, ViewChild, ChangeDetectionStrategy, ChangeDetectorRef } from '@angular/core';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { Comparator } from 'clarity-angular';
|
||||
|
||||
import { REPOSITORY_STACKVIEW_TEMPLATE } from './repository-stackview.component.html';
|
||||
import { REPOSITORY_STACKVIEW_STYLES } from './repository-stackview.component.css';
|
||||
|
||||
import { Repository, SessionInfo } from '../service/interface';
|
||||
import { ErrorHandler } from '../error-handler/error-handler';
|
||||
import { RepositoryService } from '../service/repository.service';
|
||||
import { toPromise, CustomComparator } from '../utils';
|
||||
|
||||
import { ConfirmationState, ConfirmationTargets, ConfirmationButtons } from '../shared/shared.const';
|
||||
|
||||
import { ConfirmationDialogComponent } from '../confirmation-dialog/confirmation-dialog.component';
|
||||
import { ConfirmationMessage } from '../confirmation-dialog/confirmation-message';
|
||||
import { ConfirmationAcknowledgement } from '../confirmation-dialog/confirmation-state-message';
|
||||
import { Subscription } from 'rxjs/Subscription';
|
||||
|
||||
@Component({
|
||||
selector: 'hbr-repository-stackview',
|
||||
template: REPOSITORY_STACKVIEW_TEMPLATE,
|
||||
styles: [ REPOSITORY_STACKVIEW_STYLES ],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class RepositoryStackviewComponent implements OnInit {
|
||||
|
||||
@Input() projectId: number;
|
||||
@Input() sessionInfo: SessionInfo;
|
||||
|
||||
lastFilteredRepoName: string;
|
||||
|
||||
hasProjectAdminRole: boolean;
|
||||
|
||||
repositories: Repository[];
|
||||
|
||||
@ViewChild('confirmationDialog')
|
||||
confirmationDialog: ConfirmationDialogComponent;
|
||||
|
||||
pullCountComparator: Comparator<Repository> = new CustomComparator<Repository>('pull_count', 'number');
|
||||
|
||||
tagsCountComparator: Comparator<Repository> = new CustomComparator<Repository>('tags_count', 'number');
|
||||
|
||||
|
||||
constructor(
|
||||
private errorHandler: ErrorHandler,
|
||||
private translateService: TranslateService,
|
||||
private repositoryService: RepositoryService,
|
||||
private ref: ChangeDetectorRef){}
|
||||
|
||||
confirmDeletion(message: ConfirmationAcknowledgement) {
|
||||
if (message &&
|
||||
message.source === ConfirmationTargets.REPOSITORY &&
|
||||
message.state === ConfirmationState.CONFIRMED) {
|
||||
let repoName = message.data;
|
||||
toPromise<number>(this.repositoryService
|
||||
.deleteRepository(repoName))
|
||||
.then(
|
||||
response => {
|
||||
this.refresh();
|
||||
this.translateService.get('REPOSITORY.DELETED_REPO_SUCCESS')
|
||||
.subscribe(res=>this.errorHandler.info(res));
|
||||
}).catch(error => this.errorHandler.error(error));
|
||||
}
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
if(!this.projectId) {
|
||||
this.errorHandler.error('Project ID cannot be unset.');
|
||||
return;
|
||||
}
|
||||
if(!this.sessionInfo) {
|
||||
this.errorHandler.error('Session info cannot be unset.');
|
||||
return;
|
||||
}
|
||||
|
||||
this.hasProjectAdminRole = this.sessionInfo.hasProjectAdminRole || false;
|
||||
this.lastFilteredRepoName = '';
|
||||
this.retrieve();
|
||||
}
|
||||
|
||||
retrieve() {
|
||||
toPromise<Repository[]>(this.repositoryService
|
||||
.getRepositories(this.projectId, this.lastFilteredRepoName))
|
||||
.then(
|
||||
repos => this.repositories = repos,
|
||||
error => this.errorHandler.error(error));
|
||||
let hnd = setInterval(()=>this.ref.markForCheck(), 100);
|
||||
setTimeout(()=>clearInterval(hnd), 1000);
|
||||
}
|
||||
|
||||
doSearchRepoNames(repoName: string) {
|
||||
this.lastFilteredRepoName = repoName;
|
||||
this.retrieve();
|
||||
}
|
||||
|
||||
deleteRepo(repoName: string) {
|
||||
let message = new ConfirmationMessage(
|
||||
'REPOSITORY.DELETION_TITLE_REPO',
|
||||
'REPOSITORY.DELETION_SUMMARY_REPO',
|
||||
repoName,
|
||||
repoName,
|
||||
ConfirmationTargets.REPOSITORY,
|
||||
ConfirmationButtons.DELETE_CANCEL);
|
||||
this.confirmationDialog.open(message);
|
||||
}
|
||||
|
||||
refresh() {
|
||||
this.retrieve();
|
||||
}
|
||||
}
|
@ -10,6 +10,6 @@ export const REPOSITORY_TEMPLATE = `
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
|
||||
<hbr-list-repository [projectId]="projectId" [repositories]="changedRepositories" (delete)="deleteRepo($event)" [hasProjectAdminRole]="hasProjectAdminRole" (paginate)="retrieve($event)"></hbr-list-repository>
|
||||
<hbr-list-repository [urlPrefix]="urlPrefix" [projectId]="projectId" [repositories]="changedRepositories" (delete)="deleteRepo($event)" [hasProjectAdminRole]="hasProjectAdminRole" (paginate)="retrieve($event)"></hbr-list-repository>
|
||||
</div>
|
||||
</div>`;
|
@ -1,6 +1,7 @@
|
||||
import { ComponentFixture, TestBed, async } from '@angular/core/testing';
|
||||
import { By } from '@angular/platform-browser';
|
||||
import { DebugElement } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
|
||||
import { SharedModule } from '../shared/shared.module';
|
||||
import { ConfirmationDialogComponent } from '../confirmation-dialog/confirmation-dialog.component';
|
||||
@ -13,6 +14,10 @@ import { Repository } from '../service/interface';
|
||||
import { SERVICE_CONFIG, IServiceConfig } from '../service.config';
|
||||
import { RepositoryService, RepositoryDefaultService } from '../service/repository.service';
|
||||
|
||||
class RouterStub {
|
||||
navigateByUrl(url: string) { return url; }
|
||||
}
|
||||
|
||||
describe('RepositoryComponent (inline template)', ()=> {
|
||||
|
||||
let comp: RepositoryComponent;
|
||||
@ -59,7 +64,8 @@ describe('RepositoryComponent (inline template)', ()=> {
|
||||
providers: [
|
||||
ErrorHandler,
|
||||
{ provide: SERVICE_CONFIG, useValue : config },
|
||||
{ provide: RepositoryService, useClass: RepositoryDefaultService }
|
||||
{ provide: RepositoryService, useClass: RepositoryDefaultService },
|
||||
{ provide: Router, useClass: RouterStub }
|
||||
]
|
||||
});
|
||||
}));
|
||||
|
@ -43,6 +43,7 @@ export class RepositoryComponent implements OnInit {
|
||||
|
||||
@Input() projectId: number;
|
||||
@Input() sessionInfo: SessionInfo;
|
||||
@Input() urlPrefix: string;
|
||||
|
||||
lastFilteredRepoName: string;
|
||||
|
||||
|
@ -67,7 +67,7 @@ export class AccessLogDefaultService extends AccessLogService {
|
||||
url = '/api/logs';
|
||||
}
|
||||
|
||||
return this.http.get(url+`?lines=${lines}`, HTTP_JSON_OPTIONS).toPromise()
|
||||
return this.http.get(url+`?page_size=${lines}`, HTTP_JSON_OPTIONS).toPromise()
|
||||
.then(response => response.json() as AccessLog[])
|
||||
.catch(error => Promise.reject(error));
|
||||
}
|
||||
|
@ -1,4 +1,33 @@
|
||||
export const TAG_STYLE = `
|
||||
.sub-header-title {
|
||||
margin-top: 12px;
|
||||
}`;
|
||||
margin: 12px 0;
|
||||
}
|
||||
|
||||
.embeded-datagrid {
|
||||
width: 98%;
|
||||
}
|
||||
|
||||
.hidden-tag {
|
||||
display: block; height: 0;
|
||||
}
|
||||
|
||||
:host >>> .datagrid {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
:host >>> .datagrid-placeholder {
|
||||
display: none;
|
||||
}
|
||||
|
||||
:host >>> .datagrid .datagrid-body {
|
||||
background-color: #eee;
|
||||
}
|
||||
|
||||
:host >>> .datagrid .datagrid-head .datagrid-row {
|
||||
background-color: #eee;
|
||||
}
|
||||
|
||||
:host >>> .datagrid .datagrid-body .datagrid-row-master {
|
||||
background-color: #eee;
|
||||
}
|
||||
`;
|
@ -1,6 +1,6 @@
|
||||
export const TAG_TEMPLATE = `
|
||||
<confirmation-dialog #confirmationDialog (confirmAction)="confirmDeletion($event)"></confirmation-dialog>
|
||||
<clr-modal [(clrModalOpen)]="showTagManifestOpened" [clrModalStaticBackdrop]="staticBackdrop" [clrModalClosable]="closable">
|
||||
<confirmation-dialog class="hidden-tag" #confirmationDialog (confirmAction)="confirmDeletion($event)"></confirmation-dialog>
|
||||
<clr-modal class="hidden-tag" [(clrModalOpen)]="showTagManifestOpened" [clrModalStaticBackdrop]="staticBackdrop" [clrModalClosable]="closable">
|
||||
<h3 class="modal-title">{{ manifestInfoTitle | translate }}</h3>
|
||||
<div class="modal-body">
|
||||
<div class="row col-md-12">
|
||||
@ -11,8 +11,9 @@ export const TAG_TEMPLATE = `
|
||||
<button type="button" class="btn btn-primary" (click)="showTagManifestOpened = false">{{'BUTTON.OK' | translate}}</button>
|
||||
</div>
|
||||
</clr-modal>
|
||||
<h2 class="sub-header-title">{{repoName}}</h2>
|
||||
<clr-datagrid [clrDgLoading]="loading">
|
||||
|
||||
<h2 *ngIf="!isEmbedded" class="sub-header-title">{{repoName}}</h2>
|
||||
<clr-datagrid [clrDgLoading]="loading" [class.embeded-datagrid]="isEmbedded">
|
||||
<clr-dg-column [clrDgField]="'name'">{{'REPOSITORY.TAG' | translate}}</clr-dg-column>
|
||||
<clr-dg-column>{{'REPOSITORY.PULL_COMMAND' | translate}}</clr-dg-column>
|
||||
<clr-dg-column *ngIf="withNotary">{{'REPOSITORY.SIGNED' | translate}}</clr-dg-column>
|
||||
@ -44,7 +45,7 @@ export const TAG_TEMPLATE = `
|
||||
</clr-dg-row>
|
||||
<clr-dg-footer>
|
||||
{{pagination.firstItem + 1}} - {{pagination.lastItem + 1}} {{'REPOSITORY.OF' | translate}}
|
||||
{{pagination.totalItems}} {{'REPOSITORY.ITEMS' | translate}}
|
||||
<clr-dg-pagination #pagination [clrDgPageSize]="10"></clr-dg-pagination>
|
||||
{{pagination.totalItems}} {{'REPOSITORY.ITEMS' | translate}}
|
||||
<clr-dg-pagination #pagination [clrDgPageSize]="5"></clr-dg-pagination>
|
||||
</clr-dg-footer>
|
||||
</clr-datagrid>`;
|
@ -11,7 +11,7 @@
|
||||
// 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.
|
||||
import { Component, OnInit, ViewChild, Input, ChangeDetectionStrategy, ChangeDetectorRef } from '@angular/core';
|
||||
import { Component, OnInit, ViewChild, Input, Output, EventEmitter, ChangeDetectionStrategy, ChangeDetectorRef } from '@angular/core';
|
||||
|
||||
import { TagService } from '../service/tag.service';
|
||||
import { ErrorHandler } from '../error-handler/error-handler';
|
||||
@ -43,6 +43,9 @@ export class TagComponent implements OnInit {
|
||||
@Input() projectId: number;
|
||||
@Input() repoName: string;
|
||||
@Input() sessionInfo: SessionInfo;
|
||||
@Input() isEmbedded: boolean;
|
||||
|
||||
@Output() refreshRepo = new EventEmitter<boolean>();
|
||||
|
||||
hasProjectAdminRole: boolean;
|
||||
|
||||
@ -122,6 +125,9 @@ export class TagComponent implements OnInit {
|
||||
.then(items => {
|
||||
this.tags = items;
|
||||
this.loading = false;
|
||||
if(this.tags && this.tags.length === 0) {
|
||||
this.refreshRepo.emit(true);
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
this.errorHandler.error(error);
|
||||
|
@ -104,7 +104,7 @@ export class CustomComparator<T> implements Comparator<T> {
|
||||
|
||||
compare(a: {[key: string]: any| any[]}, b: {[key: string]: any| any[]}) {
|
||||
let comp = 0;
|
||||
if(a && b && a[this.fieldName] && b[this.fieldName]) {
|
||||
if(a && b) {
|
||||
let fieldA = a[this.fieldName];
|
||||
let fieldB = b[this.fieldName];
|
||||
switch(this.type) {
|
||||
|
@ -49,7 +49,7 @@ export class AuditLogService {
|
||||
}
|
||||
|
||||
getRecentLogs(lines: number): Observable<AuditLog[]> {
|
||||
return this.http.get(logEndpoint + "?lines=" + lines, this.httpOptions)
|
||||
return this.http.get(logEndpoint + "?page_size=" + lines, this.httpOptions)
|
||||
.map(response => response.json() as AuditLog[])
|
||||
.catch(error => Observable.throw(error));
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user