mirror of
https://github.com/goharbor/harbor.git
synced 2024-12-20 23:57:42 +01:00
commit
52c7f9716b
1
AUTHORS
1
AUTHORS
@ -5,6 +5,7 @@ Alexey Erkak <eryigin at mail.ru>
|
|||||||
Allen Heavey <xheavey at gmail.com>
|
Allen Heavey <xheavey at gmail.com>
|
||||||
Amanda Zhang <amzhang at vmware.com>
|
Amanda Zhang <amzhang at vmware.com>
|
||||||
Benniu Ji <benniuji at gmail.com>
|
Benniu Ji <benniuji at gmail.com>
|
||||||
|
Bin Liu <liubin0329 at gmail.com>
|
||||||
Bobby Zhang <junzhang at vmware.com>
|
Bobby Zhang <junzhang at vmware.com>
|
||||||
Chaofeng Wu <chaofengw at vmware.com>
|
Chaofeng Wu <chaofengw at vmware.com>
|
||||||
Daniel Jiang <jiangd at vmware.com>
|
Daniel Jiang <jiangd at vmware.com>
|
||||||
|
@ -2,8 +2,8 @@ appname = registry
|
|||||||
runmode = dev
|
runmode = dev
|
||||||
|
|
||||||
[lang]
|
[lang]
|
||||||
types = en-US|zh-CN|de-DE|ru-RU
|
types = en-US|zh-CN|de-DE|ru-RU|ja-JP
|
||||||
names = en-US|zh-CN|de-DE|ru-RU
|
names = en-US|zh-CN|de-DE|ru-RU|ja-JP
|
||||||
|
|
||||||
[dev]
|
[dev]
|
||||||
httpport = 80
|
httpport = 80
|
||||||
|
@ -13,7 +13,7 @@ Project Harbor is an enterprise-class registry server, which extends the open so
|
|||||||
* **Graphical user portal**: User can easily browse, search Docker repositories, manage projects/namespaces.
|
* **Graphical user portal**: User can easily browse, search Docker repositories, manage projects/namespaces.
|
||||||
* **AD/LDAP support**: Harbor integrates with existing enterprise AD/LDAP for user authentication and management.
|
* **AD/LDAP support**: Harbor integrates with existing enterprise AD/LDAP for user authentication and management.
|
||||||
* **Auditing**: All the operations to the repositories are tracked.
|
* **Auditing**: All the operations to the repositories are tracked.
|
||||||
* **Internationalization**: Already localized for English, Chinese, German and Russian. More languages can be added.
|
* **Internationalization**: Already localized for English, Chinese, German, Japanese and Russian. More languages can be added.
|
||||||
* **RESTful API**: RESTful APIs for most administrative operations, easing intergration with external management platforms.
|
* **RESTful API**: RESTful APIs for most administrative operations, easing intergration with external management platforms.
|
||||||
|
|
||||||
### Getting Started
|
### Getting Started
|
||||||
@ -67,7 +67,7 @@ Harbor is available under the [Apache 2 license](LICENSE).
|
|||||||
<a href="https://www.caicloud.io" border="0"><img alt="CaiCloud" src="docs/img/caicloudLogoWeb.png"></a>
|
<a href="https://www.caicloud.io" border="0"><img alt="CaiCloud" src="docs/img/caicloudLogoWeb.png"></a>
|
||||||
|
|
||||||
### Users
|
### Users
|
||||||
<a href="https://www.madailicai.com/" border="0" target="_blank"><img alt="MaDaiLiCai" src="docs/img/UserMaDai.jpg"></a>
|
<a href="https://www.madailicai.com/" border="0" target="_blank"><img alt="MaDaiLiCai" src="docs/img/UserMaDai.jpg"></a> <a href="https://www.dianrong.com/" border="0" target="_blank"><img alt="Dianrong" src="docs/img/dianrong.png"></a>
|
||||||
|
|
||||||
### Supporting Technologies
|
### Supporting Technologies
|
||||||
<img alt="beego" src="docs/img/beegoLogo.png"> Harbor is powered by <a href="http://beego.me/">Beego</a>, an open source framework to build and develop applications in the Go way.
|
<img alt="beego" src="docs/img/beegoLogo.png"> Harbor is powered by <a href="http://beego.me/">Beego</a>, an open source framework to build and develop applications in the Go way.
|
||||||
|
47
ROADMAP.md
Normal file
47
ROADMAP.md
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
## Harbor Roadmap
|
||||||
|
|
||||||
|
### About this document
|
||||||
|
|
||||||
|
This document provides description of items that are gathered from the community and planned in Harbor's roadmap. This should serve as a reference point for Harbor users and contributors to understand where the project is heading, and help determine if a contribution could be conflicting with a longer term plan.
|
||||||
|
|
||||||
|
### How to help?
|
||||||
|
|
||||||
|
Discussion on the roadmap can take place in threads under [Issues](https://github.com/vmware/harbor/issues). Please open and comment on an issue if you want to provide suggestions and feedback to an item in the roadmap. Please review the roadmap to avoid potential duplicated effort.
|
||||||
|
|
||||||
|
### How to add an item to the roadmap?
|
||||||
|
Please open an issue to track any initiative on the roadmap of Harbor. We will work with and rely on our community to focus our efforts to improve Harbor.
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
|
||||||
|
### 1. Image replication between Harbor instances
|
||||||
|
Enable images to be replicated between two or more Harbor instances. This is useful to have multiple registry servers servicing a large cluster of nodes, or have distributed registry instances with identical images.
|
||||||
|
|
||||||
|
### 2. Image deletion and garbage collection
|
||||||
|
a) Images can be deleted from UI. The files of deleted images are not removed immediately.
|
||||||
|
|
||||||
|
b) The files of deleted images are recycled by an administrator during system maintenance(Garbage collection). The registry service must be shut down during the process of garbage collection.
|
||||||
|
|
||||||
|
|
||||||
|
### 3. Authentication (OAuth2)
|
||||||
|
In addition to LDAP/AD and local users, OAuth 2.0 can be used to authenticate a user.
|
||||||
|
|
||||||
|
### 4. High Availability
|
||||||
|
Support multi-node deployment of Harbor for high availability, scalability and load-balancing purposes.
|
||||||
|
|
||||||
|
### 5. Statistics and description for repositories
|
||||||
|
User can add a description to a repository. The access count of a repo can be aggregated and displayed.
|
||||||
|
|
||||||
|
|
||||||
|
### 6. Audit all operations in the system
|
||||||
|
Currently only image related operations are logged. Other operations in Harbor, such as user creation/deletion, role changes, password reset, should be tracked as well.
|
||||||
|
|
||||||
|
|
||||||
|
### 7. Migration tool to move from an existing registry to Harbor
|
||||||
|
A tool to migrate images from a vanilla registry server to Harbor, without the need to export/import a large amount of data.
|
||||||
|
|
||||||
|
|
||||||
|
### 8. Support API versioning
|
||||||
|
Provide versioning of Harbor's API.
|
||||||
|
|
26
api/base.go
26
api/base.go
@ -17,8 +17,10 @@ package api
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/astaxie/beego/validation"
|
||||||
"github.com/vmware/harbor/auth"
|
"github.com/vmware/harbor/auth"
|
||||||
"github.com/vmware/harbor/dao"
|
"github.com/vmware/harbor/dao"
|
||||||
"github.com/vmware/harbor/models"
|
"github.com/vmware/harbor/models"
|
||||||
@ -51,6 +53,30 @@ func (b *BaseAPI) DecodeJSONReq(v interface{}) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Validate validates v if it implements interface validation.ValidFormer
|
||||||
|
func (b *BaseAPI) Validate(v interface{}) {
|
||||||
|
validator := validation.Validation{}
|
||||||
|
isValid, err := validator.Valid(v)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("failed to validate: %v", err)
|
||||||
|
b.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
|
||||||
|
}
|
||||||
|
|
||||||
|
if !isValid {
|
||||||
|
message := ""
|
||||||
|
for _, e := range validator.Errors {
|
||||||
|
message += fmt.Sprintf("%s %s \n", e.Field, e.Message)
|
||||||
|
}
|
||||||
|
b.CustomAbort(http.StatusBadRequest, message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecodeJSONReqAndValidate does both decoding and validation
|
||||||
|
func (b *BaseAPI) DecodeJSONReqAndValidate(v interface{}) {
|
||||||
|
b.DecodeJSONReq(v)
|
||||||
|
b.Validate(v)
|
||||||
|
}
|
||||||
|
|
||||||
// ValidateUser checks if the request triggered by a valid user
|
// ValidateUser checks if the request triggered by a valid user
|
||||||
func (b *BaseAPI) ValidateUser() int {
|
func (b *BaseAPI) ValidateUser() int {
|
||||||
|
|
||||||
|
@ -69,9 +69,40 @@ func (pa *RepPolicyAPI) Get() {
|
|||||||
|
|
||||||
// Post creates a policy, and if it is enbled, the replication will be triggered right now.
|
// Post creates a policy, and if it is enbled, the replication will be triggered right now.
|
||||||
func (pa *RepPolicyAPI) Post() {
|
func (pa *RepPolicyAPI) Post() {
|
||||||
policy := models.RepPolicy{}
|
policy := &models.RepPolicy{}
|
||||||
pa.DecodeJSONReq(&policy)
|
pa.DecodeJSONReqAndValidate(policy)
|
||||||
pid, err := dao.AddRepPolicy(policy)
|
|
||||||
|
po, err := dao.GetRepPolicyByName(policy.Name)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("failed to get policy %s: %v", policy.Name, err)
|
||||||
|
pa.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
|
||||||
|
}
|
||||||
|
|
||||||
|
if po != nil {
|
||||||
|
pa.CustomAbort(http.StatusConflict, "name is already used")
|
||||||
|
}
|
||||||
|
|
||||||
|
project, err := dao.GetProjectByID(policy.ProjectID)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("failed to get project %d: %v", policy.ProjectID, err)
|
||||||
|
pa.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
|
||||||
|
}
|
||||||
|
|
||||||
|
if project == nil {
|
||||||
|
pa.CustomAbort(http.StatusBadRequest, fmt.Sprintf("project %d does not exist", policy.ProjectID))
|
||||||
|
}
|
||||||
|
|
||||||
|
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))
|
||||||
|
}
|
||||||
|
|
||||||
|
pid, err := dao.AddRepPolicy(*policy)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("Failed to add policy to DB, error: %v", err)
|
log.Errorf("Failed to add policy to DB, error: %v", err)
|
||||||
pa.RenderError(http.StatusInternalServerError, "Internal Error")
|
pa.RenderError(http.StatusInternalServerError, "Internal Error")
|
||||||
|
@ -164,10 +164,16 @@ func (t *TargetAPI) Get() {
|
|||||||
// Post ...
|
// Post ...
|
||||||
func (t *TargetAPI) Post() {
|
func (t *TargetAPI) Post() {
|
||||||
target := &models.RepTarget{}
|
target := &models.RepTarget{}
|
||||||
t.DecodeJSONReq(target)
|
t.DecodeJSONReqAndValidate(target)
|
||||||
|
|
||||||
if len(target.Name) == 0 || len(target.URL) == 0 {
|
ta, err := dao.GetRepTargetByName(target.Name)
|
||||||
t.CustomAbort(http.StatusBadRequest, "name or URL is nil")
|
if err != nil {
|
||||||
|
log.Errorf("failed to get target %s: %v", target.Name, err)
|
||||||
|
t.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
|
||||||
|
}
|
||||||
|
|
||||||
|
if ta != nil {
|
||||||
|
t.CustomAbort(http.StatusConflict, "name is already used")
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(target.Password) != 0 {
|
if len(target.Password) != 0 {
|
||||||
@ -187,16 +193,32 @@ func (t *TargetAPI) Post() {
|
|||||||
func (t *TargetAPI) Put() {
|
func (t *TargetAPI) Put() {
|
||||||
id := t.getIDFromURL()
|
id := t.getIDFromURL()
|
||||||
if id == 0 {
|
if id == 0 {
|
||||||
t.CustomAbort(http.StatusBadRequest, http.StatusText(http.StatusBadRequest))
|
t.CustomAbort(http.StatusBadRequest, "id can not be empty or 0")
|
||||||
}
|
}
|
||||||
|
|
||||||
target := &models.RepTarget{}
|
target := &models.RepTarget{}
|
||||||
t.DecodeJSONReq(target)
|
t.DecodeJSONReqAndValidate(target)
|
||||||
|
|
||||||
if target.ID == 0 {
|
originTarget, err := dao.GetRepTarget(id)
|
||||||
target.ID = id
|
if err != nil {
|
||||||
|
log.Errorf("failed to get target %d: %v", id, err)
|
||||||
|
t.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if target.Name != originTarget.Name {
|
||||||
|
ta, err := dao.GetRepTargetByName(target.Name)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("failed to get target %s: %v", target.Name, err)
|
||||||
|
t.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
|
||||||
|
}
|
||||||
|
|
||||||
|
if ta != nil {
|
||||||
|
t.CustomAbort(http.StatusConflict, "name is already used")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
target.ID = id
|
||||||
|
|
||||||
if len(target.Password) != 0 {
|
if len(target.Password) != 0 {
|
||||||
target.Password = utils.ReversibleEncrypt(target.Password)
|
target.Password = utils.ReversibleEncrypt(target.Password)
|
||||||
}
|
}
|
||||||
|
@ -69,7 +69,7 @@ func (c *CommonController) Login() {
|
|||||||
// SwitchLanguage handles UI request to switch between different languages and re-render template based on language.
|
// SwitchLanguage handles UI request to switch between different languages and re-render template based on language.
|
||||||
func (c *CommonController) SwitchLanguage() {
|
func (c *CommonController) SwitchLanguage() {
|
||||||
lang := c.GetString("lang")
|
lang := c.GetString("lang")
|
||||||
if lang == "en-US" || lang == "zh-CN" || lang == "de-DE" || lang == "ru-RU" {
|
if lang == "en-US" || lang == "zh-CN" || lang == "de-DE" || lang == "ru-RU" || lang == "ja-JP" {
|
||||||
c.SetSession("lang", lang)
|
c.SetSession("lang", lang)
|
||||||
c.Data["Lang"] = lang
|
c.Data["Lang"] = lang
|
||||||
}
|
}
|
||||||
|
@ -766,6 +766,78 @@ func TestAddRepTarget(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestGetRepTargetByName(t *testing.T) {
|
||||||
|
target, err := GetRepTarget(targetID)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to get target %d: %v", targetID, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
target2, err := GetRepTargetByName(target.Name)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to get target %s: %v", target.Name, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if target.Name != target2.Name {
|
||||||
|
t.Errorf("unexpected target name: %s, expected: %s", target2.Name, target.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUpdateRepTarget(t *testing.T) {
|
||||||
|
target := &models.RepTarget{
|
||||||
|
Name: "name",
|
||||||
|
URL: "http://url",
|
||||||
|
Username: "username",
|
||||||
|
Password: "password",
|
||||||
|
}
|
||||||
|
|
||||||
|
id, err := AddRepTarget(*target)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to add target: %v", err)
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
if err := DeleteRepTarget(id); err != nil {
|
||||||
|
t.Logf("failed to delete target %d: %v", id, err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
target.ID = id
|
||||||
|
target.Name = "new_name"
|
||||||
|
target.URL = "http://new_url"
|
||||||
|
target.Username = "new_username"
|
||||||
|
target.Password = "new_password"
|
||||||
|
|
||||||
|
if err = UpdateRepTarget(*target); err != nil {
|
||||||
|
t.Fatalf("failed to update target: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
target, err = GetRepTarget(id)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to get target %d: %v", id, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if target.Name != "new_name" {
|
||||||
|
t.Errorf("unexpected name: %s, expected: %s", target.Name, "new_name")
|
||||||
|
}
|
||||||
|
|
||||||
|
if target.URL != "http://new_url" {
|
||||||
|
t.Errorf("unexpected url: %s, expected: %s", target.URL, "http://new_url")
|
||||||
|
}
|
||||||
|
|
||||||
|
if target.Username != "new_username" {
|
||||||
|
t.Errorf("unexpected username: %s, expected: %s", target.Username, "new_username")
|
||||||
|
}
|
||||||
|
|
||||||
|
if target.Password != "new_password" {
|
||||||
|
t.Errorf("unexpected password: %s, expected: %s", target.Password, "new_password")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetAllRepTargets(t *testing.T) {
|
||||||
|
if _, err := GetAllRepTargets(); err != nil {
|
||||||
|
t.Fatalf("failed to get all targets: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestAddRepPolicy(t *testing.T) {
|
func TestAddRepPolicy(t *testing.T) {
|
||||||
policy := models.RepPolicy{
|
policy := models.RepPolicy{
|
||||||
ProjectID: 1,
|
ProjectID: 1,
|
||||||
@ -800,6 +872,23 @@ func TestAddRepPolicy(t *testing.T) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestGetRepPolicyByName(t *testing.T) {
|
||||||
|
policy, err := GetRepPolicy(policyID)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to get policy %d: %v", policyID, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
policy2, err := GetRepPolicyByName(policy.Name)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to get policy %s: %v", policy.Name, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if policy.Name != policy2.Name {
|
||||||
|
t.Errorf("unexpected name: %s, expected: %s", policy2.Name, policy.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
func TestDisableRepPolicy(t *testing.T) {
|
func TestDisableRepPolicy(t *testing.T) {
|
||||||
err := DisableRepPolicy(policyID)
|
err := DisableRepPolicy(policyID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -11,13 +11,13 @@ import (
|
|||||||
|
|
||||||
// AddRepTarget ...
|
// AddRepTarget ...
|
||||||
func AddRepTarget(target models.RepTarget) (int64, error) {
|
func AddRepTarget(target models.RepTarget) (int64, error) {
|
||||||
o := orm.NewOrm()
|
o := GetOrmer()
|
||||||
return o.Insert(&target)
|
return o.Insert(&target)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetRepTarget ...
|
// GetRepTarget ...
|
||||||
func GetRepTarget(id int64) (*models.RepTarget, error) {
|
func GetRepTarget(id int64) (*models.RepTarget, error) {
|
||||||
o := orm.NewOrm()
|
o := GetOrmer()
|
||||||
t := models.RepTarget{ID: id}
|
t := models.RepTarget{ID: id}
|
||||||
err := o.Read(&t)
|
err := o.Read(&t)
|
||||||
if err == orm.ErrNoRows {
|
if err == orm.ErrNoRows {
|
||||||
@ -26,28 +26,34 @@ func GetRepTarget(id int64) (*models.RepTarget, error) {
|
|||||||
return &t, err
|
return &t, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetRepTargetByName ...
|
||||||
|
func GetRepTargetByName(name string) (*models.RepTarget, error) {
|
||||||
|
o := GetOrmer()
|
||||||
|
t := models.RepTarget{Name: name}
|
||||||
|
err := o.Read(&t, "Name")
|
||||||
|
if err == orm.ErrNoRows {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return &t, err
|
||||||
|
}
|
||||||
|
|
||||||
// DeleteRepTarget ...
|
// DeleteRepTarget ...
|
||||||
func DeleteRepTarget(id int64) error {
|
func DeleteRepTarget(id int64) error {
|
||||||
o := orm.NewOrm()
|
o := GetOrmer()
|
||||||
_, err := o.Delete(&models.RepTarget{ID: id})
|
_, err := o.Delete(&models.RepTarget{ID: id})
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateRepTarget ...
|
// UpdateRepTarget ...
|
||||||
func UpdateRepTarget(target models.RepTarget) error {
|
func UpdateRepTarget(target models.RepTarget) error {
|
||||||
o := orm.NewOrm()
|
o := GetOrmer()
|
||||||
if len(target.Password) != 0 {
|
_, err := o.Update(&target, "URL", "Name", "Username", "Password")
|
||||||
_, err := o.Update(&target)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err := o.Update(&target, "URL", "Name", "Username")
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetAllRepTargets ...
|
// GetAllRepTargets ...
|
||||||
func GetAllRepTargets() ([]*models.RepTarget, error) {
|
func GetAllRepTargets() ([]*models.RepTarget, error) {
|
||||||
o := orm.NewOrm()
|
o := GetOrmer()
|
||||||
qs := o.QueryTable(&models.RepTarget{})
|
qs := o.QueryTable(&models.RepTarget{})
|
||||||
var targets []*models.RepTarget
|
var targets []*models.RepTarget
|
||||||
_, err := qs.All(&targets)
|
_, err := qs.All(&targets)
|
||||||
@ -56,7 +62,7 @@ func GetAllRepTargets() ([]*models.RepTarget, error) {
|
|||||||
|
|
||||||
// AddRepPolicy ...
|
// AddRepPolicy ...
|
||||||
func AddRepPolicy(policy models.RepPolicy) (int64, error) {
|
func AddRepPolicy(policy models.RepPolicy) (int64, error) {
|
||||||
o := orm.NewOrm()
|
o := GetOrmer()
|
||||||
sqlTpl := `insert into replication_policy (name, project_id, target_id, enabled, description, cron_str, start_time, creation_time, update_time ) values (?, ?, ?, ?, ?, ?, %s, NOW(), NOW())`
|
sqlTpl := `insert into replication_policy (name, project_id, target_id, enabled, description, cron_str, start_time, creation_time, update_time ) values (?, ?, ?, ?, ?, ?, %s, NOW(), NOW())`
|
||||||
var sql string
|
var sql string
|
||||||
if policy.Enabled == 1 {
|
if policy.Enabled == 1 {
|
||||||
@ -78,7 +84,7 @@ func AddRepPolicy(policy models.RepPolicy) (int64, error) {
|
|||||||
|
|
||||||
// GetRepPolicy ...
|
// GetRepPolicy ...
|
||||||
func GetRepPolicy(id int64) (*models.RepPolicy, error) {
|
func GetRepPolicy(id int64) (*models.RepPolicy, error) {
|
||||||
o := orm.NewOrm()
|
o := GetOrmer()
|
||||||
p := models.RepPolicy{ID: id}
|
p := models.RepPolicy{ID: id}
|
||||||
err := o.Read(&p)
|
err := o.Read(&p)
|
||||||
if err == orm.ErrNoRows {
|
if err == orm.ErrNoRows {
|
||||||
@ -87,24 +93,35 @@ func GetRepPolicy(id int64) (*models.RepPolicy, error) {
|
|||||||
return &p, err
|
return &p, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetRepPolicyByName ...
|
||||||
|
func GetRepPolicyByName(name string) (*models.RepPolicy, error) {
|
||||||
|
o := GetOrmer()
|
||||||
|
p := models.RepPolicy{Name: name}
|
||||||
|
err := o.Read(&p, "Name")
|
||||||
|
if err == orm.ErrNoRows {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return &p, err
|
||||||
|
}
|
||||||
|
|
||||||
// GetRepPolicyByProject ...
|
// GetRepPolicyByProject ...
|
||||||
func GetRepPolicyByProject(projectID int64) ([]*models.RepPolicy, error) {
|
func GetRepPolicyByProject(projectID int64) ([]*models.RepPolicy, error) {
|
||||||
var res []*models.RepPolicy
|
var res []*models.RepPolicy
|
||||||
o := orm.NewOrm()
|
o := GetOrmer()
|
||||||
_, err := o.QueryTable("replication_policy").Filter("project_id", projectID).All(&res)
|
_, err := o.QueryTable("replication_policy").Filter("project_id", projectID).All(&res)
|
||||||
return res, err
|
return res, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteRepPolicy ...
|
// DeleteRepPolicy ...
|
||||||
func DeleteRepPolicy(id int64) error {
|
func DeleteRepPolicy(id int64) error {
|
||||||
o := orm.NewOrm()
|
o := GetOrmer()
|
||||||
_, err := o.Delete(&models.RepPolicy{ID: id})
|
_, err := o.Delete(&models.RepPolicy{ID: id})
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateRepPolicyEnablement ...
|
// UpdateRepPolicyEnablement ...
|
||||||
func UpdateRepPolicyEnablement(id int64, enabled int) error {
|
func UpdateRepPolicyEnablement(id int64, enabled int) error {
|
||||||
o := orm.NewOrm()
|
o := GetOrmer()
|
||||||
p := models.RepPolicy{
|
p := models.RepPolicy{
|
||||||
ID: id,
|
ID: id,
|
||||||
Enabled: enabled}
|
Enabled: enabled}
|
||||||
@ -125,7 +142,7 @@ func DisableRepPolicy(id int64) error {
|
|||||||
|
|
||||||
// AddRepJob ...
|
// AddRepJob ...
|
||||||
func AddRepJob(job models.RepJob) (int64, error) {
|
func AddRepJob(job models.RepJob) (int64, error) {
|
||||||
o := orm.NewOrm()
|
o := GetOrmer()
|
||||||
if len(job.Status) == 0 {
|
if len(job.Status) == 0 {
|
||||||
job.Status = models.JobPending
|
job.Status = models.JobPending
|
||||||
}
|
}
|
||||||
@ -137,7 +154,7 @@ func AddRepJob(job models.RepJob) (int64, error) {
|
|||||||
|
|
||||||
// GetRepJob ...
|
// GetRepJob ...
|
||||||
func GetRepJob(id int64) (*models.RepJob, error) {
|
func GetRepJob(id int64) (*models.RepJob, error) {
|
||||||
o := orm.NewOrm()
|
o := GetOrmer()
|
||||||
j := models.RepJob{ID: id}
|
j := models.RepJob{ID: id}
|
||||||
err := o.Read(&j)
|
err := o.Read(&j)
|
||||||
if err == orm.ErrNoRows {
|
if err == orm.ErrNoRows {
|
||||||
@ -164,20 +181,20 @@ func GetRepJobToStop(policyID int64) ([]*models.RepJob, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func repJobPolicyIDQs(policyID int64) orm.QuerySeter {
|
func repJobPolicyIDQs(policyID int64) orm.QuerySeter {
|
||||||
o := orm.NewOrm()
|
o := GetOrmer()
|
||||||
return o.QueryTable("replication_job").Filter("policy_id", policyID)
|
return o.QueryTable("replication_job").Filter("policy_id", policyID)
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteRepJob ...
|
// DeleteRepJob ...
|
||||||
func DeleteRepJob(id int64) error {
|
func DeleteRepJob(id int64) error {
|
||||||
o := orm.NewOrm()
|
o := GetOrmer()
|
||||||
_, err := o.Delete(&models.RepJob{ID: id})
|
_, err := o.Delete(&models.RepJob{ID: id})
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateRepJobStatus ...
|
// UpdateRepJobStatus ...
|
||||||
func UpdateRepJobStatus(id int64, status string) error {
|
func UpdateRepJobStatus(id int64, status string) error {
|
||||||
o := orm.NewOrm()
|
o := GetOrmer()
|
||||||
j := models.RepJob{
|
j := models.RepJob{
|
||||||
ID: id,
|
ID: id,
|
||||||
Status: status,
|
Status: status,
|
||||||
|
BIN
docs/img/dianrong.png
Normal file
BIN
docs/img/dianrong.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.4 KiB |
@ -1,54 +1,56 @@
|
|||||||
# migration
|
# Migration guide
|
||||||
Migration is a module for migrating database schema between different version of project [harbor](https://github.com/vmware/harbor)
|
Migration is a module for migrating database schema between different version of project [Harbor](https://github.com/vmware/harbor)
|
||||||
|
|
||||||
|
This module is for those machine running Harbor's old version, such as 0.1.0. If your Harbor' version is up to date, please ignore this module.
|
||||||
|
|
||||||
**WARNING!!** You must backup your data before migrating
|
**WARNING!!** You must backup your data before migrating
|
||||||
|
|
||||||
###installation
|
###Installation
|
||||||
- step 1: modify migration.cfg
|
- step 1: change `db_username`, `db_password`, `db_port`, `db_name` in migration.cfg
|
||||||
- step 2: build image from dockerfile
|
- step 2: build image from dockerfile
|
||||||
```
|
```
|
||||||
cd harbor-migration
|
cd harbor-migration
|
||||||
|
|
||||||
docker build -t your-image-name .
|
docker build -t migrate-tool .
|
||||||
```
|
```
|
||||||
|
|
||||||
###migration operation
|
###Migrate Step
|
||||||
- show instruction of harbor-migration
|
- step 1: stop and remove Harbor service
|
||||||
|
|
||||||
```docker run your-image-name help```
|
|
||||||
|
|
||||||
- test mysql connection in harbor-migration
|
|
||||||
|
|
||||||
```docker run -v /data/database:/var/lib/mysql your-image-name test```
|
|
||||||
|
|
||||||
- create backup file in `/path/to/backup`
|
|
||||||
|
|
||||||
```
|
|
||||||
docker run -ti -v /data/database:/var/lib/mysql -v /path/to/backup:/harbor-migration/backup your-image-name backup
|
|
||||||
```
|
|
||||||
|
|
||||||
- restore from backup file in `/path/to/backup`
|
|
||||||
|
|
||||||
```
|
|
||||||
docker run -ti -v /data/database:/var/lib/mysql -v /path/to/backup:/harbor-migration/backup your-image-name restore
|
|
||||||
```
|
|
||||||
|
|
||||||
- perform database schema upgrade
|
|
||||||
|
|
||||||
```docker run -ti -v /data/database:/var/lib/mysql your-image-name up head```
|
|
||||||
|
|
||||||
you can use `-v /etc/localtime:/etc/localtime` to sync container timezone with host timezone.
|
|
||||||
|
|
||||||
you may change `/data/database` to the mysql volumes path you set in docker-compose.yml.
|
|
||||||
###migration step
|
|
||||||
- step 1: stop and remove harbor service
|
|
||||||
|
|
||||||
```
|
```
|
||||||
docker-compose down
|
docker-compose down
|
||||||
```
|
```
|
||||||
- step 2: perform migration operation
|
- step 2: create backup file in `/path/to/backup`
|
||||||
- step 3: rebuild newest harbor images and restart service
|
|
||||||
|
```
|
||||||
|
docker run -ti --rm -v /data/database:/var/lib/mysql -v /path/to/backup:/harbor-migration/backup migrate-tool backup
|
||||||
|
```
|
||||||
|
|
||||||
|
- step 3: perform database schema upgrade
|
||||||
|
|
||||||
|
```docker run -ti --rm -v /data/database:/var/lib/mysql migrate-tool up head```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
- step 4: rebuild newest Harbor images and restart service
|
||||||
|
|
||||||
```
|
```
|
||||||
docker-compose build && docker-compose up -d
|
docker-compose build && docker-compose up -d
|
||||||
```
|
```
|
||||||
|
|
||||||
|
You may change `/data/database` to the mysql volumes path you set in docker-compose.yml.
|
||||||
|
|
||||||
|
###Migration operation reference
|
||||||
|
- You can use `help` to show instruction of Harbor migration
|
||||||
|
|
||||||
|
```docker run migrate-tool help```
|
||||||
|
|
||||||
|
- You can use `test` to test mysql connection in Harbor migration
|
||||||
|
|
||||||
|
```docker run --rm -v /data/database:/var/lib/mysql migrate-tool test```
|
||||||
|
|
||||||
|
- You can restore from backup file in `/path/to/backup`
|
||||||
|
|
||||||
|
```
|
||||||
|
docker run -ti --rm -v /data/database:/var/lib/mysql -v /path/to/backup:/harbor-migration/backup migrate-tool restore
|
||||||
|
```
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
import sqlalchemy as sa
|
import sqlalchemy as sa
|
||||||
from sqlalchemy.ext.declarative import declarative_base
|
from sqlalchemy.ext.declarative import declarative_base
|
||||||
from sqlalchemy.orm import sessionmaker, relationship
|
from sqlalchemy.orm import sessionmaker, relationship
|
||||||
|
from sqlalchemy.dialects import mysql
|
||||||
|
|
||||||
Base = declarative_base()
|
Base = declarative_base()
|
||||||
|
|
||||||
@ -20,8 +21,8 @@ class User(Base):
|
|||||||
reset_uuid = sa.Column(sa.String(40))
|
reset_uuid = sa.Column(sa.String(40))
|
||||||
salt = sa.Column(sa.String(40))
|
salt = sa.Column(sa.String(40))
|
||||||
sysadmin_flag = sa.Column(sa.Integer)
|
sysadmin_flag = sa.Column(sa.Integer)
|
||||||
creation_time = sa.Column(sa.DateTime)
|
creation_time = sa.Column(mysql.TIMESTAMP)
|
||||||
update_time = sa.Column(sa.DateTime)
|
update_time = sa.Column(mysql.TIMESTAMP)
|
||||||
|
|
||||||
class Properties(Base):
|
class Properties(Base):
|
||||||
__tablename__ = 'properties'
|
__tablename__ = 'properties'
|
||||||
@ -35,8 +36,8 @@ class ProjectMember(Base):
|
|||||||
project_id = sa.Column(sa.Integer(), primary_key = True)
|
project_id = sa.Column(sa.Integer(), primary_key = True)
|
||||||
user_id = sa.Column(sa.Integer(), primary_key = True)
|
user_id = sa.Column(sa.Integer(), primary_key = True)
|
||||||
role = sa.Column(sa.Integer(), nullable = False)
|
role = sa.Column(sa.Integer(), nullable = False)
|
||||||
creation_time = sa.Column(sa.DateTime(), nullable = True)
|
creation_time = sa.Column(mysql.TIMESTAMP, nullable = True)
|
||||||
update_time = sa.Column(sa.DateTime(), nullable = True)
|
update_time = sa.Column(mysql.TIMESTAMP, nullable = True)
|
||||||
sa.ForeignKeyConstraint(['project_id'], [u'project.project_id'], ),
|
sa.ForeignKeyConstraint(['project_id'], [u'project.project_id'], ),
|
||||||
sa.ForeignKeyConstraint(['role'], [u'role.role_id'], ),
|
sa.ForeignKeyConstraint(['role'], [u'role.role_id'], ),
|
||||||
sa.ForeignKeyConstraint(['user_id'], [u'user.user_id'], ),
|
sa.ForeignKeyConstraint(['user_id'], [u'user.user_id'], ),
|
||||||
@ -79,8 +80,8 @@ class Project(Base):
|
|||||||
project_id = sa.Column(sa.Integer, primary_key=True)
|
project_id = sa.Column(sa.Integer, primary_key=True)
|
||||||
owner_id = sa.Column(sa.ForeignKey(u'user.user_id'), nullable=False, index=True)
|
owner_id = sa.Column(sa.ForeignKey(u'user.user_id'), nullable=False, index=True)
|
||||||
name = sa.Column(sa.String(30), nullable=False, unique=True)
|
name = sa.Column(sa.String(30), nullable=False, unique=True)
|
||||||
creation_time = sa.Column(sa.DateTime)
|
creation_time = sa.Column(mysql.TIMESTAMP)
|
||||||
update_time = sa.Column(sa.DateTime)
|
update_time = sa.Column(mysql.TIMESTAMP)
|
||||||
deleted = sa.Column(sa.Integer, nullable=False, server_default=sa.text("'0'"))
|
deleted = sa.Column(sa.Integer, nullable=False, server_default=sa.text("'0'"))
|
||||||
public = sa.Column(sa.Integer, nullable=False, server_default=sa.text("'0'"))
|
public = sa.Column(sa.Integer, nullable=False, server_default=sa.text("'0'"))
|
||||||
owner = relationship(u'User')
|
owner = relationship(u'User')
|
||||||
|
@ -27,9 +27,10 @@ branch_labels = None
|
|||||||
depends_on = None
|
depends_on = None
|
||||||
|
|
||||||
from alembic import op
|
from alembic import op
|
||||||
from datetime import datetime
|
|
||||||
from db_meta import *
|
from db_meta import *
|
||||||
|
|
||||||
|
from sqlalchemy.dialects import mysql
|
||||||
|
|
||||||
Session = sessionmaker()
|
Session = sessionmaker()
|
||||||
|
|
||||||
def upgrade():
|
def upgrade():
|
||||||
@ -44,12 +45,9 @@ def upgrade():
|
|||||||
session.add(Properties(k='schema_version', v='0.1.1'))
|
session.add(Properties(k='schema_version', v='0.1.1'))
|
||||||
|
|
||||||
#add column to table user
|
#add column to table user
|
||||||
op.add_column('user', sa.Column('creation_time', sa.DateTime(), nullable=True))
|
op.add_column('user', sa.Column('creation_time', mysql.TIMESTAMP, nullable=True))
|
||||||
op.add_column('user', sa.Column('sysadmin_flag', sa.Integer(), nullable=True))
|
op.add_column('user', sa.Column('sysadmin_flag', sa.Integer(), nullable=True))
|
||||||
op.add_column('user', sa.Column('update_time', sa.DateTime(), nullable=True))
|
op.add_column('user', sa.Column('update_time', mysql.TIMESTAMP, nullable=True))
|
||||||
|
|
||||||
#fill update_time data into table user
|
|
||||||
session.query(User).update({User.update_time: datetime.now()})
|
|
||||||
|
|
||||||
#init all sysadmin_flag = 0
|
#init all sysadmin_flag = 0
|
||||||
session.query(User).update({User.sysadmin_flag: 0})
|
session.query(User).update({User.sysadmin_flag: 0})
|
||||||
@ -62,7 +60,7 @@ def upgrade():
|
|||||||
for result in join_result:
|
for result in join_result:
|
||||||
session.add(ProjectMember(project_id=result.project_role.project_id, \
|
session.add(ProjectMember(project_id=result.project_role.project_id, \
|
||||||
user_id=result.user_id, role=result.project_role.role_id, \
|
user_id=result.user_id, role=result.project_role.role_id, \
|
||||||
creation_time=datetime.now(), update_time=datetime.now()))
|
creation_time=None, update_time=None))
|
||||||
|
|
||||||
#update sysadmin_flag
|
#update sysadmin_flag
|
||||||
sys_admin_result = session.query(UserProjectRole).\
|
sys_admin_result = session.query(UserProjectRole).\
|
||||||
@ -89,10 +87,8 @@ def upgrade():
|
|||||||
session.query(Access).update({Access.access_id: Access.access_id - 1})
|
session.query(Access).update({Access.access_id: Access.access_id - 1})
|
||||||
|
|
||||||
#add column to table project
|
#add column to table project
|
||||||
op.add_column('project', sa.Column('update_time', sa.DateTime(), nullable=True))
|
op.add_column('project', sa.Column('update_time', mysql.TIMESTAMP, nullable=True))
|
||||||
|
|
||||||
#fill update_time data into table project
|
|
||||||
session.query(Project).update({Project.update_time: datetime.now()})
|
|
||||||
session.commit()
|
session.commit()
|
||||||
|
|
||||||
def downgrade():
|
def downgrade():
|
||||||
|
@ -2,6 +2,8 @@ package models
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/astaxie/beego/validation"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -42,6 +44,33 @@ type RepPolicy struct {
|
|||||||
UpdateTime time.Time `orm:"column(update_time);auto_now" json:"update_time"`
|
UpdateTime time.Time `orm:"column(update_time);auto_now" json:"update_time"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Valid ...
|
||||||
|
func (r *RepPolicy) Valid(v *validation.Validation) {
|
||||||
|
if len(r.Name) == 0 {
|
||||||
|
v.SetError("name", "can not be empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(r.Name) > 256 {
|
||||||
|
v.SetError("name", "max length is 256")
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.ProjectID <= 0 {
|
||||||
|
v.SetError("project_id", "invalid")
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.TargetID <= 0 {
|
||||||
|
v.SetError("target_id", "invalid")
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.Enabled != 0 && r.Enabled != 1 {
|
||||||
|
v.SetError("enabled", "must be 0 or 1")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(r.CronStr) > 256 {
|
||||||
|
v.SetError("cron_str", "max length is 256")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// RepJob is the model for a replication job, which is the execution unit on job service, currently it is used to transfer/remove
|
// RepJob is the model for a replication job, which is the execution unit on job service, currently it is used to transfer/remove
|
||||||
// a repository to/from a remote registry instance.
|
// a repository to/from a remote registry instance.
|
||||||
type RepJob struct {
|
type RepJob struct {
|
||||||
@ -68,17 +97,42 @@ type RepTarget struct {
|
|||||||
UpdateTime time.Time `orm:"column(update_time);auto_now" json:"update_time"`
|
UpdateTime time.Time `orm:"column(update_time);auto_now" json:"update_time"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Valid ...
|
||||||
|
func (r *RepTarget) Valid(v *validation.Validation) {
|
||||||
|
if len(r.Name) == 0 {
|
||||||
|
v.SetError("name", "can not be empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(r.Name) > 64 {
|
||||||
|
v.SetError("name", "max length is 64")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(r.URL) == 0 {
|
||||||
|
v.SetError("endpoint", "can not be empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(r.URL) > 64 {
|
||||||
|
v.SetError("endpoint", "max length is 64")
|
||||||
|
}
|
||||||
|
|
||||||
|
// password is encoded using base64, the length of this field
|
||||||
|
// in DB is 64, so the max length in request is 48
|
||||||
|
if len(r.Password) > 48 {
|
||||||
|
v.SetError("password", "max length is 48")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//TableName is required by by beego orm to map RepTarget to table replication_target
|
//TableName is required by by beego orm to map RepTarget to table replication_target
|
||||||
func (rt *RepTarget) TableName() string {
|
func (r *RepTarget) TableName() string {
|
||||||
return "replication_target"
|
return "replication_target"
|
||||||
}
|
}
|
||||||
|
|
||||||
//TableName is required by by beego orm to map RepJob to table replication_job
|
//TableName is required by by beego orm to map RepJob to table replication_job
|
||||||
func (rj *RepJob) TableName() string {
|
func (r *RepJob) TableName() string {
|
||||||
return "replication_job"
|
return "replication_job"
|
||||||
}
|
}
|
||||||
|
|
||||||
//TableName is required by by beego orm to map RepPolicy to table replication_policy
|
//TableName is required by by beego orm to map RepPolicy to table replication_policy
|
||||||
func (rp *RepPolicy) TableName() string {
|
func (r *RepPolicy) TableName() string {
|
||||||
return "replication_policy"
|
return "replication_policy"
|
||||||
}
|
}
|
||||||
|
@ -75,6 +75,7 @@ language_en-US = English
|
|||||||
language_zh-CN = 中文
|
language_zh-CN = 中文
|
||||||
language_de-DE = Deutsch
|
language_de-DE = Deutsch
|
||||||
language_ru-RU = Русский
|
language_ru-RU = Русский
|
||||||
|
language_ja-JP = 日本語
|
||||||
copyright = Copyright
|
copyright = Copyright
|
||||||
all_rights_reserved = Alle Rechte vorbehalten.
|
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 = Project Harbor ist ein zuverlässiger Enterprise-Class Registry Server. Unternehmen können ihren eigenen Registry Server aufsetzen um die Produktivität und Sicherheit zu erhöhen. Project Harbor kann für Entwicklungs- wie auch Produktiv-Umgebungen genutzt werden.
|
||||||
|
@ -76,6 +76,7 @@ language_en-US = English
|
|||||||
language_zh-CN = 中文
|
language_zh-CN = 中文
|
||||||
language_de-DE = Deutsch
|
language_de-DE = Deutsch
|
||||||
language_ru-RU = Русский
|
language_ru-RU = Русский
|
||||||
|
language_ja-JP = 日本語
|
||||||
copyright = Copyright
|
copyright = Copyright
|
||||||
all_rights_reserved = All rights reserved.
|
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 = Project Harbor is to build an enterprise-class, reliable registry server. Enterprises can set up a private registry server in their own environment to improve productivity as well as security. Project Harbor can be used in both development and production environment.
|
||||||
|
89
static/i18n/locale_ja-JP.ini
Normal file
89
static/i18n/locale_ja-JP.ini
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
page_title_index = Harbor
|
||||||
|
page_title_sign_in = ログイン - Harbor
|
||||||
|
page_title_project = プロジェクト - Harbor
|
||||||
|
page_title_item_details = 詳しい - Harbor
|
||||||
|
page_title_registration = 登録 - Harbor
|
||||||
|
page_title_add_user = ユーザを追加 - Harbor
|
||||||
|
page_title_forgot_password = パスワードを忘れました - Harbor
|
||||||
|
title_forgot_password = パスワードを忘れました
|
||||||
|
page_title_reset_password = パスワードをリセット - Harbor
|
||||||
|
title_reset_password = パスワードをリセット
|
||||||
|
page_title_change_password = パスワードを変更 - Harbor
|
||||||
|
title_change_password = パスワードを変更
|
||||||
|
page_title_search = サーチ - Harbor
|
||||||
|
sign_in = ログイン
|
||||||
|
sign_up = 登録
|
||||||
|
add_user = ユーザを追加
|
||||||
|
log_out = ログアウト
|
||||||
|
search_placeholder = プロジェクト名またはイメージ名
|
||||||
|
change_password = パスワードを変更
|
||||||
|
username_email = ユーザ名/メールアドレス
|
||||||
|
password = パスワード
|
||||||
|
forgot_password = パスワードを忘れました
|
||||||
|
welcome = ようこそ
|
||||||
|
my_projects = マイプロジェクト
|
||||||
|
public_projects = パブリックプロジェクト
|
||||||
|
admin_options = 管理者
|
||||||
|
project_name = プロジェクト名
|
||||||
|
creation_time = 作成日時
|
||||||
|
publicity = パブリック
|
||||||
|
add_project = プロジェクトを追加
|
||||||
|
check_for_publicity = パブリックプロジェクト
|
||||||
|
button_save = 保存する
|
||||||
|
button_cancel = 取り消しする
|
||||||
|
button_submit = 送信する
|
||||||
|
username = ユーザ名
|
||||||
|
email = メールアドレス
|
||||||
|
system_admin = システム管理者
|
||||||
|
dlg_button_ok = OK
|
||||||
|
dlg_button_cancel = 取り消し
|
||||||
|
registration = 登録
|
||||||
|
username_description = ログイン際に使うユーザ名を入力してください。
|
||||||
|
email_description = メールアドレスはパスワードをリセットする際に使われます。
|
||||||
|
full_name = フルネーム
|
||||||
|
full_name_description = フルネームを入力してください。
|
||||||
|
password_description = パスワード7英数字以上で、少なくとも 1小文字、 1大文字と 1数字でなければなりません。
|
||||||
|
confirm_password = パスワードを確認する
|
||||||
|
note_to_the_admin = メモ
|
||||||
|
old_password = 現在のパスワード
|
||||||
|
new_password = 新しいパスワード
|
||||||
|
forgot_password_description = ぱプロジェクトをリセットするメールはこのアドレスに送信します。
|
||||||
|
|
||||||
|
projects = プロジェクト
|
||||||
|
repositories = リポジトリ
|
||||||
|
search = サーチ
|
||||||
|
home = ホーム
|
||||||
|
project = プロジェクト
|
||||||
|
owner = オーナー
|
||||||
|
repo = リポジトリ
|
||||||
|
user = ユーザ
|
||||||
|
logs = ログ
|
||||||
|
repo_name = リポジトリ名
|
||||||
|
repo_tag = リポジトリタグ
|
||||||
|
add_members = メンバーを追加
|
||||||
|
operation = 操作
|
||||||
|
advance = さらに絞りこみで検索
|
||||||
|
all = 全部
|
||||||
|
others = その他
|
||||||
|
start_date = 開始日
|
||||||
|
end_date = 終了日
|
||||||
|
timestamp = タイムスタンプ
|
||||||
|
role = 役割
|
||||||
|
reset_email_hint = このリンクをクリックしてパスワードリセットの処理を続けてください
|
||||||
|
reset_email_subject = パスワードをリセットします
|
||||||
|
language = 日本語
|
||||||
|
language_en-US = English
|
||||||
|
language_zh-CN = 中文
|
||||||
|
language_de-DE = Deutsch
|
||||||
|
language_ru-RU = Русский
|
||||||
|
language_ja-JP = 日本語
|
||||||
|
copyright = コピーライト
|
||||||
|
all_rights_reserved = 無断複写・転載を禁じます
|
||||||
|
index_desc = Harborは、信頼性の高いエンタープライズクラスのRegistryサーバです。タープライズユーザはHarborを利用し、プライベートのRegistryサビースを構築し、生産性および安全性を向上させる事ができます。開発環境はもちろん、生産環境にも使用する事ができます。
|
||||||
|
index_desc_0 = 主な利点:
|
||||||
|
index_desc_1 = 1. セキュリティ: 知的財産権を組織内で確保する。
|
||||||
|
index_desc_2 = 2. 効率: プライベートなので、パブリックRegistryサビースにネットワーク通信が減らす。
|
||||||
|
index_desc_3 = 3. アクセス制御: ロールベースアクセス制御機能を実装し、更に既存のユーザ管理システム(AD/LDAP)と統合することも可能。
|
||||||
|
index_desc_4 = 4. 監査: すべてRegistryサビースへの操作が記録され、検査にに利用できる。
|
||||||
|
index_desc_5 = 5. 管理UI: 使いやすい管理UIが搭載する。
|
||||||
|
index_title = エンタープライズ Registry サビース
|
@ -16,378 +16,441 @@ var global_messages = {
|
|||||||
"username_is_required" : {
|
"username_is_required" : {
|
||||||
"en-US": "Username is required.",
|
"en-US": "Username is required.",
|
||||||
"zh-CN": "用户名为必填项。",
|
"zh-CN": "用户名为必填项。",
|
||||||
|
"ja-JP": "ユーザ名は必須項目です。",
|
||||||
"de-DE": "Benutzername erforderlich.",
|
"de-DE": "Benutzername erforderlich.",
|
||||||
"ru-RU": "Требуется ввести имя пользователя."
|
"ru-RU": "Требуется ввести имя пользователя."
|
||||||
},
|
},
|
||||||
"username_has_been_taken" : {
|
"username_has_been_taken" : {
|
||||||
"en-US": "Username has been taken.",
|
"en-US": "Username has been taken.",
|
||||||
"zh-CN": "用户名已被占用。",
|
"zh-CN": "用户名已被占用。",
|
||||||
|
"ja-JP": "ユーザ名はすでに登録されました。",
|
||||||
"de-DE": "Benutzername bereits vergeben.",
|
"de-DE": "Benutzername bereits vergeben.",
|
||||||
"ru-RU": "Имя пользователя уже используется."
|
"ru-RU": "Имя пользователя уже используется."
|
||||||
},
|
},
|
||||||
"username_is_too_long" : {
|
"username_is_too_long" : {
|
||||||
"en-US": "Username is too long. (maximum 20 characters)",
|
"en-US": "Username is too long. (maximum 20 characters)",
|
||||||
"zh-CN": "用户名长度超出限制。(最长为20个字符)",
|
"zh-CN": "用户名长度超出限制。(最长为20个字符)",
|
||||||
|
"ja-JP": "ユーザ名が長すぎです。(20文字まで)",
|
||||||
"de-DE": "Benutzername ist zu lang. (maximal 20 Zeichen)",
|
"de-DE": "Benutzername ist zu lang. (maximal 20 Zeichen)",
|
||||||
"ru-RU": "Имя пользователя слишком длинное. (максимум 20 символов)"
|
"ru-RU": "Имя пользователя слишком длинное. (максимум 20 символов)"
|
||||||
},
|
},
|
||||||
"username_contains_illegal_chars": {
|
"username_contains_illegal_chars": {
|
||||||
"en-US": "Username contains illegal character(s).",
|
"en-US": "Username contains illegal character(s).",
|
||||||
"zh-CN": "用户名包含不合法的字符。",
|
"zh-CN": "用户名包含不合法的字符。",
|
||||||
|
"ja-JP": "ユーザ名に使えない文字が入っています。",
|
||||||
"de-DE": "Benutzername enthält ungültige Zeichen.",
|
"de-DE": "Benutzername enthält ungültige Zeichen.",
|
||||||
"ru-RU": "Имя пользователя содержит недопустимые символы."
|
"ru-RU": "Имя пользователя содержит недопустимые символы."
|
||||||
},
|
},
|
||||||
"email_is_required" : {
|
"email_is_required" : {
|
||||||
"en-US": "Email is required.",
|
"en-US": "Email is required.",
|
||||||
"zh-CN": "邮箱为必填项。",
|
"zh-CN": "邮箱为必填项。",
|
||||||
|
"ja-JP": "メールアドレスが必須です。",
|
||||||
"de-DE": "E-Mail Adresse erforderlich.",
|
"de-DE": "E-Mail Adresse erforderlich.",
|
||||||
"ru-RU": "Требуется ввести E-mail адрес."
|
"ru-RU": "Требуется ввести E-mail адрес."
|
||||||
},
|
},
|
||||||
"email_contains_illegal_chars" : {
|
"email_contains_illegal_chars" : {
|
||||||
"en-US": "Email contains illegal character(s).",
|
"en-US": "Email contains illegal character(s).",
|
||||||
"zh-CN": "邮箱包含不合法的字符。",
|
"zh-CN": "邮箱包含不合法的字符。",
|
||||||
|
"ja-JP": "メールアドレスに使えない文字が入っています。",
|
||||||
"de-DE": "E-Mail Adresse enthält ungültige Zeichen.",
|
"de-DE": "E-Mail Adresse enthält ungültige Zeichen.",
|
||||||
"ru-RU": "E-mail адрес содержит недопеустимые символы."
|
"ru-RU": "E-mail адрес содержит недопеустимые символы."
|
||||||
},
|
},
|
||||||
"email_has_been_taken" : {
|
"email_has_been_taken" : {
|
||||||
"en-US": "Email has been taken.",
|
"en-US": "Email has been taken.",
|
||||||
"zh-CN": "邮箱已被占用。",
|
"zh-CN": "邮箱已被占用。",
|
||||||
|
"ja-JP": "メールアドレスがすでに使われました。",
|
||||||
"de-DE": "E-Mail Adresse wird bereits verwendet.",
|
"de-DE": "E-Mail Adresse wird bereits verwendet.",
|
||||||
"ru-RU": "Такой E-mail адрес уже используется."
|
"ru-RU": "Такой E-mail адрес уже используется."
|
||||||
},
|
},
|
||||||
"email_content_illegal" : {
|
"email_content_illegal" : {
|
||||||
"en-US": "Email format is illegal.",
|
"en-US": "Email format is illegal.",
|
||||||
"zh-CN": "邮箱格式不合法。",
|
"zh-CN": "邮箱格式不合法。",
|
||||||
|
"ja-JP": "メールアドレスフォーマットエラー。",
|
||||||
"de-DE": "Format der E-Mail Adresse ist ungültig.",
|
"de-DE": "Format der E-Mail Adresse ist ungültig.",
|
||||||
"ru-RU": "Недопустимый формат E-mail адреса."
|
"ru-RU": "Недопустимый формат E-mail адреса."
|
||||||
},
|
},
|
||||||
"email_does_not_exist" : {
|
"email_does_not_exist" : {
|
||||||
"en-US": "Email does not exist.",
|
"en-US": "Email does not exist.",
|
||||||
"zh-CN": "邮箱不存在。",
|
"zh-CN": "邮箱不存在。",
|
||||||
|
"ja-JP": "メールアドレスが存在しません。",
|
||||||
"de-DE": "E-Mail Adresse existiert nicht.",
|
"de-DE": "E-Mail Adresse existiert nicht.",
|
||||||
"ru-RU": "E-mail адрес не существует."
|
"ru-RU": "E-mail адрес не существует."
|
||||||
},
|
},
|
||||||
"realname_is_required" : {
|
"realname_is_required" : {
|
||||||
"en-US": "Full name is required.",
|
"en-US": "Full name is required.",
|
||||||
"zh-CN": "全名为必填项。",
|
"zh-CN": "全名为必填项。",
|
||||||
|
"ja-JP": "フルネームが必須です。",
|
||||||
"de-DE": "Vollständiger Name erforderlich.",
|
"de-DE": "Vollständiger Name erforderlich.",
|
||||||
"ru-RU": "Требуется ввести полное имя."
|
"ru-RU": "Требуется ввести полное имя."
|
||||||
},
|
},
|
||||||
"realname_is_too_long" : {
|
"realname_is_too_long" : {
|
||||||
"en-US": "Full name is too long. (maximum 20 characters)",
|
"en-US": "Full name is too long. (maximum 20 characters)",
|
||||||
"zh-CN": "全名长度超出限制。(最长为20个字符)",
|
"zh-CN": "全名长度超出限制。(最长为20个字符)",
|
||||||
|
"ja-JP": "フルネームは長すぎです。(20文字まで)",
|
||||||
"de-DE": "Vollständiger Name zu lang. (maximal 20 Zeichen)",
|
"de-DE": "Vollständiger Name zu lang. (maximal 20 Zeichen)",
|
||||||
"ru-RU": "Полное имя слишком длинное. (максимум 20 символов)"
|
"ru-RU": "Полное имя слишком длинное. (максимум 20 символов)"
|
||||||
},
|
},
|
||||||
"realname_contains_illegal_chars" : {
|
"realname_contains_illegal_chars" : {
|
||||||
"en-US": "Full name contains illegal character(s).",
|
"en-US": "Full name contains illegal character(s).",
|
||||||
"zh-CN": "全名包含不合法的字符。",
|
"zh-CN": "全名包含不合法的字符。",
|
||||||
|
"ja-JP": "フルネームに使えない文字が入っています。",
|
||||||
"de-DE": "Vollständiger Name enthält ungültige Zeichen.",
|
"de-DE": "Vollständiger Name enthält ungültige Zeichen.",
|
||||||
"ru-RU": "Полное имя содержит недопустимые символы."
|
"ru-RU": "Полное имя содержит недопустимые символы."
|
||||||
},
|
},
|
||||||
"password_is_required" : {
|
"password_is_required" : {
|
||||||
"en-US": "Password is required.",
|
"en-US": "Password is required.",
|
||||||
"zh-CN": "密码为必填项。",
|
"zh-CN": "密码为必填项。",
|
||||||
|
"ja-JP": "パスワードは必須です。",
|
||||||
"de-DE": "Passwort erforderlich.",
|
"de-DE": "Passwort erforderlich.",
|
||||||
"ru-RU": "Требуется ввести пароль."
|
"ru-RU": "Требуется ввести пароль."
|
||||||
},
|
},
|
||||||
"password_is_invalid" : {
|
"password_is_invalid" : {
|
||||||
"en-US": "Password is invalid. At least 7 characters with 1 lowercase letter, 1 capital letter and 1 numeric character.",
|
"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个数字。",
|
"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",
|
"de-DE": "Passwort ungültig. Mindestens sieben Zeichen bestehend aus einem Kleinbuchstaben, einem Großbuchstaben und einer Zahl",
|
||||||
"ru-RU": "Такой пароль недопустим. Парольл должен содержать Минимум 7 символов, в которых будет присутствовать по меньшей мере 1 буква нижнего регистра, 1 буква верхнего регистра и 1 цифра"
|
"ru-RU": "Такой пароль недопустим. Парольл должен содержать Минимум 7 символов, в которых будет присутствовать по меньшей мере 1 буква нижнего регистра, 1 буква верхнего регистра и 1 цифра"
|
||||||
},
|
},
|
||||||
"password_is_too_long" : {
|
"password_is_too_long" : {
|
||||||
"en-US": "Password is too long. (maximum 20 characters)",
|
"en-US": "Password is too long. (maximum 20 characters)",
|
||||||
"zh-CN": "密码长度超出限制。(最长为20个字符)",
|
"zh-CN": "密码长度超出限制。(最长为20个字符)",
|
||||||
|
"ja-JP": "パスワードは長すぎです。(20文字まで)",
|
||||||
"de-DE": "Passwort zu lang. (maximal 20 Zeichen)",
|
"de-DE": "Passwort zu lang. (maximal 20 Zeichen)",
|
||||||
"ru-RU": "Пароль слишком длинный (максимум 20 символов)"
|
"ru-RU": "Пароль слишком длинный (максимум 20 символов)"
|
||||||
},
|
},
|
||||||
"password_does_not_match" : {
|
"password_does_not_match" : {
|
||||||
"en-US": "Passwords do not match.",
|
"en-US": "Passwords do not match.",
|
||||||
"zh-CN": "两次密码输入不一致。",
|
"zh-CN": "两次密码输入不一致。",
|
||||||
|
"ja-JP": "確認のパスワードが正しくありません。",
|
||||||
"de-DE": "Passwörter stimmen nicht überein.",
|
"de-DE": "Passwörter stimmen nicht überein.",
|
||||||
"ru-RU": "Пароли не совпадают."
|
"ru-RU": "Пароли не совпадают."
|
||||||
},
|
},
|
||||||
"comment_is_too_long" : {
|
"comment_is_too_long" : {
|
||||||
"en-US": "Comment is too long. (maximum 20 characters)",
|
"en-US": "Comment is too long. (maximum 20 characters)",
|
||||||
"zh-CN": "备注长度超出限制。(最长为20个字符)",
|
"zh-CN": "备注长度超出限制。(最长为20个字符)",
|
||||||
|
"ja-JP": "コメントは長すぎです。(20文字まで)",
|
||||||
"de-DE": "Kommentar zu lang. (maximal 20 Zeichen)",
|
"de-DE": "Kommentar zu lang. (maximal 20 Zeichen)",
|
||||||
"ru-RU": "Комментарий слишком длинный. (максимум 20 символов)"
|
"ru-RU": "Комментарий слишком длинный. (максимум 20 символов)"
|
||||||
},
|
},
|
||||||
"comment_contains_illegal_chars" : {
|
"comment_contains_illegal_chars" : {
|
||||||
"en-US": "Comment contains illegal character(s).",
|
"en-US": "Comment contains illegal character(s).",
|
||||||
"zh-CN": "备注包含不合法的字符。",
|
"zh-CN": "备注包含不合法的字符。",
|
||||||
|
"ja-JP": "コメントに使えない文字が入っています。",
|
||||||
"de-DE": "Kommentar enthält ungültige Zeichen.",
|
"de-DE": "Kommentar enthält ungültige Zeichen.",
|
||||||
"ru-RU": "Комментарий содержит недопустимые символы."
|
"ru-RU": "Комментарий содержит недопустимые символы."
|
||||||
},
|
},
|
||||||
"project_name_is_required" : {
|
"project_name_is_required" : {
|
||||||
"en-US": "Project name is required.",
|
"en-US": "Project name is required.",
|
||||||
"zh-CN": "项目名称为必填项。",
|
"zh-CN": "项目名称为必填项。",
|
||||||
|
"ja-JP": "プロジェクト名は必須です。",
|
||||||
"de-DE": "Projektname erforderlich.",
|
"de-DE": "Projektname erforderlich.",
|
||||||
"ru-RU": "Необходимо ввести название Проекта."
|
"ru-RU": "Необходимо ввести название Проекта."
|
||||||
},
|
},
|
||||||
"project_name_is_too_short" : {
|
"project_name_is_too_short" : {
|
||||||
"en-US": "Project name is too short. (minimum 4 characters)",
|
"en-US": "Project name is too short. (minimum 4 characters)",
|
||||||
"zh-CN": "项目名称至少要求 4个字符。",
|
"zh-CN": "项目名称至少要求 4个字符。",
|
||||||
|
"ja-JP": "プロジェクト名は4文字以上です。",
|
||||||
"de-DE": "Projektname zu kurz. (mindestens 4 Zeichen)",
|
"de-DE": "Projektname zu kurz. (mindestens 4 Zeichen)",
|
||||||
"ru-RU": "Название проекта слишком короткое. (миниму 4 символа)"
|
"ru-RU": "Название проекта слишком короткое. (миниму 4 символа)"
|
||||||
},
|
},
|
||||||
"project_name_is_too_long" : {
|
"project_name_is_too_long" : {
|
||||||
"en-US": "Project name is too long. (maximum 30 characters)",
|
"en-US": "Project name is too long. (maximum 30 characters)",
|
||||||
"zh-CN": "项目名称长度超出限制。(最长为30个字符)",
|
"zh-CN": "项目名称长度超出限制。(最长为30个字符)",
|
||||||
|
"ja-JP": "プロジェクト名は長すぎです。(30文字まで)",
|
||||||
"de-DE": "Projektname zu lang. (maximal 30 Zeichen)",
|
"de-DE": "Projektname zu lang. (maximal 30 Zeichen)",
|
||||||
"ru-RU": "Название проекта слишком длинное (максимум 30 символов)"
|
"ru-RU": "Название проекта слишком длинное (максимум 30 символов)"
|
||||||
},
|
},
|
||||||
"project_name_contains_illegal_chars" : {
|
"project_name_contains_illegal_chars" : {
|
||||||
"en-US": "Project name contains illegal character(s).",
|
"en-US": "Project name contains illegal character(s).",
|
||||||
"zh-CN": "项目名称包含不合法的字符。",
|
"zh-CN": "项目名称包含不合法的字符。",
|
||||||
|
"ja-JP": "プロジェクト名に使えない文字が入っています。",
|
||||||
"de-DE": "Projektname enthält ungültige Zeichen.",
|
"de-DE": "Projektname enthält ungültige Zeichen.",
|
||||||
"ru-RU": "Название проекта содержит недопустимые символы."
|
"ru-RU": "Название проекта содержит недопустимые символы."
|
||||||
},
|
},
|
||||||
"project_exists" : {
|
"project_exists" : {
|
||||||
"en-US": "Project exists.",
|
"en-US": "Project exists.",
|
||||||
"zh-CN": "项目已存在。",
|
"zh-CN": "项目已存在。",
|
||||||
|
"ja-JP": "プロジェクトはすでに存在しました。",
|
||||||
"de-DE": "Projekt existiert bereits.",
|
"de-DE": "Projekt existiert bereits.",
|
||||||
"ru-RU": "Такой проект уже существует."
|
"ru-RU": "Такой проект уже существует."
|
||||||
},
|
},
|
||||||
"delete_user" : {
|
"delete_user" : {
|
||||||
"en-US": "Delete User",
|
"en-US": "Delete User",
|
||||||
"zh-CN": "删除用户",
|
"zh-CN": "删除用户",
|
||||||
|
"ja-JP": "ユーザを削除",
|
||||||
"de-DE": "Benutzer löschen",
|
"de-DE": "Benutzer löschen",
|
||||||
"ru-RU": "Удалить пользователя"
|
"ru-RU": "Удалить пользователя"
|
||||||
},
|
},
|
||||||
"are_you_sure_to_delete_user" : {
|
"are_you_sure_to_delete_user" : {
|
||||||
"en-US": "Are you sure to delete ",
|
"en-US": "Are you sure to delete ",
|
||||||
"zh-CN": "确认要删除用户 ",
|
"zh-CN": "确认要删除用户 ",
|
||||||
|
"ja-JP": "ユーザを削除でよろしでしょうか ",
|
||||||
"de-DE": "Sind Sie sich sicher, dass Sie folgenden Benutzer löschen möchten: ",
|
"de-DE": "Sind Sie sich sicher, dass Sie folgenden Benutzer löschen möchten: ",
|
||||||
"ru-RU": "Вы уверены что хотите удалить пользователя? "
|
"ru-RU": "Вы уверены что хотите удалить пользователя? "
|
||||||
},
|
},
|
||||||
"input_your_username_and_password" : {
|
"input_your_username_and_password" : {
|
||||||
"en-US": "Please input your username and password.",
|
"en-US": "Please input your username and password.",
|
||||||
"zh-CN": "请输入用户名和密码。",
|
"zh-CN": "请输入用户名和密码。",
|
||||||
|
"ja-JP": "ユーザ名とパスワードを入力してください。",
|
||||||
"de-DE": "Bitte geben Sie ihr Benutzername und Passwort ein.",
|
"de-DE": "Bitte geben Sie ihr Benutzername und Passwort ein.",
|
||||||
"ru-RU": "Введите имя пользователя и пароль."
|
"ru-RU": "Введите имя пользователя и пароль."
|
||||||
},
|
},
|
||||||
"check_your_username_or_password" : {
|
"check_your_username_or_password" : {
|
||||||
"en-US": "Please check your username or password.",
|
"en-US": "Please check your username or password.",
|
||||||
"zh-CN": "请输入正确的用户名或密码。",
|
"zh-CN": "请输入正确的用户名或密码。",
|
||||||
|
"ja-JP": "正しいユーザ名とパスワードを入力してください。",
|
||||||
"de-DE": "Bitte überprüfen Sie ihren Benutzernamen und Passwort.",
|
"de-DE": "Bitte überprüfen Sie ihren Benutzernamen und Passwort.",
|
||||||
"ru-RU": "Проверьте свои имя пользователя и пароль."
|
"ru-RU": "Проверьте свои имя пользователя и пароль."
|
||||||
},
|
},
|
||||||
"title_login_failed" : {
|
"title_login_failed" : {
|
||||||
"en-US": "Login Failed",
|
"en-US": "Login Failed",
|
||||||
"zh-CN": "登录失败",
|
"zh-CN": "登录失败",
|
||||||
|
"ja-JP": "ログインに失敗しました。",
|
||||||
"de-DE": "Anmeldung fehlgeschlagen",
|
"de-DE": "Anmeldung fehlgeschlagen",
|
||||||
"ru-RU": "Ошибка входа"
|
"ru-RU": "Ошибка входа"
|
||||||
},
|
},
|
||||||
"title_change_password" : {
|
"title_change_password" : {
|
||||||
"en-US": "Change Password",
|
"en-US": "Change Password",
|
||||||
"zh-CN": "修改密码",
|
"zh-CN": "修改密码",
|
||||||
|
"ja-JP": "パスワードを変更します。",
|
||||||
"de-DE": "Passwort ändern",
|
"de-DE": "Passwort ändern",
|
||||||
"ru-RU": "Сменить пароль"
|
"ru-RU": "Сменить пароль"
|
||||||
},
|
},
|
||||||
"change_password_successfully" : {
|
"change_password_successfully" : {
|
||||||
"en-US": "Password changed successfully.",
|
"en-US": "Password changed successfully.",
|
||||||
"zh-CN": "密码已修改。",
|
"zh-CN": "密码已修改。",
|
||||||
|
"ja-JP": "パスワードを変更しました。",
|
||||||
"de-DE": "Passwort erfolgreich geändert.",
|
"de-DE": "Passwort erfolgreich geändert.",
|
||||||
"ru-RU": "Пароль успешно изменен."
|
"ru-RU": "Пароль успешно изменен."
|
||||||
},
|
},
|
||||||
"title_forgot_password" : {
|
"title_forgot_password" : {
|
||||||
"en-US": "Forgot Password",
|
"en-US": "Forgot Password",
|
||||||
"zh-CN": "忘记密码",
|
"zh-CN": "忘记密码",
|
||||||
|
"ja-JP": "パスワードをリセットします。",
|
||||||
"de-DE": "Passwort vergessen",
|
"de-DE": "Passwort vergessen",
|
||||||
"ru-RU": "Забыли пароль?"
|
"ru-RU": "Забыли пароль?"
|
||||||
},
|
},
|
||||||
"email_has_been_sent" : {
|
"email_has_been_sent" : {
|
||||||
"en-US": "Email for resetting password has been sent.",
|
"en-US": "Email for resetting password has been sent.",
|
||||||
"zh-CN": "重置密码邮件已发送。",
|
"zh-CN": "重置密码邮件已发送。",
|
||||||
|
"ja-JP": "パスワードをリセットするメールを送信しました。",
|
||||||
"de-DE": "Eine E-Mail mit einem Wiederherstellungslink wurde an Sie gesendet.",
|
"de-DE": "Eine E-Mail mit einem Wiederherstellungslink wurde an Sie gesendet.",
|
||||||
"ru-RU": "На ваш E-mail было выслано письмо с инструкциями по сбросу пароля."
|
"ru-RU": "На ваш E-mail было выслано письмо с инструкциями по сбросу пароля."
|
||||||
},
|
},
|
||||||
"send_email_failed" : {
|
"send_email_failed" : {
|
||||||
"en-US": "Failed to send Email for resetting password.",
|
"en-US": "Failed to send Email for resetting password.",
|
||||||
"zh-CN": "重置密码邮件发送失败。",
|
"zh-CN": "重置密码邮件发送失败。",
|
||||||
|
"ja-JP": "パスワードをリセットするメールを送信する際エラーが出ました",
|
||||||
"de-DE": "Fehler beim Senden der Wiederherstellungs-E-Mail.",
|
"de-DE": "Fehler beim Senden der Wiederherstellungs-E-Mail.",
|
||||||
"ru-RU": "Ошибка отправки сообщения."
|
"ru-RU": "Ошибка отправки сообщения."
|
||||||
},
|
},
|
||||||
"please_login_first" : {
|
"please_login_first" : {
|
||||||
"en-US": "Please login first.",
|
"en-US": "Please login first.",
|
||||||
"zh-CN": "请先登录。",
|
"zh-CN": "请先登录。",
|
||||||
|
"ja-JP": "この先にログインが必要です。",
|
||||||
"de-DE": "Bitte melden Sie sich zuerst an.",
|
"de-DE": "Bitte melden Sie sich zuerst an.",
|
||||||
"ru-RU": "Сначала выполните вход в систему."
|
"ru-RU": "Сначала выполните вход в систему."
|
||||||
},
|
},
|
||||||
"old_password_is_not_correct" : {
|
"old_password_is_not_correct" : {
|
||||||
"en-US": "Old password is not correct.",
|
"en-US": "Old password is not correct.",
|
||||||
"zh-CN": "原密码输入不正确。",
|
"zh-CN": "原密码输入不正确。",
|
||||||
|
"ja-JP": "現在のパスワードが正しく入力されていません。",
|
||||||
"de-DE": "Altes Passwort ist nicht korrekt.",
|
"de-DE": "Altes Passwort ist nicht korrekt.",
|
||||||
"ru-RU": "Старый пароль введен неверно."
|
"ru-RU": "Старый пароль введен неверно."
|
||||||
},
|
},
|
||||||
"please_input_new_password" : {
|
"please_input_new_password" : {
|
||||||
"en-US": "Please input new password.",
|
"en-US": "Please input new password.",
|
||||||
"zh-CN": "请输入新密码。",
|
"zh-CN": "请输入新密码。",
|
||||||
|
"ja-JP": "あたらしいパスワードを入力してください",
|
||||||
"de-DE": "Bitte geben Sie ihr neues Passwort ein.",
|
"de-DE": "Bitte geben Sie ihr neues Passwort ein.",
|
||||||
"ru-RU": "Пожалуйста, введите новый пароль."
|
"ru-RU": "Пожалуйста, введите новый пароль."
|
||||||
},
|
},
|
||||||
"invalid_reset_url": {
|
"invalid_reset_url": {
|
||||||
"en-US": "Invalid URL for resetting password.",
|
"en-US": "Invalid URL for resetting password.",
|
||||||
"zh-CN": "无效密码重置链接。",
|
"zh-CN": "无效密码重置链接。",
|
||||||
|
"ja-JP": "無効なパスワードをリセットするリンク。",
|
||||||
"de-DE": "Ungültige URL zum Passwort wiederherstellen.",
|
"de-DE": "Ungültige URL zum Passwort wiederherstellen.",
|
||||||
"ru-RU": "Неверный URL для сброса пароля."
|
"ru-RU": "Неверный URL для сброса пароля."
|
||||||
},
|
},
|
||||||
"reset_password_successfully" : {
|
"reset_password_successfully" : {
|
||||||
"en-US": "Reset password successfully.",
|
"en-US": "Reset password successfully.",
|
||||||
"zh-CN": "密码重置成功。",
|
"zh-CN": "密码重置成功。",
|
||||||
|
"ja-JP": "パスワードをリセットしました。",
|
||||||
"de-DE": "Passwort erfolgreich wiederhergestellt.",
|
"de-DE": "Passwort erfolgreich wiederhergestellt.",
|
||||||
"ru-RU": "Пароль успешно сброшен."
|
"ru-RU": "Пароль успешно сброшен."
|
||||||
},
|
},
|
||||||
"internal_error": {
|
"internal_error": {
|
||||||
"en-US": "Internal error.",
|
"en-US": "Internal error.",
|
||||||
"zh-CN": "内部错误,请联系系统管理员。",
|
"zh-CN": "内部错误,请联系系统管理员。",
|
||||||
|
"ja-JP": "エラーが出ました、管理者に連絡してください。",
|
||||||
"de-DE": "Interner Fehler.",
|
"de-DE": "Interner Fehler.",
|
||||||
"ru-RU": "Внутренняя ошибка."
|
"ru-RU": "Внутренняя ошибка."
|
||||||
},
|
},
|
||||||
"title_reset_password" : {
|
"title_reset_password" : {
|
||||||
"en-US": "Reset Password",
|
"en-US": "Reset Password",
|
||||||
"zh-CN": "重置密码",
|
"zh-CN": "重置密码",
|
||||||
|
"ja-JP": "パスワードをリセットする",
|
||||||
"de-DE": "Passwort zurücksetzen",
|
"de-DE": "Passwort zurücksetzen",
|
||||||
"ru-RU": "Сбросить пароль"
|
"ru-RU": "Сбросить пароль"
|
||||||
},
|
},
|
||||||
"title_sign_up" : {
|
"title_sign_up" : {
|
||||||
"en-US": "Sign Up",
|
"en-US": "Sign Up",
|
||||||
"zh-CN": "注册",
|
"zh-CN": "注册",
|
||||||
|
"ja-JP": "登録",
|
||||||
"de-DE": "Registrieren",
|
"de-DE": "Registrieren",
|
||||||
"ru-RU": "Регистрация"
|
"ru-RU": "Регистрация"
|
||||||
},
|
},
|
||||||
"title_add_user": {
|
"title_add_user": {
|
||||||
"en-US": "Add User",
|
"en-US": "Add User",
|
||||||
"zh-CN": "新增用户",
|
"zh-CN": "新增用户",
|
||||||
|
"ja-JP": "ユーザを追加",
|
||||||
"de-DE": "Benutzer hinzufügen",
|
"de-DE": "Benutzer hinzufügen",
|
||||||
"ru-RU": "Добавить пользователя"
|
"ru-RU": "Добавить пользователя"
|
||||||
},
|
},
|
||||||
"registered_successfully": {
|
"registered_successfully": {
|
||||||
"en-US": "Signed up successfully.",
|
"en-US": "Signed up successfully.",
|
||||||
"zh-CN": "注册成功。",
|
"zh-CN": "注册成功。",
|
||||||
|
"ja-JP": "登録しました。",
|
||||||
"de-DE": "Erfolgreich registriert.",
|
"de-DE": "Erfolgreich registriert.",
|
||||||
"ru-RU": "Регистрация прошла успешно."
|
"ru-RU": "Регистрация прошла успешно."
|
||||||
},
|
},
|
||||||
"registered_failed" : {
|
"registered_failed" : {
|
||||||
"en-US": "Failed to sign up.",
|
"en-US": "Failed to sign up.",
|
||||||
"zh-CN": "注册失败。",
|
"zh-CN": "注册失败。",
|
||||||
|
"ja-JP": "登録でませんでした。",
|
||||||
"de-DE": "Registrierung fehlgeschlagen.",
|
"de-DE": "Registrierung fehlgeschlagen.",
|
||||||
"ru-RU": "Ошибка регистрации."
|
"ru-RU": "Ошибка регистрации."
|
||||||
},
|
},
|
||||||
"added_user_successfully": {
|
"added_user_successfully": {
|
||||||
"en-US": "Added user successfully.",
|
"en-US": "Added user successfully.",
|
||||||
"zh-CN": "新增用户成功。",
|
"zh-CN": "新增用户成功。",
|
||||||
|
"ja-JP": "ユーザを追加しました。",
|
||||||
"de-DE": "Benutzer erfolgreich erstellt.",
|
"de-DE": "Benutzer erfolgreich erstellt.",
|
||||||
"ru-RU": "Пользователь успешно добавлен."
|
"ru-RU": "Пользователь успешно добавлен."
|
||||||
},
|
},
|
||||||
"added_user_failed": {
|
"added_user_failed": {
|
||||||
"en-US": "Adding user failed.",
|
"en-US": "Adding user failed.",
|
||||||
"zh-CN": "新增用户失败。",
|
"zh-CN": "新增用户失败。",
|
||||||
|
"ja-JP": "ユーザを追加できませんでした。",
|
||||||
"de-DE": "Benutzer erstellen fehlgeschlagen.",
|
"de-DE": "Benutzer erstellen fehlgeschlagen.",
|
||||||
"ru-RU": "Ошибка добавления пользователя."
|
"ru-RU": "Ошибка добавления пользователя."
|
||||||
},
|
},
|
||||||
"projects": {
|
"projects": {
|
||||||
"en-US": "Projects",
|
"en-US": "Projects",
|
||||||
"zh-CN": "项目",
|
"zh-CN": "项目",
|
||||||
|
"ja-JP": "プロジェクト",
|
||||||
"de-DE": "Projekte",
|
"de-DE": "Projekte",
|
||||||
"ru-RU": "Проекты"
|
"ru-RU": "Проекты"
|
||||||
},
|
},
|
||||||
"repositories" : {
|
"repositories" : {
|
||||||
"en-US": "Repositories",
|
"en-US": "Repositories",
|
||||||
"zh-CN": "镜像仓库",
|
"zh-CN": "镜像仓库",
|
||||||
|
"ja-JP": "リポジトリ",
|
||||||
"de-DE": "Repositories",
|
"de-DE": "Repositories",
|
||||||
"ru-RU": "Репозитории"
|
"ru-RU": "Репозитории"
|
||||||
},
|
},
|
||||||
"no_repo_exists" : {
|
"no_repo_exists" : {
|
||||||
"en-US": "No repositories found, please use 'docker push' to upload images.",
|
"en-US": "No repositories found, please use 'docker push' to upload images.",
|
||||||
"zh-CN": "未发现镜像,请用‘docker push’命令上传镜像。",
|
"zh-CN": "未发现镜像,请用‘docker push’命令上传镜像。",
|
||||||
|
"ja-JP": "イメージが見つかりませんでした。’docker push’を利用しイメージをアップロードしてください。",
|
||||||
"de-DE": "Keine Repositories gefunden, bitte benutzen Sie 'docker push' um ein Image hochzuladen.",
|
"de-DE": "Keine Repositories gefunden, bitte benutzen Sie 'docker push' um ein Image hochzuladen.",
|
||||||
"ru-RU": "Репозитории не найдены, используйте команду 'docker push' для добавления образов."
|
"ru-RU": "Репозитории не найдены, используйте команду 'docker push' для добавления образов."
|
||||||
},
|
},
|
||||||
"tag" : {
|
"tag" : {
|
||||||
"en-US": "Tag",
|
"en-US": "Tag",
|
||||||
"zh-CN": "标签",
|
"zh-CN": "标签",
|
||||||
|
"ja-JP": "タグ",
|
||||||
"de-DE": "Tag",
|
"de-DE": "Tag",
|
||||||
"ru-RU": "Метка"
|
"ru-RU": "Метка"
|
||||||
},
|
},
|
||||||
"pull_command": {
|
"pull_command": {
|
||||||
"en-US": "Pull Command",
|
"en-US": "Pull Command",
|
||||||
"zh-CN": "Pull 命令",
|
"zh-CN": "Pull 命令",
|
||||||
|
"ja-JP": "Pull コマンド",
|
||||||
"de-DE": "Pull Befehl",
|
"de-DE": "Pull Befehl",
|
||||||
"ru-RU": "Команда для скачивания образа"
|
"ru-RU": "Команда для скачивания образа"
|
||||||
},
|
},
|
||||||
"image_details" : {
|
"image_details" : {
|
||||||
"en-US": "Image Details",
|
"en-US": "Image Details",
|
||||||
"zh-CN": "镜像详细信息",
|
"zh-CN": "镜像详细信息",
|
||||||
|
"ja-JP": "イメージ詳細",
|
||||||
"de-DE": "Image Details",
|
"de-DE": "Image Details",
|
||||||
"ru-RU": "Информация об образе"
|
"ru-RU": "Информация об образе"
|
||||||
},
|
},
|
||||||
"add_members" : {
|
"add_members" : {
|
||||||
"en-US": "Add Member",
|
"en-US": "Add Member",
|
||||||
"zh-CN": "添加成员",
|
"zh-CN": "添加成员",
|
||||||
|
"ja-JP": "メンバーを追加する",
|
||||||
"de-DE": "Mitglied hinzufügen",
|
"de-DE": "Mitglied hinzufügen",
|
||||||
"ru-RU": "Добавить Участника"
|
"ru-RU": "Добавить Участника"
|
||||||
},
|
},
|
||||||
"edit_members" : {
|
"edit_members" : {
|
||||||
"en-US": "Edit Members",
|
"en-US": "Edit Members",
|
||||||
"zh-CN": "编辑成员",
|
"zh-CN": "编辑成员",
|
||||||
|
"ja-JP": "メンバーを編集する",
|
||||||
"de-DE": "Mitglieder bearbeiten",
|
"de-DE": "Mitglieder bearbeiten",
|
||||||
"ru-RU": "Редактировать Участников"
|
"ru-RU": "Редактировать Участников"
|
||||||
},
|
},
|
||||||
"add_member_failed" : {
|
"add_member_failed" : {
|
||||||
"en-US": "Adding Member Failed",
|
"en-US": "Adding Member Failed",
|
||||||
"zh-CN": "添加成员失败",
|
"zh-CN": "添加成员失败",
|
||||||
|
"ja-JP": "メンバーを追加できません出した",
|
||||||
"de-DE": "Mitglied hinzufügen fehlgeschlagen",
|
"de-DE": "Mitglied hinzufügen fehlgeschlagen",
|
||||||
"ru-RU": "Ошибка при добавлении нового участника"
|
"ru-RU": "Ошибка при добавлении нового участника"
|
||||||
},
|
},
|
||||||
"please_input_username" : {
|
"please_input_username" : {
|
||||||
"en-US": "Please input a username.",
|
"en-US": "Please input a username.",
|
||||||
"zh-CN": "请输入用户名。",
|
"zh-CN": "请输入用户名。",
|
||||||
|
"ja-JP": "ユーザ名を入力してください。",
|
||||||
"de-DE": "Bitte geben Sie einen Benutzernamen ein.",
|
"de-DE": "Bitte geben Sie einen Benutzernamen ein.",
|
||||||
"ru-RU": "Пожалуйста, введите имя пользователя."
|
"ru-RU": "Пожалуйста, введите имя пользователя."
|
||||||
},
|
},
|
||||||
"please_assign_a_role_to_user" : {
|
"please_assign_a_role_to_user" : {
|
||||||
"en-US": "Please assign a role to the user.",
|
"en-US": "Please assign a role to the user.",
|
||||||
"zh-CN": "请为用户分配角色。",
|
"zh-CN": "请为用户分配角色。",
|
||||||
|
"ja-JP": "ユーザーに役割を割り当てるしてください。",
|
||||||
"de-DE": "Bitte weisen Sie dem Benutzer eine Rolle zu.",
|
"de-DE": "Bitte weisen Sie dem Benutzer eine Rolle zu.",
|
||||||
"ru-RU": "Пожалуйста, назначьте роль пользователю."
|
"ru-RU": "Пожалуйста, назначьте роль пользователю."
|
||||||
},
|
},
|
||||||
"user_id_exists" : {
|
"user_id_exists" : {
|
||||||
"en-US": "User is already a member.",
|
"en-US": "User is already a member.",
|
||||||
"zh-CN": "用户已经是成员。",
|
"zh-CN": "用户已经是成员。",
|
||||||
|
"ja-JP": "すでにメンバーに登録しました。",
|
||||||
"de-DE": "Benutzer ist bereits Mitglied.",
|
"de-DE": "Benutzer ist bereits Mitglied.",
|
||||||
"ru-RU": "Пользователь уже является участником."
|
"ru-RU": "Пользователь уже является участником."
|
||||||
},
|
},
|
||||||
"user_id_does_not_exist" : {
|
"user_id_does_not_exist" : {
|
||||||
"en-US": "User does not exist.",
|
"en-US": "User does not exist.",
|
||||||
"zh-CN": "不存在此用户。",
|
"zh-CN": "不存在此用户。",
|
||||||
|
"ja-JP": "ユーザが見つかりませんでした。",
|
||||||
"de-DE": "Benutzer existiert nicht.",
|
"de-DE": "Benutzer existiert nicht.",
|
||||||
"ru-RU": "Пользователя с таким именем не существует."
|
"ru-RU": "Пользователя с таким именем не существует."
|
||||||
},
|
},
|
||||||
"insufficient_privileges" : {
|
"insufficient_privileges" : {
|
||||||
"en-US": "Insufficient privileges.",
|
"en-US": "Insufficient privileges.",
|
||||||
"zh-CN": "权限不足。",
|
"zh-CN": "权限不足。",
|
||||||
|
"ja-JP": "権限エラー。",
|
||||||
"de-DE": "Unzureichende Berechtigungen.",
|
"de-DE": "Unzureichende Berechtigungen.",
|
||||||
"ru-RU": "Недостаточно прав."
|
"ru-RU": "Недостаточно прав."
|
||||||
},
|
},
|
||||||
"operation_failed" : {
|
"operation_failed" : {
|
||||||
"en-US": "Operation Failed",
|
"en-US": "Operation Failed",
|
||||||
"zh-CN": "操作失败",
|
"zh-CN": "操作失败",
|
||||||
|
"ja-JP": "操作に失敗しました。",
|
||||||
"de-DE": "Befehl fehlgeschlagen",
|
"de-DE": "Befehl fehlgeschlagen",
|
||||||
"ru-RU": "Ошибка при выполнении данной операции"
|
"ru-RU": "Ошибка при выполнении данной операции"
|
||||||
},
|
},
|
||||||
"button_on" : {
|
"button_on" : {
|
||||||
"en-US": "On",
|
"en-US": "On",
|
||||||
"zh-CN": "打开",
|
"zh-CN": "打开",
|
||||||
|
"ja-JP": "オン",
|
||||||
"de-DE": "An",
|
"de-DE": "An",
|
||||||
"ru-RU": "Вкл."
|
"ru-RU": "Вкл."
|
||||||
},
|
},
|
||||||
"button_off" : {
|
"button_off" : {
|
||||||
"en-US": "Off",
|
"en-US": "Off",
|
||||||
"zh-CN": "关闭",
|
"zh-CN": "关闭",
|
||||||
|
"ja-JP": "オフ",
|
||||||
"de-DE": "Aus",
|
"de-DE": "Aus",
|
||||||
"ru-RU": "Откл."
|
"ru-RU": "Откл."
|
||||||
}
|
}
|
||||||
|
@ -76,6 +76,7 @@ language_en-US = English
|
|||||||
language_zh-CN = 中文
|
language_zh-CN = 中文
|
||||||
language_de-DE = Deutsch
|
language_de-DE = Deutsch
|
||||||
language_ru-RU = Русский
|
language_ru-RU = Русский
|
||||||
|
language_ja-JP = 日本語
|
||||||
copyright = Copyright
|
copyright = Copyright
|
||||||
all_rights_reserved = Все права защищены.
|
all_rights_reserved = Все права защищены.
|
||||||
index_desc = Проект Harbor представляет собой надежный сервер управления docker-образами корпоративного класса. Компании могут использовать данный сервер в своей инфарструктуе для повышения производительности и безопасности . Проект Harbor может использоваться как в среде разработки так и в продуктивной среде.
|
index_desc = Проект Harbor представляет собой надежный сервер управления docker-образами корпоративного класса. Компании могут использовать данный сервер в своей инфарструктуе для повышения производительности и безопасности . Проект Harbor может использоваться как в среде разработки так и в продуктивной среде.
|
||||||
|
@ -76,6 +76,7 @@ language_en-US = English
|
|||||||
language_zh-CN = 中文
|
language_zh-CN = 中文
|
||||||
language_de-DE = Deutsch
|
language_de-DE = Deutsch
|
||||||
language_ru-RU = Русский
|
language_ru-RU = Русский
|
||||||
|
language_ja-JP = 日本語
|
||||||
copyright = 版权所有
|
copyright = 版权所有
|
||||||
all_rights_reserved = 保留所有权利。
|
all_rights_reserved = 保留所有权利。
|
||||||
index_desc = Harbor是可靠的企业级Registry服务器。企业用户可使用Harbor搭建私有容器Registry服务,提高生产效率和安全度,既可应用于生产环境,也可以在开发环境中使用。
|
index_desc = Harbor是可靠的企业级Registry服务器。企业用户可使用Harbor搭建私有容器Registry服务,提高生产效率和安全度,既可应用于生产环境,也可以在开发环境中使用。
|
||||||
|
@ -70,7 +70,8 @@ var SUPPORT_LANGUAGES = {
|
|||||||
"en-US": "English",
|
"en-US": "English",
|
||||||
"zh-CN": "Chinese",
|
"zh-CN": "Chinese",
|
||||||
"de-DE": "German",
|
"de-DE": "German",
|
||||||
"ru-RU": "Russian"
|
"ru-RU": "Russian",
|
||||||
|
"ja-JP": "Japanese"
|
||||||
};
|
};
|
||||||
|
|
||||||
var DEFAULT_LANGUAGE = "en-US";
|
var DEFAULT_LANGUAGE = "en-US";
|
||||||
|
@ -62,7 +62,7 @@ jQuery(function(){
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
$.each(data, function(i, e){
|
$.each(data, function(i, e){
|
||||||
var targetId = e.replace(/\//g, "------");
|
var targetId = e.replace(/\//g, "------").replace(/\./g, "---");
|
||||||
var row = '<div class="panel panel-default" targetId="' + targetId + '">' +
|
var row = '<div class="panel panel-default" targetId="' + targetId + '">' +
|
||||||
'<div class="panel-heading" role="tab" id="heading' + i + '"+ >' +
|
'<div class="panel-heading" role="tab" id="heading' + i + '"+ >' +
|
||||||
'<h4 class="panel-title">' +
|
'<h4 class="panel-title">' +
|
||||||
@ -105,7 +105,7 @@ jQuery(function(){
|
|||||||
$('#accordionRepo').on('show.bs.collapse', function (e) {
|
$('#accordionRepo').on('show.bs.collapse', function (e) {
|
||||||
$('#accordionRepo .in').collapse('hide');
|
$('#accordionRepo .in').collapse('hide');
|
||||||
var targetId = $(e.target).attr("targetId");
|
var targetId = $(e.target).attr("targetId");
|
||||||
var repoName = targetId.replace(/------/g, "/");
|
var repoName = targetId.replace(/[-]{6}/g, "/").replace(/[-]{3}/g, '.');
|
||||||
new AjaxUtil({
|
new AjaxUtil({
|
||||||
url: "/api/repositories/tags?repo_name=" + repoName,
|
url: "/api/repositories/tags?repo_name=" + repoName,
|
||||||
type: "get",
|
type: "get",
|
||||||
@ -113,7 +113,7 @@ jQuery(function(){
|
|||||||
$('#' + targetId +' table tbody tr').remove();
|
$('#' + targetId +' table tbody tr').remove();
|
||||||
var row = [];
|
var row = [];
|
||||||
for(var i in data){
|
for(var i in data){
|
||||||
var tagName = data[i]
|
var tagName = data[i];
|
||||||
row.push('<tr><td><a href="#" imageId="' + tagName + '" repoName="' + repoName + '">' + tagName + '</a></td><td><input type="text" style="width:100%" readonly value=" docker pull '+ $("#harborRegUrl").val() +'/'+ repoName + ':' + tagName +'"></td></tr>');
|
row.push('<tr><td><a href="#" imageId="' + tagName + '" repoName="' + repoName + '">' + tagName + '</a></td><td><input type="text" style="width:100%" readonly value=" docker pull '+ $("#harborRegUrl").val() +'/'+ repoName + ':' + tagName +'"></td></tr>');
|
||||||
}
|
}
|
||||||
$('#' + targetId +' table tbody').append(row.join(""));
|
$('#' + targetId +' table tbody').append(row.join(""));
|
||||||
|
@ -38,6 +38,7 @@
|
|||||||
<li><a href="/language?lang=zh-CN">{{i18n .Lang "language_zh-CN"}}</a></li>
|
<li><a href="/language?lang=zh-CN">{{i18n .Lang "language_zh-CN"}}</a></li>
|
||||||
<li><a href="/language?lang=de-DE">{{i18n .Lang "language_de-DE"}}</a></li>
|
<li><a href="/language?lang=de-DE">{{i18n .Lang "language_de-DE"}}</a></li>
|
||||||
<li><a href="/language?lang=ru-RU">{{i18n .Lang "language_ru-RU"}}</a></li>
|
<li><a href="/language?lang=ru-RU">{{i18n .Lang "language_ru-RU"}}</a></li>
|
||||||
|
<li><a href="/language?lang=ja-JP">{{i18n .Lang "language_ja-JP"}}</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
Loading…
Reference in New Issue
Block a user