modify merge conflicts

This commit is contained in:
wemeya 2016-06-02 17:57:39 +08:00
commit eb62beba31
28 changed files with 661 additions and 122 deletions

View File

@ -2,8 +2,8 @@ appname = registry
runmode = dev
[lang]
types = en-US|zh-CN|de-DE|ru-RU
names = en-US|zh-CN|de-DE|ru-RU
types = en-US|zh-CN|de-DE|ru-RU|ja-JP
names = en-US|zh-CN|de-DE|ru-RU|ja-JP
[dev]
httpport = 80

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() {
// ToggleProjectPulic handles request PUT /api/projects/:id/publicity
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,40 @@ 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)
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 +185,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 +238,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 +279,79 @@ 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

@ -69,7 +69,7 @@ func (c *CommonController) Login() {
// SwitchLanguage handles UI request to switch between different languages and re-render template based on language.
func (c *CommonController) SwitchLanguage() {
lang := c.GetString("lang")
if lang == "en-US" || lang == "zh-CN" || lang == "de-DE" || lang == "ru-RU" {
if lang == "en-US" || lang == "zh-CN" || lang == "de-DE" || lang == "ru-RU" || lang == "ja-JP" {
c.SetSession("lang", lang)
c.Data["Lang"] = lang
}

View File

@ -17,7 +17,7 @@ package dao
import (
"strings"
"github.com/astaxie/beego/orm"
"github.com/vmware/harbor/models"
"github.com/vmware/harbor/utils/log"
)
@ -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) ([]orm.ParamsList, error) {
o := GetOrmer()
sql := "select log_id, access_log.user_id, access_log.project_id, repo_name, repo_tag, GUID, operation, op_time, 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 = 'push' or access_log.operation = 'pull') group by repo_name order by access_count desc limit ? "
queryParam := make([]interface{}, 1)
queryParam = append(queryParam, countNum)
var lists []orm.ParamsList
_, err := o.Raw(sql, queryParam).ValuesList(&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

@ -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

@ -119,8 +119,8 @@ paths:
description: Project name already exists.
500:
description: Unexpected internal errors.
/projects/{project_id}:
put:
/projects/{project_id}/publicity:
post:
summary: Update properties for a selected project.
description: |
This endpoint is aimed to toggle a project publicity status.
@ -353,7 +353,34 @@ paths:
404:
description: Project ID does not exist.
500:
description: Unexpected internal errors.
description: Unexpected internal errors.
/statistics:
get:
summary: Get projects and repositories
description: |
This endpoint is aimed to list all of the projects and repositories relevant with the loggined user, also the public projects and repositories
parameters:
- null
tags:
-Products
reponses:
200:
description: Get the projects and repositories relevant with the user successfully.
schema:
type: map
items:
- my_project_count----integer
- my_repo_count----integer
- public_project_count----integer
- public_repo_count----integer
- total_project_count----integer (if user is admin)
- total_repo_count----integer (if user is admin)
400:
description: Invalid user ID.
401:
description: User need to log in first.
500:
description: Unexpected internal errors.
/users:
get:
summary: Get registered users of Harbor.
@ -407,10 +434,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,11 +444,29 @@ paths:
format: int32
required: true
description: Registered user ID
- name: email
in: body
type: string
format: string
required: false
description: Email of the user
- name: realname
in: body
type: string
format: string
required: true
description: Realname of the user
- name: comment
in: body
type: string
format: string
required: false
description: Comment of the user
tags:
- Products
responses:
200:
description: Updated user's admin role successfully.
description: Updated user's profile successfully.
400:
description: Invalid user ID.
401:
@ -490,7 +534,35 @@ paths:
403:
description: Guests can only change their own account.
500:
description: Unexpected internal errors.
description: Unexpected internal errors.
/users/{user_id}/sysadmin:
post:
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 +669,43 @@ paths:
description: Retrieved manifests from a relevant repository successfully.
500:
description: Unexpected internal errors.
/logs:
get:
summary: Get recent logs relevant to the user in Harbor
description: |
This endpoint let user see his recent operation logs in Harbor
parameters:
- name: lines
in: query
type: integer
format: int32
required: false
description: The number of logs to be shown
- name: start_time
in: query
type: string
format: date
required: false
description: The start time of the logs to be shown
- name: end_time
in: query
type: string
format: date
required: false
description: The end time of the logs to be shown
tags:
- Products
responses:
200:
description: Get the required logs successfully.
schema:
type: array
items:
$ref: '#/definitions/AccessLog'
401:
description: Invalid user ID.
500:
description: Unexpected internal errors.
definitions:
Search:
type: object

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"`

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

@ -75,6 +75,7 @@ language_en-US = English
language_zh-CN = 中文
language_de-DE = Deutsch
language_ru-RU = Русский
language_ja-JP = 日本語
copyright = Copyright
all_rights_reserved = Alle Rechte vorbehalten.
index_desc = Project Harbor ist ein zuverlässiger Enterprise-Class Registry Server. Unternehmen können ihren eigenen Registry Server aufsetzen um die Produktivität und Sicherheit zu erhöhen. Project Harbor kann für Entwicklungs- wie auch Produktiv-Umgebungen genutzt werden.

View File

@ -76,6 +76,7 @@ language_en-US = English
language_zh-CN = 中文
language_de-DE = Deutsch
language_ru-RU = Русский
language_ja-JP = 日本語
copyright = Copyright
all_rights_reserved = All rights reserved.
index_desc = Project Harbor is to build an enterprise-class, reliable registry server. Enterprises can set up a private registry server in their own environment to improve productivity as well as security. Project Harbor can be used in both development and production environment.

View File

@ -0,0 +1,89 @@
page_title_index = Harbor
page_title_sign_in = ログイン - Harbor
page_title_project = プロジェクト - Harbor
page_title_item_details = 詳しい - Harbor
page_title_registration = 登録 - Harbor
page_title_add_user = ユーザを追加 - Harbor
page_title_forgot_password = パスワードを忘れました - Harbor
title_forgot_password = パスワードを忘れました
page_title_reset_password = パスワードをリセット - Harbor
title_reset_password = パスワードをリセット
page_title_change_password = パスワードを変更 - Harbor
title_change_password = パスワードを変更
page_title_search = サーチ - Harbor
sign_in = ログイン
sign_up = 登録
add_user = ユーザを追加
log_out = ログアウト
search_placeholder = プロジェクト名またはイメージ名
change_password = パスワードを変更
username_email = ユーザ名/メールアドレス
password = パスワード
forgot_password = パスワードを忘れました
welcome = ようこそ
my_projects = マイプロジェクト
public_projects = パブリックプロジェクト
admin_options = 管理者
project_name = プロジェクト名
creation_time = 作成日時
publicity = パブリック
add_project = プロジェクトを追加
check_for_publicity = パブリックプロジェクト
button_save = 保存する
button_cancel = 取り消しする
button_submit = 送信する
username = ユーザ名
email = メールアドレス
system_admin = システム管理者
dlg_button_ok = OK
dlg_button_cancel = 取り消し
registration = 登録
username_description = ログイン際に使うユーザ名を入力してください。
email_description = メールアドレスはパスワードをリセットする際に使われます。
full_name = フルネーム
full_name_description = フルネームを入力してください。
password_description = パスワード7英数字以上で、少なくとも 1小文字、 1大文字と 1数字でなければなりません。
confirm_password = パスワードを確認する
note_to_the_admin = メモ
old_password = 現在のパスワード
new_password = 新しいパスワード
forgot_password_description = ぱプロジェクトをリセットするメールはこのアドレスに送信します。
projects = プロジェクト
repositories = リポジトリ
search = サーチ
home = ホーム
project = プロジェクト
owner = オーナー
repo = リポジトリ
user = ユーザ
logs = ログ
repo_name = リポジトリ名
repo_tag = リポジトリタグ
add_members = メンバーを追加
operation = 操作
advance = さらに絞りこみで検索
all = 全部
others = その他
start_date = 開始日
end_date = 終了日
timestamp = タイムスタンプ
role = 役割
reset_email_hint = このリンクをクリックしてパスワードリセットの処理を続けてください
reset_email_subject = パスワードをリセットします
language = 日本語
language_en-US = English
language_zh-CN = 中文
language_de-DE = Deutsch
language_ru-RU = Русский
language_ja-JP = 日本語
copyright = コピーライト
all_rights_reserved = 無断複写・転載を禁じます
index_desc = Harborは、信頼性の高いエンタープライズクラスのRegistryサーバです。タープライズユーザはHarborを利用し、プライベートのRegistryサビースを構築し、生産性および安全性を向上させる事ができます。開発環境はもちろん、生産環境にも使用する事ができます。
index_desc_0 = 主な利点:
index_desc_1 = 1. セキュリティ: 知的財産権を組織内で確保する。
index_desc_2 = 2. 効率: プライベートなので、パブリックRegistryサビースにネットワーク通信が減らす。
index_desc_3 = 3. アクセス制御: ロールベースアクセス制御機能を実装し、更に既存のユーザ管理システムAD/LDAPと統合することも可能。
index_desc_4 = 4. 監査: すべてRegistryサビースへの操作が記録され、検査にに利用できる。
index_desc_5 = 5. 管理UI: 使いやすい管理UIが搭載する。
index_title = エンタープライズ Registry サビース

View File

@ -16,378 +16,441 @@ var global_messages = {
"username_is_required" : {
"en-US": "Username is required.",
"zh-CN": "用户名为必填项。",
"ja-JP": "ユーザ名は必須項目です。",
"de-DE": "Benutzername erforderlich.",
"ru-RU": "Требуется ввести имя пользователя."
},
"username_has_been_taken" : {
"en-US": "Username has been taken.",
"zh-CN": "用户名已被占用。",
"ja-JP": "ユーザ名はすでに登録されました。",
"de-DE": "Benutzername bereits vergeben.",
"ru-RU": "Имя пользователя уже используется."
},
"username_is_too_long" : {
"en-US": "Username is too long. (maximum 20 characters)",
"zh-CN": "用户名长度超出限制。最长为20个字符",
"ja-JP": "ユーザ名が長すぎです。20文字まで",
"de-DE": "Benutzername ist zu lang. (maximal 20 Zeichen)",
"ru-RU": "Имя пользователя слишком длинное. (максимум 20 символов)"
},
"username_contains_illegal_chars": {
"en-US": "Username contains illegal character(s).",
"zh-CN": "用户名包含不合法的字符。",
"ja-JP": "ユーザ名に使えない文字が入っています。",
"de-DE": "Benutzername enthält ungültige Zeichen.",
"ru-RU": "Имя пользователя содержит недопустимые символы."
},
"email_is_required" : {
"en-US": "Email is required.",
"zh-CN": "邮箱为必填项。",
"ja-JP": "メールアドレスが必須です。",
"de-DE": "E-Mail Adresse erforderlich.",
"ru-RU": "Требуется ввести E-mail адрес."
},
"email_contains_illegal_chars" : {
"en-US": "Email contains illegal character(s).",
"zh-CN": "邮箱包含不合法的字符。",
"ja-JP": "メールアドレスに使えない文字が入っています。",
"de-DE": "E-Mail Adresse enthält ungültige Zeichen.",
"ru-RU": "E-mail адрес содержит недопеустимые символы."
},
"email_has_been_taken" : {
"en-US": "Email has been taken.",
"zh-CN": "邮箱已被占用。",
"ja-JP": "メールアドレスがすでに使われました。",
"de-DE": "E-Mail Adresse wird bereits verwendet.",
"ru-RU": "Такой E-mail адрес уже используется."
},
"email_content_illegal" : {
"en-US": "Email format is illegal.",
"zh-CN": "邮箱格式不合法。",
"ja-JP": "メールアドレスフォーマットエラー。",
"de-DE": "Format der E-Mail Adresse ist ungültig.",
"ru-RU": "Недопустимый формат E-mail адреса."
},
"email_does_not_exist" : {
"en-US": "Email does not exist.",
"zh-CN": "邮箱不存在。",
"ja-JP": "メールアドレスが存在しません。",
"de-DE": "E-Mail Adresse existiert nicht.",
"ru-RU": "E-mail адрес не существует."
},
"realname_is_required" : {
"en-US": "Full name is required.",
"zh-CN": "全名为必填项。",
"ja-JP": "フルネームが必須です。",
"de-DE": "Vollständiger Name erforderlich.",
"ru-RU": "Требуется ввести полное имя."
},
"realname_is_too_long" : {
"en-US": "Full name is too long. (maximum 20 characters)",
"zh-CN": "全名长度超出限制。最长为20个字符",
"ja-JP": "フルネームは長すぎです。20文字まで",
"de-DE": "Vollständiger Name zu lang. (maximal 20 Zeichen)",
"ru-RU": "Полное имя слишком длинное. (максимум 20 символов)"
},
"realname_contains_illegal_chars" : {
"en-US": "Full name contains illegal character(s).",
"zh-CN": "全名包含不合法的字符。",
"ja-JP": "フルネームに使えない文字が入っています。",
"de-DE": "Vollständiger Name enthält ungültige Zeichen.",
"ru-RU": "Полное имя содержит недопустимые символы."
},
"password_is_required" : {
"en-US": "Password is required.",
"zh-CN": "密码为必填项。",
"ja-JP": "パスワードは必須です。",
"de-DE": "Passwort erforderlich.",
"ru-RU": "Требуется ввести пароль."
},
"password_is_invalid" : {
"en-US": "Password is invalid. At least 7 characters with 1 lowercase letter, 1 capital letter and 1 numeric character.",
"zh-CN": "密码无效。至少输入 7个字符且包含 1个小写字母1个大写字母和 1个数字。",
"ja-JP": "無効なパスワードです。7英数字以上で、 少なくとも1小文字、1大文字と1数字となります。",
"de-DE": "Passwort ungültig. Mindestens sieben Zeichen bestehend aus einem Kleinbuchstaben, einem Großbuchstaben und einer Zahl",
"ru-RU": "Такой пароль недопустим. Парольл должен содержать Минимум 7 символов, в которых будет присутствовать по меньшей мере 1 буква нижнего регистра, 1 буква верхнего регистра и 1 цифра"
},
"password_is_too_long" : {
"en-US": "Password is too long. (maximum 20 characters)",
"zh-CN": "密码长度超出限制。最长为20个字符",
"ja-JP": "パスワードは長すぎです。20文字まで",
"de-DE": "Passwort zu lang. (maximal 20 Zeichen)",
"ru-RU": "Пароль слишком длинный (максимум 20 символов)"
},
"password_does_not_match" : {
"en-US": "Passwords do not match.",
"zh-CN": "两次密码输入不一致。",
"ja-JP": "確認のパスワードが正しくありません。",
"de-DE": "Passwörter stimmen nicht überein.",
"ru-RU": "Пароли не совпадают."
},
"comment_is_too_long" : {
"en-US": "Comment is too long. (maximum 20 characters)",
"zh-CN": "备注长度超出限制。最长为20个字符",
"ja-JP": "コメントは長すぎです。20文字まで",
"de-DE": "Kommentar zu lang. (maximal 20 Zeichen)",
"ru-RU": "Комментарий слишком длинный. (максимум 20 символов)"
},
"comment_contains_illegal_chars" : {
"en-US": "Comment contains illegal character(s).",
"zh-CN": "备注包含不合法的字符。",
"ja-JP": "コメントに使えない文字が入っています。",
"de-DE": "Kommentar enthält ungültige Zeichen.",
"ru-RU": "Комментарий содержит недопустимые символы."
},
"project_name_is_required" : {
"en-US": "Project name is required.",
"zh-CN": "项目名称为必填项。",
"ja-JP": "プロジェクト名は必須です。",
"de-DE": "Projektname erforderlich.",
"ru-RU": "Необходимо ввести название Проекта."
},
"project_name_is_too_short" : {
"en-US": "Project name is too short. (minimum 4 characters)",
"zh-CN": "项目名称至少要求 4个字符。",
"ja-JP": "プロジェクト名は4文字以上です。",
"de-DE": "Projektname zu kurz. (mindestens 4 Zeichen)",
"ru-RU": "Название проекта слишком короткое. (миниму 4 символа)"
},
"project_name_is_too_long" : {
"en-US": "Project name is too long. (maximum 30 characters)",
"zh-CN": "项目名称长度超出限制。最长为30个字符",
"ja-JP": "プロジェクト名は長すぎです。30文字まで",
"de-DE": "Projektname zu lang. (maximal 30 Zeichen)",
"ru-RU": "Название проекта слишком длинное (максимум 30 символов)"
},
"project_name_contains_illegal_chars" : {
"en-US": "Project name contains illegal character(s).",
"zh-CN": "项目名称包含不合法的字符。",
"ja-JP": "プロジェクト名に使えない文字が入っています。",
"de-DE": "Projektname enthält ungültige Zeichen.",
"ru-RU": "Название проекта содержит недопустимые символы."
},
"project_exists" : {
"en-US": "Project exists.",
"zh-CN": "项目已存在。",
"ja-JP": "プロジェクトはすでに存在しました。",
"de-DE": "Projekt existiert bereits.",
"ru-RU": "Такой проект уже существует."
},
"delete_user" : {
"en-US": "Delete User",
"zh-CN": "删除用户",
"ja-JP": "ユーザを削除",
"de-DE": "Benutzer löschen",
"ru-RU": "Удалить пользователя"
},
"are_you_sure_to_delete_user" : {
"en-US": "Are you sure to delete ",
"zh-CN": "确认要删除用户 ",
"ja-JP": "ユーザを削除でよろしでしょうか ",
"de-DE": "Sind Sie sich sicher, dass Sie folgenden Benutzer löschen möchten: ",
"ru-RU": "Вы уверены что хотите удалить пользователя? "
},
"input_your_username_and_password" : {
"en-US": "Please input your username and password.",
"zh-CN": "请输入用户名和密码。",
"ja-JP": "ユーザ名とパスワードを入力してください。",
"de-DE": "Bitte geben Sie ihr Benutzername und Passwort ein.",
"ru-RU": "Введите имя пользователя и пароль."
},
"check_your_username_or_password" : {
"en-US": "Please check your username or password.",
"zh-CN": "请输入正确的用户名或密码。",
"ja-JP": "正しいユーザ名とパスワードを入力してください。",
"de-DE": "Bitte überprüfen Sie ihren Benutzernamen und Passwort.",
"ru-RU": "Проверьте свои имя пользователя и пароль."
},
"title_login_failed" : {
"en-US": "Login Failed",
"zh-CN": "登录失败",
"ja-JP": "ログインに失敗しました。",
"de-DE": "Anmeldung fehlgeschlagen",
"ru-RU": "Ошибка входа"
},
"title_change_password" : {
"en-US": "Change Password",
"zh-CN": "修改密码",
"ja-JP": "パスワードを変更します。",
"de-DE": "Passwort ändern",
"ru-RU": "Сменить пароль"
},
"change_password_successfully" : {
"en-US": "Password changed successfully.",
"zh-CN": "密码已修改。",
"ja-JP": "パスワードを変更しました。",
"de-DE": "Passwort erfolgreich geändert.",
"ru-RU": "Пароль успешно изменен."
},
"title_forgot_password" : {
"en-US": "Forgot Password",
"zh-CN": "忘记密码",
"ja-JP": "パスワードをリセットします。",
"de-DE": "Passwort vergessen",
"ru-RU": "Забыли пароль?"
},
"email_has_been_sent" : {
"en-US": "Email for resetting password has been sent.",
"zh-CN": "重置密码邮件已发送。",
"ja-JP": "パスワードをリセットするメールを送信しました。",
"de-DE": "Eine E-Mail mit einem Wiederherstellungslink wurde an Sie gesendet.",
"ru-RU": "На ваш E-mail было выслано письмо с инструкциями по сбросу пароля."
},
"send_email_failed" : {
"en-US": "Failed to send Email for resetting password.",
"zh-CN": "重置密码邮件发送失败。",
"ja-JP": "パスワードをリセットするメールを送信する際エラーが出ました",
"de-DE": "Fehler beim Senden der Wiederherstellungs-E-Mail.",
"ru-RU": "Ошибка отправки сообщения."
},
"please_login_first" : {
"en-US": "Please login first.",
"zh-CN": "请先登录。",
"ja-JP": "この先にログインが必要です。",
"de-DE": "Bitte melden Sie sich zuerst an.",
"ru-RU": "Сначала выполните вход в систему."
},
"old_password_is_not_correct" : {
"en-US": "Old password is not correct.",
"zh-CN": "原密码输入不正确。",
"ja-JP": "現在のパスワードが正しく入力されていません。",
"de-DE": "Altes Passwort ist nicht korrekt.",
"ru-RU": "Старый пароль введен неверно."
},
"please_input_new_password" : {
"en-US": "Please input new password.",
"zh-CN": "请输入新密码。",
"ja-JP": "あたらしいパスワードを入力してください",
"de-DE": "Bitte geben Sie ihr neues Passwort ein.",
"ru-RU": "Пожалуйста, введите новый пароль."
},
"invalid_reset_url": {
"en-US": "Invalid URL for resetting password.",
"zh-CN": "无效密码重置链接。",
"ja-JP": "無効なパスワードをリセットするリンク。",
"de-DE": "Ungültige URL zum Passwort wiederherstellen.",
"ru-RU": "Неверный URL для сброса пароля."
},
"reset_password_successfully" : {
"en-US": "Reset password successfully.",
"zh-CN": "密码重置成功。",
"ja-JP": "パスワードをリセットしました。",
"de-DE": "Passwort erfolgreich wiederhergestellt.",
"ru-RU": "Пароль успешно сброшен."
},
"internal_error": {
"en-US": "Internal error.",
"zh-CN": "内部错误,请联系系统管理员。",
"ja-JP": "エラーが出ました、管理者に連絡してください。",
"de-DE": "Interner Fehler.",
"ru-RU": "Внутренняя ошибка."
},
"title_reset_password" : {
"en-US": "Reset Password",
"zh-CN": "重置密码",
"ja-JP": "パスワードをリセットする",
"de-DE": "Passwort zurücksetzen",
"ru-RU": "Сбросить пароль"
},
"title_sign_up" : {
"en-US": "Sign Up",
"zh-CN": "注册",
"ja-JP": "登録",
"de-DE": "Registrieren",
"ru-RU": "Регистрация"
},
"title_add_user": {
"en-US": "Add User",
"zh-CN": "新增用户",
"ja-JP": "ユーザを追加",
"de-DE": "Benutzer hinzufügen",
"ru-RU": "Добавить пользователя"
},
"registered_successfully": {
"en-US": "Signed up successfully.",
"zh-CN": "注册成功。",
"ja-JP": "登録しました。",
"de-DE": "Erfolgreich registriert.",
"ru-RU": "Регистрация прошла успешно."
},
"registered_failed" : {
"en-US": "Failed to sign up.",
"zh-CN": "注册失败。",
"ja-JP": "登録でませんでした。",
"de-DE": "Registrierung fehlgeschlagen.",
"ru-RU": "Ошибка регистрации."
},
"added_user_successfully": {
"en-US": "Added user successfully.",
"zh-CN": "新增用户成功。",
"ja-JP": "ユーザを追加しました。",
"de-DE": "Benutzer erfolgreich erstellt.",
"ru-RU": "Пользователь успешно добавлен."
},
"added_user_failed": {
"en-US": "Adding user failed.",
"zh-CN": "新增用户失败。",
"ja-JP": "ユーザを追加できませんでした。",
"de-DE": "Benutzer erstellen fehlgeschlagen.",
"ru-RU": "Ошибка добавления пользователя."
},
"projects": {
"en-US": "Projects",
"zh-CN": "项目",
"ja-JP": "プロジェクト",
"de-DE": "Projekte",
"ru-RU": "Проекты"
},
"repositories" : {
"en-US": "Repositories",
"zh-CN": "镜像仓库",
"ja-JP": "リポジトリ",
"de-DE": "Repositories",
"ru-RU": "Репозитории"
},
"no_repo_exists" : {
"en-US": "No repositories found, please use 'docker push' to upload images.",
"zh-CN": "未发现镜像请用docker push命令上传镜像。",
"ja-JP": "イメージが見つかりませんでした。docker pushを利用しイメージをアップロードしてください。",
"de-DE": "Keine Repositories gefunden, bitte benutzen Sie 'docker push' um ein Image hochzuladen.",
"ru-RU": "Репозитории не найдены, используйте команду 'docker push' для добавления образов."
},
"tag" : {
"en-US": "Tag",
"zh-CN": "标签",
"ja-JP": "タグ",
"de-DE": "Tag",
"ru-RU": "Метка"
},
"pull_command": {
"en-US": "Pull Command",
"zh-CN": "Pull 命令",
"ja-JP": "Pull コマンド",
"de-DE": "Pull Befehl",
"ru-RU": "Команда для скачивания образа"
},
"image_details" : {
"en-US": "Image Details",
"zh-CN": "镜像详细信息",
"ja-JP": "イメージ詳細",
"de-DE": "Image Details",
"ru-RU": "Информация об образе"
},
"add_members" : {
"en-US": "Add Member",
"zh-CN": "添加成员",
"ja-JP": "メンバーを追加する",
"de-DE": "Mitglied hinzufügen",
"ru-RU": "Добавить Участника"
},
"edit_members" : {
"en-US": "Edit Members",
"zh-CN": "编辑成员",
"ja-JP": "メンバーを編集する",
"de-DE": "Mitglieder bearbeiten",
"ru-RU": "Редактировать Участников"
},
"add_member_failed" : {
"en-US": "Adding Member Failed",
"zh-CN": "添加成员失败",
"ja-JP": "メンバーを追加できません出した",
"de-DE": "Mitglied hinzufügen fehlgeschlagen",
"ru-RU": "Ошибка при добавлении нового участника"
},
"please_input_username" : {
"en-US": "Please input a username.",
"zh-CN": "请输入用户名。",
"ja-JP": "ユーザ名を入力してください。",
"de-DE": "Bitte geben Sie einen Benutzernamen ein.",
"ru-RU": "Пожалуйста, введите имя пользователя."
},
"please_assign_a_role_to_user" : {
"en-US": "Please assign a role to the user.",
"zh-CN": "请为用户分配角色。",
"ja-JP": "ユーザーに役割を割り当てるしてください。",
"de-DE": "Bitte weisen Sie dem Benutzer eine Rolle zu.",
"ru-RU": "Пожалуйста, назначьте роль пользователю."
},
"user_id_exists" : {
"en-US": "User is already a member.",
"zh-CN": "用户已经是成员。",
"ja-JP": "すでにメンバーに登録しました。",
"de-DE": "Benutzer ist bereits Mitglied.",
"ru-RU": "Пользователь уже является участником."
},
"user_id_does_not_exist" : {
"en-US": "User does not exist.",
"zh-CN": "不存在此用户。",
"ja-JP": "ユーザが見つかりませんでした。",
"de-DE": "Benutzer existiert nicht.",
"ru-RU": "Пользователя с таким именем не существует."
},
"insufficient_privileges" : {
"en-US": "Insufficient privileges.",
"zh-CN": "权限不足。",
"ja-JP": "権限エラー。",
"de-DE": "Unzureichende Berechtigungen.",
"ru-RU": "Недостаточно прав."
},
"operation_failed" : {
"en-US": "Operation Failed",
"zh-CN": "操作失败",
"ja-JP": "操作に失敗しました。",
"de-DE": "Befehl fehlgeschlagen",
"ru-RU": "Ошибка при выполнении данной операции"
},
"button_on" : {
"en-US": "On",
"zh-CN": "打开",
"ja-JP": "オン",
"de-DE": "An",
"ru-RU": "Вкл."
},
"button_off" : {
"en-US": "Off",
"zh-CN": "关闭",
"ja-JP": "オフ",
"de-DE": "Aus",
"ru-RU": "Откл."
}

View File

@ -76,6 +76,7 @@ language_en-US = English
language_zh-CN = 中文
language_de-DE = Deutsch
language_ru-RU = Русский
language_ja-JP = 日本語
copyright = Copyright
all_rights_reserved = Все права защищены.
index_desc = Проект Harbor представляет собой надежный сервер управления docker-образами корпоративного класса. Компании могут использовать данный сервер в своей инфарструктуе для повышения производительности и безопасности . Проект Harbor может использоваться как в среде разработки так и в продуктивной среде.

View File

@ -76,6 +76,7 @@ language_en-US = English
language_zh-CN = 中文
language_de-DE = Deutsch
language_ru-RU = Русский
language_ja-JP = 日本語
copyright = 版权所有
all_rights_reserved = 保留所有权利。
index_desc = Harbor是可靠的企业级Registry服务器。企业用户可使用Harbor搭建私有容器Registry服务提高生产效率和安全度既可应用于生产环境也可以在开发环境中使用。

View File

@ -70,7 +70,8 @@ var SUPPORT_LANGUAGES = {
"en-US": "English",
"zh-CN": "Chinese",
"de-DE": "German",
"ru-RU": "Russian"
"ru-RU": "Russian",
"ja-JP": "Japanese"
};
var DEFAULT_LANGUAGE = "en-US";

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{})

View File

@ -38,6 +38,7 @@
<li><a href="/language?lang=zh-CN">{{i18n .Lang "language_zh-CN"}}</a></li>
<li><a href="/language?lang=de-DE">{{i18n .Lang "language_de-DE"}}</a></li>
<li><a href="/language?lang=ru-RU">{{i18n .Lang "language_ru-RU"}}</a></li>
<li><a href="/language?lang=ja-JP">{{i18n .Lang "language_ja-JP"}}</a></li>
</ul>
</li>
</ul>