Merge pull request #307 from wemeya/develop

api for logs, updating user profile and listing top repositories
This commit is contained in:
Wenkai Yin 2016-06-08 17:59:10 +08:00
commit a7845df552
19 changed files with 578 additions and 115 deletions

86
api/log.go Normal file
View File

@ -0,0 +1,86 @@
/*
Copyright (c) 2016 VMware, Inc. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package api
import (
"net/http"
"strconv"
"time"
"github.com/vmware/harbor/dao"
"github.com/vmware/harbor/models"
"github.com/vmware/harbor/utils/log"
)
//LogAPI handles request api/logs
type LogAPI struct {
BaseAPI
userID int
}
//Prepare validates the URL and the user
func (l *LogAPI) Prepare() {
l.userID = l.ValidateUser()
}
//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()
}
endTime := l.GetString("end_time")
if len(endTime) != 0 {
j, err := strconv.ParseInt(endTime, 10, 64)
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()
}
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
}
var logList []models.AccessLog
logList, err = dao.GetRecentLogs(l.userID, linesNum, startTime, endTime)
if err != nil {
log.Errorf("Get recent logs error, err: %v", err)
l.CustomAbort(http.StatusInternalServerError, "Internal error")
}
l.Data["json"] = logList
l.ServeJSON()
}

View File

@ -18,6 +18,7 @@ package api
import (
"fmt"
"net/http"
"unicode"
"github.com/vmware/harbor/dao"
"github.com/vmware/harbor/models"
@ -40,6 +41,7 @@ type projectReq struct {
}
const projectNameMaxLen int = 30
const projectNameMinLen int = 4
// Prepare validates the URL and the user
func (p *ProjectAPI) Prepare() {
@ -197,10 +199,9 @@ func (p *ProjectAPI) List() {
p.ServeJSON()
}
// Put ...
func (p *ProjectAPI) Put() {
// ToggleProjectPublic ...
func (p *ProjectAPI) ToggleProjectPublic() {
p.userID = p.ValidateUser()
var req projectReq
var public int
@ -287,8 +288,16 @@ func validateProjectReq(req projectReq) error {
if len(pn) == 0 {
return fmt.Errorf("Project name can not be empty")
}
if len(pn) > projectNameMaxLen {
return fmt.Errorf("Project name is too long")
if isIllegalLength(req.ProjectName, projectNameMinLen, projectNameMaxLen) {
return fmt.Errorf("project name is illegal in length. (greater than 4 or less than 30)")
}
if isContainIllegalChar(req.ProjectName, []string{"~", "-", "$", "\\", "[", "]", "{", "}", "(", ")", "&", "^", "%", "*", "<", ">", "\"", "'", "/", "?", "@"}) {
return fmt.Errorf("project name contains illegal characters")
}
for _, v := range pn {
if !unicode.IsLower(v) {
return fmt.Errorf("project name must be in lower case")
}
}
return nil
}

View File

@ -294,3 +294,30 @@ func (ra *RepositoryAPI) getUsername() (string, error) {
return "", nil
}
//GetTopRepos handles request GET /api/repositories/top
func (ra *RepositoryAPI) GetTopRepos() {
var err error
var countNum int
count := ra.GetString("count")
if len(count) == 0 {
countNum = 10
} else {
countNum, err = strconv.Atoi(count)
if err != nil {
log.Errorf("Get parameters error--count, err: %v", err)
ra.CustomAbort(http.StatusBadRequest, "bad request of count")
}
if countNum <= 0 {
log.Warning("count must be a positive integer")
ra.CustomAbort(http.StatusBadRequest, "count is 0 or negative")
}
}
repos, err := dao.GetTopRepos(countNum)
if err != nil {
log.Errorf("error occured in get top 10 repos: %v", err)
ra.CustomAbort(http.StatusInternalServerError, "internal server error")
}
ra.Data["json"] = repos
ra.ServeJSON()
}

View File

@ -16,8 +16,10 @@
package api
import (
"fmt"
"net/http"
"os"
"regexp"
"strconv"
"strings"
@ -133,14 +135,52 @@ func (ua *UserAPI) Get() {
}
// Put ...
func (ua *UserAPI) Put() { //currently only for toggle admin, so no request body
func (ua *UserAPI) Put() {
ldapAdminUser := (ua.AuthMode == "ldap_auth" && ua.userID == 1 && ua.userID == ua.currentUserID)
if !(ua.AuthMode == "db_auth" || ldapAdminUser) {
ua.CustomAbort(http.StatusForbidden, "")
}
if !ua.IsAdmin {
log.Warningf("current user, id: %d does not have admin role, can not update other user's role", ua.currentUserID)
ua.RenderError(http.StatusForbidden, "User does not have admin role")
if ua.userID != ua.currentUserID {
log.Warning("Guests can only change their own account.")
ua.CustomAbort(http.StatusForbidden, "Guests can only change their own account.")
}
}
user := models.User{UserID: ua.userID}
ua.DecodeJSONReq(&user)
err := commonValidate(user)
if err != nil {
log.Warning("Bad request in change user profile: %v", err)
ua.RenderError(http.StatusBadRequest, "change user profile error:"+err.Error())
return
}
userQuery := models.User{UserID: ua.userID}
dao.ToggleUserAdminRole(userQuery)
u, err := dao.GetUser(userQuery)
if err != nil {
log.Errorf("Error occurred in GetUser, error: %v", err)
ua.CustomAbort(http.StatusInternalServerError, "Internal error.")
}
if u == nil {
log.Errorf("User with Id: %d does not exist", ua.userID)
ua.CustomAbort(http.StatusNotFound, "")
}
if u.Email != user.Email {
emailExist, err := dao.UserExists(user, "email")
if err != nil {
log.Errorf("Error occurred in change user profile: %v", err)
ua.CustomAbort(http.StatusInternalServerError, "Internal error.")
}
if emailExist {
log.Warning("email has already been used!")
ua.RenderError(http.StatusConflict, "email has already been used!")
return
}
}
if err := dao.ChangeUserProfile(user); err != nil {
log.Errorf("Failed to update user profile, error: %v", err)
ua.CustomAbort(http.StatusInternalServerError, err.Error())
}
}
// Post ...
@ -157,12 +197,36 @@ func (ua *UserAPI) Post() {
user := models.User{}
ua.DecodeJSONReq(&user)
err := validate(user)
if err != nil {
log.Warning("Bad request in Register: %v", err)
ua.RenderError(http.StatusBadRequest, "register error:"+err.Error())
return
}
userExist, err := dao.UserExists(user, "username")
if err != nil {
log.Errorf("Error occurred in Register: %v", err)
ua.CustomAbort(http.StatusInternalServerError, "Internal error.")
}
if userExist {
log.Warning("username has already been used!")
ua.RenderError(http.StatusConflict, "username has already been used!")
return
}
emailExist, err := dao.UserExists(user, "email")
if err != nil {
log.Errorf("Error occurred in change user profile: %v", err)
ua.CustomAbort(http.StatusInternalServerError, "Internal error.")
}
if emailExist {
log.Warning("email has already been used!")
ua.RenderError(http.StatusConflict, "email has already been used!")
return
}
userID, err := dao.Register(user)
if err != nil {
log.Errorf("Error occurred in Register: %v", err)
ua.RenderError(http.StatusInternalServerError, "Internal error.")
return
ua.CustomAbort(http.StatusInternalServerError, "Internal error.")
}
ua.Redirect(http.StatusCreated, strconv.FormatInt(userID, 10))
@ -186,9 +250,8 @@ func (ua *UserAPI) Delete() {
// ChangePassword handles PUT to /api/users/{}/password
func (ua *UserAPI) ChangePassword() {
ldapAdminUser := (ua.AuthMode == "ldap_auth" && ua.userID == 1 && ua.userID == ua.currentUserID)
if !(ua.AuthMode == "db_auth" || ldapAdminUser) {
ua.CustomAbort(http.StatusForbidden, "")
}
@ -228,3 +291,80 @@ func (ua *UserAPI) ChangePassword() {
ua.CustomAbort(http.StatusInternalServerError, "Internal error.")
}
}
// ToggleUserAdminRole handles PUT api/users/{}/toggleadmin
func (ua *UserAPI) ToggleUserAdminRole() {
if !ua.IsAdmin {
log.Warningf("current user, id: %d does not have admin role, can not update other user's role", ua.currentUserID)
ua.RenderError(http.StatusForbidden, "User does not have admin role")
return
}
userQuery := models.User{UserID: ua.userID}
ua.DecodeJSONReq(&userQuery)
if err := dao.ToggleUserAdminRole(userQuery.UserID, userQuery.HasAdminRole); err != nil {
log.Errorf("Error occurred in ToggleUserAdminRole: %v", err)
ua.CustomAbort(http.StatusInternalServerError, "Internal error.")
}
}
// validate only validate when user register
func validate(user models.User) error {
if isIllegalLength(user.Username, 0, 20) {
return fmt.Errorf("Username with illegal length.")
}
if isContainIllegalChar(user.Username, []string{",", "~", "#", "$", "%"}) {
return fmt.Errorf("Username contains illegal characters.")
}
if isIllegalLength(user.Password, 0, 20) {
return fmt.Errorf("Password with illegal length.")
}
if err := commonValidate(user); err != nil {
return err
}
return nil
}
//commonValidate validates email, realname, comment information when user register or change their profile
func commonValidate(user models.User) error {
if len(user.Email) > 0 {
if m, _ := regexp.MatchString(`^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$`, user.Email); !m {
return fmt.Errorf("Email with illegal format.")
}
} else {
return fmt.Errorf("Email can't be empty")
}
if isIllegalLength(user.Realname, 0, 20) {
return fmt.Errorf("Realname with illegal length.")
}
if isContainIllegalChar(user.Realname, []string{",", "~", "#", "$", "%"}) {
return fmt.Errorf("Realname contains illegal characters.")
}
if isIllegalLength(user.Comment, -1, 30) {
return fmt.Errorf("Comment with illegal length.")
}
return nil
}
func isIllegalLength(s string, min int, max int) bool {
if min == -1 {
return (len(s) > max)
}
if max == -1 {
return (len(s) <= min)
}
return (len(s) < min || len(s) > max)
}
func isContainIllegalChar(s string, illegalChar []string) bool {
for _, c := range illegalChar {
if strings.Index(s, c) >= 0 {
return true
}
}
return false
}

View File

@ -111,6 +111,9 @@ func (l *Auth) Authenticate(m models.AuthModel) (*models.User, error) {
u.Realname = m.Principal
u.Password = "12345678AbC"
u.Comment = "registered from LDAP."
if u.Email == "" {
u.Email = u.Username + "@placeholder.com"
}
userID, err := dao.Register(u)
if err != nil {
return nil, err

View File

@ -115,3 +115,49 @@ func AccessLog(username, projectName, repoName, repoTag, action string) error {
}
return err
}
//GetRecentLogs returns recent logs according to parameters
func GetRecentLogs(userID, linesNum int, startTime, endTime string) ([]models.AccessLog, error) {
var recentLogList []models.AccessLog
queryParam := make([]interface{}, 1)
sql := "select log_id, access_log.user_id, project_id, repo_name, repo_tag, GUID, operation, op_time, username from access_log left join user on access_log.user_id=user.user_id where project_id in (select distinct project_id from project_member where user_id = ?)"
queryParam = append(queryParam, userID)
if startTime != "" {
sql += " and op_time >= ?"
queryParam = append(queryParam, startTime)
}
if endTime != "" {
sql += " and op_time <= ?"
queryParam = append(queryParam, endTime)
}
sql += " order by op_time desc"
if linesNum != 0 {
sql += " limit ?"
queryParam = append(queryParam, linesNum)
}
o := GetOrmer()
_, err := o.Raw(sql, queryParam).QueryRows(&recentLogList)
if err != nil {
return nil, err
}
return recentLogList, nil
}
//GetTopRepos return top accessed public repos
func GetTopRepos(countNum int) ([]models.TopRepo, error) {
o := GetOrmer()
sql := "select repo_name, COUNT(repo_name) as access_count from access_log left join project on access_log.project_id=project.project_id where project.public = 1 and access_log.operation = 'pull' group by repo_name order by access_count desc limit ? "
queryParam := make([]interface{}, 1)
queryParam = append(queryParam, countNum)
var lists []models.TopRepo
_, err := o.Raw(sql, queryParam).QueryRows(&lists)
if err != nil {
return nil, err
}
return lists, nil
}

View File

@ -18,39 +18,18 @@ package dao
import (
"net"
"github.com/vmware/harbor/utils/log"
"os"
"strings"
"sync"
"time"
"github.com/astaxie/beego/orm"
_ "github.com/go-sql-driver/mysql" //register mysql driver
"github.com/vmware/harbor/utils/log"
)
// NonExistUserID : if a user does not exist, the ID of the user will be 0.
const NonExistUserID = 0
func isIllegalLength(s string, min int, max int) bool {
if min == -1 {
return (len(s) > max)
}
if max == -1 {
return (len(s) <= min)
}
return (len(s) < min || len(s) > max)
}
func isContainIllegalChar(s string, illegalChar []string) bool {
for _, c := range illegalChar {
if strings.Index(s, c) >= 0 {
return true
}
}
return false
}
// GenerateRandomString generates a random string
func GenerateRandomString() (string, error) {
o := orm.NewOrm()

View File

@ -689,7 +689,7 @@ func TestDeleteProjectMember(t *testing.T) {
}
func TestToggleAdminRole(t *testing.T) {
err := ToggleUserAdminRole(*currentUser)
err := ToggleUserAdminRole(currentUser.UserID, 1)
if err != nil {
t.Errorf("Error in toggle ToggleUserAdmin role: %v, user: %+v", err, currentUser)
}
@ -700,7 +700,7 @@ func TestToggleAdminRole(t *testing.T) {
if !isAdmin {
t.Errorf("User is not admin after toggled, user id: %d", currentUser.UserID)
}
err = ToggleUserAdminRole(*currentUser)
err = ToggleUserAdminRole(currentUser.UserID, 0)
if err != nil {
t.Errorf("Error in toggle ToggleUserAdmin role: %v, user: %+v", err, currentUser)
}
@ -713,6 +713,39 @@ func TestToggleAdminRole(t *testing.T) {
}
}
func TestChangeUserProfile(t *testing.T) {
user := models.User{UserID: currentUser.UserID, Email: username + "@163.com", Realname: "test", Comment: "Unit Test"}
err := ChangeUserProfile(user)
if err != nil {
t.Errorf("Error occurred in ChangeUserProfile: %v", err)
}
loginedUser, err := GetUser(models.User{UserID: currentUser.UserID})
if err != nil {
t.Errorf("Error occurred in GetUser: %v", err)
}
if loginedUser != nil {
if loginedUser.Email != username+"@163.com" {
t.Errorf("user email does not update, expected: %s, acutal: %s", username+"@163.com", loginedUser.Email)
}
if loginedUser.Realname != "test" {
t.Errorf("user realname does not update, expected: %s, acutal: %s", "test", loginedUser.Realname)
}
if loginedUser.Comment != "Unit Test" {
t.Errorf("user email does not update, expected: %s, acutal: %s", "Unit Test", loginedUser.Comment)
}
}
}
func TestGetRecentLogs(t *testing.T) {
logs, err := GetRecentLogs(currentUser.UserID, 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))
}
}
func TestDeleteUser(t *testing.T) {
err := DeleteUser(currentUser.UserID)
if err != nil {

View File

@ -18,7 +18,6 @@ package dao
import (
"github.com/vmware/harbor/models"
"errors"
"fmt"
"time"
@ -30,15 +29,7 @@ import (
// AddProject adds a project to the database along with project roles information and access log records.
func AddProject(project models.Project) (int64, error) {
if isIllegalLength(project.Name, 4, 30) {
return 0, errors.New("project name is illegal in length. (greater than 4 or less than 30)")
}
if isContainIllegalChar(project.Name, []string{"~", "-", "$", "\\", "[", "]", "{", "}", "(", ")", "&", "^", "%", "*", "<", ">", "\"", "'", "/", "?", "@"}) {
return 0, errors.New("project name contains illegal characters")
}
o := GetOrmer()
p, err := o.Raw("insert into project (owner_id, name, creation_time, update_time, deleted, public) values (?, ?, ?, ?, ?, ?)").Prepare()
if err != nil {
return 0, err

View File

@ -17,7 +17,6 @@ package dao
import (
"errors"
"regexp"
"time"
"github.com/vmware/harbor/models"
@ -26,14 +25,7 @@ import (
// Register is used for user to register, the password is encrypted before the record is inserted into database.
func Register(user models.User) (int64, error) {
err := validate(user)
if err != nil {
return 0, err
}
o := GetOrmer()
p, err := o.Raw("insert into user (username, password, realname, email, comment, salt, sysadmin_flag, creation_time, update_time) values (?, ?, ?, ?, ?, ?, ?, ?, ?)").Prepare()
if err != nil {
return 0, err
@ -59,46 +51,6 @@ func Register(user models.User) (int64, error) {
return userID, nil
}
func validate(user models.User) error {
if isIllegalLength(user.Username, 0, 20) {
return errors.New("Username with illegal length.")
}
if isContainIllegalChar(user.Username, []string{",", "~", "#", "$", "%"}) {
return errors.New("Username contains illegal characters.")
}
if exist, _ := UserExists(models.User{Username: user.Username}, "username"); exist {
return errors.New("Username already exists.")
}
if len(user.Email) > 0 {
if m, _ := regexp.MatchString(`^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$`, user.Email); !m {
return errors.New("Email with illegal format.")
}
if exist, _ := UserExists(models.User{Email: user.Email}, "email"); exist {
return errors.New("Email already exists.")
}
}
if isIllegalLength(user.Realname, 0, 20) {
return errors.New("Realname with illegal length.")
}
if isContainIllegalChar(user.Realname, []string{",", "~", "#", "$", "%"}) {
return errors.New("Realname contains illegal characters.")
}
if isIllegalLength(user.Password, 0, 20) {
return errors.New("Password with illegal length.")
}
if isIllegalLength(user.Comment, -1, 30) {
return errors.New("Comment with illegal length.")
}
return nil
}
// UserExists returns whether a user exists according username or Email.
func UserExists(user models.User, target string) (bool, error) {

View File

@ -109,12 +109,13 @@ func ListUsers(query models.User) ([]models.User, error) {
}
// ToggleUserAdminRole gives a user admin role.
func ToggleUserAdminRole(u models.User) error {
func ToggleUserAdminRole(userID, hasAdmin int) error {
o := GetOrmer()
sql := `update user set sysadmin_flag =not sysadmin_flag where user_id = ?`
r, err := o.Raw(sql, u.UserID).Exec()
queryParams := make([]interface{}, 1)
sql := `update user set sysadmin_flag = ? where user_id = ?`
queryParams = append(queryParams, hasAdmin)
queryParams = append(queryParams, userID)
r, err := o.Raw(sql, queryParams).Exec()
if err != nil {
return err
}
@ -229,3 +230,13 @@ func DeleteUser(userID int) error {
_, err := o.Raw(`update user set deleted = 1 where user_id = ?`, userID).Exec()
return err
}
// ChangeUserProfile ...
func ChangeUserProfile(user models.User) error {
o := GetOrmer()
if _, err := o.Update(&user, "Email", "Realname", "Comment"); err != nil {
log.Errorf("update user failed, error: %v", err)
return err
}
return nil
}

View File

@ -353,7 +353,24 @@ paths:
404:
description: Project ID does not exist.
500:
description: Unexpected internal errors.
description: Unexpected internal errors.
/statistics:
get:
summary: Get projects number and repositories number relevant to the user
description: |
This endpoint is aimed to statistic all of the projects number and repositories number relevant to the logined user, also the public projects number and repositories number. If the user is admin, he can also get total projects number and total repositories number.
tags:
- Products
responses:
200:
description: Get the projects number and repositories number relevant to the user successfully.
schema:
$ref: '#/definitions/StatisticMap'
401:
description: User need to log in first.
500:
description: Unexpected internal errors.
/users:
get:
summary: Get registered users of Harbor.
@ -407,10 +424,9 @@ paths:
description: Unexpected internal errors.
/users/{user_id}:
put:
summary: Update a registered user to change to be an administrator of Harbor.
summary: Update a registered user to change his profile.
description: |
This endpoint let a registered user change to be an administrator
of Harbor.
This endpoint let a registered user change his profile.
parameters:
- name: user_id
in: path
@ -418,6 +434,12 @@ paths:
format: int32
required: true
description: Registered user ID
- name: profile
in: body
description: Only email, realname and comment can be modified.
required: true
schema:
$ref: '#/definitions/User'
tags:
- Products
responses:
@ -490,7 +512,35 @@ paths:
403:
description: Guests can only change their own account.
500:
description: Unexpected internal errors.
description: Unexpected internal errors.
/users/{user_id}/sysadmin:
put:
summary: Update a registered user to change to be an administrator of Harbor.
description: |
This endpoint let a registered user change to be an administrator
of Harbor.
parameters:
- name: user_id
in: path
type: integer
format: int32
required: true
description: Registered user ID
tags:
- Products
responses:
200:
description: Updated user's admin role successfully.
400:
description: Invalid user ID.
401:
description: User need to log in first.
403:
description: User does not have permission of admin role.
404:
description: User ID does not exist.
500:
description: Unexpected internal errors.
/repositories:
get:
summary: Get repositories accompany with relevant project and repo name.
@ -597,6 +647,70 @@ paths:
description: Retrieved manifests from a relevant repository successfully.
500:
description: Unexpected internal errors.
/repositories/top:
get:
summary: Get public repositories which are accessed most.
description: |
This endpoint aims to let users see the most popular public repositories
parameters:
- name: count
in: query
type: integer
format: int32
required: false
description: The number of the requested public repositories, default is 10 if not provided.
tags:
- Products
responses:
200:
description: Retrieved top repositories successfully.
schema:
type: array
items:
$ref: '#/definitions/TopRepo'
400:
description: Bad request because of invalid count.
500:
description: Unexpected internal errors.
/logs:
get:
summary: Get recent logs of the projects which the user is a member of
description: |
This endpoint let user see the recent operation logs of the projects which he is member of
parameters:
- name: lines
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
in: query
type: integer
format: int64
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
tags:
- Products
responses:
200:
description: Get the required logs successfully.
schema:
type: array
items:
$ref: '#/definitions/AccessLog'
400:
description: Bad request because of invalid parameter of lines or start_time or end_time.
401:
description: User need to login first.
500:
description: Unexpected internal errors.
definitions:
Search:
type: object
@ -744,3 +858,45 @@ definitions:
user_name:
type: string
description: Username relevant to a project role member.
TopRepo:
type: object
properties:
repo_name:
type: string
description: The name of the repo
access_count:
type: integer
format: int
description: The access count of the repo
StatisticMap:
type: object
properties:
my_project_count:
type: integer
format: int32
description: The count of the projects which the user is a member of.
my_repo_count:
type: integer
format: int32
description: The count of the repositories belonging to the projects which the user is a member of.
public_project_count:
type: integer
format: int32
description: The count of the public projects.
public_repo_count:
type: integer
format: int32
description: The count of the public repositories belonging to the public projects which the user is a member of.
total_project_count:
type: integer
format: int32
description: The count of the total projects, only be seen when the is admin.
total_repo_count:
type: integer
format: int32
description: The count of the total repositories, only be seen when the user is admin.

View File

@ -21,7 +21,7 @@ import (
// AccessLog holds information about logs which are used to record the actions that user take to the resourses.
type AccessLog struct {
LogID int `orm:"column(log_id)" json:"LogId"`
LogID int `orm:"pk;column(log_id)" json:"LogId"`
UserID int `orm:"column(user_id)" json:"UserId"`
ProjectID int64 `orm:"column(project_id)" json:"ProjectId"`
RepoName string `orm:"column(repo_name)"`

View File

@ -7,5 +7,9 @@ import (
func init() {
orm.RegisterModel(new(RepTarget),
new(RepPolicy),
new(RepJob))
new(RepJob),
new(User),
new(Project),
new(Role),
new(AccessLog))
}

View File

@ -21,7 +21,7 @@ import (
// Project holds the details of a project.
type Project struct {
ProjectID int64 `orm:"column(project_id)" json:"ProjectId"`
ProjectID int64 `orm:"pk;column(project_id)" json:"ProjectId"`
OwnerID int `orm:"column(owner_id)" json:"OwnerId"`
Name string `orm:"column(name)"`
CreationTime time.Time `orm:"column(creation_time)"`

View File

@ -26,7 +26,7 @@ const (
// Role holds the details of a role.
type Role struct {
RoleID int `orm:"column(role_id)" json:"role_id"`
RoleID int `orm:"pk;column(role_id)" json:"role_id"`
RoleCode string `orm:"column(role_code)" json:"role_code"`
Name string `orm:"column(name)" json:"role_name"`

22
models/toprepo.go Normal file
View File

@ -0,0 +1,22 @@
/*
Copyright (c) 2016 VMware, Inc. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package models
// TopRepo holds information about repository that accessed most
type TopRepo struct {
RepoName string `json:"name"`
AccessCount int64 `json:"count"`
}

View File

@ -21,7 +21,7 @@ import (
// User holds the details of a user.
type User struct {
UserID int `orm:"column(user_id)" json:"UserId"`
UserID int `orm:"pk;column(user_id)" json:"UserId"`
Username string `orm:"column(username)" json:"username"`
Email string `orm:"column(email)" json:"email"`
Password string `orm:"column(password)" json:"password"`
@ -29,11 +29,11 @@ type User struct {
Comment string `orm:"column(comment)" json:"comment"`
Deleted int `orm:"column(deleted)"`
Rolename string
RoleID int `json:"RoleId"`
RoleList []Role
HasAdminRole int `orm:"column(sysadmin_flag)"`
ResetUUID string `orm:"column(reset_uuid)" json:"ResetUuid"`
Salt string `orm:"column(salt)"`
RoleID int `json:"RoleId"`
RoleList []*Role `orm:"rel(m2m)"`
HasAdminRole int `orm:"column(sysadmin_flag)"`
ResetUUID string `orm:"column(reset_uuid)" json:"ResetUuid"`
Salt string `orm:"column(salt)"`
CreationTime time.Time `orm:"creation_time" json:"creation_time"`
UpdateTime time.Time `orm:"update_time" json:"update_time"`

View File

@ -55,6 +55,7 @@ func initRouters() {
beego.Router("/api/projects/:pid([0-9]+)/members/?:mid", &api.ProjectMemberAPI{})
beego.Router("/api/projects/", &api.ProjectAPI{}, "get:List")
beego.Router("/api/projects/?:id", &api.ProjectAPI{})
beego.Router("/api/projects/:id/publicity", &api.ProjectAPI{}, "put:ToggleProjectPublic")
beego.Router("/api/statistics", &api.StatisticAPI{})
beego.Router("/api/projects/:id([0-9]+)/logs/filter", &api.ProjectAPI{}, "post:FilterAccessLog")
beego.Router("/api/users/?:id", &api.UserAPI{})
@ -68,6 +69,9 @@ func initRouters() {
beego.Router("/api/policies/replication/:id([0-9]+)/enablement", &api.RepPolicyAPI{}, "put:UpdateEnablement")
beego.Router("/api/targets/?:id([0-9]+)", &api.TargetAPI{})
beego.Router("/api/targets/ping", &api.TargetAPI{}, "post:Ping")
beego.Router("/api/users/:id/sysadmin", &api.UserAPI{}, "put:ToggleUserAdminRole")
beego.Router("/api/repositories/top", &api.RepositoryAPI{}, "get:GetTopRepos")
beego.Router("api/logs", &api.LogAPI{})
//external service that hosted on harbor process:
beego.Router("/service/notifications", &service.NotificationHandler{})