This commit is contained in:
Tan Jiang 2016-06-17 18:58:16 +08:00
commit bc5d71ee86
299 changed files with 822 additions and 4924 deletions

View File

@ -1,16 +1,16 @@
/*
Copyright (c) 2016 VMware, Inc. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Copyright (c) 2016 VMware, Inc. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package api
@ -55,17 +55,25 @@ func (ra *RepJobAPI) Prepare() {
}
// Get gets all the jobs according to the policy
func (ra *RepJobAPI) Get() {
policyID, err := ra.GetInt64("policy_id")
if err != nil {
log.Errorf("Failed to get policy id, error: %v", err)
ra.RenderError(http.StatusBadRequest, "Invalid policy id")
return
// List filters jobs according to the policy and repository
func (ra *RepJobAPI) List() {
var policyID int64
var repository string
var err error
policyIDStr := ra.GetString("policy_id")
if len(policyIDStr) != 0 {
policyID, err = strconv.ParseInt(policyIDStr, 10, 64)
if err != nil || policyID <= 0 {
ra.CustomAbort(http.StatusBadRequest, fmt.Sprintf("invalid policy ID: %s", policyIDStr))
}
}
jobs, err := dao.GetRepJobByPolicy(policyID)
repository = ra.GetString("repository")
jobs, err := dao.FilterRepJobs(repository, policyID)
if err != nil {
log.Errorf("Failed to query job from db, error: %v", err)
log.Errorf("failed to filter jobs according policy ID %d and repository %s: %v", policyID, repository, err)
ra.RenderError(http.StatusInternalServerError, "Failed to query job")
return
}

View File

@ -141,7 +141,7 @@ func (pa *RepPolicyAPI) Post() {
pa.Redirect(http.StatusCreated, strconv.FormatInt(pid, 10))
}
// Put modifies name and description of policy
// Put modifies name, description, target and enablement of policy
func (pa *RepPolicyAPI) Put() {
id := pa.GetIDFromURL()
originalPolicy, err := dao.GetRepPolicy(id)
@ -157,7 +157,6 @@ func (pa *RepPolicyAPI) Put() {
policy := &models.RepPolicy{}
pa.DecodeJSONReq(policy)
policy.ProjectID = originalPolicy.ProjectID
policy.TargetID = originalPolicy.TargetID
pa.Validate(policy)
if policy.Name != originalPolicy.Name {
@ -172,19 +171,77 @@ func (pa *RepPolicyAPI) Put() {
}
}
if policy.TargetID != originalPolicy.TargetID {
target, err := dao.GetRepTarget(policy.TargetID)
if err != nil {
log.Errorf("failed to get target %d: %v", policy.TargetID, err)
pa.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
}
if target == nil {
pa.CustomAbort(http.StatusBadRequest, fmt.Sprintf("target %d does not exist", policy.TargetID))
}
}
policy.ID = id
isTargetChanged := !(policy.TargetID == originalPolicy.TargetID)
isEnablementChanged := !(policy.Enabled == policy.Enabled)
var shouldStop, shouldTrigger bool
// if target and enablement are not changed, do nothing
if !isTargetChanged && !isEnablementChanged {
shouldStop = false
shouldTrigger = false
} else if !isTargetChanged && isEnablementChanged {
// target is not changed, but enablement is changed
if policy.Enabled == 0 {
shouldStop = true
shouldTrigger = false
} else {
shouldStop = false
shouldTrigger = true
}
} else if isTargetChanged && !isEnablementChanged {
// target is changed, but enablement is not changed
if policy.Enabled == 0 {
// enablement is 0, do nothing
shouldStop = false
shouldTrigger = false
} else {
// enablement is 1, so stop original target's jobs
// and trigger new target's jobs
shouldStop = true
shouldTrigger = true
}
} else {
// both target and enablement are changed
// enablement: 1 -> 0
if policy.Enabled == 0 {
shouldStop = true
shouldTrigger = false
} else {
shouldStop = false
shouldTrigger = true
}
}
if shouldStop {
if err := postReplicationAction(id, "stop"); err != nil {
log.Errorf("failed to stop replication of %d: %v", id, err)
pa.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
}
log.Infof("replication of %d has been stopped", id)
}
if err = dao.UpdateRepPolicy(policy); err != nil {
log.Errorf("failed to update policy %d: %v", id, err)
pa.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
}
if policy.Enabled == originalPolicy.Enabled {
return
}
//enablement has been modified
if policy.Enabled == 1 {
if shouldTrigger {
go func() {
if err := TriggerReplication(id, "", nil, models.RepOpTransfer); err != nil {
log.Errorf("failed to trigger replication of %d: %v", id, err)
@ -192,14 +249,6 @@ func (pa *RepPolicyAPI) Put() {
log.Infof("replication of %d triggered", id)
}
}()
} else {
go func() {
if err := postReplicationAction(id, "stop"); err != nil {
log.Errorf("failed to stop replication of %d: %v", id, err)
} else {
log.Infof("try to stop replication of %d", id)
}
}()
}
}

View File

@ -258,6 +258,16 @@ func (t *TargetAPI) Delete() {
t.CustomAbort(http.StatusNotFound, http.StatusText(http.StatusNotFound))
}
policies, err := dao.GetRepPolicyByTarget(id)
if err != nil {
log.Errorf("failed to get policies according target %d: %v", id, err)
t.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
}
if len(policies) > 0 {
t.CustomAbort(http.StatusBadRequest, "the target is used by policies, can not be deleted")
}
if err = dao.DeleteRepTarget(id); err != nil {
log.Errorf("failed to delete target %d: %v", id, err)
t.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))

View File

@ -292,7 +292,7 @@ func (ua *UserAPI) ChangePassword() {
}
}
// ToggleUserAdminRole handles PUT api/users/{}/toggleadmin
// ToggleUserAdminRole handles PUT api/users/{}/sysadmin
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)

View File

@ -1,6 +1,6 @@
package ng
package controllers
// AccountSettingController handles request to /ng/account_setting
// AccountSettingController handles request to /account_setting
type AccountSettingController struct {
BaseController
}

View File

@ -1,6 +1,6 @@
package ng
package controllers
// AdminOptionController handles requests to /ng/admin_option
// AdminOptionController handles requests to /admin_option
type AdminOptionController struct {
BaseController
}

View File

@ -1,47 +1,24 @@
/*
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 controllers
import (
"net/http"
"os"
"path/filepath"
"strings"
"github.com/astaxie/beego"
"github.com/beego/i18n"
"github.com/vmware/harbor/auth"
"github.com/vmware/harbor/dao"
"github.com/vmware/harbor/models"
"github.com/vmware/harbor/utils/log"
)
// CommonController handles request from UI that doesn't expect a page, such as /login /logout ...
type CommonController struct {
BaseController
}
// Render returns nil.
func (c *CommonController) Render() error {
return nil
}
// BaseController wraps common methods such as i18n support, forward, which can be leveraged by other UI render controllers.
type BaseController struct {
beego.Controller
i18n.Locale
SelfRegistration bool
IsLdapAdminUser bool
IsAdmin bool
AuthMode string
}
@ -52,6 +29,8 @@ type langType struct {
}
const (
viewPath = "sections"
prefixNg = ""
defaultLang = "en-US"
)
@ -98,6 +77,7 @@ func (b *BaseController) Prepare() {
b.Data["Lang"] = curLang.Lang
b.Data["CurLang"] = curLang.Name
b.Data["RestLangs"] = restLangs
b.Data["SupportLanguages"] = supportLanguages
authMode := strings.ToLower(os.Getenv("AUTH_MODE"))
if authMode == "" {
@ -106,50 +86,92 @@ func (b *BaseController) Prepare() {
b.AuthMode = authMode
b.Data["AuthMode"] = b.AuthMode
selfRegistration := strings.ToLower(os.Getenv("SELF_REGISTRATION"))
if selfRegistration == "on" {
b.SelfRegistration = true
}
sessionUserID := b.GetSession("userId")
if sessionUserID != nil {
b.Data["Username"] = b.GetSession("username")
b.Data["UserId"] = sessionUserID.(int)
if (sessionUserID == 1 && b.AuthMode == "ldap_auth") {
b.IsLdapAdminUser = true
}
var err error
b.IsAdmin, err = dao.IsAdminRole(sessionUserID.(int))
if err != nil {
log.Errorf("Error occurred in IsAdminRole:%v", err)
b.CustomAbort(http.StatusInternalServerError, "Internal error.")
}
}
b.Data["IsAdmin"] = b.IsAdmin
b.Data["SelfRegistration"] = b.SelfRegistration
b.Data["IsLdapAdminUser"] = b.IsLdapAdminUser
}
// ForwardTo setup layout and template for content for a page.
func (b *BaseController) ForwardTo(pageTitle string, pageName string) {
b.Layout = "segment/base-layout.tpl"
b.TplName = "segment/base-layout.tpl"
b.Data["PageTitle"] = b.Tr(pageTitle)
// Forward to setup layout and template for content for a page.
func (b *BaseController) Forward(title, templateName string) {
b.Layout = filepath.Join(prefixNg, "layout.htm")
b.TplName = filepath.Join(prefixNg, templateName)
b.Data["Title"] = title
b.LayoutSections = make(map[string]string)
b.LayoutSections["HeaderInc"] = "segment/header-include.tpl"
b.LayoutSections["HeaderContent"] = "segment/header-content.tpl"
b.LayoutSections["BodyContent"] = pageName + ".tpl"
b.LayoutSections["ModalDialog"] = "segment/modal-dialog.tpl"
b.LayoutSections["FootContent"] = "segment/foot-content.tpl"
b.LayoutSections["HeaderInclude"] = filepath.Join(prefixNg, viewPath, "header-include.htm")
b.LayoutSections["FooterInclude"] = filepath.Join(prefixNg, viewPath, "footer-include.htm")
b.LayoutSections["HeaderContent"] = filepath.Join(prefixNg, viewPath, "header-content.htm")
b.LayoutSections["FooterContent"] = filepath.Join(prefixNg, viewPath, "footer-content.htm")
}
var langTypes []*langType
// CommonController handles request from UI that doesn't expect a page, such as /SwitchLanguage /logout ...
type CommonController struct {
BaseController
}
// Render returns nil.
func (cc *CommonController) Render() error {
return nil
}
// Login handles login request from UI.
func (cc *CommonController) Login() {
principal := cc.GetString("principal")
password := cc.GetString("password")
user, err := auth.Login(models.AuthModel{
Principal: principal,
Password: password,
})
if err != nil {
log.Errorf("Error occurred in UserLogin: %v", err)
cc.CustomAbort(http.StatusUnauthorized, "")
}
if user == nil {
cc.CustomAbort(http.StatusUnauthorized, "")
}
cc.SetSession("userId", user.UserID)
cc.SetSession("username", user.Username)
}
// LogOut Habor UI
func (cc *CommonController) LogOut() {
cc.DestroySession()
}
// SwitchLanguage User can swith to prefered language
func (cc *CommonController) SwitchLanguage() {
lang := cc.GetString("lang")
if _, exist := supportLanguages[lang]; exist {
cc.SetSession("lang", lang)
cc.Data["Lang"] = lang
}
cc.Redirect(cc.Ctx.Request.Header.Get("Referer"), http.StatusFound)
}
// UserExists checks if user exists when user input value in sign in form.
func (cc *CommonController) UserExists() {
target := cc.GetString("target")
value := cc.GetString("value")
user := models.User{}
switch target {
case "username":
user.Username = value
case "email":
user.Email = value
}
exist, err := dao.UserExists(user, target)
if err != nil {
log.Errorf("Error occurred in UserExists: %v", err)
cc.CustomAbort(http.StatusInternalServerError, "Internal error.")
}
cc.Data["json"] = exist
cc.ServeJSON()
}
func init() {
//conf/app.conf -> os.Getenv("config_path")
@ -179,9 +201,4 @@ func init() {
supportLanguages[v] = t
}
for _, lang := range langs {
if err := i18n.SetMessage(lang, "static/i18n/"+"locale_"+lang+".ini"); err != nil {
log.Errorf("Fail to set message file: %s", err.Error())
}
}
}

View File

@ -1,6 +1,6 @@
package ng
package controllers
// DashboardController handles requests to /ng/dashboard
// DashboardController handles requests to /dashboard
type DashboardController struct {
BaseController
}

View File

@ -1,6 +1,6 @@
package ng
package controllers
// IndexController handles request to /ng
// IndexController handles request to /
type IndexController struct {
BaseController
}

View File

@ -1,102 +0,0 @@
/*
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 controllers
import (
"net/http"
"net/url"
"os"
"github.com/vmware/harbor/dao"
"github.com/vmware/harbor/utils/log"
)
// ItemDetailController handles requet to /registry/detail, which shows the detail of a project.
type ItemDetailController struct {
BaseController
}
// Get will check if user has permission to view a certain project, if not user will be redirected to signin or his homepage.
// If the check is passed it renders the project detail page.
func (idc *ItemDetailController) Get() {
projectID, _ := idc.GetInt64("project_id")
if projectID <= 0 {
log.Errorf("Invalid project id: %d", projectID)
idc.Redirect("/signIn", http.StatusFound)
return
}
project, err := dao.GetProjectByID(projectID)
if err != nil {
log.Errorf("Error occurred in GetProjectById: %v", err)
idc.CustomAbort(http.StatusInternalServerError, "Internal error.")
}
if project == nil {
idc.Redirect("/signIn", http.StatusFound)
return
}
sessionUserID := idc.GetSession("userId")
if project.Public != 1 && sessionUserID == nil {
idc.Redirect("/signIn?uri="+url.QueryEscape(idc.Ctx.Input.URI()), http.StatusFound)
return
}
if sessionUserID != nil {
userID := sessionUserID.(int)
idc.Data["Username"] = idc.GetSession("username")
idc.Data["UserId"] = userID
roleList, err := dao.GetUserProjectRoles(userID, projectID)
if err != nil {
log.Errorf("Error occurred in GetUserProjectRoles: %v", err)
idc.CustomAbort(http.StatusInternalServerError, "Internal error.")
}
isAdmin, err := dao.IsAdminRole(userID)
if err != nil {
log.Errorf("Error occurred in IsAdminRole: %v", err)
idc.CustomAbort(http.StatusInternalServerError, "Internal error.")
}
if !isAdmin && (project.Public == 0 && len(roleList) == 0) {
idc.Redirect("/registry/project", http.StatusFound)
return
}
if len(roleList) > 0 {
idc.Data["RoleId"] = roleList[0].RoleID
}
}
idc.Data["ProjectId"] = project.ProjectID
idc.Data["ProjectName"] = project.Name
idc.Data["OwnerName"] = project.OwnerName
idc.Data["OwnerId"] = project.OwnerID
idc.Data["HarborRegUrl"] = os.Getenv("HARBOR_REG_URL")
idc.Data["RepoName"] = idc.GetString("repo_name")
idc.ForwardTo("page_title_item_details", "item-detail")
}

View File

@ -1,82 +0,0 @@
/*
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 controllers
import (
"net/http"
"github.com/vmware/harbor/auth"
"github.com/vmware/harbor/models"
"github.com/vmware/harbor/utils/log"
)
// IndexController handles request to /
type IndexController struct {
BaseController
}
// Get renders the index page.
func (c *IndexController) Get() {
c.Data["Username"] = c.GetSession("username")
c.ForwardTo("page_title_index", "index")
}
// SignInController handles request to /signIn
type SignInController struct {
BaseController
}
// Get renders Sign In page.
func (sic *SignInController) Get() {
sic.ForwardTo("page_title_sign_in", "sign-in")
}
// Login handles login request from UI.
func (c *CommonController) Login() {
principal := c.GetString("principal")
password := c.GetString("password")
user, err := auth.Login(models.AuthModel{
Principal: principal,
Password: password,
})
if err != nil {
log.Errorf("Error occurred in UserLogin: %v", err)
c.CustomAbort(http.StatusUnauthorized, "")
}
if user == nil {
c.CustomAbort(http.StatusUnauthorized, "")
}
c.SetSession("userId", user.UserID)
c.SetSession("username", user.Username)
}
// 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" || lang == "ja-JP" {
c.SetSession("lang", lang)
c.Data["Lang"] = lang
}
c.Redirect(c.Ctx.Request.Header.Get("Referer"), http.StatusFound)
}
// Logout handles UI request to logout.
func (c *CommonController) Logout() {
c.DestroySession()
}

View File

@ -1,4 +1,4 @@
package ng
package controllers
import (
"net/http"
@ -8,10 +8,12 @@ import (
"github.com/vmware/harbor/utils/log"
)
// NavigationDetailController handles requests to /navigation_detail
type NavigationDetailController struct {
BaseController
}
// Get renders user's navigation details header
func (ndc *NavigationDetailController) Get() {
sessionUserID := ndc.GetSession("userId")
var isAdmin int
@ -29,6 +31,6 @@ func (ndc *NavigationDetailController) Get() {
isAdmin = u.HasAdminRole
}
ndc.Data["IsAdmin"] = isAdmin
ndc.TplName = "ng/navigation-detail.htm"
ndc.TplName = "navigation-detail.htm"
ndc.Render()
}

View File

@ -1,4 +1,4 @@
package ng
package controllers
import (
"net/http"
@ -8,7 +8,7 @@ import (
"github.com/vmware/harbor/utils/log"
)
// NavigationHeaderController handles requests to /ng/navigation_header
// NavigationHeaderController handles requests to /navigation_header
type NavigationHeaderController struct {
BaseController
}
@ -34,6 +34,6 @@ func (nhc *NavigationHeaderController) Get() {
}
nhc.Data["HasLoggedIn"] = hasLoggedIn
nhc.Data["IsAdmin"] = isAdmin
nhc.TplName = "ng/navigation-header.htm"
nhc.TplName = "navigation-header.htm"
nhc.Render()
}

View File

@ -1,162 +0,0 @@
package ng
import (
"net/http"
"os"
"path/filepath"
"strings"
"github.com/astaxie/beego"
"github.com/beego/i18n"
"github.com/vmware/harbor/utils/log"
)
// BaseController wraps common methods such as i18n support, forward, which can be leveraged by other UI render controllers.
type BaseController struct {
beego.Controller
i18n.Locale
SelfRegistration bool
IsAdmin bool
AuthMode string
}
type langType struct {
Lang string
Name string
}
const (
viewPath = "sections"
prefixNg = "ng"
defaultLang = "en-US"
)
var supportLanguages map[string]langType
// Prepare extracts the language information from request and populate data for rendering templates.
func (b *BaseController) Prepare() {
var lang string
al := b.Ctx.Request.Header.Get("Accept-Language")
if len(al) > 4 {
al = al[:5] // Only compare first 5 letters.
if i18n.IsExist(al) {
lang = al
}
}
if _, exist := supportLanguages[lang]; exist == false { //Check if support the request language.
lang = defaultLang //Set default language if not supported.
}
sessionLang := b.GetSession("lang")
if sessionLang != nil {
b.SetSession("Lang", lang)
lang = sessionLang.(string)
}
curLang := langType{
Lang: lang,
}
restLangs := make([]*langType, 0, len(langTypes)-1)
for _, v := range langTypes {
if lang != v.Lang {
restLangs = append(restLangs, v)
} else {
curLang.Name = v.Name
}
}
// Set language properties.
b.Lang = lang
b.Data["Lang"] = curLang.Lang
b.Data["CurLang"] = curLang.Name
b.Data["RestLangs"] = restLangs
b.Data["SupportLanguages"] = supportLanguages
authMode := strings.ToLower(os.Getenv("AUTH_MODE"))
if authMode == "" {
authMode = "db_auth"
}
b.AuthMode = authMode
b.Data["AuthMode"] = b.AuthMode
}
// Forward to setup layout and template for content for a page.
func (b *BaseController) Forward(title, templateName string) {
b.Layout = filepath.Join(prefixNg, "layout.htm")
b.TplName = filepath.Join(prefixNg, templateName)
b.Data["Title"] = title
b.LayoutSections = make(map[string]string)
b.LayoutSections["HeaderInclude"] = filepath.Join(prefixNg, viewPath, "header-include.htm")
b.LayoutSections["FooterInclude"] = filepath.Join(prefixNg, viewPath, "footer-include.htm")
b.LayoutSections["HeaderContent"] = filepath.Join(prefixNg, viewPath, "header-content.htm")
b.LayoutSections["FooterContent"] = filepath.Join(prefixNg, viewPath, "footer-content.htm")
}
var langTypes []*langType
// CommonController handles request from UI that doesn't expect a page, such as /SwitchLanguage /logout ...
type CommonController struct {
BaseController
}
// Render returns nil.
func (cc *CommonController) Render() error {
return nil
}
// LogOut Habor UI
func (cc *CommonController) LogOut() {
cc.DestroySession()
}
// SwitchLanguage User can swith to prefered language
func (cc *CommonController) SwitchLanguage() {
lang := cc.GetString("lang")
if _, exist := supportLanguages[lang]; exist {
cc.SetSession("lang", lang)
cc.Data["Lang"] = lang
}
cc.Redirect(cc.Ctx.Request.Header.Get("Referer"), http.StatusFound)
}
func init() {
//conf/app.conf -> os.Getenv("config_path")
configPath := os.Getenv("CONFIG_PATH")
if len(configPath) != 0 {
log.Infof("Config path: %s", configPath)
beego.AppConfigPath = configPath
if err := beego.ParseConfig(); err != nil {
log.Warningf("Failed to parse config file: %s, error: %v", configPath, err)
}
}
beego.AddFuncMap("i18n", i18n.Tr)
langs := strings.Split(beego.AppConfig.String("lang::types"), "|")
names := strings.Split(beego.AppConfig.String("lang::names"), "|")
supportLanguages = make(map[string]langType)
langTypes = make([]*langType, 0, len(langs))
for i, v := range langs {
t := langType{
Lang: v,
Name: names[i],
}
langTypes = append(langTypes, &t)
supportLanguages[v] = t
}
for _, lang := range langs {
if err := i18n.SetMessage(lang, "static/i18n/"+"locale_"+lang+".ini"); err != nil {
log.Errorf("Fail to set message file: %s", err.Error())
}
}
}

View File

@ -1,169 +0,0 @@
package ng
import (
"bytes"
"net/http"
"os"
"regexp"
"text/template"
"github.com/astaxie/beego"
"github.com/vmware/harbor/dao"
"github.com/vmware/harbor/models"
"github.com/vmware/harbor/utils"
"github.com/vmware/harbor/utils/log"
)
type messageDetail struct {
Hint string
URL string
UUID string
}
// SendEmail verifies the Email address and contact SMTP server to send reset password Email.
func (cc *CommonController) SendEmail() {
email := cc.GetString("email")
pass, _ := 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,}))$`, email)
if !pass {
cc.CustomAbort(http.StatusBadRequest, "email_content_illegal")
} else {
queryUser := models.User{Email: email}
exist, err := dao.UserExists(queryUser, "email")
if err != nil {
log.Errorf("Error occurred in UserExists: %v", err)
cc.CustomAbort(http.StatusInternalServerError, "Internal error.")
}
if !exist {
cc.CustomAbort(http.StatusNotFound, "email_does_not_exist")
}
messageTemplate, err := template.ParseFiles("views/ng/reset-password-mail.tpl")
if err != nil {
log.Errorf("Parse email template file failed: %v", err)
cc.CustomAbort(http.StatusInternalServerError, err.Error())
}
message := new(bytes.Buffer)
harborURL := os.Getenv("HARBOR_URL")
if harborURL == "" {
harborURL = "localhost"
}
uuid, err := dao.GenerateRandomString()
if err != nil {
log.Errorf("Error occurred in GenerateRandomString: %v", err)
cc.CustomAbort(http.StatusInternalServerError, "Internal error.")
}
err = messageTemplate.Execute(message, messageDetail{
Hint: cc.Tr("reset_email_hint"),
URL: harborURL,
UUID: uuid,
})
if err != nil {
log.Errorf("Message template error: %v", err)
cc.CustomAbort(http.StatusInternalServerError, "internal_error")
}
config, err := beego.AppConfig.GetSection("mail")
if err != nil {
log.Errorf("Can not load app.conf: %v", err)
cc.CustomAbort(http.StatusInternalServerError, "internal_error")
}
mail := utils.Mail{
From: config["from"],
To: []string{email},
Subject: cc.Tr("reset_email_subject"),
Message: message.String()}
err = mail.SendMail()
if err != nil {
log.Errorf("Send email failed: %v", err)
cc.CustomAbort(http.StatusInternalServerError, "send_email_failed")
}
user := models.User{ResetUUID: uuid, Email: email}
dao.UpdateUserResetUUID(user)
}
}
// ForgotPasswordController handles requests to /ng/forgot_password
type ForgotPasswordController struct {
BaseController
}
// Get renders forgot password page
func (fpc *ForgotPasswordController) Get() {
fpc.Forward("Forgot Password", "forgot-password.htm")
}
// ResetPasswordController handles request to /resetPassword
type ResetPasswordController struct {
BaseController
}
// Get checks if reset_uuid in the reset link is valid and render the result page for user to reset password.
func (rpc *ResetPasswordController) Get() {
resetUUID := rpc.GetString("reset_uuid")
if resetUUID == "" {
log.Error("Reset uuid is blank.")
rpc.Redirect("/", http.StatusFound)
return
}
queryUser := models.User{ResetUUID: resetUUID}
user, err := dao.GetUser(queryUser)
if err != nil {
log.Errorf("Error occurred in GetUser: %v", err)
rpc.CustomAbort(http.StatusInternalServerError, "Internal error.")
}
if user != nil {
rpc.Data["ResetUuid"] = user.ResetUUID
rpc.Forward("Reset Password", "reset-password.htm")
} else {
rpc.Redirect("/", http.StatusFound)
}
}
// ResetPassword handles request from the reset page and reset password
func (cc *CommonController) ResetPassword() {
resetUUID := cc.GetString("reset_uuid")
if resetUUID == "" {
cc.CustomAbort(http.StatusBadRequest, "Reset uuid is blank.")
}
queryUser := models.User{ResetUUID: resetUUID}
user, err := dao.GetUser(queryUser)
if err != nil {
log.Errorf("Error occurred in GetUser: %v", err)
cc.CustomAbort(http.StatusInternalServerError, "Internal error.")
}
if user == nil {
log.Error("User does not exist")
cc.CustomAbort(http.StatusBadRequest, "User does not exist")
}
password := cc.GetString("password")
if password != "" {
user.Password = password
err = dao.ResetUserPassword(*user)
if err != nil {
log.Errorf("Error occurred in ResetUserPassword: %v", err)
cc.CustomAbort(http.StatusInternalServerError, "Internal error.")
}
} else {
cc.CustomAbort(http.StatusBadRequest, "password_is_required")
}
}

View File

@ -1,11 +0,0 @@
package ng
// ProjectController handles requests to /ng/projec
type ProjectController struct {
BaseController
}
// Get renders project page
func (pc *ProjectController) Get() {
pc.Forward("My Projects", "project.htm")
}

View File

@ -1,11 +0,0 @@
package ng
// SearchController handles request to ng/search
type SearchController struct {
BaseController
}
// Get rendlers search bar
func (sc *SearchController) Get() {
sc.Forward("Search", "search.htm")
}

View File

@ -1,4 +1,4 @@
package ng
package controllers
import (
"net/http"
@ -8,7 +8,7 @@ import (
"github.com/vmware/harbor/utils/log"
)
// OptionalMenuController handles request to /ng/optional_menu
// OptionalMenuController handles request to /optional_menu
type OptionalMenuController struct {
BaseController
}
@ -33,7 +33,7 @@ func (omc *OptionalMenuController) Get() {
omc.Data["Username"] = u.Username
}
omc.Data["HasLoggedIn"] = hasLoggedIn
omc.TplName = "ng/optional-menu.htm"
omc.TplName = "optional-menu.htm"
omc.Render()
}

View File

@ -1,18 +1,3 @@
/*
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 controllers
import (
@ -22,40 +7,13 @@ import (
"regexp"
"text/template"
"github.com/astaxie/beego"
"github.com/vmware/harbor/dao"
"github.com/vmware/harbor/models"
"github.com/vmware/harbor/utils"
"github.com/vmware/harbor/utils/log"
"github.com/astaxie/beego"
)
// ChangePasswordController handles request to /changePassword
type ChangePasswordController struct {
BaseController
}
// Get renders the page for user to change password.
func (cpc *ChangePasswordController) Get() {
sessionUserID := cpc.GetSession("userId")
if sessionUserID == nil {
cpc.Redirect("/signIn", http.StatusFound)
return
}
cpc.Data["Username"] = cpc.GetSession("username")
cpc.ForwardTo("page_title_change_password", "change-password")
}
// ForgotPasswordController handles request to /forgotPassword
type ForgotPasswordController struct {
BaseController
}
// Get Renders the page for user to input Email to reset password.
func (fpc *ForgotPasswordController) Get() {
fpc.ForwardTo("page_title_forgot_password", "forgot-password")
}
type messageDetail struct {
Hint string
URL string
@ -137,6 +95,16 @@ func (cc *CommonController) SendEmail() {
}
// ForgotPasswordController handles requests to /forgot_password
type ForgotPasswordController struct {
BaseController
}
// Get renders forgot password page
func (fpc *ForgotPasswordController) Get() {
fpc.Forward("Forgot Password", "forgot-password.htm")
}
// ResetPasswordController handles request to /resetPassword
type ResetPasswordController struct {
BaseController
@ -161,7 +129,7 @@ func (rpc *ResetPasswordController) Get() {
if user != nil {
rpc.Data["ResetUuid"] = user.ResetUUID
rpc.ForwardTo("page_title_reset_password", "reset-password")
rpc.Forward("Reset Password", "reset-password.htm")
} else {
rpc.Redirect("/", http.StatusFound)
}

View File

@ -1,27 +1,11 @@
/*
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 controllers
// ProjectController handles request to /registry/project
// ProjectController handles requests to /project
type ProjectController struct {
BaseController
}
// Get renders project page.
func (p *ProjectController) Get() {
p.Data["Username"] = p.GetSession("username")
p.ForwardTo("page_title_project", "project")
// Get renders project page
func (pc *ProjectController) Get() {
pc.Forward("My Projects", "project.htm")
}

View File

@ -1,87 +0,0 @@
/*
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 controllers
import (
"net/http"
"github.com/vmware/harbor/dao"
"github.com/vmware/harbor/models"
"github.com/vmware/harbor/utils/log"
)
// RegisterController handles request to /register
type RegisterController struct {
BaseController
}
// Get renders the Sign In page, it only works if the auth mode is set to db_auth
func (rc *RegisterController) Get() {
if !rc.SelfRegistration {
log.Warning("Registration is disabled when self-registration is off.")
rc.Redirect("/signIn", http.StatusFound)
}
if rc.AuthMode == "db_auth" {
rc.ForwardTo("page_title_registration", "register")
} else {
rc.Redirect("/signIn", http.StatusFound)
}
}
// AddUserController handles request for adding user with an admin role user
type AddUserController struct {
BaseController
}
// Get renders the Sign In page, it only works if the auth mode is set to db_auth
func (ac *AddUserController) Get() {
if !ac.IsAdmin {
log.Warning("Add user can only be used by admin role user.")
ac.Redirect("/signIn", http.StatusFound)
}
if ac.AuthMode == "db_auth" {
ac.ForwardTo("page_title_add_user", "register")
} else {
ac.Redirect("/signIn", http.StatusFound)
}
}
// UserExists checks if user exists when user input value in sign in form.
func (cc *CommonController) UserExists() {
target := cc.GetString("target")
value := cc.GetString("value")
user := models.User{}
switch target {
case "username":
user.Username = value
case "email":
user.Email = value
}
exist, err := dao.UserExists(user, target)
if err != nil {
log.Errorf("Error occurred in UserExists: %v", err)
cc.CustomAbort(http.StatusInternalServerError, "Internal error.")
}
cc.Data["json"] = exist
cc.ServeJSON()
}

View File

@ -1,8 +1,8 @@
package ng
package controllers
import "os"
// RepositoryController handles request to /ng/repository
// RepositoryController handles request to /repository
type RepositoryController struct {
BaseController
}

View File

@ -1,18 +1,3 @@
/*
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 controllers
// SearchController handles request to /search
@ -20,9 +5,7 @@ type SearchController struct {
BaseController
}
// Get renders page for displaying search result.
// Get rendlers search bar
func (sc *SearchController) Get() {
sc.Data["Username"] = sc.GetSession("username")
sc.Data["QueryParam"] = sc.GetString("q")
sc.ForwardTo("page_title_search", "search")
sc.Forward("Search", "search.htm")
}

View File

@ -1,4 +1,4 @@
package ng
package controllers
import (
"net/http"
@ -8,7 +8,7 @@ import (
"github.com/vmware/harbor/utils/log"
)
// SignInController handles requests to /ng/sign_in
// SignInController handles requests to /sign_in
type SignInController struct {
BaseController
}
@ -34,6 +34,6 @@ func (sic *SignInController) Get() {
}
sic.Data["Username"] = username
sic.Data["HasLoggedIn"] = hasLoggedIn
sic.TplName = "ng/sign-in.htm"
sic.TplName = "sign-in.htm"
sic.Render()
}

View File

@ -1,6 +1,6 @@
package ng
package controllers
// SignUpController handles requests to /ng/sign_up
// SignUpController handles requests to /sign_up
type SignUpController struct {
BaseController
}

View File

@ -911,6 +911,21 @@ func TestAddRepPolicy(t *testing.T) {
}
func TestGetRepPolicyByTarget(t *testing.T) {
policies, err := GetRepPolicyByTarget(targetID)
if err != nil {
t.Fatalf("failed to get policy according target %d: %v", targetID, err)
}
if len(policies) == 0 {
t.Fatal("unexpected length of policies 0, expected is >0")
}
if policies[0].ID != policyID {
t.Fatalf("unexpected policy: %d, expected: %d", policies[0].ID, policyID)
}
}
func TestGetRepPolicyByName(t *testing.T) {
policy, err := GetRepPolicy(policyID)
if err != nil {
@ -1102,9 +1117,27 @@ func TestDeleteRepJob(t *testing.T) {
j, err := GetRepJob(jobID)
if err != nil {
t.Errorf("Error occured in GetRepJob:%v", err)
return
}
if j != nil {
t.Errorf("Able to find rep job after deletion, id: %d", jobID)
return
}
}
func TestFilterRepJobs(t *testing.T) {
jobs, err := FilterRepJobs("", policyID)
if err != nil {
log.Errorf("Error occured in FilterRepJobs: %v, policy ID: %d", err, policyID)
return
}
if len(jobs) != 1 {
log.Errorf("Unexpected length of jobs, expected: 1, in fact: %d", len(jobs))
return
}
if jobs[0].ID != jobID {
log.Errorf("Unexpected job ID in the result, expected: %d, in fact: %d", jobID, jobs[0].ID)
return
}
}

View File

@ -192,10 +192,24 @@ func GetRepPolicyByProject(projectID int64) ([]*models.RepPolicy, error) {
return policies, nil
}
// GetRepPolicyByTarget ...
func GetRepPolicyByTarget(targetID int64) ([]*models.RepPolicy, error) {
o := GetOrmer()
sql := `select * from replication_policy where target_id = ?`
var policies []*models.RepPolicy
if _, err := o.Raw(sql, targetID).QueryRows(&policies); err != nil {
return nil, err
}
return policies, nil
}
// UpdateRepPolicy ...
func UpdateRepPolicy(policy *models.RepPolicy) error {
o := GetOrmer()
_, err := o.Update(policy, "Name", "Enabled", "Description", "CronStr")
_, err := o.Update(policy, "TargetID", "Name", "Enabled", "Description", "CronStr")
return err
}
@ -259,6 +273,38 @@ func GetRepJobByPolicy(policyID int64) ([]*models.RepJob, error) {
return res, err
}
// FilterRepJobs filters jobs by repo and policy ID
func FilterRepJobs(repo string, policyID int64) ([]*models.RepJob, error) {
o := GetOrmer()
var args []interface{}
sql := `select * from replication_job `
if len(repo) != 0 && policyID != 0 {
sql += `where repository like ? and policy_id = ? `
args = append(args, "%"+repo+"%")
args = append(args, policyID)
} else if len(repo) != 0 {
sql += `where repository like ? `
args = append(args, "%"+repo+"%")
} else if policyID != 0 {
sql += `where policy_id = ? `
args = append(args, policyID)
}
sql += `order by creation_time`
var jobs []*models.RepJob
if _, err := o.Raw(sql, args).QueryRows(&jobs); err != nil {
return nil, err
}
genTagListForJob(jobs...)
return jobs, nil
}
// GetRepJobToStop get jobs that are possibly being handled by workers of a certain policy.
func GetRepJobToStop(policyID int64) ([]*models.RepJob, error) {
var res []*models.RepJob

View File

@ -18,6 +18,7 @@ package replication
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"net/http"
@ -43,6 +44,11 @@ const (
StatePushManifest = "push_manifest"
)
var (
// ErrConflict represents http 409 error
ErrConflict = errors.New("conflict")
)
// BaseHandler holds informations shared by other state handlers
type BaseHandler struct {
project string // project_name
@ -136,15 +142,24 @@ type Checker struct {
// Enter check existence of project, if it does not exist, create it,
// if it exists, check whether the user has write privilege to it.
func (c *Checker) Enter() (string, error) {
enter:
exist, canWrite, err := c.projectExist()
if err != nil {
c.logger.Errorf("an error occurred while checking existence of project %s on %s with user %s : %v", c.project, c.dstURL, c.dstUsr, err)
return "", err
}
if !exist {
if err := c.createProject(); err != nil {
c.logger.Errorf("an error occurred while creating project %s on %s with user %s : %v", c.project, c.dstURL, c.dstUsr, err)
return "", err
err := c.createProject()
if err != nil {
// other job may be also doing the same thing when the current job
// is creating project, so when the response code is 409, re-check
// the existence of project
if err == ErrConflict {
goto enter
} else {
c.logger.Errorf("an error occurred while creating project %s on %s with user %s : %v", c.project, c.dstURL, c.dstUsr, err)
return "", err
}
}
c.logger.Infof("project %s is created on %s with user %s", c.project, c.dstURL, c.dstUsr)
return StatePullManifest, nil
@ -246,6 +261,10 @@ func (c *Checker) createProject() error {
}
if resp.StatusCode != http.StatusCreated {
if resp.StatusCode == http.StatusConflict {
return ErrConflict
}
defer resp.Body.Close()
message, err := ioutil.ReadAll(resp.Body)
if err != nil {

View File

@ -1,88 +0,0 @@
page_title_index = Harbor
page_title_sign_in = Anmelden - Harbor
page_title_project = Projekt - Harbor
page_title_item_details = Details - Harbor
page_title_registration = Registrieren - Harbor
page_title_add_user = Benutzer anlegen - Harbor
page_title_forgot_password = Passwort vergessen - Harbor
title_forgot_password = Passwort vergessen
page_title_reset_password = Passwort zurücksetzen - Harbor
title_reset_password = Passwort zurücksetzen
page_title_change_password = Passwort ändern - Harbor
title_change_password = Passwort ändern
page_title_search = Suche - Harbor
sign_in = Anmelden
sign_up = Registrieren
add_user = Benutzer anlegen
log_out = Abmelden
search_placeholder = Projekte oder Repositories
change_password = Passwort ändern
username_email = Benutzername/E-Mail
password = Passwort
forgot_password = Passwort vergessen
welcome = Willkommen
my_projects = Meine Projekte
public_projects = Öffentliche Projekte
admin_options = Admin Optionen
project_name = Projektname
creation_time = Erstellungsdatum
publicity = Öffentlich
add_project = Projekt hinzufügen
check_for_publicity = öffentliches Projekt
button_save = Speichern
button_cancel = Abbrechen
button_submit = Absenden
username = Benutzername
email = E-Mail
system_admin = System Admininistrator
dlg_button_ok = OK
dlg_button_cancel = Abbrechen
registration = Registrieren
username_description = Dies wird Ihr Benutzername sein.
email_description = Die E-Mail Adresse wird für das Zurücksetzen des Passworts genutzt.
full_name = Sollständiger Name
full_name_description = Vor- und Nachname.
password_description = Mindestens sieben Zeichen bestehend aus einem Kleinbuchstaben, einem Großbuchstaben und einer Zahl
confirm_password = Passwort bestätigen
note_to_the_admin = Kommentar
old_password = Altes Passwort
new_password = Neues Passwort
forgot_password_description = Bitte gebe die E-Mail Adresse ein, die du zur Registrierung verwendet hast. Ein Link zur Wiederherstellung wird dir per E-Mail an diese Adresse geschickt.
projects = Projekte
repositories = Repositories
search = Suche
home = Home
project = Projekt
owner = Besitzer
repo = Repositories
user = Benutzer
logs = Logs
repo_name = Repository
add_members = Benutzer hinzufügen
operation = Aktion
advance = erweiterte Suche
all = Alle
others = Andere
start_date = Start Datum
end_date = End Datum
timestamp = Zeitstempel
role = Rolle
reset_email_hint = Bitte klicke auf diesen Link um dein Passwort zurückzusetzen
reset_email_subject = Passwort zurücksetzen
language = Deutsch
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.
index_desc_0 = Vorteile:
index_desc_1 = 1. Sicherheit: Halten Sie ihr geistiges Eigentum innerhalb der Organisation.
index_desc_2 = 2. Effizienz: Ein privater Registry Server innerhalb des Netzwerks ihrer Organisation kann den Traffic zu öffentlichen Services im Internet signifikant reduzieren.
index_desc_3 = 3. Zugriffskontrolle: RBAC (Role Based Access Control) wird zur Verfügung gestellt. Benutzerverwaltung kann mit bestehenden Identitätsservices wie AD/LDAP integriert werden.
index_desc_4 = 4. Audit: Jeglicher Zugriff auf die Registry wird protokolliert und kann für ein Audit verwendet werden.
index_desc_5 = 5. GUI: Benutzerfreundliche Verwaltung über eine einzige Management-Konsole
index_title = Ein Enterprise-Class Registry Server

View File

@ -1,89 +0,0 @@
page_title_index = Harbor
page_title_sign_in = Sign In - Harbor
page_title_project = Project - Harbor
page_title_item_details = Details - Harbor
page_title_registration = Sign Up - Harbor
page_title_add_user = Add User - Harbor
page_title_forgot_password = Forgot Password - Harbor
title_forgot_password = Forgot Password
page_title_reset_password = Reset Password - Harbor
title_reset_password = Reset Password
page_title_change_password = Change Password - Harbor
title_change_password = Change Password
page_title_search = Search - Harbor
sign_in = Sign In
sign_up = Sign Up
add_user = Add User
log_out = Log Out
search_placeholder = projects or repositories
change_password = Change Password
username_email = Username/Email
password = Password
forgot_password = Forgot Password
welcome = Welcome
my_projects = My Projects
public_projects = Public Projects
admin_options = Admin Options
project_name = Project Name
creation_time = Creation Time
publicity = Publicity
add_project = Add Project
check_for_publicity = Public project
button_save = Save
button_cancel = Cancel
button_submit = Submit
username = Username
email = Email
system_admin = System Admin
dlg_button_ok = OK
dlg_button_cancel = Cancel
registration = Sign Up
username_description = This will be your username.
email_description = The Email address will be used for resetting password.
full_name = Full Name
full_name_description = First name & Last name.
password_description = At least 7 characters with 1 lowercase letter, 1 capital letter and 1 numeric character.
confirm_password = Confirm Password
note_to_the_admin = Comments
old_password = Old Password
new_password = New Password
forgot_password_description = Please input the Email used when you signed up, a reset password Email will be sent to you.
projects = Projects
repositories = Repositories
search = Search
home = Home
project = Project
owner = Owner
repo = Repositories
user = Users
logs = Logs
repo_name = Repository Name
repo_tag = Tag
add_members = Add Members
operation = Operation
advance = Advanced Search
all = All
others = Others
start_date = Start Date
end_date = End Date
timestamp = Timestamp
role = Role
reset_email_hint = Please click this link to reset your password
reset_email_subject = Reset your password
language = English
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.
index_desc_0 = Key benefits:
index_desc_1 = 1. Security: Keep their intellectual properties within their organizations.
index_desc_2 = 2. Efficiency: A private registry server is set up within the organization's network and can reduce significantly the internet traffic to the public service.
index_desc_3 = 3. Access Control: RBAC (Role Based Access Control) is provided. User management can be integrated with existing enterprise identity services like AD/LDAP.
index_desc_4 = 4. Audit: All access to the registry are logged and can be used for audit purpose.
index_desc_5 = 5. GUI: User friendly single-pane-of-glass management console.
index_title = An enterprise-class registry server

View File

@ -1,89 +0,0 @@
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

@ -1,457 +0,0 @@
/*
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.
*/
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

