mirror of
https://github.com/goharbor/harbor.git
synced 2025-02-10 17:01:27 +01:00
modify merge conflicts
This commit is contained in:
commit
eb62beba31
@ -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
86
api/log.go
Normal 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()
|
||||
}
|
@ -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
|
||||
}
|
||||
|
@ -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()
|
||||
}
|
||||
|
147
api/user.go
147
api/user.go
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
23
dao/base.go
23
dao/base.go
@ -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()
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
|
@ -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) {
|
||||
|
||||
|
21
dao/user.go
21
dao/user.go
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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)"`
|
||||
|
@ -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))
|
||||
}
|
||||
|
@ -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)"`
|
||||
|
@ -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"`
|
||||
|
||||
|
@ -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"`
|
||||
|
@ -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.
|
||||
|
@ -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.
|
||||
|
89
static/i18n/locale_ja-JP.ini
Normal file
89
static/i18n/locale_ja-JP.ini
Normal 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 サビース
|
@ -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": "Откл."
|
||||
}
|
||||
|
@ -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 может использоваться как в среде разработки так и в продуктивной среде.
|
||||
|
@ -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服务,提高生产效率和安全度,既可应用于生产环境,也可以在开发环境中使用。
|
||||
|
@ -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";
|
||||
|
@ -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{})
|
||||
|
@ -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>
|
||||
|
Loading…
Reference in New Issue
Block a user