@ -1,89 +0,0 @@
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 = Логин/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 = Email
system_admin = Системный администратор
dlg_button_ok = OK
dlg_button_cancel = Отмена
registration = Регистрация
username_description = Ваше имя пользователя.
email_description = Email адрес, который будет использоваться для сброса пароля.
full_name = Полное Имя
full_name_description = Имя и Фамилия.
password_description = Минимум 7 символов, в которых будет присутствовать по меньшей мере 1 буква нижнего регистра, 1 буква верхнего регистра и 1 цифра.
confirm_password = Подтвердить Пароль
note_to_the_admin = Комментарии
old_password = Старый Пароль
new_password = Новый Пароль
forgot_password_description = Введите Email, который вы использовали для регистрации, вам будет выслано письмо для сброса пароля.
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 = Copyright
all_rights_reserved = Все права защищены.
index_desc = Проект Harbor представляет собой надежный сервер управления docker-образами корпоративного класса. Компании могут использовать данный сервер в своей инфарструктуе для повышения производительности и безопасности . Проект Harbor может использоваться как в среде разработки так и в продуктивной среде.
index_desc_0 = Основные преимущества данного решения:
index_desc_1 = 1. Безопасность: Хранение интеллектуальной собственности внутри организации.
index_desc_2 = 2. Эффективность: сервер хранения docker образов устанавливается в рамках внутренней сети организации, и может значительно сократить расход Интернет траффика
index_desc_3 = 3. Управление доступом: реализована модель RBAC (Ролевая модель управление доступом). Управление пользователями может быть интегрировано с существующими корпоративными сервисами идентификациями такими как AD/LDAP.
index_desc_4 = 4. Аудит: Любой доступ к хранилищу логируется и может быть использован для последующего анализа.
index_desc_5 = 5. GUI-интерфейс: удобная, единая консоль управления.
index_title = Сервер управления docker-образами корпоративного класса

View File

@ -1,89 +0,0 @@
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 = 确定
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服务可显著降低访问公共Registry服务的网络需求。
index_desc_3 = 3. 访问控制: 提供基于角色的访问控制,可集成企业目前拥有的用户管理系统(如:AD/LDAP
index_desc_4 = 4. 审计: 所有访问Registry服务的操作均被记录便于日后审计。
index_desc_5 = 5. 管理界面: 具有友好易用图形管理界面。
index_title = 企业级 Registry 服务

View File

@ -1,15 +0,0 @@
(function() {
'use strict';
angular
.module('harbor.repository')
.controller('DeleteRepositoryController', DeleteRepositoryController);
DeleteRepositoryController.$inject = ['DeleteRepositoryService'];
function DeleteRepositoryController(DeleteRepositoryService) {
}
})();

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,38 +0,0 @@
/*
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.
*/
.footer {
width: 100%;
/* Set the fixed height of the footer here */
height: 60px;
}
.div-height {
overflow-y: auto;
}
.blue {
color: #428bca;
}
sub {
font-size: 6pt;
color: blue;
}
.table-responsive {
height: 430px;
overflow-y: auto;
}
table > tbody > tr > td {
vertical-align: bottom;
}

View File

@ -16,6 +16,7 @@
clear: both;
background-color: #A8A8A8;
height: 44px;
z-index: 10;
}
.footer p {
padding-top: 8px;

View File

@ -1,12 +1,11 @@
.container-custom {
position: relative;
height: 680px;
height: 100%;
}
.extend-height {
height: 100%;
min-height: 1px;
max-height: 680px;
min-width: 1024px;
overflow: hidden;
}
@ -16,10 +15,10 @@
padding: 15px;
margin-top: 20px;
background-color: #FFFFFF;
height: 660px;
height: 100%;
width: 100%;
min-height: 1px;
max-height: 660px;
max-height: 100%;
}
.search-pane {

View File

@ -1,27 +0,0 @@
/*
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.
*/
body {
background-color: #fff;
}
.form-signin {
padding-top: 40px;
padding-bottom: 40px;
max-width: 380px;
margin: 10% auto;
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

View File

Before

Width:  |  Height:  |  Size: 3.6 KiB

After

Width:  |  Height:  |  Size: 3.6 KiB

View File

Before

Width:  |  Height:  |  Size: 72 KiB

After

Width:  |  Height:  |  Size: 72 KiB

View File

Before

Width:  |  Height:  |  Size: 80 KiB

After

Width:  |  Height:  |  Size: 80 KiB

View File

Before

Width:  |  Height:  |  Size: 86 KiB

After

Width:  |  Height:  |  Size: 86 KiB

View File

Before

Width:  |  Height:  |  Size: 160 KiB

After

Width:  |  Height:  |  Size: 160 KiB

View File

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 34 KiB

View File

@ -1,98 +0,0 @@
/*
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.
*/
jQuery(function(){
new AjaxUtil({
url: "/api/users/current",
type: "get",
success: function(data, status, xhr){},
errors: {
403: ""
},
error: function(jqXhr){
if(jqXhr && jqXhr.status == 401){
document.location = "/signIn";
}
}
}).exec();
$("#divErrMsg").css({"display": "none"});
$("#OldPassword,#Password,#ConfirmedPassword").on("blur", validateCallback);
validateOptions.Items = ["#OldPassword", "#Password", "#ConfirmedPassword"];
function bindEnterKey(){
$(document).on("keydown", function(e){
if(e.keyCode == 13){
e.preventDefault();
if($("#txtCommonSearch").is(":focus")){
document.location = "/search?q=" + $("#txtCommonSearch").val();
}else{
$("#btnSubmit").trigger("click");
}
}
});
}
function unbindEnterKey(){
$(document).off("keydown");
}
bindEnterKey();
var spinner = new Spinner({scale:1}).spin();
$("#btnSubmit").on("click", function(){
validateOptions.Validate(function(){
var oldPassword = $("#OldPassword").val();
var password = $("#Password").val();
new AjaxUtil({
url: "/api/users/current/password",
type: "put",
data: {"old_password": oldPassword, "new_password" : password},
beforeSend: function(e){
unbindEnterKey();
$("h1").append(spinner.el);
$("#btnSubmit").prop("disabled", true);
},
complete: function(xhr, status){
spinner.stop();
$("#btnSubmit").prop("disabled", false);
if(xhr && xhr.status == 200){
$("#dlgModal")
.dialogModal({
"title": i18n.getMessage("title_change_password"),
"content": i18n.getMessage("change_password_successfully"),
"callback": function(){
window.close();
}
});
}
},
error: function(jqXhr, status, error){
if(jqXhr && jqXhr.responseText.length){
$("#dlgModal")
.dialogModal({
"title": i18n.getMessage("title_change_password"),
"content": i18n.getMessage(jqXhr.responseText),
"callback": function(){
bindEnterKey();
return;
}
});
}
}
}).exec();
});
});
});

View File

@ -1,169 +0,0 @@
/*
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.
*/
var AjaxUtil = function(params){
this.url = params.url;
this.data = params.data;
this.dataRaw = params.dataRaw;
this.type = params.type;
this.errors = params.errors || {};
this.success = params.success;
this.complete = params.complete;
this.error = params.error;
};
AjaxUtil.prototype.exec = function(){
var self = this;
return $.ajax({
url: self.url,
contentType: (self.dataRaw ? "application/x-www-form-urlencoded; charset=UTF-8" : "application/json; charset=utf-8"),
data: JSON.stringify(self.data) || self.dataRaw,
type: self.type,
dataType: "json",
success: function(data, status, xhr){
if(self.success != null){
self.success(data, status, xhr);
}
},
complete: function(jqXhr, status) {
if(self.complete != null){
self.complete(jqXhr, status);
}
},
error: function(jqXhr){
if(self.error != null){
self.error(jqXhr);
}else{
var errorMessage = self.errors[jqXhr.status] || jqXhr.responseText;
if(jqXhr.status == 401){
var lastUri = location.pathname + location.search;
if(lastUri != ""){
document.location = "/signIn?uri=" + encodeURIComponent(lastUri);
}else{
document.location = "/signIn";
}
}else if($.trim(errorMessage).length > 0){
$("#dlgModal").dialogModal({"title": i18n.getMessage("operation_failed"), "content": errorMessage});
}
}
}
});
};
var SUPPORT_LANGUAGES = {
"en-US": "English",
"zh-CN": "Chinese",
"de-DE": "German",
"ru-RU": "Russian",
"ja-JP": "Japanese"
};
var DEFAULT_LANGUAGE = "en-US";
var I18n = function(messages) {
this.messages = messages;
};
I18n.prototype.isSupportLanguage = function(lang){
return (lang in SUPPORT_LANGUAGES);
}
I18n.prototype.getLocale = function(){
var lang = $("#currentLanguage").val();
if(this.isSupportLanguage(lang)){
return lang;
}else{
return DEFAULT_LANGUAGE;
}
};
I18n.prototype.getMessage = function(key){
return this.messages[key][this.getLocale()];
};
var i18n = new I18n(global_messages);
moment().locale(i18n.getLocale());
jQuery(function(){
$("#aLogout").on("click", function(){
new AjaxUtil({
url:'/logout',
dataRaw:{"timestamp" : new Date().getTime()},
type: "get",
complete: function(jqXhr){
if(jqXhr && jqXhr.status == 200){
document.location = "/";
}
}
}).exec();
});
$.fn.dialogModal = function(options){
var settings = $.extend({
title: '',
content: '',
text: false,
callback: null,
enableCancel: false,
}, options || {});
if(settings.enableCancel){
$("#dlgCancel").show();
$("#dlgCancel").on("click", function(){
$(self).modal('close');
});
}
var self = this;
$("#dlgLabel", self).text(settings.title);
if(options.text){
$("#dlgBody", self).html(settings.content);
}else if(typeof settings.content == "object"){
$(".modal-dialog", self).addClass("modal-lg");
var lines = ['<form class="form-horizontal">'];
for(var item in settings.content){
lines.push('<div class="form-group">'+
'<label class="col-sm-2 control-label">'+ item +'</label>' +
'<div class="col-sm-10"><p class="form-control-static">' + settings.content[item] + '</p></div>' +
'</div>');
}
lines.push('</form>');
$("#dlgBody", self).html(lines.join(""));
}else{
$(".modal-dialog", self).removeClass("modal-lg");
$("#dlgBody", self).text(settings.content);
}
if(settings.callback != null){
var hasEntered = false;
$("#dlgConfirm").on("click", function(e){
if(!hasEntered) {
hasEntered = true;
settings.callback();
}
});
}
$(self).modal('show');
}
});

View File

@ -10,8 +10,8 @@
<h5 class="page-header">//vm.projectType | tr//: <span class="badge">//vm.resultCount//</span></h5>
<div class="project-list pane-container">
<ul class="list-group">
<li class="list-group-item" ng-repeat="item in vm.projects | name: vm.filterInput: 'Name'" ng-click="vm.selectItem(item)">
<span ng-show="item.ProjectId == vm.selectedId" class="glyphicon glyphicon-ok project-selected"></span> <a href="#/repositories?project_id=//item.project_id//">//item.name//</a>
<li class="list-group-item" ng-repeat="item in vm.projects | name: vm.filterInput: 'name'" ng-click="vm.selectItem(item)">
<span ng-show="item.project_id === vm.selectedId" class="glyphicon glyphicon-ok project-selected"></span> <a href="#/repositories?project_id=//item.project_id//">//item.name//</a>
</li>
</ul>
</div>

View File

@ -69,7 +69,7 @@
vm.resultCount = vm.projects.length;
$scope.$watch('vm.filterInput', function(current, origin) {
vm.resultCount = $filter('name')(vm.projects, vm.filterInput, 'Name').length;
vm.resultCount = $filter('name')(vm.projects, vm.filterInput, 'name').length;
});
}
@ -109,7 +109,7 @@
function retrieveProjects() {
var directive = {
restrict: 'E',
templateUrl: '/static/ng/resources/js/components/details/retrieve-projects.directive.html',
templateUrl: '/static/resources/js/components/details/retrieve-projects.directive.html',
scope: {
'isOpen': '=',
'selectedProject': '=',

View File

@ -33,7 +33,7 @@
function switchPaneProjects() {
var directive = {
restrict: 'E',
templateUrl: '/static/ng/resources/js/components/details/switch-pane-projects.directive.html',
templateUrl: '/static/resources/js/components/details/switch-pane-projects.directive.html',
scope: {
'isOpen': '=',
'selectedProject': '='

View File

@ -72,7 +72,7 @@
function advancedSearch() {
var directive = {
'restrict': 'E',
'templateUrl': '/static/ng/resources/js/components/log/advanced-search.directive.html',
'templateUrl': '/static/resources/js/components/log/advanced-search.directive.html',
'scope': {
'isOpen': '=',
'op': '=',

View File

@ -102,7 +102,7 @@
function listLog() {
var directive = {
restrict: 'E',
templateUrl: '/static/ng/resources/js/components/log/list-log.directive.html',
templateUrl: '/static/resources/js/components/log/list-log.directive.html',
scope: true,
controller: ListLogController,
controllerAs: 'vm',

View File

@ -15,7 +15,7 @@
function modalDialog() {
var directive = {
'restrict': 'E',
'templateUrl': '/static/ng/resources/js/components/modal-dialog/modal-dialog.directive.html',
'templateUrl': '/static/resources/js/components/modal-dialog/modal-dialog.directive.html',
'link': link,
'scope': {
'contentType': '@',

View File

@ -22,7 +22,7 @@
function setLanguage(language) {
I18nService().setCurrentLanguage(language);
$window.location.href = '/ng/language?lang=' + language;
$window.location.href = '/language?lang=' + language;
}
function logOut() {
LogOutService()
@ -32,7 +32,7 @@
function logOutSuccess(data, status) {
currentUser.unset();
I18nService().unset();
$window.location.href= '/ng';
$window.location.href= '/';
}
function logOutFailed(data, status) {
console.log('Failed to log out:' + data);
@ -42,7 +42,7 @@
function optionalMenu() {
var directive = {
'restrict': 'E',
'templateUrl': '/ng/optional_menu',
'templateUrl': '/optional_menu',
'scope': true,
'controller': OptionalMenuController,
'controllerAs': 'vm',

View File

@ -65,7 +65,7 @@
function addProjectMember() {
var directive = {
'restrict': 'E',
'templateUrl': '/static/ng/resources/js/components/project-member/add-project-member.directive.html',
'templateUrl': '/static/resources/js/components/project-member/add-project-member.directive.html',
'scope': {
'projectId': '@',
'isOpen': '=',

View File

@ -64,7 +64,7 @@
function editProjectMember() {
var directive = {
'restrict': 'A',
'templateUrl': '/static/ng/resources/js/components/project-member/edit-project-member.directive.html',
'templateUrl': '/static/resources/js/components/project-member/edit-project-member.directive.html',
'scope': {
'username': '=',
'userId': '=',

View File

@ -59,7 +59,7 @@
function listProjectMember() {
var directive = {
restrict: 'E',
templateUrl: '/static/ng/resources/js/components/project-member/list-project-member.directive.html',
templateUrl: '/static/resources/js/components/project-member/list-project-member.directive.html',
scope: true,
controller: ListProjectMemberController,
controllerAs: 'vm',

View File

@ -9,7 +9,6 @@
function roles() {
return [
{'id': '0', 'name': 'N/A', 'roleName': 'n/a'},
{'id': '1', 'name': 'Project Admin', 'roleName': 'projectAdmin'},
{'id': '2', 'name': 'Developer', 'roleName': 'developer'},
{'id': '3', 'name': 'Guest', 'roleName': 'guest'}

View File

@ -29,7 +29,7 @@
function switchRole() {
var directive = {
'restrict': 'E',
'templateUrl': '/static/ng/resources/js/components/project-member/switch-role.directive.html',
'templateUrl': '/static/resources/js/components/project-member/switch-role.directive.html',
'scope': {
'roles': '=',
'editMode': '=',

View File

@ -40,13 +40,12 @@
}
function addProjectFailed(data, status) {
if(status === 409) {
vm.hasError = true;
vm.errorMessage = 'project_already_exist';
vm.hasError = true;
if(status == 400) {
vm.errorMessage = 'project_name_is_invalid';
}
if(status === 500) {
vm.hasError = true;
vm.errorMessage = 'project_name_is_invalid';
if(status === 409) {
vm.errorMessage = 'project_already_exist';
}
console.log('Failed to add project:' + status);
}
@ -64,7 +63,7 @@
function addProject() {
var directive = {
'restrict': 'E',
'templateUrl': '/static/ng/resources/js/components/project/add-project.directive.html',
'templateUrl': '/static/resources/js/components/project/add-project.directive.html',
'controller': AddProjectController,
'scope' : {
'isOpen': '='

View File

@ -43,7 +43,7 @@
function publicityButton() {
var directive = {
'restrict': 'E',
'templateUrl': '/static/ng/resources/js/components/project/publicity-button.directive.html',
'templateUrl': '/static/resources/js/components/project/publicity-button.directive.html',
'scope': {
'isPublic': '=',
'owned': '=',

View File

@ -19,27 +19,30 @@
var vm0 = $scope.replication.policy;
var vm1 = $scope.replication.destination;
vm.selectDestination = selectDestination;
vm.projectId = getParameterByName('project_id', $location.absUrl());
$scope.$on('$locationChangeSuccess', function() {
vm.projectId = getParameterByName('project_id', $location.absUrl());
});
vm.addNew = addNew;
vm.edit = edit;
vm.prepareDestination = prepareDestination;
vm.createPolicy = createPolicy;
vm.updatePolicy = updatePolicy;
vm.create = create;
vm.update = update;
vm.pingDestination = pingDestination;
$scope.$watch('vm.destinations', function(current) {
if(current) {
console.log('destination:' + angular.toJson(current));
vm1.selection = current[0];
vm1.endpoint = vm1.selection.endpoint;
vm1.username = vm1.selection.username;
vm1.password = vm1.selection.password;
}
});
$scope.$watch('vm.action+","+vm.policyId', function(current) {
if(current) {
console.log('Current action for replication policy:' + current);
@ -48,9 +51,11 @@
vm.policyId = Number(parts[1]);
switch(parts[0]) {
case 'ADD_NEW':
vm.addNew(); break;
vm.addNew();
break;
case 'EDIT':
vm.edit(vm.policyId); break;
vm.edit(vm.policyId);
break;
}
}
});
@ -68,7 +73,7 @@
.error(listDestinationFailed);
}
function addNew() {
function addNew() {
vm0.name = '';
vm0.description = '';
vm0.enabled = true;
@ -81,13 +86,13 @@
.error(listReplicationPolicyFailed);
}
function createPolicy(policy) {
function create(policy) {
CreateReplicationPolicyService(policy)
.success(createReplicationPolicySuccess)
.error(createReplicationPolicyFailed);
}
function updatePolicy(policy) {
function update(policy) {
console.log('Update policy ID:' + vm.policyId);
UpdateReplicationPolicyService(vm.policyId, policy)
.success(updateReplicationPolicySuccess)
@ -119,7 +124,7 @@
}
function listDestinationSuccess(data, status) {
vm.destinations = data;
vm.destinations = data || [];
}
function listDestinationFailed(data, status) {
console.log('Failed list destination:' + data);
@ -136,13 +141,17 @@
}
function createReplicationPolicySuccess(data, status) {
console.log('Successful create replication policy.');
vm.clearUp();
vm.reload();
}
function createReplicationPolicyFailed(data, status) {
if(status === 409) {
alert('Policy name already exists.');
}
console.log('Failed create replication policy.');
}
function updateReplicationPolicySuccess(data, status) {
console.log('Successful update replication policy.');
vm.reload();
}
function updateReplicationPolicyFailed(data, status) {
console.log('Failed update replication policy.');
@ -164,7 +173,7 @@
function createPolicy() {
var directive = {
'restrict': 'E',
'templateUrl': '/static/ng/resources/js/components/replication/create-policy.directive.html',
'templateUrl': '/static/resources/js/components/replication/create-policy.directive.html',
'scope': {
'policyId': '@',
'modalTitle': '@',
@ -178,16 +187,17 @@
};
return directive;
function link(scope, element, attr, ctrl) {
element.find('#createPolicyModal').on('shown.bs.modal', function() {
function link(scope, element, attr, ctrl) {
element.find('#createPolicyModal').on('show.bs.modal', function() {
ctrl.prepareDestination();
scope.form.$setPristine();
});
scope.form.$setUntouched();
});
ctrl.save = save;
function save(form) {
console.log(angular.toJson(form));
var postPayload = {
'projectId': Number(ctrl.projectId),
'targetId': form.destination.selection.id,
@ -199,14 +209,14 @@
};
switch(ctrl.action) {
case 'ADD_NEW':
ctrl.createPolicy(postPayload); break;
ctrl.create(postPayload);
break;
case 'EDIT':
ctrl.updatePolicy(postPayload); break;
ctrl.update(postPayload);
break;
}
element.find('#createPolicyModal').modal('hide');
ctrl.reload();
}
}
}

View File

@ -4,7 +4,7 @@
<div class="input-group">
<input type="text" class="form-control" placeholder="" ng-model="vm.replicationPolicyName" size="30">
<span class="input-group-btn">
<button class="btn btn-primary" type="button" ng-click="vm.search()"><span class="glyphicon glyphicon-search"></span></button>
<button class="btn btn-primary" type="button" ng-click="vm.searchReplicationPolicy()"><span class="glyphicon glyphicon-search"></span></button>
</span>
</div>
<button ng-if="!vm.isOpen" class="btn btn-success" type="button" ng-click="vm.addReplication()" data-toggle="modal" data-target="#createPolicyModal"><span class="glyphicon glyphicon-plus"></span>New Replication</button>
@ -49,15 +49,15 @@
</table>
</div>
</div>
<div class="col-xs-4 col-md-12 well well-sm well-custom well-split"><div class="col-md-offset-10">//vm.projects ? vm.projects.length : 0// // 'items' | tr //</div></div>
<div class="col-xs-4 col-md-12 well well-sm well-custom well-split"><div class="col-md-offset-10">//vm.replicationPolicies ? vm.replicationPolicies.length : 0// // 'items' | tr //</div></div>
<p class="split-handle"><span class="glyphicon glyphicon-align-justify"></span></p>
<h4 class="h4-custom-down">Replication Jobs</h4>
<hr class="hr-line"/>
<div class="form-inline">
<div class="input-group">
<input type="text" class="form-control" placeholder="" ng-model="vm.replicationJobnName" size="30">
<input type="text" class="form-control" placeholder="" ng-model="vm.replicationJobName" size="30">
<span class="input-group-btn">
<button class="btn btn-primary" type="button" ng-click="vm.search()"><span class="glyphicon glyphicon-search"></span></button>
<button class="btn btn-primary" type="button" ng-click="vm.searchReplicationJob()"><span class="glyphicon glyphicon-search"></span></button>
</span>
</div>
</div>
@ -67,18 +67,20 @@
<thead>
<th width="20%">Name</th>
<th width="25%">Operation</th>
<th width="40%">Start Time</th>
<th width="25%">Start Time</th>
<th width="15%">Status</th>
<th width="15%">Logs</th>
</thead>
<tbody>
<tr ng-if="vm.replicationJobs.length == 0">
<td colspan="4" height="100%" class="empty-hint" ><h3 class="text-muted">No replication jobs.</h3></td>
</tr>
<tr ng-if="vm.replicationJobs.length > 0" ng-repeat="r in vm.replicationJobs">
<td>//r.tags != null ? r.tags.join(',') : 'N/A'//</td>
<td>//r.repository//</td>
<td>//r.operation//</td>
<td>//r.update_time | dateL : 'YYYY-MM-DD HH:mm:ss'//</td>
<td>//r.status//</td>
<td><a href="javascript:void(0);" ng-click="vm.downloadLog(r.id)"><span style="margin-left: 10px;" class="glyphicon glyphicon-file"></span></a></td>
</tr>
</tbody>
</table>

View File

@ -6,9 +6,9 @@
.module('harbor.replication')
.directive('listReplication', listReplication);
ListReplicationController.$inject = ['$scope', 'getParameterByName', '$location', 'ListReplicationPolicyService', 'ToggleReplicationPolicyService', 'ListReplicationJobService'];
ListReplicationController.$inject = ['$scope', 'getParameterByName', '$location', 'ListReplicationPolicyService', 'ToggleReplicationPolicyService', 'ListReplicationJobService', '$window'];
function ListReplicationController($scope, getParameterByName, $location, ListReplicationPolicyService, ToggleReplicationPolicyService, ListReplicationJobService) {
function ListReplicationController($scope, getParameterByName, $location, ListReplicationPolicyService, ToggleReplicationPolicyService, ListReplicationJobService, $window) {
var vm = this;
$scope.$on('$locationChangeSuccess', function() {
@ -19,19 +19,30 @@
vm.addReplication = addReplication;
vm.editReplication = editReplication;
vm.search = search;
vm.searchReplicationPolicy = searchReplicationPolicy;
vm.searchReplicationJob = searchReplicationJob;
vm.retrievePolicy = retrievePolicy;
vm.retrieveJob = retrieveJob;
vm.togglePolicy = togglePolicy;
vm.downloadLog = downloadLog;
vm.last = false;
vm.projectId = getParameterByName('project_id', $location.absUrl());
vm.retrievePolicy();
function search() {
function searchReplicationPolicy() {
vm.retrievePolicy();
}
function searchReplicationJob() {
if(vm.lastPolicyId !== -1) {
vm.retrieveJob(vm.lastPolicyId);
}
}
function retrievePolicy() {
ListReplicationPolicyService('', vm.projectId, vm.replicationPolicyName)
.success(listReplicationPolicySuccess)
@ -45,6 +56,7 @@
}
function listReplicationPolicySuccess(data, status) {
vm.replicationJobs = [];
vm.replicationPolicies = data || [];
}
@ -88,12 +100,15 @@
console.log('Failed toggle replication policy.');
}
function downloadLog(policyId) {
$window.open('/api/jobs/replication/' + policyId + '/log', '_blank');
}
}
function listReplication($timeout) {
var directive = {
'restrict': 'E',
'templateUrl': '/static/ng/resources/js/components/replication/list-replication.directive.html',
'templateUrl': '/static/resources/js/components/replication/list-replication.directive.html',
'scope': true,
'link': link,
'controller': ListReplicationController,
@ -151,7 +166,7 @@
}
});
});
function trClickHandler(e) {
element
.find('#upon-pane table>tbody>tr')

Some files were not shown because too many files have changed in this diff Show More