mirror of
https://github.com/goharbor/harbor.git
synced 2024-10-27 21:59:38 +01:00
Merge remote-tracking branch 'upstream/job-service' into job-service
This commit is contained in:
commit
a8f4a949d9
82
.travis.yml
82
.travis.yml
@ -1,3 +1,5 @@
|
||||
sudo: true
|
||||
|
||||
language: go
|
||||
|
||||
go:
|
||||
@ -5,36 +7,78 @@ go:
|
||||
|
||||
go_import_path: github.com/vmware/harbor
|
||||
|
||||
#service:
|
||||
# - mysql
|
||||
services:
|
||||
- docker
|
||||
- mysql
|
||||
|
||||
env: DB_HOST=127.0.0.1 DB_PORT=3306 DB_USR=root DB_PWD=
|
||||
dist: trusty
|
||||
addons:
|
||||
apt:
|
||||
packages:
|
||||
- mysql-server-5.6
|
||||
- mysql-client-core-5.6
|
||||
- mysql-client-5.6
|
||||
|
||||
env:
|
||||
DB_HOST: 127.0.0.1
|
||||
DB_PORT: 3306
|
||||
DB_USR: root
|
||||
DB_PWD:
|
||||
DOCKER_COMPOSE_VERSION: 1.7.1
|
||||
HARBOR_ADMIN: admin
|
||||
HARBOR_ADMIN_PASSWD: Harbor12345
|
||||
|
||||
before_install:
|
||||
- sudo ./tests/hostcfg.sh
|
||||
- cd Deploy
|
||||
- sudo ./prepare
|
||||
- cd ..
|
||||
|
||||
install:
|
||||
- sudo apt-get update && sudo apt-get install -y libldap2-dev
|
||||
- sudo apt-get remove mysql-common mysql-server-5.5 mysql-server-core-5.5 mysql-client-5.5 mysql-client-core-5.5
|
||||
- sudo apt-get autoremove
|
||||
- sudo apt-get install libaio1
|
||||
- wget -O mysql-5.6.14.deb http://dev.mysql.com/get/Downloads/MySQL-5.6/mysql-5.6.14-debian6.0-x86_64.deb/from/http://cdn.mysql.com/
|
||||
- sudo dpkg -i mysql-5.6.14.deb
|
||||
- sudo cp /opt/mysql/server-5.6/support-files/mysql.server /etc/init.d/mysql.server
|
||||
- sudo ln -s /opt/mysql/server-5.6/bin/* /usr/bin/
|
||||
- sudo sed -i'' 's/table_cache/table_open_cache/' /etc/mysql/my.cnf
|
||||
- sudo sed -i'' 's/log_slow_queries/slow_query_log/' /etc/mysql/my.cnf
|
||||
- sudo sed -i'' 's/basedir[^=]\+=.*$/basedir = \/opt\/mysql\/server-5.6/' /etc/mysql/my.cnf
|
||||
- sudo /etc/init.d/mysql.server start
|
||||
- mysql --version
|
||||
# - sudo apt-get remove -y mysql-common mysql-server-5.5 mysql-server-core-5.5 mysql-client-5.5 mysql-client-core-5.5
|
||||
# - sudo apt-get autoremove -y
|
||||
# - sudo apt-get install -y libaio1
|
||||
# - wget -O mysql-5.6.14.deb http://dev.mysql.com/get/Downloads/MySQL-5.6/mysql-5.6.14-debian6.0-x86_64.deb/from/http://cdn.mysql.com/
|
||||
# - sudo dpkg -i mysql-5.6.14.deb
|
||||
# - sudo cp /opt/mysql/server-5.6/support-files/mysql.server /etc/init.d/mysql.server
|
||||
# - sudo ln -s /opt/mysql/server-5.6/bin/* /usr/bin/
|
||||
# - sudo sed -i'' 's/table_cache/table_open_cache/' /etc/mysql/my.cnf
|
||||
# - sudo sed -i'' 's/log_slow_queries/slow_query_log/' /etc/mysql/my.cnf
|
||||
# - sudo sed -i'' 's/basedir[^=]\+=.*$/basedir = \/opt\/mysql\/server-5.6/' /etc/mysql/my.cnf
|
||||
# - sudo /etc/init.d/mysql.server start
|
||||
# - mysql --version
|
||||
- go get -d github.com/docker/distribution
|
||||
- go get -d github.com/docker/libtrust
|
||||
- go get -d github.com/go-sql-driver/mysql
|
||||
- go get github.com/golang/lint/golint
|
||||
- go get github.com/GeertJohan/fgt
|
||||
|
||||
- sudo apt-get install -y -o Dpkg::Options::="--force-confdef" -o Dpkg::Options::="--force-confold" docker-engine=1.11.1-0~trusty
|
||||
- sudo rm /usr/local/bin/docker-compose
|
||||
- curl -L https://github.com/docker/compose/releases/download/${DOCKER_COMPOSE_VERSION}/docker-compose-`uname -s`-`uname -m` > docker-compose
|
||||
- chmod +x docker-compose
|
||||
- sudo mv docker-compose /usr/local/bin
|
||||
- sudo sed -i '$a DOCKER_OPTS=\"$DOCKER_OPTS --insecure-registry 127.0.0.1\"' /etc/default/docker
|
||||
- sudo service docker restart
|
||||
- go get github.com/dghubble/sling
|
||||
- go get github.com/stretchr/testify
|
||||
|
||||
before_script:
|
||||
# create tables and load data
|
||||
- mysql < ./Deploy/db/registry.sql -uroot --verbose
|
||||
|
||||
script:
|
||||
- go list ./... | grep -v /vendor/ | xargs -L1 fgt golint
|
||||
- go list ./... | grep -v 'vendor' | xargs -L1 go vet
|
||||
- go list ./... | grep -v 'vendor' | xargs -L1 go test -v
|
||||
- go list ./... | grep -v 'tests' | grep -v /vendor/ | xargs -L1 fgt golint
|
||||
- go list ./... | grep -v 'tests' | grep -v 'vendor' | xargs -L1 go vet
|
||||
- go list ./... | grep -v 'tests' | grep -v 'vendor' | xargs -L1 go test -v
|
||||
|
||||
- docker-compose -f Deploy/docker-compose.yml up -d
|
||||
|
||||
- docker ps
|
||||
- go run tests/startuptest.go http://localhost/
|
||||
- go run tests/userlogintest.go -name ${HARBOR_ADMIN} -passwd ${HARBOR_ADMIN_PASSWD}
|
||||
|
||||
|
||||
# test for API
|
||||
- sudo ./tests/testprepare.sh
|
||||
- go test -v ./tests/apitests
|
||||
|
1
AUTHORS
1
AUTHORS
@ -5,6 +5,7 @@ Alexey Erkak <eryigin at mail.ru>
|
||||
Allen Heavey <xheavey at gmail.com>
|
||||
Amanda Zhang <amzhang at vmware.com>
|
||||
Benniu Ji <benniuji at gmail.com>
|
||||
Bin Liu <liubin0329 at gmail.com>
|
||||
Bobby Zhang <junzhang at vmware.com>
|
||||
Chaofeng Wu <chaofengw at vmware.com>
|
||||
Daniel Jiang <jiangd at vmware.com>
|
||||
|
@ -2,8 +2,8 @@ appname = registry
|
||||
runmode = dev
|
||||
|
||||
[lang]
|
||||
types = en-US|zh-CN|de-DE|ru-RU
|
||||
names = en-US|zh-CN|de-DE|ru-RU
|
||||
types = en-US|zh-CN|de-DE|ru-RU|ja-JP
|
||||
names = en-US|zh-CN|de-DE|ru-RU|ja-JP
|
||||
|
||||
[dev]
|
||||
httpport = 80
|
||||
|
@ -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.
|
||||
* **AD/LDAP support**: Harbor integrates with existing enterprise AD/LDAP for user authentication and management.
|
||||
* **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.
|
||||
|
||||
### 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>
|
||||
|
||||
### 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
|
||||
<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.
|
||||
|
42
api/base.go
42
api/base.go
@ -17,8 +17,11 @@ package api
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/astaxie/beego/validation"
|
||||
"github.com/vmware/harbor/auth"
|
||||
"github.com/vmware/harbor/dao"
|
||||
"github.com/vmware/harbor/models"
|
||||
@ -51,6 +54,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
|
||||
func (b *BaseAPI) ValidateUser() int {
|
||||
|
||||
@ -94,3 +121,18 @@ func (b *BaseAPI) Redirect(statusCode int, resouceID string) {
|
||||
|
||||
b.Ctx.Redirect(statusCode, resoucreURI)
|
||||
}
|
||||
|
||||
// GetIDFromURL checks the ID in request URL
|
||||
func (b *BaseAPI) GetIDFromURL() int64 {
|
||||
idStr := b.Ctx.Input.Param(":id")
|
||||
if len(idStr) == 0 {
|
||||
b.CustomAbort(http.StatusBadRequest, "invalid ID in URL")
|
||||
}
|
||||
|
||||
id, err := strconv.ParseInt(idStr, 10, 64)
|
||||
if err != nil || id <= 0 {
|
||||
b.CustomAbort(http.StatusBadRequest, "invalid ID in URL")
|
||||
}
|
||||
|
||||
return id
|
||||
}
|
||||
|
86
api/log.go
Normal file
86
api/log.go
Normal file
@ -0,0 +1,86 @@
|
||||
/*
|
||||
Copyright (c) 2016 VMware, Inc. All Rights Reserved.
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package api
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/vmware/harbor/dao"
|
||||
"github.com/vmware/harbor/models"
|
||||
"github.com/vmware/harbor/utils/log"
|
||||
)
|
||||
|
||||
//LogAPI handles request api/logs
|
||||
type LogAPI struct {
|
||||
BaseAPI
|
||||
userID int
|
||||
}
|
||||
|
||||
//Prepare validates the URL and the user
|
||||
func (l *LogAPI) Prepare() {
|
||||
l.userID = l.ValidateUser()
|
||||
}
|
||||
|
||||
//Get returns the recent logs according to parameters
|
||||
func (l *LogAPI) Get() {
|
||||
var err error
|
||||
startTime := l.GetString("start_time")
|
||||
if len(startTime) != 0 {
|
||||
i, err := strconv.ParseInt(startTime, 10, 64)
|
||||
if err != nil {
|
||||
log.Errorf("Parse startTime to int error, err: %v", err)
|
||||
l.CustomAbort(http.StatusBadRequest, "startTime is not a valid integer")
|
||||
}
|
||||
startTime = time.Unix(i, 0).String()
|
||||
}
|
||||
|
||||
endTime := l.GetString("end_time")
|
||||
if len(endTime) != 0 {
|
||||
j, err := strconv.ParseInt(endTime, 10, 64)
|
||||
if err != nil {
|
||||
log.Errorf("Parse endTime to int error, err: %v", err)
|
||||
l.CustomAbort(http.StatusBadRequest, "endTime is not a valid integer")
|
||||
}
|
||||
endTime = time.Unix(j, 0).String()
|
||||
}
|
||||
|
||||
var linesNum int
|
||||
lines := l.GetString("lines")
|
||||
if len(lines) != 0 {
|
||||
linesNum, err = strconv.Atoi(lines)
|
||||
if err != nil {
|
||||
log.Errorf("Get parameters error--lines, err: %v", err)
|
||||
l.CustomAbort(http.StatusBadRequest, "bad request of lines")
|
||||
}
|
||||
if linesNum <= 0 {
|
||||
log.Warning("lines must be a positive integer")
|
||||
l.CustomAbort(http.StatusBadRequest, "lines is 0 or negative")
|
||||
}
|
||||
} else if len(startTime) == 0 && len(endTime) == 0 {
|
||||
linesNum = 10
|
||||
}
|
||||
|
||||
var logList []models.AccessLog
|
||||
logList, err = dao.GetRecentLogs(l.userID, linesNum, startTime, endTime)
|
||||
if err != nil {
|
||||
log.Errorf("Get recent logs error, err: %v", err)
|
||||
l.CustomAbort(http.StatusInternalServerError, "Internal error")
|
||||
}
|
||||
l.Data["json"] = logList
|
||||
l.ServeJSON()
|
||||
}
|
@ -33,7 +33,7 @@ type ProjectMemberAPI struct {
|
||||
}
|
||||
|
||||
type memberReq struct {
|
||||
Username string `json:"user_name"`
|
||||
Username string `json:"username"`
|
||||
UserID int `json:"user_id"`
|
||||
Roles []int `json:"roles"`
|
||||
}
|
||||
@ -104,7 +104,7 @@ func (pma *ProjectMemberAPI) Get() {
|
||||
log.Errorf("Error occurred in GetUser, error: %v", err)
|
||||
pma.CustomAbort(http.StatusInternalServerError, "Internal error.")
|
||||
}
|
||||
result["user_name"] = user.Username
|
||||
result["username"] = user.Username
|
||||
result["user_id"] = pma.memberID
|
||||
result["roles"] = roleList
|
||||
pma.Data["json"] = result
|
||||
|
@ -18,6 +18,7 @@ package api
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/vmware/harbor/dao"
|
||||
"github.com/vmware/harbor/models"
|
||||
@ -40,6 +41,7 @@ type projectReq struct {
|
||||
}
|
||||
|
||||
const projectNameMaxLen int = 30
|
||||
const projectNameMinLen int = 4
|
||||
|
||||
// Prepare validates the URL and the user
|
||||
func (p *ProjectAPI) Prepare() {
|
||||
@ -75,7 +77,7 @@ func (p *ProjectAPI) Post() {
|
||||
err := validateProjectReq(req)
|
||||
if err != nil {
|
||||
log.Errorf("Invalid project request, error: %v", err)
|
||||
p.RenderError(http.StatusBadRequest, "Invalid request for creating project")
|
||||
p.RenderError(http.StatusBadRequest, fmt.Sprintf("invalid request: %v", err))
|
||||
return
|
||||
}
|
||||
projectName := req.ProjectName
|
||||
@ -197,10 +199,9 @@ func (p *ProjectAPI) List() {
|
||||
p.ServeJSON()
|
||||
}
|
||||
|
||||
// Put ...
|
||||
func (p *ProjectAPI) Put() {
|
||||
// ToggleProjectPublic ...
|
||||
func (p *ProjectAPI) ToggleProjectPublic() {
|
||||
p.userID = p.ValidateUser()
|
||||
|
||||
var req projectReq
|
||||
var public int
|
||||
|
||||
@ -287,8 +288,16 @@ func validateProjectReq(req projectReq) error {
|
||||
if len(pn) == 0 {
|
||||
return fmt.Errorf("Project name can not be empty")
|
||||
}
|
||||
if len(pn) > projectNameMaxLen {
|
||||
return fmt.Errorf("Project name is too long")
|
||||
if isIllegalLength(req.ProjectName, projectNameMinLen, projectNameMaxLen) {
|
||||
return fmt.Errorf("project name is illegal in length. (greater than 4 or less than 30)")
|
||||
}
|
||||
if isContainIllegalChar(req.ProjectName, []string{"~", "-", "$", "\\", "[", "]", "{", "}", "(", ")", "&", "^", "%", "*", "<", ">", "\"", "'", "/", "?", "@"}) {
|
||||
return fmt.Errorf("project name contains illegal characters")
|
||||
}
|
||||
|
||||
if pn != strings.ToLower(pn) {
|
||||
return fmt.Errorf("project name must be in lower case")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -14,12 +14,9 @@ import (
|
||||
// RepPolicyAPI handles /api/replicationPolicies /api/replicationPolicies/:id/enablement
|
||||
type RepPolicyAPI struct {
|
||||
BaseAPI
|
||||
policyID int64
|
||||
policy *models.RepPolicy
|
||||
}
|
||||
|
||||
// Prepare validates whether the user has system admin role
|
||||
// and parsed the policy ID if it exists
|
||||
func (pa *RepPolicyAPI) Prepare() {
|
||||
uid := pa.ValidateUser()
|
||||
var err error
|
||||
@ -30,38 +27,45 @@ func (pa *RepPolicyAPI) Prepare() {
|
||||
if !isAdmin {
|
||||
pa.CustomAbort(http.StatusForbidden, "")
|
||||
}
|
||||
idStr := pa.Ctx.Input.Param(":id")
|
||||
if len(idStr) > 0 {
|
||||
pa.policyID, err = strconv.ParseInt(idStr, 10, 64)
|
||||
if err != nil {
|
||||
log.Errorf("Error parsing policy id: %s, error: %v", idStr, err)
|
||||
pa.CustomAbort(http.StatusBadRequest, "invalid policy id")
|
||||
}
|
||||
p, err := dao.GetRepPolicy(pa.policyID)
|
||||
if err != nil {
|
||||
log.Errorf("Error occurred in GetRepPolicy, error: %v", err)
|
||||
pa.CustomAbort(http.StatusInternalServerError, "Internal error.")
|
||||
}
|
||||
if p == nil {
|
||||
pa.CustomAbort(http.StatusNotFound, fmt.Sprintf("policy does not exist, id: %v", pa.policyID))
|
||||
}
|
||||
pa.policy = p
|
||||
}
|
||||
}
|
||||
|
||||
// Get gets all the policies according to the project
|
||||
// Get ...
|
||||
func (pa *RepPolicyAPI) Get() {
|
||||
projectID, err := pa.GetInt64("project_id")
|
||||
id := pa.GetIDFromURL()
|
||||
policy, err := dao.GetRepPolicy(id)
|
||||
if err != nil {
|
||||
log.Errorf("Failed to get project id, error: %v", err)
|
||||
pa.RenderError(http.StatusBadRequest, "Invalid project id")
|
||||
return
|
||||
log.Errorf("failed to get policy %d: %v", id, err)
|
||||
pa.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
|
||||
}
|
||||
policies, err := dao.GetRepPolicyByProject(projectID)
|
||||
|
||||
if policy == nil {
|
||||
pa.CustomAbort(http.StatusNotFound, http.StatusText(http.StatusNotFound))
|
||||
}
|
||||
|
||||
pa.Data["json"] = policy
|
||||
pa.ServeJSON()
|
||||
}
|
||||
|
||||
// List filters policies by name and project_id, if name and project_id
|
||||
// are nil, List returns all policies
|
||||
func (pa *RepPolicyAPI) List() {
|
||||
name := pa.GetString("name")
|
||||
projectIDStr := pa.GetString("project_id")
|
||||
|
||||
var projectID int64
|
||||
var err error
|
||||
|
||||
if len(projectIDStr) != 0 {
|
||||
projectID, err = strconv.ParseInt(projectIDStr, 10, 64)
|
||||
if err != nil || projectID <= 0 {
|
||||
pa.CustomAbort(http.StatusBadRequest, "invalid project ID")
|
||||
}
|
||||
}
|
||||
|
||||
policies, err := dao.FilterRepPolicies(name, projectID)
|
||||
if err != nil {
|
||||
log.Errorf("Failed to query policies from db, error: %v", err)
|
||||
pa.RenderError(http.StatusInternalServerError, "Failed to query policies")
|
||||
return
|
||||
log.Errorf("failed to filter policies %s project ID %d: %v", name, projectID, err)
|
||||
pa.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
|
||||
}
|
||||
pa.Data["json"] = policies
|
||||
pa.ServeJSON()
|
||||
@ -69,9 +73,40 @@ func (pa *RepPolicyAPI) Get() {
|
||||
|
||||
// Post creates a policy, and if it is enbled, the replication will be triggered right now.
|
||||
func (pa *RepPolicyAPI) Post() {
|
||||
policy := models.RepPolicy{}
|
||||
pa.DecodeJSONReq(&policy)
|
||||
pid, err := dao.AddRepPolicy(policy)
|
||||
policy := &models.RepPolicy{}
|
||||
pa.DecodeJSONReqAndValidate(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 {
|
||||
log.Errorf("Failed to add policy to DB, error: %v", err)
|
||||
pa.RenderError(http.StatusInternalServerError, "Internal Error")
|
||||
@ -91,12 +126,85 @@ func (pa *RepPolicyAPI) Post() {
|
||||
pa.Redirect(http.StatusCreated, strconv.FormatInt(pid, 10))
|
||||
}
|
||||
|
||||
// Put modifies name and description of policy
|
||||
func (pa *RepPolicyAPI) Put() {
|
||||
id := pa.GetIDFromURL()
|
||||
originalPolicy, err := dao.GetRepPolicy(id)
|
||||
if err != nil {
|
||||
log.Errorf("failed to get policy %d: %v", id, err)
|
||||
pa.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
|
||||
}
|
||||
|
||||
if originalPolicy == nil {
|
||||
pa.CustomAbort(http.StatusNotFound, http.StatusText(http.StatusNotFound))
|
||||
}
|
||||
|
||||
policy := &models.RepPolicy{}
|
||||
pa.DecodeJSONReq(policy)
|
||||
policy.ProjectID = originalPolicy.ProjectID
|
||||
policy.TargetID = originalPolicy.TargetID
|
||||
pa.Validate(policy)
|
||||
|
||||
if policy.Name != originalPolicy.Name {
|
||||
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")
|
||||
}
|
||||
}
|
||||
|
||||
policy.ID = 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 {
|
||||
go func() {
|
||||
if err := TriggerReplication(id, "", nil, models.RepOpTransfer); err != nil {
|
||||
log.Errorf("failed to trigger replication of %d: %v", id, err)
|
||||
} else {
|
||||
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)
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
type enablementReq struct {
|
||||
Enabled int `json:"enabled"`
|
||||
}
|
||||
|
||||
// UpdateEnablement changes the enablement of the policy
|
||||
func (pa *RepPolicyAPI) UpdateEnablement() {
|
||||
id := pa.GetIDFromURL()
|
||||
policy, err := dao.GetRepPolicy(id)
|
||||
if err != nil {
|
||||
log.Errorf("failed to get policy %d: %v", id, err)
|
||||
pa.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
|
||||
}
|
||||
|
||||
if policy == nil {
|
||||
pa.CustomAbort(http.StatusNotFound, http.StatusText(http.StatusNotFound))
|
||||
}
|
||||
|
||||
e := enablementReq{}
|
||||
pa.DecodeJSONReq(&e)
|
||||
if e.Enabled != 0 && e.Enabled != 1 {
|
||||
@ -104,11 +212,11 @@ func (pa *RepPolicyAPI) UpdateEnablement() {
|
||||
return
|
||||
}
|
||||
|
||||
if pa.policy.Enabled == e.Enabled {
|
||||
if policy.Enabled == e.Enabled {
|
||||
return
|
||||
}
|
||||
|
||||
if err := dao.UpdateRepPolicyEnablement(pa.policyID, e.Enabled); err != nil {
|
||||
if err := dao.UpdateRepPolicyEnablement(id, e.Enabled); err != nil {
|
||||
log.Errorf("Failed to update policy enablement in DB, error: %v", err)
|
||||
pa.RenderError(http.StatusInternalServerError, "Internal Error")
|
||||
return
|
||||
@ -116,10 +224,18 @@ func (pa *RepPolicyAPI) UpdateEnablement() {
|
||||
|
||||
if e.Enabled == 1 {
|
||||
go func() {
|
||||
if err := TriggerReplication(pa.policyID, "", nil, models.RepOpTransfer); err != nil {
|
||||
log.Errorf("failed to trigger replication of %d: %v", pa.policyID, err)
|
||||
if err := TriggerReplication(id, "", nil, models.RepOpTransfer); err != nil {
|
||||
log.Errorf("failed to trigger replication of %d: %v", id, err)
|
||||
} else {
|
||||
log.Infof("replication of %d triggered", pa.policyID)
|
||||
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)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
@ -133,6 +133,12 @@ func (ra *RepositoryAPI) Delete() {
|
||||
log.Errorf("error occurred while listing tags of %s: %v", repoName, err)
|
||||
ra.CustomAbort(http.StatusInternalServerError, "internal error")
|
||||
}
|
||||
|
||||
// TODO remove the logic if the bug of registry is fixed
|
||||
if len(tagList) == 0 {
|
||||
ra.CustomAbort(http.StatusNotFound, http.StatusText(http.StatusNotFound))
|
||||
}
|
||||
|
||||
tags = append(tags, tagList...)
|
||||
} else {
|
||||
tags = append(tags, tag)
|
||||
@ -294,3 +300,30 @@ func (ra *RepositoryAPI) getUsername() (string, error) {
|
||||
|
||||
return "", nil
|
||||
}
|
||||
|
||||
//GetTopRepos handles request GET /api/repositories/top
|
||||
func (ra *RepositoryAPI) GetTopRepos() {
|
||||
var err error
|
||||
var countNum int
|
||||
count := ra.GetString("count")
|
||||
if len(count) == 0 {
|
||||
countNum = 10
|
||||
} else {
|
||||
countNum, err = strconv.Atoi(count)
|
||||
if err != nil {
|
||||
log.Errorf("Get parameters error--count, err: %v", err)
|
||||
ra.CustomAbort(http.StatusBadRequest, "bad request of count")
|
||||
}
|
||||
if countNum <= 0 {
|
||||
log.Warning("count must be a positive integer")
|
||||
ra.CustomAbort(http.StatusBadRequest, "count is 0 or negative")
|
||||
}
|
||||
}
|
||||
repos, err := dao.GetTopRepos(countNum)
|
||||
if err != nil {
|
||||
log.Errorf("error occured in get top 10 repos: %v", err)
|
||||
ra.CustomAbort(http.StatusInternalServerError, "internal server error")
|
||||
}
|
||||
ra.Data["json"] = repos
|
||||
ra.ServeJSON()
|
||||
}
|
||||
|
108
api/target.go
108
api/target.go
@ -120,23 +120,7 @@ func (t *TargetAPI) Ping() {
|
||||
|
||||
// Get ...
|
||||
func (t *TargetAPI) Get() {
|
||||
id := t.getIDFromURL()
|
||||
// list targets
|
||||
if id == 0 {
|
||||
targets, err := dao.GetAllRepTargets()
|
||||
if err != nil {
|
||||
log.Errorf("failed to get all targets: %v", err)
|
||||
t.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
|
||||
}
|
||||
|
||||
for _, target := range targets {
|
||||
target.Password = ""
|
||||
}
|
||||
|
||||
t.Data["json"] = targets
|
||||
t.ServeJSON()
|
||||
return
|
||||
}
|
||||
id := t.GetIDFromURL()
|
||||
|
||||
target, err := dao.GetRepTarget(id)
|
||||
if err != nil {
|
||||
@ -148,6 +132,9 @@ func (t *TargetAPI) Get() {
|
||||
t.CustomAbort(http.StatusNotFound, http.StatusText(http.StatusNotFound))
|
||||
}
|
||||
|
||||
// The reason why the password is returned is that when user just wants to
|
||||
// modify other fields of target he does not need to input the password again.
|
||||
// The security issue can be fixed by enable https.
|
||||
if len(target.Password) != 0 {
|
||||
pwd, err := utils.ReversibleDecrypt(target.Password)
|
||||
if err != nil {
|
||||
@ -161,13 +148,46 @@ func (t *TargetAPI) Get() {
|
||||
t.ServeJSON()
|
||||
}
|
||||
|
||||
// List ...
|
||||
func (t *TargetAPI) List() {
|
||||
name := t.GetString("name")
|
||||
targets, err := dao.FilterRepTargets(name)
|
||||
if err != nil {
|
||||
log.Errorf("failed to filter targets %s: %v", name, err)
|
||||
t.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
|
||||
}
|
||||
|
||||
for _, target := range targets {
|
||||
if len(target.Password) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
str, err := utils.ReversibleDecrypt(target.Password)
|
||||
if err != nil {
|
||||
log.Errorf("failed to decrypt password: %v", err)
|
||||
t.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
|
||||
}
|
||||
target.Password = str
|
||||
}
|
||||
|
||||
t.Data["json"] = targets
|
||||
t.ServeJSON()
|
||||
return
|
||||
}
|
||||
|
||||
// Post ...
|
||||
func (t *TargetAPI) Post() {
|
||||
target := &models.RepTarget{}
|
||||
t.DecodeJSONReq(target)
|
||||
t.DecodeJSONReqAndValidate(target)
|
||||
|
||||
if len(target.Name) == 0 || len(target.URL) == 0 {
|
||||
t.CustomAbort(http.StatusBadRequest, "name or URL is nil")
|
||||
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")
|
||||
}
|
||||
|
||||
if len(target.Password) != 0 {
|
||||
@ -185,18 +205,35 @@ func (t *TargetAPI) Post() {
|
||||
|
||||
// Put ...
|
||||
func (t *TargetAPI) Put() {
|
||||
id := t.getIDFromURL()
|
||||
if id == 0 {
|
||||
t.CustomAbort(http.StatusBadRequest, http.StatusText(http.StatusBadRequest))
|
||||
id := t.GetIDFromURL()
|
||||
|
||||
originalTarget, err := dao.GetRepTarget(id)
|
||||
if err != nil {
|
||||
log.Errorf("failed to get target %d: %v", id, err)
|
||||
t.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
|
||||
}
|
||||
|
||||
if originalTarget == nil {
|
||||
t.CustomAbort(http.StatusNotFound, http.StatusText(http.StatusNotFound))
|
||||
}
|
||||
|
||||
target := &models.RepTarget{}
|
||||
t.DecodeJSONReq(target)
|
||||
t.DecodeJSONReqAndValidate(target)
|
||||
|
||||
if target.ID == 0 {
|
||||
target.ID = id
|
||||
if target.Name != originalTarget.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 {
|
||||
target.Password = utils.ReversibleEncrypt(target.Password)
|
||||
}
|
||||
@ -209,10 +246,7 @@ func (t *TargetAPI) Put() {
|
||||
|
||||
// Delete ...
|
||||
func (t *TargetAPI) Delete() {
|
||||
id := t.getIDFromURL()
|
||||
if id == 0 {
|
||||
t.CustomAbort(http.StatusBadRequest, http.StatusText(http.StatusBadRequest))
|
||||
}
|
||||
id := t.GetIDFromURL()
|
||||
|
||||
target, err := dao.GetRepTarget(id)
|
||||
if err != nil {
|
||||
@ -229,17 +263,3 @@ func (t *TargetAPI) Delete() {
|
||||
t.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
|
||||
}
|
||||
}
|
||||
|
||||
func (t *TargetAPI) getIDFromURL() int64 {
|
||||
idStr := t.Ctx.Input.Param(":id")
|
||||
if len(idStr) == 0 {
|
||||
return 0
|
||||
}
|
||||
|
||||
id, err := strconv.ParseInt(idStr, 10, 64)
|
||||
if err != nil {
|
||||
t.CustomAbort(http.StatusBadRequest, "invalid ID in request URL")
|
||||
}
|
||||
|
||||
return id
|
||||
}
|
||||
|
158
api/user.go
158
api/user.go
@ -16,8 +16,10 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
@ -133,14 +135,52 @@ func (ua *UserAPI) Get() {
|
||||
}
|
||||
|
||||
// Put ...
|
||||
func (ua *UserAPI) Put() { //currently only for toggle admin, so no request body
|
||||
func (ua *UserAPI) Put() {
|
||||
ldapAdminUser := (ua.AuthMode == "ldap_auth" && ua.userID == 1 && ua.userID == ua.currentUserID)
|
||||
|
||||
if !(ua.AuthMode == "db_auth" || ldapAdminUser) {
|
||||
ua.CustomAbort(http.StatusForbidden, "")
|
||||
}
|
||||
if !ua.IsAdmin {
|
||||
log.Warningf("current user, id: %d does not have admin role, can not update other user's role", ua.currentUserID)
|
||||
ua.RenderError(http.StatusForbidden, "User does not have admin role")
|
||||
if ua.userID != ua.currentUserID {
|
||||
log.Warning("Guests can only change their own account.")
|
||||
ua.CustomAbort(http.StatusForbidden, "Guests can only change their own account.")
|
||||
}
|
||||
}
|
||||
user := models.User{UserID: ua.userID}
|
||||
ua.DecodeJSONReq(&user)
|
||||
err := commonValidate(user)
|
||||
if err != nil {
|
||||
log.Warning("Bad request in change user profile: %v", err)
|
||||
ua.RenderError(http.StatusBadRequest, "change user profile error:"+err.Error())
|
||||
return
|
||||
}
|
||||
userQuery := models.User{UserID: ua.userID}
|
||||
dao.ToggleUserAdminRole(userQuery)
|
||||
u, err := dao.GetUser(userQuery)
|
||||
if err != nil {
|
||||
log.Errorf("Error occurred in GetUser, error: %v", err)
|
||||
ua.CustomAbort(http.StatusInternalServerError, "Internal error.")
|
||||
}
|
||||
if u == nil {
|
||||
log.Errorf("User with Id: %d does not exist", ua.userID)
|
||||
ua.CustomAbort(http.StatusNotFound, "")
|
||||
}
|
||||
if u.Email != user.Email {
|
||||
emailExist, err := dao.UserExists(user, "email")
|
||||
if err != nil {
|
||||
log.Errorf("Error occurred in change user profile: %v", err)
|
||||
ua.CustomAbort(http.StatusInternalServerError, "Internal error.")
|
||||
}
|
||||
if emailExist {
|
||||
log.Warning("email has already been used!")
|
||||
ua.RenderError(http.StatusConflict, "email has already been used!")
|
||||
return
|
||||
}
|
||||
}
|
||||
if err := dao.ChangeUserProfile(user); err != nil {
|
||||
log.Errorf("Failed to update user profile, error: %v", err)
|
||||
ua.CustomAbort(http.StatusInternalServerError, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
// Post ...
|
||||
@ -157,12 +197,36 @@ func (ua *UserAPI) Post() {
|
||||
|
||||
user := models.User{}
|
||||
ua.DecodeJSONReq(&user)
|
||||
|
||||
err := validate(user)
|
||||
if err != nil {
|
||||
log.Warning("Bad request in Register: %v", err)
|
||||
ua.RenderError(http.StatusBadRequest, "register error:"+err.Error())
|
||||
return
|
||||
}
|
||||
userExist, err := dao.UserExists(user, "username")
|
||||
if err != nil {
|
||||
log.Errorf("Error occurred in Register: %v", err)
|
||||
ua.CustomAbort(http.StatusInternalServerError, "Internal error.")
|
||||
}
|
||||
if userExist {
|
||||
log.Warning("username has already been used!")
|
||||
ua.RenderError(http.StatusConflict, "username has already been used!")
|
||||
return
|
||||
}
|
||||
emailExist, err := dao.UserExists(user, "email")
|
||||
if err != nil {
|
||||
log.Errorf("Error occurred in change user profile: %v", err)
|
||||
ua.CustomAbort(http.StatusInternalServerError, "Internal error.")
|
||||
}
|
||||
if emailExist {
|
||||
log.Warning("email has already been used!")
|
||||
ua.RenderError(http.StatusConflict, "email has already been used!")
|
||||
return
|
||||
}
|
||||
userID, err := dao.Register(user)
|
||||
if err != nil {
|
||||
log.Errorf("Error occurred in Register: %v", err)
|
||||
ua.RenderError(http.StatusInternalServerError, "Internal error.")
|
||||
return
|
||||
ua.CustomAbort(http.StatusInternalServerError, "Internal error.")
|
||||
}
|
||||
|
||||
ua.Redirect(http.StatusCreated, strconv.FormatInt(userID, 10))
|
||||
@ -186,9 +250,8 @@ func (ua *UserAPI) Delete() {
|
||||
|
||||
// ChangePassword handles PUT to /api/users/{}/password
|
||||
func (ua *UserAPI) ChangePassword() {
|
||||
|
||||
ldapAdminUser := (ua.AuthMode == "ldap_auth" && ua.userID == 1 && ua.userID == ua.currentUserID)
|
||||
|
||||
|
||||
if !(ua.AuthMode == "db_auth" || ldapAdminUser) {
|
||||
ua.CustomAbort(http.StatusForbidden, "")
|
||||
}
|
||||
@ -228,3 +291,80 @@ func (ua *UserAPI) ChangePassword() {
|
||||
ua.CustomAbort(http.StatusInternalServerError, "Internal error.")
|
||||
}
|
||||
}
|
||||
|
||||
// ToggleUserAdminRole handles PUT api/users/{}/toggleadmin
|
||||
func (ua *UserAPI) ToggleUserAdminRole() {
|
||||
if !ua.IsAdmin {
|
||||
log.Warningf("current user, id: %d does not have admin role, can not update other user's role", ua.currentUserID)
|
||||
ua.RenderError(http.StatusForbidden, "User does not have admin role")
|
||||
return
|
||||
}
|
||||
userQuery := models.User{UserID: ua.userID}
|
||||
ua.DecodeJSONReq(&userQuery)
|
||||
if err := dao.ToggleUserAdminRole(userQuery.UserID, userQuery.HasAdminRole); err != nil {
|
||||
log.Errorf("Error occurred in ToggleUserAdminRole: %v", err)
|
||||
ua.CustomAbort(http.StatusInternalServerError, "Internal error.")
|
||||
}
|
||||
}
|
||||
|
||||
// validate only validate when user register
|
||||
func validate(user models.User) error {
|
||||
|
||||
if isIllegalLength(user.Username, 0, 20) {
|
||||
return fmt.Errorf("Username with illegal length.")
|
||||
}
|
||||
if isContainIllegalChar(user.Username, []string{",", "~", "#", "$", "%"}) {
|
||||
return fmt.Errorf("Username contains illegal characters.")
|
||||
}
|
||||
if isIllegalLength(user.Password, 0, 20) {
|
||||
return fmt.Errorf("Password with illegal length.")
|
||||
}
|
||||
if err := commonValidate(user); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
//commonValidate validates email, realname, comment information when user register or change their profile
|
||||
func commonValidate(user models.User) error {
|
||||
|
||||
if len(user.Email) > 0 {
|
||||
if m, _ := regexp.MatchString(`^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$`, user.Email); !m {
|
||||
return fmt.Errorf("Email with illegal format.")
|
||||
}
|
||||
} else {
|
||||
return fmt.Errorf("Email can't be empty")
|
||||
}
|
||||
|
||||
if isIllegalLength(user.Realname, 0, 20) {
|
||||
return fmt.Errorf("Realname with illegal length.")
|
||||
}
|
||||
|
||||
if isContainIllegalChar(user.Realname, []string{",", "~", "#", "$", "%"}) {
|
||||
return fmt.Errorf("Realname contains illegal characters.")
|
||||
}
|
||||
if isIllegalLength(user.Comment, -1, 30) {
|
||||
return fmt.Errorf("Comment with illegal length.")
|
||||
}
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
func isIllegalLength(s string, min int, max int) bool {
|
||||
if min == -1 {
|
||||
return (len(s) > max)
|
||||
}
|
||||
if max == -1 {
|
||||
return (len(s) <= min)
|
||||
}
|
||||
return (len(s) < min || len(s) > max)
|
||||
}
|
||||
|
||||
func isContainIllegalChar(s string, illegalChar []string) bool {
|
||||
for _, c := range illegalChar {
|
||||
if strings.Index(s, c) >= 0 {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
53
api/utils.go
53
api/utils.go
@ -59,11 +59,13 @@ func listRoles(userID int, projectID int64) ([]models.Role, error) {
|
||||
roles := make([]models.Role, 0, 1)
|
||||
isSysAdmin, err := dao.IsAdminRole(userID)
|
||||
if err != nil {
|
||||
log.Errorf("failed to determine whether the user %d is system admin: %v", userID, err)
|
||||
return roles, err
|
||||
}
|
||||
if isSysAdmin {
|
||||
role, err := dao.GetRoleByID(models.PROJECTADMIN)
|
||||
if err != nil {
|
||||
log.Errorf("failed to get role %d: %v", models.PROJECTADMIN, err)
|
||||
return roles, err
|
||||
}
|
||||
roles = append(roles, *role)
|
||||
@ -72,6 +74,7 @@ func listRoles(userID int, projectID int64) ([]models.Role, error) {
|
||||
|
||||
rs, err := dao.GetUserProjectRoles(userID, projectID)
|
||||
if err != nil {
|
||||
log.Errorf("failed to get user %d 's roles for project %d: %v", userID, projectID, err)
|
||||
return roles, err
|
||||
}
|
||||
roles = append(roles, rs...)
|
||||
@ -167,26 +170,64 @@ func TriggerReplicationByRepository(repository string, tags []string, operation
|
||||
}
|
||||
}
|
||||
|
||||
func postReplicationAction(policyID int64, acton string) error {
|
||||
data := struct {
|
||||
PolicyID int64 `json:"policy_id"`
|
||||
Action string `json:"action"`
|
||||
}{
|
||||
PolicyID: policyID,
|
||||
Action: acton,
|
||||
}
|
||||
|
||||
b, err := json.Marshal(&data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
url := buildReplicationActionURL()
|
||||
|
||||
resp, err := http.DefaultClient.Post(url, "application/json", bytes.NewBuffer(b))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if resp.StatusCode == http.StatusOK {
|
||||
return nil
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
|
||||
b, err = ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return fmt.Errorf("%d %s", resp.StatusCode, string(b))
|
||||
}
|
||||
|
||||
func buildReplicationURL() string {
|
||||
url := getJobServiceURL()
|
||||
url = strings.TrimSpace(url)
|
||||
url = strings.TrimRight(url, "/")
|
||||
|
||||
return fmt.Sprintf("%s/api/jobs/replication", url)
|
||||
}
|
||||
|
||||
func buildJobLogURL(jobID string) string {
|
||||
url := getJobServiceURL()
|
||||
url = strings.TrimSpace(url)
|
||||
url = strings.TrimRight(url, "/")
|
||||
|
||||
return fmt.Sprintf("%s/api/jobs/replication/%s/log", url, jobID)
|
||||
}
|
||||
|
||||
func buildReplicationActionURL() string {
|
||||
url := getJobServiceURL()
|
||||
return fmt.Sprintf("%s/api/jobs/replication/actions", url)
|
||||
}
|
||||
|
||||
func getJobServiceURL() string {
|
||||
url := os.Getenv("JOB_SERVICE_URL")
|
||||
url = strings.TrimSpace(url)
|
||||
url = strings.TrimRight(url, "/")
|
||||
|
||||
if len(url) == 0 {
|
||||
url = "http://jobservice"
|
||||
}
|
||||
|
||||
return url
|
||||
}
|
||||
|
@ -111,6 +111,9 @@ func (l *Auth) Authenticate(m models.AuthModel) (*models.User, error) {
|
||||
u.Realname = m.Principal
|
||||
u.Password = "12345678AbC"
|
||||
u.Comment = "registered from LDAP."
|
||||
if u.Email == "" {
|
||||
u.Email = u.Username + "@placeholder.com"
|
||||
}
|
||||
userID, err := dao.Register(u)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -69,7 +69,7 @@ func (c *CommonController) Login() {
|
||||
// SwitchLanguage handles UI request to switch between different languages and re-render template based on language.
|
||||
func (c *CommonController) SwitchLanguage() {
|
||||
lang := c.GetString("lang")
|
||||
if lang == "en-US" || lang == "zh-CN" || lang == "de-DE" || lang == "ru-RU" {
|
||||
if lang == "en-US" || lang == "zh-CN" || lang == "de-DE" || lang == "ru-RU" || lang == "ja-JP" {
|
||||
c.SetSession("lang", lang)
|
||||
c.Data["Lang"] = lang
|
||||
}
|
||||
|
@ -115,3 +115,49 @@ func AccessLog(username, projectName, repoName, repoTag, action string) error {
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
//GetRecentLogs returns recent logs according to parameters
|
||||
func GetRecentLogs(userID, linesNum int, startTime, endTime string) ([]models.AccessLog, error) {
|
||||
var recentLogList []models.AccessLog
|
||||
queryParam := make([]interface{}, 1)
|
||||
|
||||
sql := "select log_id, access_log.user_id, project_id, repo_name, repo_tag, GUID, operation, op_time, username from access_log left join user on access_log.user_id=user.user_id where project_id in (select distinct project_id from project_member where user_id = ?)"
|
||||
queryParam = append(queryParam, userID)
|
||||
if startTime != "" {
|
||||
sql += " and op_time >= ?"
|
||||
queryParam = append(queryParam, startTime)
|
||||
}
|
||||
|
||||
if endTime != "" {
|
||||
sql += " and op_time <= ?"
|
||||
queryParam = append(queryParam, endTime)
|
||||
}
|
||||
|
||||
sql += " order by op_time desc"
|
||||
if linesNum != 0 {
|
||||
sql += " limit ?"
|
||||
queryParam = append(queryParam, linesNum)
|
||||
}
|
||||
o := GetOrmer()
|
||||
_, err := o.Raw(sql, queryParam).QueryRows(&recentLogList)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return recentLogList, nil
|
||||
}
|
||||
|
||||
//GetTopRepos return top accessed public repos
|
||||
func GetTopRepos(countNum int) ([]models.TopRepo, error) {
|
||||
|
||||
o := GetOrmer()
|
||||
|
||||
sql := "select repo_name, COUNT(repo_name) as access_count from access_log left join project on access_log.project_id=project.project_id where project.public = 1 and access_log.operation = 'pull' group by repo_name order by access_count desc limit ? "
|
||||
queryParam := make([]interface{}, 1)
|
||||
queryParam = append(queryParam, countNum)
|
||||
var lists []models.TopRepo
|
||||
_, err := o.Raw(sql, queryParam).QueryRows(&lists)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return lists, nil
|
||||
}
|
||||
|
23
dao/base.go
23
dao/base.go
@ -18,39 +18,18 @@ package dao
|
||||
import (
|
||||
"net"
|
||||
|
||||
"github.com/vmware/harbor/utils/log"
|
||||
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/astaxie/beego/orm"
|
||||
_ "github.com/go-sql-driver/mysql" //register mysql driver
|
||||
"github.com/vmware/harbor/utils/log"
|
||||
)
|
||||
|
||||
// NonExistUserID : if a user does not exist, the ID of the user will be 0.
|
||||
const NonExistUserID = 0
|
||||
|
||||
func isIllegalLength(s string, min int, max int) bool {
|
||||
if min == -1 {
|
||||
return (len(s) > max)
|
||||
}
|
||||
if max == -1 {
|
||||
return (len(s) <= min)
|
||||
}
|
||||
return (len(s) < min || len(s) > max)
|
||||
}
|
||||
|
||||
func isContainIllegalChar(s string, illegalChar []string) bool {
|
||||
for _, c := range illegalChar {
|
||||
if strings.Index(s, c) >= 0 {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// GenerateRandomString generates a random string
|
||||
func GenerateRandomString() (string, error) {
|
||||
o := orm.NewOrm()
|
||||
|
151
dao/dao_test.go
151
dao/dao_test.go
@ -689,7 +689,7 @@ func TestDeleteProjectMember(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestToggleAdminRole(t *testing.T) {
|
||||
err := ToggleUserAdminRole(*currentUser)
|
||||
err := ToggleUserAdminRole(currentUser.UserID, 1)
|
||||
if err != nil {
|
||||
t.Errorf("Error in toggle ToggleUserAdmin role: %v, user: %+v", err, currentUser)
|
||||
}
|
||||
@ -700,7 +700,7 @@ func TestToggleAdminRole(t *testing.T) {
|
||||
if !isAdmin {
|
||||
t.Errorf("User is not admin after toggled, user id: %d", currentUser.UserID)
|
||||
}
|
||||
err = ToggleUserAdminRole(*currentUser)
|
||||
err = ToggleUserAdminRole(currentUser.UserID, 0)
|
||||
if err != nil {
|
||||
t.Errorf("Error in toggle ToggleUserAdmin role: %v, user: %+v", err, currentUser)
|
||||
}
|
||||
@ -713,6 +713,39 @@ func TestToggleAdminRole(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestChangeUserProfile(t *testing.T) {
|
||||
user := models.User{UserID: currentUser.UserID, Email: username + "@163.com", Realname: "test", Comment: "Unit Test"}
|
||||
err := ChangeUserProfile(user)
|
||||
if err != nil {
|
||||
t.Errorf("Error occurred in ChangeUserProfile: %v", err)
|
||||
}
|
||||
loginedUser, err := GetUser(models.User{UserID: currentUser.UserID})
|
||||
if err != nil {
|
||||
t.Errorf("Error occurred in GetUser: %v", err)
|
||||
}
|
||||
if loginedUser != nil {
|
||||
if loginedUser.Email != username+"@163.com" {
|
||||
t.Errorf("user email does not update, expected: %s, acutal: %s", username+"@163.com", loginedUser.Email)
|
||||
}
|
||||
if loginedUser.Realname != "test" {
|
||||
t.Errorf("user realname does not update, expected: %s, acutal: %s", "test", loginedUser.Realname)
|
||||
}
|
||||
if loginedUser.Comment != "Unit Test" {
|
||||
t.Errorf("user email does not update, expected: %s, acutal: %s", "Unit Test", loginedUser.Comment)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetRecentLogs(t *testing.T) {
|
||||
logs, err := GetRecentLogs(currentUser.UserID, 10, "2016-05-13 00:00:00", time.Now().String())
|
||||
if err != nil {
|
||||
t.Errorf("error occured in getting recent logs, error: %v", err)
|
||||
}
|
||||
if len(logs) <= 0 {
|
||||
t.Errorf("get logs error, expected: %d, actual: %d", 1, len(logs))
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeleteUser(t *testing.T) {
|
||||
err := DeleteUser(currentUser.UserID)
|
||||
if err != nil {
|
||||
@ -731,6 +764,7 @@ var targetID, policyID, policyID2, policyID3, jobID, jobID2, jobID3 int64
|
||||
|
||||
func TestAddRepTarget(t *testing.T) {
|
||||
target := models.RepTarget{
|
||||
Name: "test",
|
||||
URL: "127.0.0.1:5000",
|
||||
Username: "admin",
|
||||
Password: "admin",
|
||||
@ -766,6 +800,83 @@ 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 TestFilterRepTargets(t *testing.T) {
|
||||
targets, err := FilterRepTargets("test")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to get all targets: %v", err)
|
||||
}
|
||||
|
||||
if len(targets) == 0 {
|
||||
t.Errorf("unexpected num of targets: %d, expected: %d", len(targets), 1)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAddRepPolicy(t *testing.T) {
|
||||
policy := models.RepPolicy{
|
||||
ProjectID: 1,
|
||||
@ -800,6 +911,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) {
|
||||
err := DisableRepPolicy(policyID)
|
||||
if err != nil {
|
||||
@ -1021,6 +1149,23 @@ func TestDeleteRepTarget(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestFilterRepPolicies(t *testing.T) {
|
||||
_, err := FilterRepPolicies("name", 0)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to filter policy")
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateRepPolicy(t *testing.T) {
|
||||
policy := &models.RepPolicy{
|
||||
ID: policyID,
|
||||
Name: "new_policy_name",
|
||||
}
|
||||
if err := UpdateRepPolicy(policy); err != nil {
|
||||
t.Fatalf("failed to update policy")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeleteRepPolicy(t *testing.T) {
|
||||
err := DeleteRepPolicy(policyID)
|
||||
if err != nil {
|
||||
@ -1029,7 +1174,7 @@ func TestDeleteRepPolicy(t *testing.T) {
|
||||
}
|
||||
t.Logf("delete rep policy, id: %d", policyID)
|
||||
p, err := GetRepPolicy(policyID)
|
||||
if err != nil {
|
||||
if err != nil && err != orm.ErrNoRows {
|
||||
t.Errorf("Error occured in GetRepPolicy:%v", err)
|
||||
}
|
||||
if p != nil {
|
||||
|
@ -18,7 +18,6 @@ package dao
|
||||
import (
|
||||
"github.com/vmware/harbor/models"
|
||||
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
@ -30,15 +29,7 @@ import (
|
||||
// AddProject adds a project to the database along with project roles information and access log records.
|
||||
func AddProject(project models.Project) (int64, error) {
|
||||
|
||||
if isIllegalLength(project.Name, 4, 30) {
|
||||
return 0, errors.New("project name is illegal in length. (greater than 4 or less than 30)")
|
||||
}
|
||||
if isContainIllegalChar(project.Name, []string{"~", "-", "$", "\\", "[", "]", "{", "}", "(", ")", "&", "^", "%", "*", "<", ">", "\"", "'", "/", "?", "@"}) {
|
||||
return 0, errors.New("project name contains illegal characters")
|
||||
}
|
||||
|
||||
o := GetOrmer()
|
||||
|
||||
p, err := o.Raw("insert into project (owner_id, name, creation_time, update_time, deleted, public) values (?, ?, ?, ?, ?, ?)").Prepare()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
|
@ -58,7 +58,7 @@ func DeleteProjectMember(projectID int64, userID int) error {
|
||||
func GetUserByProject(projectID int64, queryUser models.User) ([]models.User, error) {
|
||||
o := GetOrmer()
|
||||
u := []models.User{}
|
||||
sql := `select u.user_id, u.username, r.name rolename, r.role_id
|
||||
sql := `select u.user_id, u.username, r.name rolename, r.role_id as role
|
||||
from user u
|
||||
join project_member pm
|
||||
on pm.project_id = ? and u.user_id = pm.user_id
|
||||
|
@ -17,7 +17,6 @@ package dao
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"regexp"
|
||||
"time"
|
||||
|
||||
"github.com/vmware/harbor/models"
|
||||
@ -26,14 +25,7 @@ import (
|
||||
|
||||
// Register is used for user to register, the password is encrypted before the record is inserted into database.
|
||||
func Register(user models.User) (int64, error) {
|
||||
|
||||
err := validate(user)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
o := GetOrmer()
|
||||
|
||||
p, err := o.Raw("insert into user (username, password, realname, email, comment, salt, sysadmin_flag, creation_time, update_time) values (?, ?, ?, ?, ?, ?, ?, ?, ?)").Prepare()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
@ -59,46 +51,6 @@ func Register(user models.User) (int64, error) {
|
||||
return userID, nil
|
||||
}
|
||||
|
||||
func validate(user models.User) error {
|
||||
|
||||
if isIllegalLength(user.Username, 0, 20) {
|
||||
return errors.New("Username with illegal length.")
|
||||
}
|
||||
if isContainIllegalChar(user.Username, []string{",", "~", "#", "$", "%"}) {
|
||||
return errors.New("Username contains illegal characters.")
|
||||
}
|
||||
|
||||
if exist, _ := UserExists(models.User{Username: user.Username}, "username"); exist {
|
||||
return errors.New("Username already exists.")
|
||||
}
|
||||
|
||||
if len(user.Email) > 0 {
|
||||
if m, _ := regexp.MatchString(`^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$`, user.Email); !m {
|
||||
return errors.New("Email with illegal format.")
|
||||
}
|
||||
if exist, _ := UserExists(models.User{Email: user.Email}, "email"); exist {
|
||||
return errors.New("Email already exists.")
|
||||
}
|
||||
}
|
||||
|
||||
if isIllegalLength(user.Realname, 0, 20) {
|
||||
return errors.New("Realname with illegal length.")
|
||||
}
|
||||
|
||||
if isContainIllegalChar(user.Realname, []string{",", "~", "#", "$", "%"}) {
|
||||
return errors.New("Realname contains illegal characters.")
|
||||
}
|
||||
|
||||
if isIllegalLength(user.Password, 0, 20) {
|
||||
return errors.New("Password with illegal length.")
|
||||
}
|
||||
|
||||
if isIllegalLength(user.Comment, -1, 30) {
|
||||
return errors.New("Comment with illegal length.")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// UserExists returns whether a user exists according username or Email.
|
||||
func UserExists(user models.User, target string) (bool, error) {
|
||||
|
||||
|
@ -11,13 +11,13 @@ import (
|
||||
|
||||
// AddRepTarget ...
|
||||
func AddRepTarget(target models.RepTarget) (int64, error) {
|
||||
o := orm.NewOrm()
|
||||
o := GetOrmer()
|
||||
return o.Insert(&target)
|
||||
}
|
||||
|
||||
// GetRepTarget ...
|
||||
func GetRepTarget(id int64) (*models.RepTarget, error) {
|
||||
o := orm.NewOrm()
|
||||
o := GetOrmer()
|
||||
t := models.RepTarget{ID: id}
|
||||
err := o.Read(&t)
|
||||
if err == orm.ErrNoRows {
|
||||
@ -26,37 +26,56 @@ func GetRepTarget(id int64) (*models.RepTarget, error) {
|
||||
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 ...
|
||||
func DeleteRepTarget(id int64) error {
|
||||
o := orm.NewOrm()
|
||||
o := GetOrmer()
|
||||
_, err := o.Delete(&models.RepTarget{ID: id})
|
||||
return err
|
||||
}
|
||||
|
||||
// UpdateRepTarget ...
|
||||
func UpdateRepTarget(target models.RepTarget) error {
|
||||
o := orm.NewOrm()
|
||||
if len(target.Password) != 0 {
|
||||
_, err := o.Update(&target)
|
||||
return err
|
||||
}
|
||||
|
||||
_, err := o.Update(&target, "URL", "Name", "Username")
|
||||
o := GetOrmer()
|
||||
_, err := o.Update(&target, "URL", "Name", "Username", "Password")
|
||||
return err
|
||||
}
|
||||
|
||||
// GetAllRepTargets ...
|
||||
func GetAllRepTargets() ([]*models.RepTarget, error) {
|
||||
o := orm.NewOrm()
|
||||
qs := o.QueryTable(&models.RepTarget{})
|
||||
// FilterRepTargets filters targets by name
|
||||
func FilterRepTargets(name string) ([]*models.RepTarget, error) {
|
||||
o := GetOrmer()
|
||||
|
||||
var args []interface{}
|
||||
|
||||
sql := `select * from replication_target `
|
||||
if len(name) != 0 {
|
||||
sql += `where name like ? `
|
||||
args = append(args, "%"+name+"%")
|
||||
}
|
||||
sql += `order by creation_time`
|
||||
|
||||
var targets []*models.RepTarget
|
||||
_, err := qs.All(&targets)
|
||||
return targets, err
|
||||
|
||||
if _, err := o.Raw(sql, args).QueryRows(&targets); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return targets, nil
|
||||
}
|
||||
|
||||
// AddRepPolicy ...
|
||||
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())`
|
||||
var sql string
|
||||
if policy.Enabled == 1 {
|
||||
@ -78,33 +97,103 @@ func AddRepPolicy(policy models.RepPolicy) (int64, error) {
|
||||
|
||||
// GetRepPolicy ...
|
||||
func GetRepPolicy(id int64) (*models.RepPolicy, error) {
|
||||
o := orm.NewOrm()
|
||||
p := models.RepPolicy{ID: id}
|
||||
err := o.Read(&p)
|
||||
if err == orm.ErrNoRows {
|
||||
return nil, nil
|
||||
o := GetOrmer()
|
||||
sql := `select * from replication_policy where id = ?`
|
||||
|
||||
var policy models.RepPolicy
|
||||
|
||||
if err := o.Raw(sql, id).QueryRow(&policy); err != nil {
|
||||
if err == orm.ErrNoRows {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return &p, err
|
||||
|
||||
return &policy, nil
|
||||
}
|
||||
|
||||
// FilterRepPolicies filters policies by name and project ID
|
||||
func FilterRepPolicies(name string, projectID int64) ([]*models.RepPolicy, error) {
|
||||
o := GetOrmer()
|
||||
|
||||
var args []interface{}
|
||||
|
||||
sql := `select rp.id, rp.project_id, p.name as project_name, rp.target_id,
|
||||
rt.name as target_name, rp.name, rp.enabled, rp.description,
|
||||
rp.cron_str, rp.start_time, rp.creation_time, rp.update_time
|
||||
from replication_policy rp
|
||||
join project p on rp.project_id=p.project_id
|
||||
join replication_target rt on rp.target_id=rt.id `
|
||||
|
||||
if len(name) != 0 && projectID != 0 {
|
||||
sql += `where rp.name like ? and rp.project_id = ? `
|
||||
args = append(args, "%"+name+"%")
|
||||
args = append(args, projectID)
|
||||
} else if len(name) != 0 {
|
||||
sql += `where rp.name like ? `
|
||||
args = append(args, "%"+name+"%")
|
||||
} else if projectID != 0 {
|
||||
sql += `where rp.project_id = ? `
|
||||
args = append(args, projectID)
|
||||
}
|
||||
|
||||
sql += `order by rp.creation_time`
|
||||
|
||||
var policies []*models.RepPolicy
|
||||
if _, err := o.Raw(sql, args).QueryRows(&policies); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return policies, nil
|
||||
}
|
||||
|
||||
// GetRepPolicyByName ...
|
||||
func GetRepPolicyByName(name string) (*models.RepPolicy, error) {
|
||||
o := GetOrmer()
|
||||
sql := `select * from replication_policy where name = ?`
|
||||
|
||||
var policy models.RepPolicy
|
||||
|
||||
if err := o.Raw(sql, name).QueryRow(&policy); err != nil {
|
||||
if err == orm.ErrNoRows {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &policy, nil
|
||||
}
|
||||
|
||||
// GetRepPolicyByProject ...
|
||||
func GetRepPolicyByProject(projectID int64) ([]*models.RepPolicy, error) {
|
||||
var res []*models.RepPolicy
|
||||
o := orm.NewOrm()
|
||||
_, err := o.QueryTable("replication_policy").Filter("project_id", projectID).All(&res)
|
||||
return res, err
|
||||
o := GetOrmer()
|
||||
sql := `select * from replication_policy where project_id = ?`
|
||||
|
||||
var policies []*models.RepPolicy
|
||||
|
||||
if _, err := o.Raw(sql, projectID).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")
|
||||
return err
|
||||
}
|
||||
|
||||
// DeleteRepPolicy ...
|
||||
func DeleteRepPolicy(id int64) error {
|
||||
o := orm.NewOrm()
|
||||
o := GetOrmer()
|
||||
_, err := o.Delete(&models.RepPolicy{ID: id})
|
||||
return err
|
||||
}
|
||||
|
||||
// UpdateRepPolicyEnablement ...
|
||||
func UpdateRepPolicyEnablement(id int64, enabled int) error {
|
||||
o := orm.NewOrm()
|
||||
o := GetOrmer()
|
||||
p := models.RepPolicy{
|
||||
ID: id,
|
||||
Enabled: enabled}
|
||||
@ -125,7 +214,7 @@ func DisableRepPolicy(id int64) error {
|
||||
|
||||
// AddRepJob ...
|
||||
func AddRepJob(job models.RepJob) (int64, error) {
|
||||
o := orm.NewOrm()
|
||||
o := GetOrmer()
|
||||
if len(job.Status) == 0 {
|
||||
job.Status = models.JobPending
|
||||
}
|
||||
@ -137,7 +226,7 @@ func AddRepJob(job models.RepJob) (int64, error) {
|
||||
|
||||
// GetRepJob ...
|
||||
func GetRepJob(id int64) (*models.RepJob, error) {
|
||||
o := orm.NewOrm()
|
||||
o := GetOrmer()
|
||||
j := models.RepJob{ID: id}
|
||||
err := o.Read(&j)
|
||||
if err == orm.ErrNoRows {
|
||||
@ -164,20 +253,20 @@ func GetRepJobToStop(policyID int64) ([]*models.RepJob, error) {
|
||||
}
|
||||
|
||||
func repJobPolicyIDQs(policyID int64) orm.QuerySeter {
|
||||
o := orm.NewOrm()
|
||||
o := GetOrmer()
|
||||
return o.QueryTable("replication_job").Filter("policy_id", policyID)
|
||||
}
|
||||
|
||||
// DeleteRepJob ...
|
||||
func DeleteRepJob(id int64) error {
|
||||
o := orm.NewOrm()
|
||||
o := GetOrmer()
|
||||
_, err := o.Delete(&models.RepJob{ID: id})
|
||||
return err
|
||||
}
|
||||
|
||||
// UpdateRepJobStatus ...
|
||||
func UpdateRepJobStatus(id int64, status string) error {
|
||||
o := orm.NewOrm()
|
||||
o := GetOrmer()
|
||||
j := models.RepJob{
|
||||
ID: id,
|
||||
Status: status,
|
||||
|
@ -18,6 +18,7 @@ package dao
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/astaxie/beego/orm"
|
||||
"github.com/vmware/harbor/models"
|
||||
)
|
||||
|
||||
@ -83,6 +84,9 @@ func GetRoleByID(id int) (*models.Role, error) {
|
||||
|
||||
var role models.Role
|
||||
if err := o.Raw(sql, id).QueryRow(&role); err != nil {
|
||||
if err == orm.ErrNoRows {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return &role, nil
|
||||
|
21
dao/user.go
21
dao/user.go
@ -109,12 +109,13 @@ func ListUsers(query models.User) ([]models.User, error) {
|
||||
}
|
||||
|
||||
// ToggleUserAdminRole gives a user admin role.
|
||||
func ToggleUserAdminRole(u models.User) error {
|
||||
func ToggleUserAdminRole(userID, hasAdmin int) error {
|
||||
o := GetOrmer()
|
||||
|
||||
sql := `update user set sysadmin_flag =not sysadmin_flag where user_id = ?`
|
||||
|
||||
r, err := o.Raw(sql, u.UserID).Exec()
|
||||
queryParams := make([]interface{}, 1)
|
||||
sql := `update user set sysadmin_flag = ? where user_id = ?`
|
||||
queryParams = append(queryParams, hasAdmin)
|
||||
queryParams = append(queryParams, userID)
|
||||
r, err := o.Raw(sql, queryParams).Exec()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -229,3 +230,13 @@ func DeleteUser(userID int) error {
|
||||
_, err := o.Raw(`update user set deleted = 1 where user_id = ?`, userID).Exec()
|
||||
return err
|
||||
}
|
||||
|
||||
// ChangeUserProfile ...
|
||||
func ChangeUserProfile(user models.User) error {
|
||||
o := GetOrmer()
|
||||
if _, err := o.Update(&user, "Email", "Realname", "Comment"); err != nil {
|
||||
log.Errorf("update user failed, error: %v", err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
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 |
@ -4,7 +4,7 @@ swagger: '2.0'
|
||||
info:
|
||||
title: Harbor API
|
||||
description: These APIs provide services for manipulating Harbor project.
|
||||
version: "0.1.0"
|
||||
version: "0.1.1"
|
||||
# the domain of the service
|
||||
host: localhost
|
||||
# array of all schemes that your API supports
|
||||
@ -119,7 +119,7 @@ paths:
|
||||
description: Project name already exists.
|
||||
500:
|
||||
description: Unexpected internal errors.
|
||||
/projects/{project_id}:
|
||||
/projects/{project_id}/publicity:
|
||||
put:
|
||||
summary: Update properties for a selected project.
|
||||
description: |
|
||||
@ -167,7 +167,7 @@ paths:
|
||||
- name: access_log
|
||||
in: body
|
||||
schema:
|
||||
$ref: '#/definitions/AccessLog'
|
||||
$ref: '#/definitions/AccessLogFilter'
|
||||
description: Search results of access logs.
|
||||
tags:
|
||||
- Products
|
||||
@ -204,7 +204,7 @@ paths:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/definitions/Role'
|
||||
$ref: '#/definitions/User'
|
||||
400:
|
||||
description: Illegal format of provided ID value.
|
||||
401:
|
||||
@ -353,7 +353,24 @@ paths:
|
||||
404:
|
||||
description: Project ID does not exist.
|
||||
500:
|
||||
description: Unexpected internal errors.
|
||||
description: Unexpected internal errors.
|
||||
/statistics:
|
||||
get:
|
||||
summary: Get projects number and repositories number relevant to the user
|
||||
description: |
|
||||
This endpoint is aimed to statistic all of the projects number and repositories number relevant to the logined user, also the public projects number and repositories number. If the user is admin, he can also get total projects number and total repositories number.
|
||||
tags:
|
||||
- Products
|
||||
responses:
|
||||
200:
|
||||
description: Get the projects number and repositories number relevant to the user successfully.
|
||||
schema:
|
||||
$ref: '#/definitions/StatisticMap'
|
||||
401:
|
||||
description: User need to log in first.
|
||||
500:
|
||||
description: Unexpected internal errors.
|
||||
|
||||
/users:
|
||||
get:
|
||||
summary: Get registered users of Harbor.
|
||||
@ -407,10 +424,9 @@ paths:
|
||||
description: Unexpected internal errors.
|
||||
/users/{user_id}:
|
||||
put:
|
||||
summary: Update a registered user to change to be an administrator of Harbor.
|
||||
summary: Update a registered user to change his profile.
|
||||
description: |
|
||||
This endpoint let a registered user change to be an administrator
|
||||
of Harbor.
|
||||
This endpoint let a registered user change his profile.
|
||||
parameters:
|
||||
- name: user_id
|
||||
in: path
|
||||
@ -418,6 +434,12 @@ paths:
|
||||
format: int32
|
||||
required: true
|
||||
description: Registered user ID
|
||||
- name: profile
|
||||
in: body
|
||||
description: Only email, realname and comment can be modified.
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/User'
|
||||
tags:
|
||||
- Products
|
||||
responses:
|
||||
@ -490,7 +512,35 @@ paths:
|
||||
403:
|
||||
description: Guests can only change their own account.
|
||||
500:
|
||||
description: Unexpected internal errors.
|
||||
description: Unexpected internal errors.
|
||||
/users/{user_id}/sysadmin:
|
||||
put:
|
||||
summary: Update a registered user to change to be an administrator of Harbor.
|
||||
description: |
|
||||
This endpoint let a registered user change to be an administrator
|
||||
of Harbor.
|
||||
parameters:
|
||||
- name: user_id
|
||||
in: path
|
||||
type: integer
|
||||
format: int32
|
||||
required: true
|
||||
description: Registered user ID
|
||||
tags:
|
||||
- Products
|
||||
responses:
|
||||
200:
|
||||
description: Updated user's admin role successfully.
|
||||
400:
|
||||
description: Invalid user ID.
|
||||
401:
|
||||
description: User need to log in first.
|
||||
403:
|
||||
description: User does not have permission of admin role.
|
||||
404:
|
||||
description: User ID does not exist.
|
||||
500:
|
||||
description: Unexpected internal errors.
|
||||
/repositories:
|
||||
get:
|
||||
summary: Get repositories accompany with relevant project and repo name.
|
||||
@ -517,7 +567,7 @@ paths:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/definitions/Repository'
|
||||
type: string
|
||||
400:
|
||||
description: Invalid project ID.
|
||||
403:
|
||||
@ -597,6 +647,70 @@ paths:
|
||||
description: Retrieved manifests from a relevant repository successfully.
|
||||
500:
|
||||
description: Unexpected internal errors.
|
||||
/repositories/top:
|
||||
get:
|
||||
summary: Get public repositories which are accessed most.
|
||||
description: |
|
||||
This endpoint aims to let users see the most popular public repositories
|
||||
parameters:
|
||||
- name: count
|
||||
in: query
|
||||
type: integer
|
||||
format: int32
|
||||
required: false
|
||||
description: The number of the requested public repositories, default is 10 if not provided.
|
||||
tags:
|
||||
- Products
|
||||
responses:
|
||||
200:
|
||||
description: Retrieved top repositories successfully.
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/definitions/TopRepo'
|
||||
400:
|
||||
description: Bad request because of invalid count.
|
||||
500:
|
||||
description: Unexpected internal errors.
|
||||
/logs:
|
||||
get:
|
||||
summary: Get recent logs of the projects which the user is a member of
|
||||
description: |
|
||||
This endpoint let user see the recent operation logs of the projects which he is member of
|
||||
parameters:
|
||||
- name: lines
|
||||
in: query
|
||||
type: integer
|
||||
format: int32
|
||||
required: false
|
||||
description: The number of logs to be shown, default is 10 if lines, start_time, end_time are not provided.
|
||||
- name: start_time
|
||||
in: query
|
||||
type: integer
|
||||
format: int64
|
||||
required: false
|
||||
description: The start time of logs to be shown in unix timestap
|
||||
- name: end_time
|
||||
in: query
|
||||
type: integer
|
||||
format: int64
|
||||
required: false
|
||||
description: The end time of logs to be shown in unix timestap
|
||||
tags:
|
||||
- Products
|
||||
responses:
|
||||
200:
|
||||
description: Get the required logs successfully.
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/definitions/AccessLog'
|
||||
400:
|
||||
description: Bad request because of invalid parameter of lines or start_time or end_time.
|
||||
401:
|
||||
description: User need to login first.
|
||||
500:
|
||||
description: Unexpected internal errors.
|
||||
definitions:
|
||||
Search:
|
||||
type: object
|
||||
@ -605,12 +719,41 @@ definitions:
|
||||
description: Search results of the projects that matched the filter keywords.
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/definitions/Project'
|
||||
$ref: '#/definitions/SearchProject'
|
||||
repositories:
|
||||
description: Search results of the repositories that matched the filter keywords.
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/definitions/Repository'
|
||||
$ref: '#/definitions/SearchRepository'
|
||||
SearchProject:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: integer
|
||||
format: int64
|
||||
description: The ID of project
|
||||
name:
|
||||
type: string
|
||||
description: The name of the project
|
||||
public:
|
||||
type: integer
|
||||
format: int
|
||||
description: The flag to indicate the publicity of the project (1 is public, 0 is non-public)
|
||||
SearchRepository:
|
||||
type: object
|
||||
properties:
|
||||
repository_name:
|
||||
type: string
|
||||
description: The name of the repository
|
||||
project_name:
|
||||
type: string
|
||||
description: The name of the project that the repository belongs to
|
||||
project_id:
|
||||
type: integer
|
||||
description: The ID of the project that the repository belongs to
|
||||
project_public:
|
||||
type: integer
|
||||
description: The flag to indicate the publicity of the project that the repository belongs to (1 is public, 0 is not)
|
||||
Project:
|
||||
type: object
|
||||
properties:
|
||||
@ -622,30 +765,39 @@ definitions:
|
||||
type: integer
|
||||
format: int32
|
||||
description: The owner ID of the project always means the creator of the project.
|
||||
project_name:
|
||||
name:
|
||||
type: string
|
||||
description: The name of the project.
|
||||
creation_time:
|
||||
type: string
|
||||
description: The creation time of the project.
|
||||
update_time:
|
||||
type: string
|
||||
description: The update time of the project.
|
||||
deleted:
|
||||
type: integer
|
||||
format: int32
|
||||
description: A deletion mark of the project.
|
||||
description: A deletion mark of the project (1 means it's deleted, 0 is not)
|
||||
user_id:
|
||||
type: integer
|
||||
format: int32
|
||||
description: A relation field to the user table.
|
||||
owner_name:
|
||||
type: string
|
||||
description: The owner name of tthe project always means the creator of the project.
|
||||
description: The owner name of the project.
|
||||
public:
|
||||
type: boolean
|
||||
format: boolean
|
||||
description: The public status of the project.
|
||||
togglable:
|
||||
type: boolean
|
||||
description: Correspond to the UI about showing the public status of the project.
|
||||
description: Correspond to the UI about whether the project's publicity is updatable (for UI)
|
||||
current_user_role_id:
|
||||
type: integer
|
||||
description: The role ID of the current user who triggered the API (for UI)
|
||||
repo_count:
|
||||
type: integer
|
||||
description: The number of the repositories under this project.
|
||||
Repository:
|
||||
type: object
|
||||
properties:
|
||||
@ -680,6 +832,7 @@ definitions:
|
||||
user_id:
|
||||
type: integer
|
||||
format: int32
|
||||
description: The ID of the user.
|
||||
username:
|
||||
type: string
|
||||
email:
|
||||
@ -702,7 +855,7 @@ definitions:
|
||||
new_password:
|
||||
type: string
|
||||
description: New password for marking as to be updated.
|
||||
AccessLog:
|
||||
AccessLogFilter:
|
||||
type: object
|
||||
properties:
|
||||
username:
|
||||
@ -711,14 +864,32 @@ definitions:
|
||||
keywords:
|
||||
type: string
|
||||
description: Operation name specified when project created.
|
||||
beginTimestamp:
|
||||
begin_timestamp:
|
||||
type: integer
|
||||
format: int32
|
||||
format: int64
|
||||
description: Begin timestamp for querying access logs.
|
||||
endTimestamp:
|
||||
end_timestamp:
|
||||
type: integer
|
||||
format: int32
|
||||
format: int64
|
||||
description: End timestamp for querying accessl logs.
|
||||
AccessLog:
|
||||
type: object
|
||||
properties:
|
||||
log_id:
|
||||
type: integer
|
||||
description: The ID of the log entry.
|
||||
repo_name:
|
||||
type: string
|
||||
description: Name of the repository in this log entry.
|
||||
repo_tag:
|
||||
type: string
|
||||
description: Tag of the repository in this log entry.
|
||||
operation:
|
||||
type: string
|
||||
description: The operation against the repository in this log entry.
|
||||
op_time:
|
||||
type: time
|
||||
description: The time when this operation is triggered.
|
||||
Role:
|
||||
type: object
|
||||
properties:
|
||||
@ -741,6 +912,48 @@ definitions:
|
||||
type: integer
|
||||
format: int32
|
||||
description: Role ID for updating project role member.
|
||||
user_name:
|
||||
username:
|
||||
type: string
|
||||
description: Username relevant to a project role member.
|
||||
TopRepo:
|
||||
type: object
|
||||
properties:
|
||||
repo_name:
|
||||
type: string
|
||||
description: The name of the repo
|
||||
access_count:
|
||||
type: integer
|
||||
format: int
|
||||
description: The access count of the repo
|
||||
StatisticMap:
|
||||
type: object
|
||||
properties:
|
||||
my_project_count:
|
||||
type: integer
|
||||
format: int32
|
||||
description: The count of the projects which the user is a member of.
|
||||
my_repo_count:
|
||||
type: integer
|
||||
format: int32
|
||||
description: The count of the repositories belonging to the projects which the user is a member of.
|
||||
public_project_count:
|
||||
type: integer
|
||||
format: int32
|
||||
description: The count of the public projects.
|
||||
public_repo_count:
|
||||
type: integer
|
||||
format: int32
|
||||
description: The count of the public repositories belonging to the public projects which the user is a member of.
|
||||
total_project_count:
|
||||
type: integer
|
||||
format: int32
|
||||
description: The count of the total projects, only be seen when the is admin.
|
||||
total_repo_count:
|
||||
type: integer
|
||||
format: int32
|
||||
description: The count of the total repositories, only be seen when the user is admin.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -1,54 +1,56 @@
|
||||
# migration
|
||||
Migration is a module for migrating database schema between different version of project [harbor](https://github.com/vmware/harbor)
|
||||
# Migration guide
|
||||
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
|
||||
|
||||
###installation
|
||||
- step 1: modify migration.cfg
|
||||
###Installation
|
||||
- step 1: change `db_username`, `db_password`, `db_port`, `db_name` in migration.cfg
|
||||
- step 2: build image from dockerfile
|
||||
```
|
||||
cd harbor-migration
|
||||
|
||||
docker build -t your-image-name .
|
||||
docker build -t migrate-tool .
|
||||
```
|
||||
|
||||
###migration operation
|
||||
- show instruction of harbor-migration
|
||||
|
||||
```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
|
||||
###Migrate Step
|
||||
- step 1: stop and remove Harbor service
|
||||
|
||||
```
|
||||
docker-compose down
|
||||
```
|
||||
- step 2: perform migration operation
|
||||
- step 3: rebuild newest harbor images and restart service
|
||||
- step 2: create 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 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
|
||||
```
|
||||
|
||||
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
|
||||
from sqlalchemy.ext.declarative import declarative_base
|
||||
from sqlalchemy.orm import sessionmaker, relationship
|
||||
from sqlalchemy.dialects import mysql
|
||||
|
||||
Base = declarative_base()
|
||||
|
||||
@ -20,8 +21,8 @@ class User(Base):
|
||||
reset_uuid = sa.Column(sa.String(40))
|
||||
salt = sa.Column(sa.String(40))
|
||||
sysadmin_flag = sa.Column(sa.Integer)
|
||||
creation_time = sa.Column(sa.DateTime)
|
||||
update_time = sa.Column(sa.DateTime)
|
||||
creation_time = sa.Column(mysql.TIMESTAMP)
|
||||
update_time = sa.Column(mysql.TIMESTAMP)
|
||||
|
||||
class Properties(Base):
|
||||
__tablename__ = 'properties'
|
||||
@ -35,8 +36,8 @@ class ProjectMember(Base):
|
||||
project_id = sa.Column(sa.Integer(), primary_key = True)
|
||||
user_id = sa.Column(sa.Integer(), primary_key = True)
|
||||
role = sa.Column(sa.Integer(), nullable = False)
|
||||
creation_time = sa.Column(sa.DateTime(), nullable = True)
|
||||
update_time = sa.Column(sa.DateTime(), nullable = True)
|
||||
creation_time = sa.Column(mysql.TIMESTAMP, nullable = True)
|
||||
update_time = sa.Column(mysql.TIMESTAMP, nullable = True)
|
||||
sa.ForeignKeyConstraint(['project_id'], [u'project.project_id'], ),
|
||||
sa.ForeignKeyConstraint(['role'], [u'role.role_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)
|
||||
owner_id = sa.Column(sa.ForeignKey(u'user.user_id'), nullable=False, index=True)
|
||||
name = sa.Column(sa.String(30), nullable=False, unique=True)
|
||||
creation_time = sa.Column(sa.DateTime)
|
||||
update_time = sa.Column(sa.DateTime)
|
||||
creation_time = sa.Column(mysql.TIMESTAMP)
|
||||
update_time = sa.Column(mysql.TIMESTAMP)
|
||||
deleted = 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')
|
||||
|
@ -27,9 +27,10 @@ branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
from alembic import op
|
||||
from datetime import datetime
|
||||
from db_meta import *
|
||||
|
||||
from sqlalchemy.dialects import mysql
|
||||
|
||||
Session = sessionmaker()
|
||||
|
||||
def upgrade():
|
||||
@ -44,12 +45,9 @@ def upgrade():
|
||||
session.add(Properties(k='schema_version', v='0.1.1'))
|
||||
|
||||
#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('update_time', sa.DateTime(), nullable=True))
|
||||
|
||||
#fill update_time data into table user
|
||||
session.query(User).update({User.update_time: datetime.now()})
|
||||
op.add_column('user', sa.Column('update_time', mysql.TIMESTAMP, nullable=True))
|
||||
|
||||
#init all sysadmin_flag = 0
|
||||
session.query(User).update({User.sysadmin_flag: 0})
|
||||
@ -62,7 +60,7 @@ def upgrade():
|
||||
for result in join_result:
|
||||
session.add(ProjectMember(project_id=result.project_role.project_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
|
||||
sys_admin_result = session.query(UserProjectRole).\
|
||||
@ -88,11 +86,9 @@ def upgrade():
|
||||
session.delete(acc)
|
||||
session.query(Access).update({Access.access_id: Access.access_id - 1})
|
||||
|
||||
#add column to table project
|
||||
op.add_column('project', sa.Column('update_time', sa.DateTime(), nullable=True))
|
||||
#add column to table project
|
||||
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()
|
||||
|
||||
def downgrade():
|
||||
|
@ -21,19 +21,18 @@ import (
|
||||
|
||||
// AccessLog holds information about logs which are used to record the actions that user take to the resourses.
|
||||
type AccessLog struct {
|
||||
LogID int `orm:"column(log_id)" json:"LogId"`
|
||||
UserID int `orm:"column(user_id)" json:"UserId"`
|
||||
ProjectID int64 `orm:"column(project_id)" json:"ProjectId"`
|
||||
RepoName string `orm:"column(repo_name)"`
|
||||
RepoTag string `orm:"column(repo_tag)"`
|
||||
GUID string `orm:"column(GUID)" json:"Guid"`
|
||||
Operation string `orm:"column(operation)"`
|
||||
OpTime time.Time `orm:"column(op_time)"`
|
||||
Username string
|
||||
Keywords string
|
||||
|
||||
LogID int `orm:"pk;column(log_id)" json:"log_id"`
|
||||
UserID int `orm:"column(user_id)" json:"user_id"`
|
||||
ProjectID int64 `orm:"column(project_id)" json:"project_id"`
|
||||
RepoName string `orm:"column(repo_name)" json:"repo_name"`
|
||||
RepoTag string `orm:"column(repo_tag)" json:"repo_tag"`
|
||||
GUID string `orm:"column(GUID)" json:"guid"`
|
||||
Operation string `orm:"column(operation)" json:"operation"`
|
||||
OpTime time.Time `orm:"column(op_time)" json:"op_time"`
|
||||
Username string `json:"username"`
|
||||
Keywords string `json:"keywords"`
|
||||
BeginTime time.Time
|
||||
BeginTimestamp int64
|
||||
BeginTimestamp int64 `json:"begin_timestamp"`
|
||||
EndTime time.Time
|
||||
EndTimestamp int64
|
||||
EndTimestamp int64 `json:"end_timestamp"`
|
||||
}
|
||||
|
@ -7,5 +7,9 @@ import (
|
||||
func init() {
|
||||
orm.RegisterModel(new(RepTarget),
|
||||
new(RepPolicy),
|
||||
new(RepJob))
|
||||
new(RepJob),
|
||||
new(User),
|
||||
new(Project),
|
||||
new(Role),
|
||||
new(AccessLog))
|
||||
}
|
||||
|
@ -21,19 +21,19 @@ import (
|
||||
|
||||
// Project holds the details of a project.
|
||||
type Project struct {
|
||||
ProjectID int64 `orm:"column(project_id)" json:"ProjectId"`
|
||||
OwnerID int `orm:"column(owner_id)" json:"OwnerId"`
|
||||
Name string `orm:"column(name)"`
|
||||
CreationTime time.Time `orm:"column(creation_time)"`
|
||||
CreationTimeStr string
|
||||
Deleted int `orm:"column(deleted)"`
|
||||
UserID int `json:"UserId"`
|
||||
OwnerName string
|
||||
Public int `orm:"column(public)"`
|
||||
ProjectID int64 `orm:"pk;column(project_id)" json:"project_id"`
|
||||
OwnerID int `orm:"column(owner_id)" json:"owner_id"`
|
||||
Name string `orm:"column(name)" json:"name"`
|
||||
CreationTime time.Time `orm:"column(creation_time)" json:"creation_time"`
|
||||
CreationTimeStr string `json:"creation_time_str"`
|
||||
Deleted int `orm:"column(deleted)" json:"deleted"`
|
||||
//UserID int `json:"UserId"`
|
||||
OwnerName string `json:"owner_name"`
|
||||
Public int `orm:"column(public)" json:"public"`
|
||||
//This field does not have correspondent column in DB, this is just for UI to disable button
|
||||
Togglable bool
|
||||
|
||||
UpdateTime time.Time `orm:"update_time" json:"update_time"`
|
||||
Role int `json:"role_id"`
|
||||
Role int `json:"current_user_role_id"`
|
||||
RepoCount int `json:"repo_count"`
|
||||
}
|
||||
|
@ -2,6 +2,8 @@ package models
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/astaxie/beego/validation"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -29,10 +31,12 @@ const (
|
||||
|
||||
// RepPolicy is the model for a replication policy, which associate to a project and a target (destination)
|
||||
type RepPolicy struct {
|
||||
ID int64 `orm:"column(id)" json:"id"`
|
||||
ProjectID int64 `orm:"column(project_id)" json:"project_id"`
|
||||
TargetID int64 `orm:"column(target_id)" json:"target_id"`
|
||||
Name string `orm:"column(name)" json:"name"`
|
||||
ID int64 `orm:"column(id)" json:"id"`
|
||||
ProjectID int64 `orm:"column(project_id)" json:"project_id"`
|
||||
ProjectName string `json:"project_name,omitempty"`
|
||||
TargetID int64 `orm:"column(target_id)" json:"target_id"`
|
||||
TargetName string `json:"target_name,omitempty"`
|
||||
Name string `orm:"column(name)" json:"name"`
|
||||
// Target RepTarget `orm:"-" json:"target"`
|
||||
Enabled int `orm:"column(enabled)" json:"enabled"`
|
||||
Description string `orm:"column(description)" json:"description"`
|
||||
@ -42,6 +46,33 @@ type RepPolicy struct {
|
||||
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
|
||||
// a repository to/from a remote registry instance.
|
||||
type RepJob struct {
|
||||
@ -60,7 +91,7 @@ type RepJob struct {
|
||||
// RepTarget is the model for a replication targe, i.e. destination, which wraps the endpoint URL and username/password of a remote registry.
|
||||
type RepTarget struct {
|
||||
ID int64 `orm:"column(id)" json:"id"`
|
||||
URL string `orm:"column(url)" json:"url"`
|
||||
URL string `orm:"column(url)" json:"endpoint"`
|
||||
Name string `orm:"column(name)" json:"name"`
|
||||
Username string `orm:"column(username)" json:"username"`
|
||||
Password string `orm:"column(password)" json:"password"`
|
||||
@ -68,17 +99,42 @@ type RepTarget struct {
|
||||
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
|
||||
func (rt *RepTarget) TableName() string {
|
||||
func (r *RepTarget) TableName() string {
|
||||
return "replication_target"
|
||||
}
|
||||
|
||||
//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"
|
||||
}
|
||||
|
||||
//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"
|
||||
}
|
||||
|
@ -26,7 +26,7 @@ const (
|
||||
|
||||
// Role holds the details of a role.
|
||||
type Role struct {
|
||||
RoleID int `orm:"column(role_id)" json:"role_id"`
|
||||
RoleID int `orm:"pk;column(role_id)" json:"role_id"`
|
||||
RoleCode string `orm:"column(role_code)" json:"role_code"`
|
||||
Name string `orm:"column(name)" json:"role_name"`
|
||||
|
||||
|
22
models/toprepo.go
Normal file
22
models/toprepo.go
Normal file
@ -0,0 +1,22 @@
|
||||
/*
|
||||
Copyright (c) 2016 VMware, Inc. All Rights Reserved.
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package models
|
||||
|
||||
// TopRepo holds information about repository that accessed most
|
||||
type TopRepo struct {
|
||||
RepoName string `json:"name"`
|
||||
AccessCount int64 `json:"count"`
|
||||
}
|
@ -21,20 +21,21 @@ import (
|
||||
|
||||
// User holds the details of a user.
|
||||
type User struct {
|
||||
UserID int `orm:"column(user_id)" json:"UserId"`
|
||||
Username string `orm:"column(username)" json:"username"`
|
||||
Email string `orm:"column(email)" json:"email"`
|
||||
Password string `orm:"column(password)" json:"password"`
|
||||
Realname string `orm:"column(realname)" json:"realname"`
|
||||
Comment string `orm:"column(comment)" json:"comment"`
|
||||
Deleted int `orm:"column(deleted)"`
|
||||
Rolename string
|
||||
RoleID int `json:"RoleId"`
|
||||
RoleList []Role
|
||||
HasAdminRole int `orm:"column(sysadmin_flag)"`
|
||||
ResetUUID string `orm:"column(reset_uuid)" json:"ResetUuid"`
|
||||
Salt string `orm:"column(salt)"`
|
||||
|
||||
UserID int `orm:"pk;column(user_id)" json:"user_id"`
|
||||
Username string `orm:"column(username)" json:"username"`
|
||||
Email string `orm:"column(email)" json:"email"`
|
||||
Password string `orm:"column(password)" json:"password"`
|
||||
Realname string `orm:"column(realname)" json:"realname"`
|
||||
Comment string `orm:"column(comment)" json:"comment"`
|
||||
Deleted int `orm:"column(deleted)" json:"deleted"`
|
||||
Rolename string `json:"role_name"`
|
||||
//if this field is named as "RoleID", beego orm can not map role_id
|
||||
//to it.
|
||||
Role int `json:"role_id"`
|
||||
// RoleList []Role `json:"role_list"`
|
||||
HasAdminRole int `orm:"column(sysadmin_flag)" json:"has_admin_role"`
|
||||
ResetUUID string `orm:"column(reset_uuid)" json:"reset_uuid"`
|
||||
Salt string `orm:"column(salt)"`
|
||||
CreationTime time.Time `orm:"creation_time" json:"creation_time"`
|
||||
UpdateTime time.Time `orm:"update_time" json:"update_time"`
|
||||
}
|
||||
|
@ -38,7 +38,7 @@ type Handler struct {
|
||||
// checkes the permission agains local DB and generates jwt token.
|
||||
func (h *Handler) Get() {
|
||||
|
||||
var username string
|
||||
var username, password string
|
||||
request := h.Ctx.Request
|
||||
service := h.GetString("service")
|
||||
scopes := h.GetStrings("scope")
|
||||
@ -49,7 +49,7 @@ func (h *Handler) Get() {
|
||||
log.Debugf("Will grant all access as this request is from job service with legal secret.")
|
||||
username = "job-service-user"
|
||||
} else {
|
||||
username, password, _ := request.BasicAuth()
|
||||
username, password, _ = request.BasicAuth()
|
||||
authenticated := authenticate(username, password)
|
||||
|
||||
if len(scopes) == 0 && !authenticated {
|
||||
|
@ -75,6 +75,7 @@ language_en-US = English
|
||||
language_zh-CN = 中文
|
||||
language_de-DE = Deutsch
|
||||
language_ru-RU = Русский
|
||||
language_ja-JP = 日本語
|
||||
copyright = Copyright
|
||||
all_rights_reserved = Alle Rechte vorbehalten.
|
||||
index_desc = Project Harbor ist ein zuverlässiger Enterprise-Class Registry Server. Unternehmen können ihren eigenen Registry Server aufsetzen um die Produktivität und Sicherheit zu erhöhen. Project Harbor kann für Entwicklungs- wie auch Produktiv-Umgebungen genutzt werden.
|
||||
|
@ -76,6 +76,7 @@ language_en-US = English
|
||||
language_zh-CN = 中文
|
||||
language_de-DE = Deutsch
|
||||
language_ru-RU = Русский
|
||||
language_ja-JP = 日本語
|
||||
copyright = Copyright
|
||||
all_rights_reserved = All rights reserved.
|
||||
index_desc = Project Harbor is to build an enterprise-class, reliable registry server. Enterprises can set up a private registry server in their own environment to improve productivity as well as security. Project Harbor can be used in both development and production environment.
|
||||
|
89
static/i18n/locale_ja-JP.ini
Normal file
89
static/i18n/locale_ja-JP.ini
Normal file
@ -0,0 +1,89 @@
|
||||
page_title_index = Harbor
|
||||
page_title_sign_in = ログイン - Harbor
|
||||
page_title_project = プロジェクト - Harbor
|
||||
page_title_item_details = 詳しい - Harbor
|
||||
page_title_registration = 登録 - Harbor
|
||||
page_title_add_user = ユーザを追加 - Harbor
|
||||
page_title_forgot_password = パスワードを忘れました - Harbor
|
||||
title_forgot_password = パスワードを忘れました
|
||||
page_title_reset_password = パスワードをリセット - Harbor
|
||||
title_reset_password = パスワードをリセット
|
||||
page_title_change_password = パスワードを変更 - Harbor
|
||||
title_change_password = パスワードを変更
|
||||
page_title_search = サーチ - Harbor
|
||||
sign_in = ログイン
|
||||
sign_up = 登録
|
||||
add_user = ユーザを追加
|
||||
log_out = ログアウト
|
||||
search_placeholder = プロジェクト名またはイメージ名
|
||||
change_password = パスワードを変更
|
||||
username_email = ユーザ名/メールアドレス
|
||||
password = パスワード
|
||||
forgot_password = パスワードを忘れました
|
||||
welcome = ようこそ
|
||||
my_projects = マイプロジェクト
|
||||
public_projects = パブリックプロジェクト
|
||||
admin_options = 管理者
|
||||
project_name = プロジェクト名
|
||||
creation_time = 作成日時
|
||||
publicity = パブリック
|
||||
add_project = プロジェクトを追加
|
||||
check_for_publicity = パブリックプロジェクト
|
||||
button_save = 保存する
|
||||
button_cancel = 取り消しする
|
||||
button_submit = 送信する
|
||||
username = ユーザ名
|
||||
email = メールアドレス
|
||||
system_admin = システム管理者
|
||||
dlg_button_ok = OK
|
||||
dlg_button_cancel = 取り消し
|
||||
registration = 登録
|
||||
username_description = ログイン際に使うユーザ名を入力してください。
|
||||
email_description = メールアドレスはパスワードをリセットする際に使われます。
|
||||
full_name = フルネーム
|
||||
full_name_description = フルネームを入力してください。
|
||||
password_description = パスワード7英数字以上で、少なくとも 1小文字、 1大文字と 1数字でなければなりません。
|
||||
confirm_password = パスワードを確認する
|
||||
note_to_the_admin = メモ
|
||||
old_password = 現在のパスワード
|
||||
new_password = 新しいパスワード
|
||||
forgot_password_description = ぱプロジェクトをリセットするメールはこのアドレスに送信します。
|
||||
|
||||
projects = プロジェクト
|
||||
repositories = リポジトリ
|
||||
search = サーチ
|
||||
home = ホーム
|
||||
project = プロジェクト
|
||||
owner = オーナー
|
||||
repo = リポジトリ
|
||||
user = ユーザ
|
||||
logs = ログ
|
||||
repo_name = リポジトリ名
|
||||
repo_tag = リポジトリタグ
|
||||
add_members = メンバーを追加
|
||||
operation = 操作
|
||||
advance = さらに絞りこみで検索
|
||||
all = 全部
|
||||
others = その他
|
||||
start_date = 開始日
|
||||
end_date = 終了日
|
||||
timestamp = タイムスタンプ
|
||||
role = 役割
|
||||
reset_email_hint = このリンクをクリックしてパスワードリセットの処理を続けてください
|
||||
reset_email_subject = パスワードをリセットします
|
||||
language = 日本語
|
||||
language_en-US = English
|
||||
language_zh-CN = 中文
|
||||
language_de-DE = Deutsch
|
||||
language_ru-RU = Русский
|
||||
language_ja-JP = 日本語
|
||||
copyright = コピーライト
|
||||
all_rights_reserved = 無断複写・転載を禁じます
|
||||
index_desc = Harborは、信頼性の高いエンタープライズクラスのRegistryサーバです。タープライズユーザはHarborを利用し、プライベートのRegistryサビースを構築し、生産性および安全性を向上させる事ができます。開発環境はもちろん、生産環境にも使用する事ができます。
|
||||
index_desc_0 = 主な利点:
|
||||
index_desc_1 = 1. セキュリティ: 知的財産権を組織内で確保する。
|
||||
index_desc_2 = 2. 効率: プライベートなので、パブリックRegistryサビースにネットワーク通信が減らす。
|
||||
index_desc_3 = 3. アクセス制御: ロールベースアクセス制御機能を実装し、更に既存のユーザ管理システム(AD/LDAP)と統合することも可能。
|
||||
index_desc_4 = 4. 監査: すべてRegistryサビースへの操作が記録され、検査にに利用できる。
|
||||
index_desc_5 = 5. 管理UI: 使いやすい管理UIが搭載する。
|
||||
index_title = エンタープライズ Registry サビース
|
@ -16,378 +16,441 @@ var global_messages = {
|
||||
"username_is_required" : {
|
||||
"en-US": "Username is required.",
|
||||
"zh-CN": "用户名为必填项。",
|
||||
"ja-JP": "ユーザ名は必須項目です。",
|
||||
"de-DE": "Benutzername erforderlich.",
|
||||
"ru-RU": "Требуется ввести имя пользователя."
|
||||
},
|
||||
"username_has_been_taken" : {
|
||||
"en-US": "Username has been taken.",
|
||||
"zh-CN": "用户名已被占用。",
|
||||
"ja-JP": "ユーザ名はすでに登録されました。",
|
||||
"de-DE": "Benutzername bereits vergeben.",
|
||||
"ru-RU": "Имя пользователя уже используется."
|
||||
},
|
||||
"username_is_too_long" : {
|
||||
"en-US": "Username is too long. (maximum 20 characters)",
|
||||
"zh-CN": "用户名长度超出限制。(最长为20个字符)",
|
||||
"ja-JP": "ユーザ名が長すぎです。(20文字まで)",
|
||||
"de-DE": "Benutzername ist zu lang. (maximal 20 Zeichen)",
|
||||
"ru-RU": "Имя пользователя слишком длинное. (максимум 20 символов)"
|
||||
},
|
||||
"username_contains_illegal_chars": {
|
||||
"en-US": "Username contains illegal character(s).",
|
||||
"zh-CN": "用户名包含不合法的字符。",
|
||||
"ja-JP": "ユーザ名に使えない文字が入っています。",
|
||||
"de-DE": "Benutzername enthält ungültige Zeichen.",
|
||||
"ru-RU": "Имя пользователя содержит недопустимые символы."
|
||||
},
|
||||
"email_is_required" : {
|
||||
"en-US": "Email is required.",
|
||||
"zh-CN": "邮箱为必填项。",
|
||||
"ja-JP": "メールアドレスが必須です。",
|
||||
"de-DE": "E-Mail Adresse erforderlich.",
|
||||
"ru-RU": "Требуется ввести E-mail адрес."
|
||||
},
|
||||
"email_contains_illegal_chars" : {
|
||||
"en-US": "Email contains illegal character(s).",
|
||||
"zh-CN": "邮箱包含不合法的字符。",
|
||||
"ja-JP": "メールアドレスに使えない文字が入っています。",
|
||||
"de-DE": "E-Mail Adresse enthält ungültige Zeichen.",
|
||||
"ru-RU": "E-mail адрес содержит недопеустимые символы."
|
||||
},
|
||||
"email_has_been_taken" : {
|
||||
"en-US": "Email has been taken.",
|
||||
"zh-CN": "邮箱已被占用。",
|
||||
"ja-JP": "メールアドレスがすでに使われました。",
|
||||
"de-DE": "E-Mail Adresse wird bereits verwendet.",
|
||||
"ru-RU": "Такой E-mail адрес уже используется."
|
||||
},
|
||||
"email_content_illegal" : {
|
||||
"en-US": "Email format is illegal.",
|
||||
"zh-CN": "邮箱格式不合法。",
|
||||
"ja-JP": "メールアドレスフォーマットエラー。",
|
||||
"de-DE": "Format der E-Mail Adresse ist ungültig.",
|
||||
"ru-RU": "Недопустимый формат E-mail адреса."
|
||||
},
|
||||
"email_does_not_exist" : {
|
||||
"en-US": "Email does not exist.",
|
||||
"zh-CN": "邮箱不存在。",
|
||||
"ja-JP": "メールアドレスが存在しません。",
|
||||
"de-DE": "E-Mail Adresse existiert nicht.",
|
||||
"ru-RU": "E-mail адрес не существует."
|
||||
},
|
||||
"realname_is_required" : {
|
||||
"en-US": "Full name is required.",
|
||||
"zh-CN": "全名为必填项。",
|
||||
"ja-JP": "フルネームが必須です。",
|
||||
"de-DE": "Vollständiger Name erforderlich.",
|
||||
"ru-RU": "Требуется ввести полное имя."
|
||||
},
|
||||
"realname_is_too_long" : {
|
||||
"en-US": "Full name is too long. (maximum 20 characters)",
|
||||
"zh-CN": "全名长度超出限制。(最长为20个字符)",
|
||||
"ja-JP": "フルネームは長すぎです。(20文字まで)",
|
||||
"de-DE": "Vollständiger Name zu lang. (maximal 20 Zeichen)",
|
||||
"ru-RU": "Полное имя слишком длинное. (максимум 20 символов)"
|
||||
},
|
||||
"realname_contains_illegal_chars" : {
|
||||
"en-US": "Full name contains illegal character(s).",
|
||||
"zh-CN": "全名包含不合法的字符。",
|
||||
"ja-JP": "フルネームに使えない文字が入っています。",
|
||||
"de-DE": "Vollständiger Name enthält ungültige Zeichen.",
|
||||
"ru-RU": "Полное имя содержит недопустимые символы."
|
||||
},
|
||||
"password_is_required" : {
|
||||
"en-US": "Password is required.",
|
||||
"zh-CN": "密码为必填项。",
|
||||
"ja-JP": "パスワードは必須です。",
|
||||
"de-DE": "Passwort erforderlich.",
|
||||
"ru-RU": "Требуется ввести пароль."
|
||||
},
|
||||
"password_is_invalid" : {
|
||||
"en-US": "Password is invalid. At least 7 characters with 1 lowercase letter, 1 capital letter and 1 numeric character.",
|
||||
"zh-CN": "密码无效。至少输入 7个字符且包含 1个小写字母,1个大写字母和 1个数字。",
|
||||
"ja-JP": "無効なパスワードです。7英数字以上で、 少なくとも1小文字、1大文字と1数字となります。",
|
||||
"de-DE": "Passwort ungültig. Mindestens sieben Zeichen bestehend aus einem Kleinbuchstaben, einem Großbuchstaben und einer Zahl",
|
||||
"ru-RU": "Такой пароль недопустим. Парольл должен содержать Минимум 7 символов, в которых будет присутствовать по меньшей мере 1 буква нижнего регистра, 1 буква верхнего регистра и 1 цифра"
|
||||
},
|
||||
"password_is_too_long" : {
|
||||
"en-US": "Password is too long. (maximum 20 characters)",
|
||||
"zh-CN": "密码长度超出限制。(最长为20个字符)",
|
||||
"ja-JP": "パスワードは長すぎです。(20文字まで)",
|
||||
"de-DE": "Passwort zu lang. (maximal 20 Zeichen)",
|
||||
"ru-RU": "Пароль слишком длинный (максимум 20 символов)"
|
||||
},
|
||||
"password_does_not_match" : {
|
||||
"en-US": "Passwords do not match.",
|
||||
"zh-CN": "两次密码输入不一致。",
|
||||
"ja-JP": "確認のパスワードが正しくありません。",
|
||||
"de-DE": "Passwörter stimmen nicht überein.",
|
||||
"ru-RU": "Пароли не совпадают."
|
||||
},
|
||||
"comment_is_too_long" : {
|
||||
"en-US": "Comment is too long. (maximum 20 characters)",
|
||||
"zh-CN": "备注长度超出限制。(最长为20个字符)",
|
||||
"ja-JP": "コメントは長すぎです。(20文字まで)",
|
||||
"de-DE": "Kommentar zu lang. (maximal 20 Zeichen)",
|
||||
"ru-RU": "Комментарий слишком длинный. (максимум 20 символов)"
|
||||
},
|
||||
"comment_contains_illegal_chars" : {
|
||||
"en-US": "Comment contains illegal character(s).",
|
||||
"zh-CN": "备注包含不合法的字符。",
|
||||
"ja-JP": "コメントに使えない文字が入っています。",
|
||||
"de-DE": "Kommentar enthält ungültige Zeichen.",
|
||||
"ru-RU": "Комментарий содержит недопустимые символы."
|
||||
},
|
||||
"project_name_is_required" : {
|
||||
"en-US": "Project name is required.",
|
||||
"zh-CN": "项目名称为必填项。",
|
||||
"ja-JP": "プロジェクト名は必須です。",
|
||||
"de-DE": "Projektname erforderlich.",
|
||||
"ru-RU": "Необходимо ввести название Проекта."
|
||||
},
|
||||
"project_name_is_too_short" : {
|
||||
"en-US": "Project name is too short. (minimum 4 characters)",
|
||||
"zh-CN": "项目名称至少要求 4个字符。",
|
||||
"ja-JP": "プロジェクト名は4文字以上です。",
|
||||
"de-DE": "Projektname zu kurz. (mindestens 4 Zeichen)",
|
||||
"ru-RU": "Название проекта слишком короткое. (миниму 4 символа)"
|
||||
},
|
||||
"project_name_is_too_long" : {
|
||||
"en-US": "Project name is too long. (maximum 30 characters)",
|
||||
"zh-CN": "项目名称长度超出限制。(最长为30个字符)",
|
||||
"ja-JP": "プロジェクト名は長すぎです。(30文字まで)",
|
||||
"de-DE": "Projektname zu lang. (maximal 30 Zeichen)",
|
||||
"ru-RU": "Название проекта слишком длинное (максимум 30 символов)"
|
||||
},
|
||||
"project_name_contains_illegal_chars" : {
|
||||
"en-US": "Project name contains illegal character(s).",
|
||||
"zh-CN": "项目名称包含不合法的字符。",
|
||||
"ja-JP": "プロジェクト名に使えない文字が入っています。",
|
||||
"de-DE": "Projektname enthält ungültige Zeichen.",
|
||||
"ru-RU": "Название проекта содержит недопустимые символы."
|
||||
},
|
||||
"project_exists" : {
|
||||
"en-US": "Project exists.",
|
||||
"zh-CN": "项目已存在。",
|
||||
"ja-JP": "プロジェクトはすでに存在しました。",
|
||||
"de-DE": "Projekt existiert bereits.",
|
||||
"ru-RU": "Такой проект уже существует."
|
||||
},
|
||||
"delete_user" : {
|
||||
"en-US": "Delete User",
|
||||
"zh-CN": "删除用户",
|
||||
"ja-JP": "ユーザを削除",
|
||||
"de-DE": "Benutzer löschen",
|
||||
"ru-RU": "Удалить пользователя"
|
||||
},
|
||||
"are_you_sure_to_delete_user" : {
|
||||
"en-US": "Are you sure to delete ",
|
||||
"zh-CN": "确认要删除用户 ",
|
||||
"ja-JP": "ユーザを削除でよろしでしょうか ",
|
||||
"de-DE": "Sind Sie sich sicher, dass Sie folgenden Benutzer löschen möchten: ",
|
||||
"ru-RU": "Вы уверены что хотите удалить пользователя? "
|
||||
},
|
||||
"input_your_username_and_password" : {
|
||||
"en-US": "Please input your username and password.",
|
||||
"zh-CN": "请输入用户名和密码。",
|
||||
"ja-JP": "ユーザ名とパスワードを入力してください。",
|
||||
"de-DE": "Bitte geben Sie ihr Benutzername und Passwort ein.",
|
||||
"ru-RU": "Введите имя пользователя и пароль."
|
||||
},
|
||||
"check_your_username_or_password" : {
|
||||
"en-US": "Please check your username or password.",
|
||||
"zh-CN": "请输入正确的用户名或密码。",
|
||||
"ja-JP": "正しいユーザ名とパスワードを入力してください。",
|
||||
"de-DE": "Bitte überprüfen Sie ihren Benutzernamen und Passwort.",
|
||||
"ru-RU": "Проверьте свои имя пользователя и пароль."
|
||||
},
|
||||
"title_login_failed" : {
|
||||
"en-US": "Login Failed",
|
||||
"zh-CN": "登录失败",
|
||||
"ja-JP": "ログインに失敗しました。",
|
||||
"de-DE": "Anmeldung fehlgeschlagen",
|
||||
"ru-RU": "Ошибка входа"
|
||||
},
|
||||
"title_change_password" : {
|
||||
"en-US": "Change Password",
|
||||
"zh-CN": "修改密码",
|
||||
"ja-JP": "パスワードを変更します。",
|
||||
"de-DE": "Passwort ändern",
|
||||
"ru-RU": "Сменить пароль"
|
||||
},
|
||||
"change_password_successfully" : {
|
||||
"en-US": "Password changed successfully.",
|
||||
"zh-CN": "密码已修改。",
|
||||
"ja-JP": "パスワードを変更しました。",
|
||||
"de-DE": "Passwort erfolgreich geändert.",
|
||||
"ru-RU": "Пароль успешно изменен."
|
||||
},
|
||||
"title_forgot_password" : {
|
||||
"en-US": "Forgot Password",
|
||||
"zh-CN": "忘记密码",
|
||||
"ja-JP": "パスワードをリセットします。",
|
||||
"de-DE": "Passwort vergessen",
|
||||
"ru-RU": "Забыли пароль?"
|
||||
},
|
||||
"email_has_been_sent" : {
|
||||
"en-US": "Email for resetting password has been sent.",
|
||||
"zh-CN": "重置密码邮件已发送。",
|
||||
"ja-JP": "パスワードをリセットするメールを送信しました。",
|
||||
"de-DE": "Eine E-Mail mit einem Wiederherstellungslink wurde an Sie gesendet.",
|
||||
"ru-RU": "На ваш E-mail было выслано письмо с инструкциями по сбросу пароля."
|
||||
},
|
||||
"send_email_failed" : {
|
||||
"en-US": "Failed to send Email for resetting password.",
|
||||
"zh-CN": "重置密码邮件发送失败。",
|
||||
"ja-JP": "パスワードをリセットするメールを送信する際エラーが出ました",
|
||||
"de-DE": "Fehler beim Senden der Wiederherstellungs-E-Mail.",
|
||||
"ru-RU": "Ошибка отправки сообщения."
|
||||
},
|
||||
"please_login_first" : {
|
||||
"en-US": "Please login first.",
|
||||
"zh-CN": "请先登录。",
|
||||
"ja-JP": "この先にログインが必要です。",
|
||||
"de-DE": "Bitte melden Sie sich zuerst an.",
|
||||
"ru-RU": "Сначала выполните вход в систему."
|
||||
},
|
||||
"old_password_is_not_correct" : {
|
||||
"en-US": "Old password is not correct.",
|
||||
"zh-CN": "原密码输入不正确。",
|
||||
"ja-JP": "現在のパスワードが正しく入力されていません。",
|
||||
"de-DE": "Altes Passwort ist nicht korrekt.",
|
||||
"ru-RU": "Старый пароль введен неверно."
|
||||
},
|
||||
"please_input_new_password" : {
|
||||
"en-US": "Please input new password.",
|
||||
"zh-CN": "请输入新密码。",
|
||||
"ja-JP": "あたらしいパスワードを入力してください",
|
||||
"de-DE": "Bitte geben Sie ihr neues Passwort ein.",
|
||||
"ru-RU": "Пожалуйста, введите новый пароль."
|
||||
},
|
||||
"invalid_reset_url": {
|
||||
"en-US": "Invalid URL for resetting password.",
|
||||
"zh-CN": "无效密码重置链接。",
|
||||
"ja-JP": "無効なパスワードをリセットするリンク。",
|
||||
"de-DE": "Ungültige URL zum Passwort wiederherstellen.",
|
||||
"ru-RU": "Неверный URL для сброса пароля."
|
||||
},
|
||||
"reset_password_successfully" : {
|
||||
"en-US": "Reset password successfully.",
|
||||
"zh-CN": "密码重置成功。",
|
||||
"ja-JP": "パスワードをリセットしました。",
|
||||
"de-DE": "Passwort erfolgreich wiederhergestellt.",
|
||||
"ru-RU": "Пароль успешно сброшен."
|
||||
},
|
||||
"internal_error": {
|
||||
"en-US": "Internal error.",
|
||||
"zh-CN": "内部错误,请联系系统管理员。",
|
||||
"ja-JP": "エラーが出ました、管理者に連絡してください。",
|
||||
"de-DE": "Interner Fehler.",
|
||||
"ru-RU": "Внутренняя ошибка."
|
||||
},
|
||||
"title_reset_password" : {
|
||||
"en-US": "Reset Password",
|
||||
"zh-CN": "重置密码",
|
||||
"ja-JP": "パスワードをリセットする",
|
||||
"de-DE": "Passwort zurücksetzen",
|
||||
"ru-RU": "Сбросить пароль"
|
||||
},
|
||||
"title_sign_up" : {
|
||||
"en-US": "Sign Up",
|
||||
"zh-CN": "注册",
|
||||
"ja-JP": "登録",
|
||||
"de-DE": "Registrieren",
|
||||
"ru-RU": "Регистрация"
|
||||
},
|
||||
"title_add_user": {
|
||||
"en-US": "Add User",
|
||||
"zh-CN": "新增用户",
|
||||
"ja-JP": "ユーザを追加",
|
||||
"de-DE": "Benutzer hinzufügen",
|
||||
"ru-RU": "Добавить пользователя"
|
||||
},
|
||||
"registered_successfully": {
|
||||
"en-US": "Signed up successfully.",
|
||||
"zh-CN": "注册成功。",
|
||||
"ja-JP": "登録しました。",
|
||||
"de-DE": "Erfolgreich registriert.",
|
||||
"ru-RU": "Регистрация прошла успешно."
|
||||
},
|
||||
"registered_failed" : {
|
||||
"en-US": "Failed to sign up.",
|
||||
"zh-CN": "注册失败。",
|
||||
"ja-JP": "登録でませんでした。",
|
||||
"de-DE": "Registrierung fehlgeschlagen.",
|
||||
"ru-RU": "Ошибка регистрации."
|
||||
},
|
||||
"added_user_successfully": {
|
||||
"en-US": "Added user successfully.",
|
||||
"zh-CN": "新增用户成功。",
|
||||
"ja-JP": "ユーザを追加しました。",
|
||||
"de-DE": "Benutzer erfolgreich erstellt.",
|
||||
"ru-RU": "Пользователь успешно добавлен."
|
||||
},
|
||||
"added_user_failed": {
|
||||
"en-US": "Adding user failed.",
|
||||
"zh-CN": "新增用户失败。",
|
||||
"ja-JP": "ユーザを追加できませんでした。",
|
||||
"de-DE": "Benutzer erstellen fehlgeschlagen.",
|
||||
"ru-RU": "Ошибка добавления пользователя."
|
||||
},
|
||||
"projects": {
|
||||
"en-US": "Projects",
|
||||
"zh-CN": "项目",
|
||||
"ja-JP": "プロジェクト",
|
||||
"de-DE": "Projekte",
|
||||
"ru-RU": "Проекты"
|
||||
},
|
||||
"repositories" : {
|
||||
"en-US": "Repositories",
|
||||
"zh-CN": "镜像仓库",
|
||||
"ja-JP": "リポジトリ",
|
||||
"de-DE": "Repositories",
|
||||
"ru-RU": "Репозитории"
|
||||
},
|
||||
"no_repo_exists" : {
|
||||
"en-US": "No repositories found, please use 'docker push' to upload images.",
|
||||
"zh-CN": "未发现镜像,请用‘docker push’命令上传镜像。",
|
||||
"ja-JP": "イメージが見つかりませんでした。’docker push’を利用しイメージをアップロードしてください。",
|
||||
"de-DE": "Keine Repositories gefunden, bitte benutzen Sie 'docker push' um ein Image hochzuladen.",
|
||||
"ru-RU": "Репозитории не найдены, используйте команду 'docker push' для добавления образов."
|
||||
},
|
||||
"tag" : {
|
||||
"en-US": "Tag",
|
||||
"zh-CN": "标签",
|
||||
"ja-JP": "タグ",
|
||||
"de-DE": "Tag",
|
||||
"ru-RU": "Метка"
|
||||
},
|
||||
"pull_command": {
|
||||
"en-US": "Pull Command",
|
||||
"zh-CN": "Pull 命令",
|
||||
"ja-JP": "Pull コマンド",
|
||||
"de-DE": "Pull Befehl",
|
||||
"ru-RU": "Команда для скачивания образа"
|
||||
},
|
||||
"image_details" : {
|
||||
"en-US": "Image Details",
|
||||
"zh-CN": "镜像详细信息",
|
||||
"ja-JP": "イメージ詳細",
|
||||
"de-DE": "Image Details",
|
||||
"ru-RU": "Информация об образе"
|
||||
},
|
||||
"add_members" : {
|
||||
"en-US": "Add Member",
|
||||
"zh-CN": "添加成员",
|
||||
"ja-JP": "メンバーを追加する",
|
||||
"de-DE": "Mitglied hinzufügen",
|
||||
"ru-RU": "Добавить Участника"
|
||||
},
|
||||
"edit_members" : {
|
||||
"en-US": "Edit Members",
|
||||
"zh-CN": "编辑成员",
|
||||
"ja-JP": "メンバーを編集する",
|
||||
"de-DE": "Mitglieder bearbeiten",
|
||||
"ru-RU": "Редактировать Участников"
|
||||
},
|
||||
"add_member_failed" : {
|
||||
"en-US": "Adding Member Failed",
|
||||
"zh-CN": "添加成员失败",
|
||||
"ja-JP": "メンバーを追加できません出した",
|
||||
"de-DE": "Mitglied hinzufügen fehlgeschlagen",
|
||||
"ru-RU": "Ошибка при добавлении нового участника"
|
||||
},
|
||||
"please_input_username" : {
|
||||
"en-US": "Please input a username.",
|
||||
"zh-CN": "请输入用户名。",
|
||||
"ja-JP": "ユーザ名を入力してください。",
|
||||
"de-DE": "Bitte geben Sie einen Benutzernamen ein.",
|
||||
"ru-RU": "Пожалуйста, введите имя пользователя."
|
||||
},
|
||||
"please_assign_a_role_to_user" : {
|
||||
"en-US": "Please assign a role to the user.",
|
||||
"zh-CN": "请为用户分配角色。",
|
||||
"ja-JP": "ユーザーに役割を割り当てるしてください。",
|
||||
"de-DE": "Bitte weisen Sie dem Benutzer eine Rolle zu.",
|
||||
"ru-RU": "Пожалуйста, назначьте роль пользователю."
|
||||
},
|
||||
"user_id_exists" : {
|
||||
"en-US": "User is already a member.",
|
||||
"zh-CN": "用户已经是成员。",
|
||||
"ja-JP": "すでにメンバーに登録しました。",
|
||||
"de-DE": "Benutzer ist bereits Mitglied.",
|
||||
"ru-RU": "Пользователь уже является участником."
|
||||
},
|
||||
"user_id_does_not_exist" : {
|
||||
"en-US": "User does not exist.",
|
||||
"zh-CN": "不存在此用户。",
|
||||
"ja-JP": "ユーザが見つかりませんでした。",
|
||||
"de-DE": "Benutzer existiert nicht.",
|
||||
"ru-RU": "Пользователя с таким именем не существует."
|
||||
},
|
||||
"insufficient_privileges" : {
|
||||
"en-US": "Insufficient privileges.",
|
||||
"zh-CN": "权限不足。",
|
||||
"ja-JP": "権限エラー。",
|
||||
"de-DE": "Unzureichende Berechtigungen.",
|
||||
"ru-RU": "Недостаточно прав."
|
||||
},
|
||||
"operation_failed" : {
|
||||
"en-US": "Operation Failed",
|
||||
"zh-CN": "操作失败",
|
||||
"ja-JP": "操作に失敗しました。",
|
||||
"de-DE": "Befehl fehlgeschlagen",
|
||||
"ru-RU": "Ошибка при выполнении данной операции"
|
||||
},
|
||||
"button_on" : {
|
||||
"en-US": "On",
|
||||
"zh-CN": "打开",
|
||||
"ja-JP": "オン",
|
||||
"de-DE": "An",
|
||||
"ru-RU": "Вкл."
|
||||
},
|
||||
"button_off" : {
|
||||
"en-US": "Off",
|
||||
"zh-CN": "关闭",
|
||||
"ja-JP": "オフ",
|
||||
"de-DE": "Aus",
|
||||
"ru-RU": "Откл."
|
||||
}
|
||||
|
@ -76,6 +76,7 @@ language_en-US = English
|
||||
language_zh-CN = 中文
|
||||
language_de-DE = Deutsch
|
||||
language_ru-RU = Русский
|
||||
language_ja-JP = 日本語
|
||||
copyright = Copyright
|
||||
all_rights_reserved = Все права защищены.
|
||||
index_desc = Проект Harbor представляет собой надежный сервер управления docker-образами корпоративного класса. Компании могут использовать данный сервер в своей инфарструктуе для повышения производительности и безопасности . Проект Harbor может использоваться как в среде разработки так и в продуктивной среде.
|
||||
|
@ -76,6 +76,7 @@ language_en-US = English
|
||||
language_zh-CN = 中文
|
||||
language_de-DE = Deutsch
|
||||
language_ru-RU = Русский
|
||||
language_ja-JP = 日本語
|
||||
copyright = 版权所有
|
||||
all_rights_reserved = 保留所有权利。
|
||||
index_desc = Harbor是可靠的企业级Registry服务器。企业用户可使用Harbor搭建私有容器Registry服务,提高生产效率和安全度,既可应用于生产环境,也可以在开发环境中使用。
|
||||
|
@ -13,7 +13,6 @@
|
||||
limitations under the License.
|
||||
*/
|
||||
.footer {
|
||||
margin-top: 60px;
|
||||
width: 100%;
|
||||
/* Set the fixed height of the footer here */
|
||||
height: 60px;
|
||||
|
@ -24,7 +24,7 @@ jQuery(function(){
|
||||
error: function(jqXhr){
|
||||
if(jqXhr && jqXhr.status == 401){
|
||||
document.location = "/signIn";
|
||||
}
|
||||
}
|
||||
}
|
||||
}).exec();
|
||||
|
||||
@ -36,12 +36,12 @@ jQuery(function(){
|
||||
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");
|
||||
}
|
||||
e.preventDefault();
|
||||
if($("#txtCommonSearch").is(":focus")){
|
||||
document.location = "/search?q=" + $("#txtCommonSearch").val();
|
||||
}else{
|
||||
$("#btnSubmit").trigger("click");
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -61,35 +61,35 @@ jQuery(function(){
|
||||
type: "put",
|
||||
data: {"old_password": oldPassword, "new_password" : password},
|
||||
beforeSend: function(e){
|
||||
unbindEnterKey();
|
||||
$("h1").append(spinner.el);
|
||||
$("#btnSubmit").prop("disabled", true);
|
||||
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();
|
||||
}
|
||||
});
|
||||
.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;
|
||||
}
|
||||
});
|
||||
.dialogModal({
|
||||
"title": i18n.getMessage("title_change_password"),
|
||||
"content": i18n.getMessage(jqXhr.responseText),
|
||||
"callback": function(){
|
||||
bindEnterKey();
|
||||
return;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}).exec();
|
||||
|
@ -12,8 +12,9 @@
|
||||
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;
|
||||
@ -31,46 +32,47 @@ 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";
|
||||
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});
|
||||
}
|
||||
}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"
|
||||
"ru-RU": "Russian",
|
||||
"ja-JP": "Japanese"
|
||||
};
|
||||
|
||||
var DEFAULT_LANGUAGE = "en-US";
|
||||
@ -133,7 +135,7 @@ jQuery(function(){
|
||||
|
||||
var self = this;
|
||||
$("#dlgLabel", self).text(settings.title);
|
||||
|
||||
|
||||
if(options.text){
|
||||
$("#dlgBody", self).html(settings.content);
|
||||
}else if(typeof settings.content == "object"){
|
||||
@ -141,9 +143,9 @@ jQuery(function(){
|
||||
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>');
|
||||
'<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(""));
|
||||
@ -153,8 +155,13 @@ jQuery(function(){
|
||||
}
|
||||
|
||||
if(settings.callback != null){
|
||||
$("#dlgConfirm").on("click", function(){
|
||||
settings.callback();
|
||||
var hasEntered = false;
|
||||
$("#dlgConfirm").on("click", function(e){
|
||||
if(!hasEntered) {
|
||||
hasEntered = true;
|
||||
settings.callback();
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
$(self).modal('show');
|
||||
|
@ -13,26 +13,26 @@
|
||||
limitations under the License.
|
||||
*/
|
||||
jQuery(function(){
|
||||
|
||||
|
||||
$("#divErrMsg").css({"display": "none"});
|
||||
|
||||
validateOptions.Items = ["#EmailF"];
|
||||
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");
|
||||
}
|
||||
e.preventDefault();
|
||||
if($("#txtCommonSearch").is(":focus")){
|
||||
document.location = "/search?q=" + $("#txtCommonSearch").val();
|
||||
}else{
|
||||
$("#btnSubmit").trigger("click");
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
function unbindEnterKey(){
|
||||
$(document).off("keydown");
|
||||
}
|
||||
bindEnterKey();
|
||||
bindEnterKey();
|
||||
var spinner = new Spinner({scale:1}).spin();
|
||||
|
||||
$("#btnSubmit").on("click", function(){
|
||||
@ -44,20 +44,20 @@ jQuery(function(){
|
||||
"type": "get",
|
||||
"data": {"username": username, "email": email},
|
||||
"beforeSend": function(e){
|
||||
unbindEnterKey();
|
||||
$("h1").append(spinner.el);
|
||||
$("#btnSubmit").prop("disabled", true);
|
||||
unbindEnterKey();
|
||||
$("h1").append(spinner.el);
|
||||
$("#btnSubmit").prop("disabled", true);
|
||||
},
|
||||
"success": function(data, status, xhr){
|
||||
if(xhr && xhr.status == 200){
|
||||
$("#dlgModal")
|
||||
.dialogModal({
|
||||
"title": i18n.getMessage("title_forgot_password"),
|
||||
"content": i18n.getMessage("email_has_been_sent"),
|
||||
"callback": function(){
|
||||
document.location="/";
|
||||
}
|
||||
});
|
||||
.dialogModal({
|
||||
"title": i18n.getMessage("title_forgot_password"),
|
||||
"content": i18n.getMessage("email_has_been_sent"),
|
||||
"callback": function(){
|
||||
document.location="/";
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
},
|
||||
@ -68,14 +68,14 @@ jQuery(function(){
|
||||
"error": function(jqXhr, status, error){
|
||||
if(jqXhr){
|
||||
$("#dlgModal")
|
||||
.dialogModal({
|
||||
"title": i18n.getMessage("title_forgot_password"),
|
||||
"content": i18n.getMessage(jqXhr.responseText),
|
||||
"callback": function(){
|
||||
bindEnterKey();
|
||||
return;
|
||||
}
|
||||
});
|
||||
.dialogModal({
|
||||
"title": i18n.getMessage("title_forgot_password"),
|
||||
"content": i18n.getMessage(jqXhr.responseText),
|
||||
"callback": function(){
|
||||
bindEnterKey();
|
||||
return;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -13,20 +13,20 @@
|
||||
limitations under the License.
|
||||
*/
|
||||
jQuery(function(){
|
||||
$("#btnSignUp").css({"visibility": "visible"});
|
||||
$("#btnSignUp").css({"visibility": "visible"});
|
||||
|
||||
$(document).on("keydown", function(e){
|
||||
if(e.keyCode == 13){
|
||||
e.preventDefault();
|
||||
if($("#txtCommonSearch").is(":focus")){
|
||||
document.location = "/search?q=" + $("#txtCommonSearch").val();
|
||||
document.location = "/search?q=" + $("#txtCommonSearch").val();
|
||||
}
|
||||
}
|
||||
});
|
||||
$("#btnSignIn").on("click", function(){
|
||||
document.location = "/signIn";
|
||||
});
|
||||
$("#btnSignUp").on("click", function(){
|
||||
$("#btnSignUp").on("click", function(){
|
||||
document.location = "/register";
|
||||
});
|
||||
});
|
@ -23,461 +23,451 @@ jQuery(function(){
|
||||
if(jqXhr.status == 403){
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}).exec()
|
||||
).then(function(){
|
||||
noNeedToLoginCallback();
|
||||
needToLoginCallback();
|
||||
}).fail(function(){
|
||||
noNeedToLoginCallback();
|
||||
});
|
||||
|
||||
function noNeedToLoginCallback(){
|
||||
|
||||
$("#tabItemDetail a:first").tab("show");
|
||||
$("#btnFilterOption button:first").addClass("active");
|
||||
$("#divErrMsg").hide();
|
||||
|
||||
if($("#public").val() == 1){
|
||||
$("#tabItemDetail li:eq(1)").hide();
|
||||
$("#tabItemDetail li:eq(2)").hide();
|
||||
}
|
||||
|
||||
listRepo($("#repoName").val());
|
||||
|
||||
function listRepo(repoName){
|
||||
).then(function(){
|
||||
noNeedToLoginCallback();
|
||||
needToLoginCallback();
|
||||
}).fail(function(){
|
||||
noNeedToLoginCallback();
|
||||
});
|
||||
|
||||
function noNeedToLoginCallback(){
|
||||
|
||||
$("#tabItemDetail a:first").tab("show");
|
||||
$("#btnFilterOption button:first").addClass("active");
|
||||
$("#divErrMsg").hide();
|
||||
|
||||
if($("#public").val() == 1){
|
||||
$("#tabItemDetail li:eq(1)").hide();
|
||||
$("#tabItemDetail li:eq(2)").hide();
|
||||
}
|
||||
|
||||
listRepo($("#repoName").val());
|
||||
|
||||
function listRepo(repoName){
|
||||
|
||||
$("#divErrMsg").hide();
|
||||
|
||||
new AjaxUtil({
|
||||
url: "/api/repositories?project_id=" + $("#projectId").val() + "&q=" + repoName,
|
||||
type: "get",
|
||||
success: function(data, status, xhr){
|
||||
if(xhr && xhr.status == 200){
|
||||
$("#accordionRepo").children().remove();
|
||||
if(data == null){
|
||||
$("#divErrMsg").show();
|
||||
$("#divErrMsg center").html(i18n.getMessage("no_repo_exists"));
|
||||
return;
|
||||
}
|
||||
$.each(data, function(i, e){
|
||||
var targetId = e.replace(/\//g, "------");
|
||||
var row = '<div class="panel panel-default" targetId="' + targetId + '">' +
|
||||
new AjaxUtil({
|
||||
url: "/api/repositories?project_id=" + $("#projectId").val() + "&q=" + repoName,
|
||||
type: "get",
|
||||
success: function(data, status, xhr){
|
||||
if(xhr && xhr.status == 200){
|
||||
$("#accordionRepo").children().remove();
|
||||
if(data == null){
|
||||
$("#divErrMsg").show();
|
||||
$("#divErrMsg center").html(i18n.getMessage("no_repo_exists"));
|
||||
return;
|
||||
}
|
||||
$.each(data, function(i, e){
|
||||
var targetId = e.replace(/\//g, "------").replace(/\./g, "---");
|
||||
var row = '<div class="panel panel-default" targetId="' + targetId + '">' +
|
||||
'<div class="panel-heading" role="tab" id="heading' + i + '"+ >' +
|
||||
'<h4 class="panel-title">' +
|
||||
'<a data-toggle="collapse" data-parent="#accordion" href="#collapse'+ i + '" aria-expanded="true" aria-controls="collapse' + i + '">' +
|
||||
'<span class="list-group-item-heading"> <span class="glyphicon glyphicon-book blue"></span> ' + e + ' </span>' +
|
||||
'</a>' +
|
||||
'</h4>' +
|
||||
'</div>' +
|
||||
'<div id="collapse' + i + '" targetId="' + targetId + '" class="panel-collapse collapse" role="tabpanel" aria-labelledby="heading' + i + '">' +
|
||||
'<div class="panel-body" id="' + targetId + '">' +
|
||||
'<div class="table-responsive" style="height: auto;">' +
|
||||
'<table class="table table-striped table-bordered table-condensed">' +
|
||||
'<thead>' +
|
||||
'<tr>' +
|
||||
'<th class="st-sort-ascent" st-sort="name" st-sort-default=""><span class="glyphicon glyphicon-tag blue"></span> ' + i18n.getMessage("tag")+ ' </th>' +
|
||||
'<th class="st-sort-ascent" st-sort="name" st-sort-default=""><span class="glyphicon glyphicon-tag blue"></span> ' + i18n.getMessage("pull_command") + ' </th>' +
|
||||
'</tr>' +
|
||||
'</thead>' +
|
||||
'<tbody>' +
|
||||
'</tbody>' +
|
||||
'</table>'
|
||||
'</div>' +
|
||||
'</div>' +
|
||||
'</div>' +
|
||||
'</div>';
|
||||
$("#accordionRepo").append(row);
|
||||
});
|
||||
if(repoName != ""){
|
||||
$("#txtRepoName").val(repoName);
|
||||
$("#accordionRepo #heading0 a").trigger("click");
|
||||
'<h4 class="panel-title">' +
|
||||
'<a data-toggle="collapse" data-parent="#accordion" href="#collapse'+ i + '" aria-expanded="true" aria-controls="collapse' + i + '">' +
|
||||
'<span class="list-group-item-heading"> <span class="glyphicon glyphicon-book blue"></span> ' + e + ' </span>' +
|
||||
'</a>' +
|
||||
'</h4>' +
|
||||
'</div>' +
|
||||
'<div id="collapse' + i + '" targetId="' + targetId + '" class="panel-collapse collapse" role="tabpanel" aria-labelledby="heading' + i + '">' +
|
||||
'<div class="panel-body" id="' + targetId + '">' +
|
||||
'<div class="table-responsive" style="height: auto;">' +
|
||||
'<table class="table table-striped table-bordered table-condensed">' +
|
||||
'<thead>' +
|
||||
'<tr>' +
|
||||
'<th class="st-sort-ascent" st-sort="name" st-sort-default=""><span class="glyphicon glyphicon-tag blue"></span> ' + i18n.getMessage("tag")+ ' </th>' +
|
||||
'<th class="st-sort-ascent" st-sort="name" st-sort-default=""><span class="glyphicon glyphicon-tag blue"></span> ' + i18n.getMessage("pull_command") + ' </th>' +
|
||||
'</tr>' +
|
||||
'</thead>' +
|
||||
'<tbody>' +
|
||||
'</tbody>' +
|
||||
'</table>'
|
||||
'</div>' +
|
||||
'</div>' +
|
||||
'</div>' +
|
||||
'</div>';
|
||||
$("#accordionRepo").append(row);
|
||||
});
|
||||
if(repoName != ""){
|
||||
$("#txtRepoName").val(repoName);
|
||||
$("#accordionRepo #heading0 a").trigger("click");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}).exec();
|
||||
}
|
||||
$("#btnSearchRepo").on("click", function(){
|
||||
listRepo($.trim($("#txtRepoName").val()));
|
||||
});
|
||||
|
||||
$('#accordionRepo').on('show.bs.collapse', function (e) {
|
||||
$('#accordionRepo .in').collapse('hide');
|
||||
var targetId = $(e.target).attr("targetId");
|
||||
var repoName = targetId.replace(/------/g, "/");
|
||||
new AjaxUtil({
|
||||
url: "/api/repositories/tags?repo_name=" + repoName,
|
||||
type: "get",
|
||||
success: function(data, status, xhr){
|
||||
$('#' + targetId +' table tbody tr').remove();
|
||||
var row = [];
|
||||
for(var i in data){
|
||||
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>');
|
||||
}
|
||||
$('#' + targetId +' table tbody').append(row.join(""));
|
||||
$('#' + targetId +' table tbody tr a').on("click", function(e){
|
||||
var imageId = $(this).attr("imageId");
|
||||
var repoName = $(this).attr("repoName");
|
||||
new AjaxUtil({
|
||||
url: "/api/repositories/manifests?repo_name=" + repoName + "&tag=" + imageId,
|
||||
type: "get",
|
||||
success: function(data, status, xhr){
|
||||
if(data){
|
||||
for(var i in data){
|
||||
if(data[i] == ""){
|
||||
data[i] = "N/A";
|
||||
}
|
||||
}
|
||||
data.Created = moment(new Date(data.Created)).format("YYYY-MM-DD HH:mm:ss");
|
||||
|
||||
$("#dlgModal").dialogModal({"title": i18n.getMessage("image_details"), "content": data});
|
||||
}
|
||||
}
|
||||
}).exec();
|
||||
});
|
||||
}
|
||||
}).exec();
|
||||
});
|
||||
}
|
||||
|
||||
function needToLoginCallback(){
|
||||
|
||||
var hasAuthorization = false;
|
||||
|
||||
$.when(
|
||||
new AjaxUtil({
|
||||
url: "/api/projects/" + $("#projectId").val() + "/members/current",
|
||||
type: "get",
|
||||
success: function(data, status, xhr){
|
||||
if(xhr && xhr.status == 200 && data.roles != null && data.roles.length > 0){
|
||||
hasAuthorization = true;
|
||||
}
|
||||
}
|
||||
}).exec())
|
||||
.done(function(){
|
||||
|
||||
if(!hasAuthorization) return false;
|
||||
|
||||
$("#tabItemDetail a:eq(1)").css({"visibility": "visible"});
|
||||
$("#tabItemDetail a:eq(2)").css({"visibility": "visible"});
|
||||
|
||||
$(".glyphicon .glyphicon-pencil", "#tblUser").on("click", function(e){
|
||||
$("#txtUserName").hide();
|
||||
$("#lblUserName").show();
|
||||
$("#dlgUserTitle").text(i18n.getMessage("edit_members"));
|
||||
});
|
||||
|
||||
$("#btnAddUser").on("click", function(){
|
||||
$("#operationType").val("add");
|
||||
$("#spnSearch").show();
|
||||
$("#txtUserName").prop("disabled", false)
|
||||
$("#txtUserName").val("");
|
||||
$("#lstRole input[name=chooseRole]:radio").prop("checked", false);
|
||||
$("#dlgUserTitle").text(i18n.getMessage("add_members"));
|
||||
});
|
||||
|
||||
$("#btnSave").on("click", function(){
|
||||
|
||||
var username = $("#txtUserName").val();
|
||||
if($.trim(username).length == 0){
|
||||
$("#dlgModal").dialogModal({"title": i18n.getMessage("add_member_failed"), "content": i18n.getMessage("please_input_username")});
|
||||
return;
|
||||
}
|
||||
var projectId = $("#projectId").val();
|
||||
var operationType = $("#operationType").val();
|
||||
var userId = $("#editUserId").val();
|
||||
|
||||
var checkedRole = $("#lstRole input[name='chooseRole']:checked")
|
||||
if(checkedRole.length == 0){
|
||||
$("#dlgModal").dialogModal({"title": i18n.getMessage("add_member_failed"), "content": i18n.getMessage("please_assign_a_role_to_user")});
|
||||
return;
|
||||
}
|
||||
|
||||
var checkedRoleItemList = [];
|
||||
$.each(checkedRole, function(i, e){
|
||||
checkedRoleItemList.push(new Number($(this).val()));
|
||||
});
|
||||
|
||||
var ajaxOpts = {};
|
||||
if(operationType == "add"){
|
||||
ajaxOpts.url = "/api/projects/" + projectId + "/members/";
|
||||
ajaxOpts.type = "post";
|
||||
ajaxOpts.data = {"roles" : checkedRoleItemList, "user_name": username};
|
||||
}else if(operationType == "edit"){
|
||||
ajaxOpts.url = "/api/projects/" + projectId + "/members/" + userId;
|
||||
ajaxOpts.type = "put";
|
||||
ajaxOpts.data = {"roles" : checkedRoleItemList};
|
||||
}
|
||||
|
||||
new AjaxUtil({
|
||||
url: ajaxOpts.url,
|
||||
data: ajaxOpts.data,
|
||||
type: ajaxOpts.type,
|
||||
complete: function(jqXhr, status){
|
||||
if(jqXhr && jqXhr.status == 200){
|
||||
$("#btnCancel").trigger("click");
|
||||
listUser(null);
|
||||
}
|
||||
},
|
||||
errors: {
|
||||
404: i18n.getMessage("user_id_does_not_exist"),
|
||||
409: i18n.getMessage("user_id_exists"),
|
||||
403: i18n.getMessage("insufficient_privileges")
|
||||
}
|
||||
}).exec();
|
||||
});
|
||||
|
||||
var name_mapping = {
|
||||
"projectAdmin": "Project Admin",
|
||||
"developer": "Developer",
|
||||
"guest": "Guest"
|
||||
}
|
||||
|
||||
function listUserByProjectCallback(userList){
|
||||
var loginedUserId = $("#userId").val();
|
||||
var loginedUserRoleId = $("#roleId").val();
|
||||
var ownerId = $("#ownerId").val();
|
||||
|
||||
$("#tblUser tbody tr").remove();
|
||||
for(var i = 0; i < userList.length; ){
|
||||
|
||||
var userId = userList[i].UserId;
|
||||
var roleId = userList[i].RoleId;
|
||||
var username = userList[i].username;
|
||||
var roleNameList = [];
|
||||
|
||||
for(var j = i; j < userList.length; i++, j++){
|
||||
if(userList[j].UserId == userId){
|
||||
roleNameList.push(name_mapping[userList[j].Rolename]);
|
||||
}else{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
var row = '<tr>' +
|
||||
'<td>' + username + '</td>' +
|
||||
'<td>' + roleNameList.join(",") + '</td>' +
|
||||
'<td>';
|
||||
var isShowOperations = true;
|
||||
if(loginedUserRoleId >= 3 /*role: developer guest*/){
|
||||
isShowOperations = false;
|
||||
}else if(ownerId == userId){
|
||||
isShowOperations = false;
|
||||
}else if (loginedUserId == userId){
|
||||
isShowOperations = false;
|
||||
}
|
||||
if(isShowOperations){
|
||||
row += '<a href="#" userid="' + userId + '" class="glyphicon glyphicon-pencil" data-toggle="modal" data-target="#dlgUser"></a> ' +
|
||||
'<a href="#" userid="' + userId + '" roleid="' + roleId + '" class="glyphicon glyphicon-trash"></a>';
|
||||
}
|
||||
|
||||
row += '</td></tr>';
|
||||
$("#tblUser tbody").append(row);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
function searchAccessLogCallback(LogList){
|
||||
$("#tabOperationLog tbody tr").remove();
|
||||
$.each(LogList || [], function(i, e){
|
||||
$("#tabOperationLog tbody").append(
|
||||
'<tr>' +
|
||||
'<td>' + e.Username + '</td>' +
|
||||
'<td>' + e.RepoName + '</td>' +
|
||||
'<td>' + e.RepoTag + '</td>' +
|
||||
'<td>' + e.Operation + '</td>' +
|
||||
'<td>' + moment(new Date(e.OpTime)).format("YYYY-MM-DD HH:mm:ss") + '</td>' +
|
||||
'</tr>');
|
||||
});
|
||||
}
|
||||
|
||||
function getUserRoleCallback(userId){
|
||||
new AjaxUtil({
|
||||
url: "/api/projects/" + $("#projectId").val() + "/members/" + userId,
|
||||
type: "get",
|
||||
success: function(data, status, xhr){
|
||||
var user = data;
|
||||
$("#operationType").val("edit");
|
||||
$("#editUserId").val(user.user_id);
|
||||
$("#spnSearch").hide();
|
||||
$("#txtUserName").val(user.user_name);
|
||||
$("#txtUserName").prop("disabled", true);
|
||||
$("#btnSave").removeClass("disabled");
|
||||
$("#dlgUserTitle").text(i18n.getMessage("edit_members"));
|
||||
$("#lstRole input[name=chooseRole]:radio").not('[value=' + user.role_id + ']').prop("checked", false)
|
||||
$.each(user.roles, function(i, e){
|
||||
$("#lstRole input[name=chooseRole]:radio").filter('[value=' + e.role_id + ']').prop("checked", "checked");
|
||||
});
|
||||
}
|
||||
}).exec();
|
||||
}
|
||||
function listUser(username){
|
||||
$.when(
|
||||
new AjaxUtil({
|
||||
url: "/api/projects/" + $("#projectId").val() + "/members?username=" + (username == null ? "" : username),
|
||||
type: "get",
|
||||
errors: {
|
||||
403: ""
|
||||
},
|
||||
success: function(data, status, xhr){
|
||||
return data || [];
|
||||
}
|
||||
}).exec()
|
||||
).done(function(userList){
|
||||
listUserByProjectCallback(userList || []);
|
||||
$("#tblUser .glyphicon-pencil").on("click", function(e){
|
||||
var userId = $(this).attr("userid")
|
||||
getUserRoleCallback(userId);
|
||||
});
|
||||
$("#tblUser .glyphicon-trash").on("click", function(){
|
||||
var userId = $(this).attr("userid");
|
||||
new AjaxUtil({
|
||||
url: "/api/projects/" + $("#projectId").val() + "/members/" + userId,
|
||||
type: "delete",
|
||||
complete: function(jqXhr, status){
|
||||
if(jqXhr && jqXhr.status == 200){
|
||||
listUser(null);
|
||||
}
|
||||
}
|
||||
}).exec();
|
||||
});
|
||||
});
|
||||
}
|
||||
listUser(null);
|
||||
listOperationLogs();
|
||||
|
||||
function listOperationLogs(){
|
||||
var projectId = $("#projectId").val();
|
||||
|
||||
$.when(
|
||||
new AjaxUtil({
|
||||
url : "/api/projects/" + projectId + "/logs/filter",
|
||||
data: {},
|
||||
type: "post",
|
||||
success: function(data){
|
||||
return data || [];
|
||||
}
|
||||
}).exec()
|
||||
).done(function(operationLogs){
|
||||
searchAccessLogCallback(operationLogs);
|
||||
});
|
||||
}
|
||||
|
||||
$("#btnSearchUser").on("click", function(){
|
||||
var username = $("#txtSearchUser").val();
|
||||
if($.trim(username).length == 0){
|
||||
username = null;
|
||||
}
|
||||
listUser(username);
|
||||
$("#btnSearchRepo").on("click", function(){
|
||||
listRepo($.trim($("#txtRepoName").val()));
|
||||
});
|
||||
|
||||
function toUTCSeconds(date, hour, min, sec) {
|
||||
var t = new Date(date);
|
||||
t.setHours(hour);
|
||||
t.setMinutes(min);
|
||||
t.setSeconds(sec);
|
||||
var utcTime = new Date(t.getUTCFullYear(),
|
||||
$('#accordionRepo').on('show.bs.collapse', function (e) {
|
||||
$('#accordionRepo .in').collapse('hide');
|
||||
var targetId = $(e.target).attr("targetId");
|
||||
var repoName = targetId.replace(/[-]{6}/g, "/").replace(/[-]{3}/g, ".");
|
||||
new AjaxUtil({
|
||||
url: "/api/repositories/tags?repo_name=" + repoName,
|
||||
type: "get",
|
||||
success: function(data, status, xhr){
|
||||
$('#' + targetId +' table tbody tr').remove();
|
||||
var row = [];
|
||||
for(var i in data){
|
||||
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>');
|
||||
}
|
||||
$('#' + targetId +' table tbody').append(row.join(""));
|
||||
$('#' + targetId +' table tbody tr a').on("click", function(e){
|
||||
var imageId = $(this).attr("imageId");
|
||||
var repoName = $(this).attr("repoName");
|
||||
new AjaxUtil({
|
||||
url: "/api/repositories/manifests?repo_name=" + repoName + "&tag=" + imageId,
|
||||
type: "get",
|
||||
success: function(data, status, xhr){
|
||||
if(data){
|
||||
for(var i in data){
|
||||
if(data[i] == ""){
|
||||
data[i] = "N/A";
|
||||
}
|
||||
}
|
||||
data.Created = moment(new Date(data.Created)).format("YYYY-MM-DD HH:mm:ss");
|
||||
$("#dlgModal").dialogModal({"title": i18n.getMessage("image_details"), "content": data});
|
||||
}
|
||||
}
|
||||
}).exec();
|
||||
});
|
||||
}
|
||||
}).exec();
|
||||
});
|
||||
}
|
||||
|
||||
function needToLoginCallback(){
|
||||
|
||||
var hasAuthorization = false;
|
||||
|
||||
$.when(
|
||||
new AjaxUtil({
|
||||
url: "/api/projects/" + $("#projectId").val() + "/members/current",
|
||||
type: "get",
|
||||
success: function(data, status, xhr){
|
||||
if(xhr && xhr.status == 200 && data.roles != null && data.roles.length > 0){
|
||||
hasAuthorization = true;
|
||||
}
|
||||
}
|
||||
}).exec())
|
||||
.done(function(){
|
||||
|
||||
if(!hasAuthorization) return false;
|
||||
|
||||
$("#tabItemDetail a:eq(1)").css({"visibility": "visible"});
|
||||
$("#tabItemDetail a:eq(2)").css({"visibility": "visible"});
|
||||
|
||||
$(".glyphicon .glyphicon-pencil", "#tblUser").on("click", function(e){
|
||||
$("#txtUserName").hide();
|
||||
$("#lblUserName").show();
|
||||
$("#dlgUserTitle").text(i18n.getMessage("edit_members"));
|
||||
});
|
||||
|
||||
$("#btnAddUser").on("click", function(){
|
||||
$("#operationType").val("add");
|
||||
$("#spnSearch").show();
|
||||
$("#txtUserName").prop("disabled", false)
|
||||
$("#txtUserName").val("");
|
||||
$("#lstRole input[name=chooseRole]:radio").prop("checked", false);
|
||||
$("#dlgUserTitle").text(i18n.getMessage("add_members"));
|
||||
});
|
||||
|
||||
$("#btnSave").on("click", function(){
|
||||
|
||||
var username = $("#txtUserName").val();
|
||||
if($.trim(username).length == 0){
|
||||
$("#dlgModal").dialogModal({"title": i18n.getMessage("add_member_failed"), "content": i18n.getMessage("please_input_username")});
|
||||
return;
|
||||
}
|
||||
var projectId = $("#projectId").val();
|
||||
var operationType = $("#operationType").val();
|
||||
var userId = $("#editUserId").val();
|
||||
|
||||
var checkedRole = $("#lstRole input[name='chooseRole']:checked")
|
||||
if(checkedRole.length == 0){
|
||||
$("#dlgModal").dialogModal({"title": i18n.getMessage("add_member_failed"), "content": i18n.getMessage("please_assign_a_role_to_user")});
|
||||
return;
|
||||
}
|
||||
|
||||
var checkedRoleItemList = [];
|
||||
$.each(checkedRole, function(i, e){
|
||||
checkedRoleItemList.push(new Number($(this).val()));
|
||||
});
|
||||
|
||||
var ajaxOpts = {};
|
||||
if(operationType == "add"){
|
||||
ajaxOpts.url = "/api/projects/" + projectId + "/members/";
|
||||
ajaxOpts.type = "post";
|
||||
ajaxOpts.data = {"roles" : checkedRoleItemList, "username": username};
|
||||
}else if(operationType == "edit"){
|
||||
ajaxOpts.url = "/api/projects/" + projectId + "/members/" + userId;
|
||||
ajaxOpts.type = "put";
|
||||
ajaxOpts.data = {"roles" : checkedRoleItemList};
|
||||
}
|
||||
|
||||
new AjaxUtil({
|
||||
url: ajaxOpts.url,
|
||||
data: ajaxOpts.data,
|
||||
type: ajaxOpts.type,
|
||||
complete: function(jqXhr, status){
|
||||
if(jqXhr && jqXhr.status == 200){
|
||||
$("#btnCancel").trigger("click");
|
||||
listUser(null);
|
||||
}
|
||||
},
|
||||
errors: {
|
||||
404: i18n.getMessage("user_id_does_not_exist"),
|
||||
409: i18n.getMessage("user_id_exists"),
|
||||
403: i18n.getMessage("insufficient_privileges")
|
||||
}
|
||||
}).exec();
|
||||
});
|
||||
|
||||
var name_mapping = {
|
||||
"projectAdmin": "Project Admin",
|
||||
"developer": "Developer",
|
||||
"guest": "Guest"
|
||||
}
|
||||
|
||||
function listUserByProjectCallback(userList){
|
||||
var loginedUserId = $("#userId").val();
|
||||
var loginedUserRoleId = $("#roleId").val();
|
||||
var ownerId = $("#ownerId").val();
|
||||
|
||||
$("#tblUser tbody tr").remove();
|
||||
for(var i = 0; i < userList.length; i++){
|
||||
|
||||
var userId = userList[i].user_id;
|
||||
var roleId = userList[i].role_id;
|
||||
var username = userList[i].username;
|
||||
|
||||
var row = '<tr>' +
|
||||
'<td>' + username + '</td>' +
|
||||
'<td>' + name_mapping[userList[i].role_name] + '</td>' +
|
||||
'<td>';
|
||||
var isShowOperations = true;
|
||||
if(loginedUserRoleId >= 3 /*role: developer guest*/){
|
||||
isShowOperations = false;
|
||||
}else if(ownerId == userId){
|
||||
isShowOperations = false;
|
||||
}else if (loginedUserId == userId){
|
||||
isShowOperations = false;
|
||||
}
|
||||
if(isShowOperations){
|
||||
row += '<a href="#" userid="' + userId + '" class="glyphicon glyphicon-pencil" data-toggle="modal" data-target="#dlgUser"></a> ' +
|
||||
'<a href="#" userid="' + userId + '" roleid="' + roleId + '" class="glyphicon glyphicon-trash"></a>';
|
||||
}
|
||||
|
||||
row += '</td></tr>';
|
||||
$("#tblUser tbody").append(row);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
function searchAccessLogCallback(LogList){
|
||||
$("#tabOperationLog tbody tr").remove();
|
||||
$.each(LogList || [], function(i, e){
|
||||
$("#tabOperationLog tbody").append(
|
||||
'<tr>' +
|
||||
'<td>' + e.username + '</td>' +
|
||||
'<td>' + e.repo_name + '</td>' +
|
||||
'<td>' + e.repo_tag + '</td>' +
|
||||
'<td>' + e.operation + '</td>' +
|
||||
'<td>' + moment(new Date(e.op_time)).format("YYYY-MM-DD HH:mm:ss") + '</td>' +
|
||||
'</tr>');
|
||||
});
|
||||
}
|
||||
|
||||
function getUserRoleCallback(userId){
|
||||
new AjaxUtil({
|
||||
url: "/api/projects/" + $("#projectId").val() + "/members/" + userId,
|
||||
type: "get",
|
||||
success: function(data, status, xhr){
|
||||
var user = data;
|
||||
$("#operationType").val("edit");
|
||||
$("#editUserId").val(user.user_id);
|
||||
$("#spnSearch").hide();
|
||||
$("#txtUserName").val(user.username);
|
||||
$("#txtUserName").prop("disabled", true);
|
||||
$("#btnSave").removeClass("disabled");
|
||||
$("#dlgUserTitle").text(i18n.getMessage("edit_members"));
|
||||
$("#lstRole input[name=chooseRole]:radio").not('[value=' + user.role_id + ']').prop("checked", false)
|
||||
$.each(user.roles, function(i, e){
|
||||
$("#lstRole input[name=chooseRole]:radio").filter('[value=' + e.role_id + ']').prop("checked", "checked");
|
||||
});
|
||||
}
|
||||
}).exec();
|
||||
}
|
||||
function listUser(username){
|
||||
$.when(
|
||||
new AjaxUtil({
|
||||
url: "/api/projects/" + $("#projectId").val() + "/members?username=" + (username == null ? "" : username),
|
||||
type: "get",
|
||||
errors: {
|
||||
403: ""
|
||||
},
|
||||
success: function(data, status, xhr){
|
||||
return data || [];
|
||||
}
|
||||
}).exec()
|
||||
).done(function(userList){
|
||||
listUserByProjectCallback(userList || []);
|
||||
$("#tblUser .glyphicon-pencil").on("click", function(e){
|
||||
var userId = $(this).attr("userid")
|
||||
getUserRoleCallback(userId);
|
||||
});
|
||||
$("#tblUser .glyphicon-trash").on("click", function(){
|
||||
var userId = $(this).attr("userid");
|
||||
new AjaxUtil({
|
||||
url: "/api/projects/" + $("#projectId").val() + "/members/" + userId,
|
||||
type: "delete",
|
||||
complete: function(jqXhr, status){
|
||||
if(jqXhr && jqXhr.status == 200){
|
||||
listUser(null);
|
||||
}
|
||||
}
|
||||
}).exec();
|
||||
});
|
||||
});
|
||||
}
|
||||
listUser(null);
|
||||
listOperationLogs();
|
||||
|
||||
function listOperationLogs(){
|
||||
var projectId = $("#projectId").val();
|
||||
|
||||
$.when(
|
||||
new AjaxUtil({
|
||||
url : "/api/projects/" + projectId + "/logs/filter",
|
||||
data: {},
|
||||
type: "post",
|
||||
success: function(data){
|
||||
return data || [];
|
||||
}
|
||||
}).exec()
|
||||
).done(function(operationLogs){
|
||||
searchAccessLogCallback(operationLogs);
|
||||
});
|
||||
}
|
||||
|
||||
$("#btnSearchUser").on("click", function(){
|
||||
var username = $("#txtSearchUser").val();
|
||||
if($.trim(username).length == 0){
|
||||
username = null;
|
||||
}
|
||||
listUser(username);
|
||||
});
|
||||
|
||||
function toUTCSeconds(date, hour, min, sec) {
|
||||
var t = new Date(date);
|
||||
t.setHours(hour);
|
||||
t.setMinutes(min);
|
||||
t.setSeconds(sec);
|
||||
var utcTime = new Date(t.getUTCFullYear(),
|
||||
t.getUTCMonth(),
|
||||
t.getUTCDate(),
|
||||
t.getUTCHours(),
|
||||
t.getUTCMinutes(),
|
||||
t.getUTCSeconds());
|
||||
return utcTime.getTime() / 1000;
|
||||
}
|
||||
|
||||
$("#btnFilterLog").on("click", function(){
|
||||
|
||||
var projectId = $("#projectId").val();
|
||||
var username = $("#txtSearchUserName").val();
|
||||
|
||||
var beginTimestamp = 0;
|
||||
var endTimestamp = 0;
|
||||
|
||||
if($("#begindatepicker").val() != ""){
|
||||
beginTimestamp = toUTCSeconds($("#begindatepicker").val(), 0, 0, 0);
|
||||
}
|
||||
if($("#enddatepicker").val() != ""){
|
||||
endTimestamp = toUTCSeconds($("#enddatepicker").val(), 23, 59, 59);
|
||||
}
|
||||
|
||||
new AjaxUtil({
|
||||
url: "/api/projects/" + projectId + "/logs/filter",
|
||||
data:{"username":username, "project_id" : projectId, "keywords" : getKeyWords() , "beginTimestamp" : beginTimestamp, "endTimestamp" : endTimestamp},
|
||||
type: "post",
|
||||
success: function(data, status, xhr){
|
||||
if(xhr && xhr.status == 200){
|
||||
searchAccessLogCallback(data);
|
||||
return utcTime.getTime() / 1000;
|
||||
}
|
||||
}
|
||||
}).exec();
|
||||
});
|
||||
|
||||
$("#spnFilterOption input[name=chkAll]").on("click", function(){
|
||||
$("#spnFilterOption input[name=chkOperation]").prop("checked", $(this).prop("checked"));
|
||||
});
|
||||
|
||||
$("#spnFilterOption input[name=chkOperation]").on("click", function(){
|
||||
if(!$(this).prop("checked")){
|
||||
$("#spnFilterOption input[name=chkAll]").prop("checked", false);
|
||||
}
|
||||
|
||||
var selectedAll = true;
|
||||
|
||||
$("#spnFilterOption input[name=chkOperation]").each(function(i, e){
|
||||
if(!$(e).prop("checked")){
|
||||
selectedAll = false;
|
||||
}
|
||||
});
|
||||
|
||||
if(selectedAll){
|
||||
$("#spnFilterOption input[name=chkAll]").prop("checked", true);
|
||||
}
|
||||
});
|
||||
|
||||
function getKeyWords(){
|
||||
var keywords = "";
|
||||
var checkedItemList=$("#spnFilterOption input[name=chkOperation]:checked");
|
||||
var keywords = [];
|
||||
$.each(checkedItemList, function(i, e){
|
||||
var itemValue = $(e).val();
|
||||
if(itemValue == "others" && $.trim($("#txtOthers").val()).length > 0){
|
||||
keywords.push($("#txtOthers").val());
|
||||
}else{
|
||||
keywords.push($(e).val());
|
||||
}
|
||||
});
|
||||
return keywords.join("/");
|
||||
}
|
||||
|
||||
$('#datetimepicker1').datetimepicker({
|
||||
locale: i18n.getLocale(),
|
||||
ignoreReadonly: true,
|
||||
format: 'L',
|
||||
showClear: true
|
||||
});
|
||||
$('#datetimepicker2').datetimepicker({
|
||||
locale: i18n.getLocale(),
|
||||
ignoreReadonly: true,
|
||||
format: 'L',
|
||||
showClear: true
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
$(document).on("keydown", function(e){
|
||||
if(e.keyCode == 13){
|
||||
e.preventDefault();
|
||||
if($("#tabItemDetail li:eq(0)").is(":focus") || $("#txtRepoName").is(":focus")){
|
||||
$("#btnSearchRepo").trigger("click");
|
||||
}else if($("#tabItemDetail li:eq(1)").is(":focus") || $("#txtSearchUser").is(":focus")){
|
||||
$("#btnSearchUser").trigger("click");
|
||||
}else if($("#tabItemDetail li:eq(2)").is(":focus") || $("#txtSearchUserName").is(":focus")){
|
||||
$("#btnFilterLog").trigger("click");
|
||||
}else if($("#txtUserName").is(":focus") || $("#lstRole :radio").is(":focus")){
|
||||
$("#btnSave").trigger("click");
|
||||
}
|
||||
|
||||
$("#btnFilterLog").on("click", function(){
|
||||
|
||||
var projectId = $("#projectId").val();
|
||||
var username = $("#txtSearchUserName").val();
|
||||
|
||||
var beginTimestamp = 0;
|
||||
var endTimestamp = 0;
|
||||
|
||||
if($("#begindatepicker").val() != ""){
|
||||
beginTimestamp = toUTCSeconds($("#begindatepicker").val(), 0, 0, 0);
|
||||
}
|
||||
if($("#enddatepicker").val() != ""){
|
||||
endTimestamp = toUTCSeconds($("#enddatepicker").val(), 23, 59, 59);
|
||||
}
|
||||
|
||||
new AjaxUtil({
|
||||
url: "/api/projects/" + projectId + "/logs/filter",
|
||||
data:{"username":username, "project_id" : Number(projectId), "keywords" : getKeyWords() , "begin_timestamp" : beginTimestamp, "end_timestamp" : endTimestamp},
|
||||
type: "post",
|
||||
success: function(data, status, xhr){
|
||||
if(xhr && xhr.status == 200){
|
||||
searchAccessLogCallback(data);
|
||||
}
|
||||
}
|
||||
}).exec();
|
||||
});
|
||||
|
||||
$("#spnFilterOption input[name=chkAll]").on("click", function(){
|
||||
$("#spnFilterOption input[name=chkOperation]").prop("checked", $(this).prop("checked"));
|
||||
});
|
||||
|
||||
$("#spnFilterOption input[name=chkOperation]").on("click", function(){
|
||||
if(!$(this).prop("checked")){
|
||||
$("#spnFilterOption input[name=chkAll]").prop("checked", false);
|
||||
}
|
||||
|
||||
var selectedAll = true;
|
||||
|
||||
$("#spnFilterOption input[name=chkOperation]").each(function(i, e){
|
||||
if(!$(e).prop("checked")){
|
||||
selectedAll = false;
|
||||
}
|
||||
});
|
||||
|
||||
if(selectedAll){
|
||||
$("#spnFilterOption input[name=chkAll]").prop("checked", true);
|
||||
}
|
||||
});
|
||||
|
||||
function getKeyWords(){
|
||||
var keywords = "";
|
||||
var checkedItemList=$("#spnFilterOption input[name=chkOperation]:checked");
|
||||
var keywords = [];
|
||||
$.each(checkedItemList, function(i, e){
|
||||
var itemValue = $(e).val();
|
||||
if(itemValue == "others" && $.trim($("#txtOthers").val()).length > 0){
|
||||
keywords.push($("#txtOthers").val());
|
||||
}else{
|
||||
keywords.push($(e).val());
|
||||
}
|
||||
});
|
||||
return keywords.join("/");
|
||||
}
|
||||
|
||||
$('#datetimepicker1').datetimepicker({
|
||||
locale: i18n.getLocale(),
|
||||
ignoreReadonly: true,
|
||||
format: 'L',
|
||||
showClear: true
|
||||
});
|
||||
$('#datetimepicker2').datetimepicker({
|
||||
locale: i18n.getLocale(),
|
||||
ignoreReadonly: true,
|
||||
format: 'L',
|
||||
showClear: true
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
$(document).on("keydown", function(e){
|
||||
if(e.keyCode == 13){
|
||||
e.preventDefault();
|
||||
if($("#tabItemDetail li:eq(0)").is(":focus") || $("#txtRepoName").is(":focus")){
|
||||
$("#btnSearchRepo").trigger("click");
|
||||
}else if($("#tabItemDetail li:eq(1)").is(":focus") || $("#txtSearchUser").is(":focus")){
|
||||
$("#btnSearchUser").trigger("click");
|
||||
}else if($("#tabItemDetail li:eq(2)").is(":focus") || $("#txtSearchUserName").is(":focus")){
|
||||
$("#btnFilterLog").trigger("click");
|
||||
}else if($("#txtUserName").is(":focus") || $("#lstRole :radio").is(":focus")){
|
||||
$("#btnSave").trigger("click");
|
||||
}
|
||||
});
|
||||
})
|
||||
}
|
||||
});
|
||||
})
|
@ -24,7 +24,7 @@ jQuery(function(){
|
||||
},
|
||||
error: function(jqXhr){
|
||||
if(jqXhr.status == 401)
|
||||
return false;
|
||||
return false;
|
||||
}
|
||||
}).exec();
|
||||
|
||||
|
@ -13,13 +13,13 @@
|
||||
limitations under the License.
|
||||
*/
|
||||
jQuery(function(){
|
||||
|
||||
|
||||
new AjaxUtil({
|
||||
url: "/api/users/current",
|
||||
type: "get",
|
||||
success: function(data, status, xhr){
|
||||
if(xhr && xhr.status == 200){
|
||||
if(data.HasAdminRole == 1) {
|
||||
if(data.has_admin_role == 1) {
|
||||
renderForAdminRole();
|
||||
}
|
||||
renderForAnyRole();
|
||||
@ -29,57 +29,57 @@ jQuery(function(){
|
||||
|
||||
function renderForAnyRole(){
|
||||
$("#tabProject a:first").tab("show");
|
||||
|
||||
|
||||
$(document).on("keydown", function(e){
|
||||
if(e.keyCode == 13){
|
||||
e.preventDefault();
|
||||
if($("#tabProject li:eq(0)").is(":focus") || $("#txtSearchProject").is(":focus")){
|
||||
$("#btnSearch").trigger("click");
|
||||
}else if($("#tabProject li:eq(1)").is(":focus") || $("#txtSearchPublicProjects").is(":focus")){
|
||||
$("#btnSearchPublicProjects").trigger("click");
|
||||
}else if($("#tabProject li:eq(1)").is(":focus") || $("#txtSearchUsername").is(":focus")){
|
||||
$("#btnSearchUsername").trigger("click");
|
||||
}else if($("#dlgAddProject").is(":focus") || $("#projectName").is(":focus")){
|
||||
$("#btnSave").trigger("click");
|
||||
}
|
||||
e.preventDefault();
|
||||
if($("#tabProject li:eq(0)").is(":focus") || $("#txtSearchProject").is(":focus")){
|
||||
$("#btnSearch").trigger("click");
|
||||
}else if($("#tabProject li:eq(1)").is(":focus") || $("#txtSearchPublicProjects").is(":focus")){
|
||||
$("#btnSearchPublicProjects").trigger("click");
|
||||
}else if($("#tabProject li:eq(1)").is(":focus") || $("#txtSearchUsername").is(":focus")){
|
||||
$("#btnSearchUsername").trigger("click");
|
||||
}else if($("#dlgAddProject").is(":focus") || $("#projectName").is(":focus")){
|
||||
$("#btnSave").trigger("click");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
function listProject(projectName, isPublic){
|
||||
currentPublic = isPublic;
|
||||
$.when(
|
||||
new AjaxUtil({
|
||||
url: "/api/projects?is_public=" + isPublic + "&project_name=" + (projectName == null ? "" : projectName) + "×tamp=" + new Date().getTime(),
|
||||
type: "get",
|
||||
success: function(data, status, xhr){
|
||||
$("#tblProject tbody tr").remove();
|
||||
$.each(data || [], function(i, e){
|
||||
var row = '<tr>' +
|
||||
'<td style="vertical-align: middle;"><a href="/registry/detail?project_id=' + e.ProjectId + '">' + e.Name + '</a></td>' +
|
||||
'<td style="vertical-align: middle;">' + moment(new Date(e.CreationTime)).format("YYYY-MM-DD HH:mm:ss") + '</td>';
|
||||
if(e.Public == 1 && e.Togglable){
|
||||
row += '<td><button type="button" class="btn btn-success" projectid="' + e.ProjectId + '">' + i18n.getMessage("button_on")+ '</button></td>'
|
||||
} else if (e.Public == 1) {
|
||||
row += '<td><button type="button" class="btn btn-success" projectid="' + e.ProjectId + '" disabled>' + i18n.getMessage("button_on")+ '</button></td>';
|
||||
} else if (e.Public == 0 && e.Togglable) {
|
||||
row += '<td><button type="button" class="btn btn-danger" projectid="' + e.ProjectId + '">' + i18n.getMessage("button_off")+ '</button></td>';
|
||||
} else if (e.Public == 0) {
|
||||
row += '<td><button type="button" class="btn btn-danger" projectid="' + e.ProjectId + '" disabled>' + i18n.getMessage("button_off")+ '</button></td>';
|
||||
row += '</tr>';
|
||||
}
|
||||
$("#tblProject tbody").append(row);
|
||||
});
|
||||
}
|
||||
}).exec())
|
||||
url: "/api/projects?is_public=" + isPublic + "&project_name=" + (projectName == null ? "" : projectName) + "×tamp=" + new Date().getTime(),
|
||||
type: "get",
|
||||
success: function(data, status, xhr){
|
||||
$("#tblProject tbody tr").remove();
|
||||
$.each(data || [], function(i, e){
|
||||
var row = '<tr>' +
|
||||
'<td style="vertical-align: middle;"><a href="/registry/detail?project_id=' + e.project_id + '">' + e.name + '</a></td>' +
|
||||
'<td style="vertical-align: middle;">' + moment(new Date(e.creation_time)).format("YYYY-MM-DD HH:mm:ss") + '</td>';
|
||||
if(e.public == 1 && e.Togglable){
|
||||
row += '<td><button type="button" class="btn btn-success" projectid="' + e.project_id + '">' + i18n.getMessage("button_on")+ '</button></td>'
|
||||
} else if (e.public == 1) {
|
||||
row += '<td><button type="button" class="btn btn-success" projectid="' + e.project_id + '" disabled>' + i18n.getMessage("button_on")+ '</button></td>';
|
||||
} else if (e.public == 0 && e.Togglable) {
|
||||
row += '<td><button type="button" class="btn btn-danger" projectid="' + e.project_id + '">' + i18n.getMessage("button_off")+ '</button></td>';
|
||||
} else if (e.public == 0) {
|
||||
row += '<td><button type="button" class="btn btn-danger" projectid="' + e.project_id + '" disabled>' + i18n.getMessage("button_off")+ '</button></td>';
|
||||
row += '</tr>';
|
||||
}
|
||||
$("#tblProject tbody").append(row);
|
||||
});
|
||||
}
|
||||
}).exec())
|
||||
.done(function() {
|
||||
$("#tblProject tbody tr :button").on("click", function(){
|
||||
var projectId = $(this).attr("projectid");
|
||||
var self = this;
|
||||
new AjaxUtil({
|
||||
url: "/api/projects/" + projectId,
|
||||
data: {"public": ($(self).hasClass("btn-success") ? false : true)},
|
||||
type: "put",
|
||||
complete: function(jqXhr, status) {
|
||||
$("#tblProject tbody tr :button").on("click", function(){
|
||||
var projectId = $(this).attr("projectid");
|
||||
var self = this;
|
||||
new AjaxUtil({
|
||||
url: "/api/projects/" + projectId,
|
||||
data: {"public": ($(self).hasClass("btn-success") ? false : true)},
|
||||
type: "put",
|
||||
complete: function(jqXhr, status) {
|
||||
if($(self).hasClass("btn-success")){
|
||||
$(self).removeClass("btn-success").addClass("btn-danger");
|
||||
$(self).html(i18n.getMessage("button_off"));
|
||||
@ -88,9 +88,9 @@ jQuery(function(){
|
||||
$(self).html(i18n.getMessage("button_on"));
|
||||
}
|
||||
}
|
||||
}).exec();
|
||||
});
|
||||
});
|
||||
}).exec();
|
||||
});
|
||||
});
|
||||
}
|
||||
listProject(null, 0);
|
||||
var currentPublic = 0;
|
||||
@ -119,7 +119,7 @@ jQuery(function(){
|
||||
$("#projectName").val("");
|
||||
$("#projectName").parent().addClass("has-feedback");
|
||||
$("#projectName").siblings("span").removeClass("glyphicon-warning-sign").removeClass("glyphicon-ok");
|
||||
$("#isPublic").prop('checked', false);
|
||||
$("#isPublic").prop('checked', false);
|
||||
});
|
||||
|
||||
$("#btnSave").on("click", function(){
|
||||
@ -161,52 +161,52 @@ jQuery(function(){
|
||||
$("#tblUser tbody tr").remove();
|
||||
$.each(data || [], function(i, e){
|
||||
var row = '<tr>' +
|
||||
'<td style="vertical-align: middle;">' + e.username + '</td>' +
|
||||
'<td style="vertical-align: middle;">' + e.email + '</td>';
|
||||
if(e.HasAdminRole == 1){
|
||||
row += '<td style="padding-left: 30px;"><button type="button" class="btn btn-success" userid="' + e.UserId + '">' + i18n.getMessage("button_on") + '</button></td>';
|
||||
'<td style="vertical-align: middle;">' + e.username + '</td>' +
|
||||
'<td style="vertical-align: middle;">' + e.email + '</td>';
|
||||
if(e.has_admin_role == 1){
|
||||
row += '<td style="padding-left: 30px;"><button type="button" class="btn btn-success" userid="' + e.user_id + '">' + i18n.getMessage("button_on") + '</button></td>';
|
||||
} else {
|
||||
row += '<td style="padding-left: 30px;"><button type="button" class="btn btn-danger" userid="' + e.UserId + '">' + i18n.getMessage("button_off") + '</button></td>';
|
||||
row += '<td style="padding-left: 30px;"><button type="button" class="btn btn-danger" userid="' + e.user_id + '">' + i18n.getMessage("button_off") + '</button></td>';
|
||||
}
|
||||
row += '<td style="padding-left: 30px; vertical-align: middle;"><a href="#" style="visibility: hidden;" class="tdDeleteUser" userid="' + e.UserId + '" username="' + e.Username + '"><span class="glyphicon glyphicon-trash"></span></a></td>';
|
||||
row += '<td style="padding-left: 30px; vertical-align: middle;"><a href="#" style="visibility: hidden;" class="tdDeleteUser" userid="' + e.user_id + '" username="' + e.username + '"><span class="glyphicon glyphicon-trash"></span></a></td>';
|
||||
row += '</tr>';
|
||||
$("#tblUser tbody").append(row);
|
||||
});
|
||||
}
|
||||
}).exec()
|
||||
).done(function(){
|
||||
$("#tblUser tbody tr :button").on("click",function(){
|
||||
var userId = $(this).attr("userid");
|
||||
var self = this;
|
||||
new AjaxUtil({
|
||||
url: "/api/users/" + userId,
|
||||
type: "put",
|
||||
complete: function(jqXhr, status){
|
||||
if(jqXhr && jqXhr.status == 200){
|
||||
if($(self).hasClass("btn-success")){
|
||||
$(self).removeClass("btn-success").addClass("btn-danger");
|
||||
$(self).html(i18n.getMessage("button_off"));
|
||||
}else{
|
||||
$(self).removeClass("btn-danger").addClass("btn-success");
|
||||
$(self).html(i18n.getMessage("button_on"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}).exec();
|
||||
});
|
||||
$("#tblUser tbody tr").on("mouseover", function(){
|
||||
$(".tdDeleteUser", this).css({"visibility":"visible"});
|
||||
}).on("mouseout", function(){
|
||||
$(".tdDeleteUser", this).css({"visibility":"hidden"});
|
||||
});
|
||||
$("#tblUser tbody tr .tdDeleteUser").on("click", function(){
|
||||
var userId = $(this).attr("userid");
|
||||
$("#dlgModal")
|
||||
).done(function(){
|
||||
$("#tblUser tbody tr :button").on("click",function(){
|
||||
var userId = $(this).attr("userid");
|
||||
var self = this;
|
||||
new AjaxUtil({
|
||||
url: "/api/users/" + userId,
|
||||
type: "put",
|
||||
complete: function(jqXhr, status){
|
||||
if(jqXhr && jqXhr.status == 200){
|
||||
if($(self).hasClass("btn-success")){
|
||||
$(self).removeClass("btn-success").addClass("btn-danger");
|
||||
$(self).html(i18n.getMessage("button_off"));
|
||||
}else{
|
||||
$(self).removeClass("btn-danger").addClass("btn-success");
|
||||
$(self).html(i18n.getMessage("button_on"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}).exec();
|
||||
});
|
||||
$("#tblUser tbody tr").on("mouseover", function(e){
|
||||
$(".tdDeleteUser", this).css({"visibility":"visible"});
|
||||
}).on("mouseout", function(e){
|
||||
$(".tdDeleteUser", this).css({"visibility":"hidden"});
|
||||
});
|
||||
$("#tblUser tbody tr .tdDeleteUser").on("click", function(e){
|
||||
var userId = $(this).attr("userid");
|
||||
$("#dlgModal")
|
||||
.dialogModal({
|
||||
"title": i18n.getMessage("delete_user"),
|
||||
"content": i18n.getMessage("are_you_sure_to_delete_user") + $(this).attr("username") + " ?",
|
||||
"enableCancel": true,
|
||||
"callback": function(){
|
||||
"callback": function(){
|
||||
new AjaxUtil({
|
||||
url: "/api/users/" + userId,
|
||||
type: "delete",
|
||||
@ -218,17 +218,18 @@ jQuery(function(){
|
||||
error: function(jqXhr){}
|
||||
}).exec();
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
}
|
||||
listUserAdminRole(null);
|
||||
$("#btnSearchUsername").on("click", function(){
|
||||
var username = $("#txtSearchUsername").val();
|
||||
if($.trim(username).length == 0){
|
||||
username = null;
|
||||
}
|
||||
listUserAdminRole(username);
|
||||
});
|
||||
}
|
||||
listUserAdminRole(null);
|
||||
$("#btnSearchUsername").on("click", function(){
|
||||
var username = $("#txtSearchUsername").val();
|
||||
if($.trim(username).length == 0){
|
||||
username = null;
|
||||
}
|
||||
listUserAdminRole(username);
|
||||
});
|
||||
}
|
||||
})
|
||||
})
|
||||
|
@ -30,14 +30,14 @@ jQuery(function(){
|
||||
|
||||
$("#btnPageSignUp").on("click", function(){
|
||||
validateOptions.Validate(function() {
|
||||
var username = $.trim($("#Username").val());
|
||||
var email = $.trim($("#Email").val());
|
||||
var password = $.trim($("#Password").val());
|
||||
var confirmedPassword = $.trim($("#ConfirmedPassword").val());
|
||||
var realname = $.trim($("#Realname").val());
|
||||
var comment = $.trim($("#Comment").val());
|
||||
var isAdmin = $("#isAdmin").val();
|
||||
|
||||
var username = $.trim($("#Username").val());
|
||||
var email = $.trim($("#Email").val());
|
||||
var password = $.trim($("#Password").val());
|
||||
var confirmedPassword = $.trim($("#ConfirmedPassword").val());
|
||||
var realname = $.trim($("#Realname").val());
|
||||
var comment = $.trim($("#Comment").val());
|
||||
var isAdmin = $("#isAdmin").val();
|
||||
|
||||
new AjaxUtil({
|
||||
url : "/api/users",
|
||||
data: {"username": username, "password": password, "realname": realname, "comment": comment, "email": email},
|
||||
@ -47,29 +47,29 @@ jQuery(function(){
|
||||
},
|
||||
error:function(jqxhr, status, error){
|
||||
$("#dlgModal")
|
||||
.dialogModal({
|
||||
"title": i18n.getMessage("title_sign_up"),
|
||||
"content": i18n.getMessage("internal_error"),
|
||||
"callback": function(){
|
||||
return;
|
||||
}
|
||||
});
|
||||
.dialogModal({
|
||||
"title": i18n.getMessage("title_sign_up"),
|
||||
"content": i18n.getMessage("internal_error"),
|
||||
"callback": function(){
|
||||
return;
|
||||
}
|
||||
});
|
||||
},
|
||||
complete: function(xhr, status){
|
||||
$("#btnPageSignUp").prop("disabled", false);
|
||||
if(xhr && xhr.status == 201){
|
||||
$("#dlgModal")
|
||||
.dialogModal({
|
||||
"title": isAdmin == "true" ? i18n.getMessage("title_add_user") : i18n.getMessage("title_sign_up"),
|
||||
"content": isAdmin == "true" ? i18n.getMessage("added_user_successfully") : i18n.getMessage("registered_successfully"),
|
||||
"callback": function(){
|
||||
if(isAdmin == "true") {
|
||||
document.location = "/registry/project";
|
||||
}else{
|
||||
document.location = "/signIn";
|
||||
}
|
||||
.dialogModal({
|
||||
"title": isAdmin == "true" ? i18n.getMessage("title_add_user") : i18n.getMessage("title_sign_up"),
|
||||
"content": isAdmin == "true" ? i18n.getMessage("added_user_successfully") : i18n.getMessage("registered_successfully"),
|
||||
"callback": function(){
|
||||
if(isAdmin == "true") {
|
||||
document.location = "/registry/project";
|
||||
}else{
|
||||
document.location = "/signIn";
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}).exec();
|
||||
|
@ -17,11 +17,11 @@ jQuery(function(){
|
||||
|
||||
$("#Password,#ConfirmedPassword").on("blur", validateCallback);
|
||||
validateOptions.Items = ["#Password", "#ConfirmedPassword"];
|
||||
function bindEnterKey(){
|
||||
function bindEnterKey(){
|
||||
$(document).on("keydown", function(e){
|
||||
if(e.keyCode == 13){
|
||||
e.preventDefault();
|
||||
$("#btnSubmit").trigger("click");
|
||||
e.preventDefault();
|
||||
$("#btnSubmit").trigger("click");
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -30,7 +30,6 @@ jQuery(function(){
|
||||
}
|
||||
bindEnterKey();
|
||||
|
||||
|
||||
var spinner = new Spinner({scale:1}).spin();
|
||||
|
||||
$("#btnSubmit").on("click", function(){
|
||||
@ -42,20 +41,20 @@ jQuery(function(){
|
||||
"type": "post",
|
||||
"data": {"reset_uuid": resetUuid, "password": password},
|
||||
"beforeSend": function(e){
|
||||
unbindEnterKey();
|
||||
$("h1").append(spinner.el);
|
||||
$("#btnSubmit").prop("disabled", true);
|
||||
unbindEnterKey();
|
||||
$("h1").append(spinner.el);
|
||||
$("#btnSubmit").prop("disabled", true);
|
||||
},
|
||||
"success": function(data, status, xhr){
|
||||
if(xhr && xhr.status == 200){
|
||||
$("#dlgModal")
|
||||
.dialogModal({
|
||||
"title": i18n.getMessage("title_reset_password"),
|
||||
"content": i18n.getMessage("reset_password_successfully"),
|
||||
"callback": function(){
|
||||
document.location="/signIn";
|
||||
}
|
||||
});
|
||||
.dialogModal({
|
||||
"title": i18n.getMessage("title_reset_password"),
|
||||
"content": i18n.getMessage("reset_password_successfully"),
|
||||
"callback": function(){
|
||||
document.location="/signIn";
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
},
|
||||
@ -66,14 +65,14 @@ jQuery(function(){
|
||||
"error": function(jqXhr, status, error){
|
||||
if(jqXhr){
|
||||
$("#dlgModal")
|
||||
.dialogModal({
|
||||
"title": i18n.getMessage("title_reset_password"),
|
||||
"content": i18n.getMessage(jqXhr.responseText),
|
||||
"callback": function(){
|
||||
bindEnterKey();
|
||||
return;
|
||||
}
|
||||
});
|
||||
.dialogModal({
|
||||
"title": i18n.getMessage("title_reset_password"),
|
||||
"content": i18n.getMessage(jqXhr.responseText),
|
||||
"callback": function(){
|
||||
bindEnterKey();
|
||||
return;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -60,12 +60,12 @@ jQuery(function(){
|
||||
$.each(data, function(i, e){
|
||||
var project, description, repoName;
|
||||
switch(discriminator){
|
||||
case "project":
|
||||
case "project":
|
||||
project = new Project(e.id, e.name, e.public);
|
||||
description = project.name;
|
||||
repoName = "";
|
||||
break;
|
||||
case "repository":
|
||||
case "repository":
|
||||
project = new Project(e.project_id, e.project_name, e.project_public);
|
||||
description = e.repository_name;
|
||||
repoName = e.repository_name.substring(e.repository_name.lastIndexOf("/") + 1);
|
||||
|
@ -56,7 +56,7 @@ jQuery(function(){
|
||||
success: function(jqXhr, status){
|
||||
var lastUri = location.search;
|
||||
if(lastUri != "" && lastUri.indexOf("=") > 0){
|
||||
document.location = decodeURIComponent(lastUri.split("=")[1]);
|
||||
document.location = decodeURIComponent(lastUri.split("=")[1]);
|
||||
}else{
|
||||
document.location = "/registry/project";
|
||||
}
|
||||
@ -69,10 +69,10 @@ jQuery(function(){
|
||||
i18nKey = "check_your_username_or_password"
|
||||
}
|
||||
$("#dlgModal")
|
||||
.dialogModal({
|
||||
"title": i18n.getMessage("title_login_failed"),
|
||||
"content": i18n.getMessage(i18nKey)
|
||||
});
|
||||
.dialogModal({
|
||||
"title": i18n.getMessage("title_login_failed"),
|
||||
"content": i18n.getMessage(i18nKey)
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
@ -26,18 +26,18 @@ var validateOptions = {
|
||||
"Username" :{
|
||||
"Required": { "value" : true, "errMsg" : i18n.getMessage("username_is_required")},
|
||||
"CheckExist": { "value" : function(value){
|
||||
var result = true;
|
||||
$.ajax({
|
||||
url: "/userExists",
|
||||
data: {"target": "username", "value" : value},
|
||||
var result = true;
|
||||
$.ajax({
|
||||
url: "/userExists",
|
||||
data: {"target": "username", "value" : value},
|
||||
dataType: "json",
|
||||
type: "post",
|
||||
async: false,
|
||||
success: function(data){
|
||||
result = data;
|
||||
}
|
||||
});
|
||||
return result;
|
||||
type: "post",
|
||||
async: false,
|
||||
success: function(data){
|
||||
result = data;
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}, "errMsg" : i18n.getMessage("username_has_been_taken")},
|
||||
"MaxLength": {"value" : 20, "errMsg" : i18n.getMessage("username_is_too_long")},
|
||||
"IllegalChar": {"value": [",","~","#", "$", "%"] , "errMsg": i18n.getMessage("username_contains_illegal_chars")}
|
||||
@ -45,40 +45,40 @@ var validateOptions = {
|
||||
"Email" :{
|
||||
"Required": { "value" : true, "errMsg" : i18n.getMessage("email_is_required")},
|
||||
"RegExp": {"value": /^(([^<>()[\]\\.,;:\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,}))$/,
|
||||
"errMsg": i18n.getMessage("email_contains_illegal_chars")},
|
||||
"errMsg": i18n.getMessage("email_contains_illegal_chars")},
|
||||
"CheckExist": { "value" : function(value){
|
||||
var result = true;
|
||||
$.ajax({
|
||||
url: "/userExists",
|
||||
data: {"target": "email", "value": value},
|
||||
dataType: "json",
|
||||
type: "post",
|
||||
async: false,
|
||||
success: function(data){
|
||||
result = data;
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}, "errMsg" : i18n.getMessage("email_has_been_taken")}
|
||||
var result = true;
|
||||
$.ajax({
|
||||
url: "/userExists",
|
||||
data: {"target": "email", "value": value},
|
||||
dataType: "json",
|
||||
type: "post",
|
||||
async: false,
|
||||
success: function(data){
|
||||
result = data;
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}, "errMsg" : i18n.getMessage("email_has_been_taken")}
|
||||
},
|
||||
"EmailF" :{
|
||||
"Required": { "value" : true, "errMsg" : i18n.getMessage("email_is_required")},
|
||||
"RegExp": {"value": /^(([^<>()[\]\\.,;:\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,}))$/,
|
||||
"errMsg": i18n.getMessage("email_content_illegal")},
|
||||
"errMsg": i18n.getMessage("email_content_illegal")},
|
||||
"CheckIfNotExist": { "value" : function(value){
|
||||
var result = true;
|
||||
$.ajax({
|
||||
url: "/userExists",
|
||||
data: {"target": "email", "value": value},
|
||||
dataType: "json",
|
||||
type: "post",
|
||||
async: false,
|
||||
success: function(data){
|
||||
result = data;
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}, "errMsg" : i18n.getMessage("email_does_not_exist")}
|
||||
var result = true;
|
||||
$.ajax({
|
||||
url: "/userExists",
|
||||
data: {"target": "email", "value": value},
|
||||
dataType: "json",
|
||||
type: "post",
|
||||
async: false,
|
||||
success: function(data){
|
||||
result = data;
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}, "errMsg" : i18n.getMessage("email_does_not_exist")}
|
||||
},
|
||||
"Realname" :{
|
||||
"Required": { "value" : true, "errMsg" : i18n.getMessage("realname_is_required")},
|
||||
@ -119,7 +119,7 @@ function validateCallback(target){
|
||||
var currentId = $(target).attr("id");
|
||||
var validateItem = validateOptions[currentId];
|
||||
|
||||
var errMsg = "";
|
||||
var errMsg = "";
|
||||
|
||||
for(var checkTitle in validateItem){
|
||||
|
||||
|
23
tests/apitests/apilib/accesslog.go
Normal file
23
tests/apitests/apilib/accesslog.go
Normal file
@ -0,0 +1,23 @@
|
||||
/*
|
||||
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 HarborAPI
|
||||
|
||||
type AccessLog struct {
|
||||
Username string `json:"username,omitempty"`
|
||||
Keywords string `json:"keywords,omitempty"`
|
||||
BeginTimestamp int32 `json:"beginTimestamp,omitempty"`
|
||||
EndTimestamp int32 `json:"endTimestamp,omitempty"`
|
||||
}
|
224
tests/apitests/apilib/harborapi.go
Normal file
224
tests/apitests/apilib/harborapi.go
Normal file
@ -0,0 +1,224 @@
|
||||
//Package HarborAPI
|
||||
//These APIs provide services for manipulating Harbor project.
|
||||
package HarborAPI
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
//"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
|
||||
"github.com/dghubble/sling"
|
||||
)
|
||||
|
||||
type HarborAPI struct {
|
||||
basePath string
|
||||
}
|
||||
|
||||
func NewHarborAPI() *HarborAPI {
|
||||
return &HarborAPI{
|
||||
basePath: "http://localhost",
|
||||
}
|
||||
}
|
||||
|
||||
func NewHarborAPIWithBasePath(basePath string) *HarborAPI {
|
||||
return &HarborAPI{
|
||||
basePath: basePath,
|
||||
}
|
||||
}
|
||||
|
||||
type UsrInfo struct {
|
||||
Name string
|
||||
Passwd string
|
||||
}
|
||||
|
||||
//Search for projects and repositories
|
||||
//Implementation Notes
|
||||
//The Search endpoint returns information about the projects and repositories
|
||||
//offered at public status or related to the current logged in user.
|
||||
//The response includes the project and repository list in a proper display order.
|
||||
//@param q Search parameter for project and repository name.
|
||||
//@return []Search
|
||||
//func (a HarborAPI) SearchGet (q string) (Search, error) {
|
||||
func (a HarborAPI) SearchGet(q string) (Search, error) {
|
||||
|
||||
_sling := sling.New().Get(a.basePath)
|
||||
|
||||
// create path and map variables
|
||||
path := "/api/search"
|
||||
|
||||
_sling = _sling.Path(path)
|
||||
|
||||
type QueryParams struct {
|
||||
Query string `url:"q,omitempty"`
|
||||
}
|
||||
|
||||
_sling = _sling.QueryStruct(&QueryParams{Query: q})
|
||||
|
||||
// accept header
|
||||
accepts := []string{"application/json", "text/plain"}
|
||||
for key := range accepts {
|
||||
_sling = _sling.Set("Accept", accepts[key])
|
||||
break // only use the first Accept
|
||||
}
|
||||
|
||||
req, err := _sling.Request()
|
||||
|
||||
client := &http.Client{}
|
||||
httpResponse, err := client.Do(req)
|
||||
defer httpResponse.Body.Close()
|
||||
|
||||
body, err := ioutil.ReadAll(httpResponse.Body)
|
||||
if err != nil {
|
||||
// handle error
|
||||
}
|
||||
|
||||
var successPayload = new(Search)
|
||||
err = json.Unmarshal(body, &successPayload)
|
||||
return *successPayload, err
|
||||
}
|
||||
|
||||
//Create a new project.
|
||||
//Implementation Notes
|
||||
//This endpoint is for user to create a new project.
|
||||
//@param project New created project.
|
||||
//@return void
|
||||
//func (a HarborAPI) ProjectsPost (prjUsr UsrInfo, project Project) (int, error) {
|
||||
func (a HarborAPI) ProjectsPost(prjUsr UsrInfo, project Project) (int, error) {
|
||||
|
||||
_sling := sling.New().Post(a.basePath)
|
||||
|
||||
// create path and map variables
|
||||
path := "/api/projects"
|
||||
|
||||
_sling = _sling.Path(path)
|
||||
|
||||
// accept header
|
||||
accepts := []string{"application/json", "text/plain"}
|
||||
for key := range accepts {
|
||||
_sling = _sling.Set("Accept", accepts[key])
|
||||
break // only use the first Accept
|
||||
}
|
||||
|
||||
// body params
|
||||
_sling = _sling.BodyJSON(project)
|
||||
|
||||
req, err := _sling.Request()
|
||||
req.SetBasicAuth(prjUsr.Name, prjUsr.Passwd)
|
||||
|
||||
client := &http.Client{}
|
||||
httpResponse, err := client.Do(req)
|
||||
defer httpResponse.Body.Close()
|
||||
|
||||
return httpResponse.StatusCode, err
|
||||
}
|
||||
|
||||
//Delete a repository or a tag in a repository.
|
||||
//Delete a repository or a tag in a repository.
|
||||
//This endpoint let user delete repositories and tags with repo name and tag.\n
|
||||
//@param repoName The name of repository which will be deleted.
|
||||
//@param tag Tag of a repository.
|
||||
//@return void
|
||||
//func (a HarborAPI) RepositoriesDelete(prjUsr UsrInfo, repoName string, tag string) (int, error) {
|
||||
func (a HarborAPI) RepositoriesDelete(prjUsr UsrInfo, repoName string, tag string) (int, error) {
|
||||
_sling := sling.New().Delete(a.basePath)
|
||||
|
||||
// create path and map variables
|
||||
path := "/api/repositories"
|
||||
|
||||
_sling = _sling.Path(path)
|
||||
|
||||
type QueryParams struct {
|
||||
RepoName string `url:"repo_name,omitempty"`
|
||||
Tag string `url:"tag,omitempty"`
|
||||
}
|
||||
|
||||
_sling = _sling.QueryStruct(&QueryParams{RepoName: repoName, Tag: tag})
|
||||
// accept header
|
||||
accepts := []string{"application/json", "text/plain"}
|
||||
for key := range accepts {
|
||||
_sling = _sling.Set("Accept", accepts[key])
|
||||
break // only use the first Accept
|
||||
}
|
||||
|
||||
req, err := _sling.Request()
|
||||
req.SetBasicAuth(prjUsr.Name, prjUsr.Passwd)
|
||||
//fmt.Printf("request %+v", req)
|
||||
|
||||
client := &http.Client{}
|
||||
httpResponse, err := client.Do(req)
|
||||
defer httpResponse.Body.Close()
|
||||
|
||||
if err != nil {
|
||||
// handle error
|
||||
}
|
||||
return httpResponse.StatusCode, err
|
||||
}
|
||||
|
||||
//Return projects created by Harbor
|
||||
//func (a HarborApi) ProjectsGet (projectName string, isPublic int32) ([]Project, error) {
|
||||
// }
|
||||
|
||||
//Check if the project name user provided already exists.
|
||||
//func (a HarborApi) ProjectsHead (projectName string) (error) {
|
||||
//}
|
||||
|
||||
//Get access logs accompany with a relevant project.
|
||||
//func (a HarborApi) ProjectsProjectIdLogsFilterPost (projectId int32, accessLog AccessLog) ([]AccessLog, error) {
|
||||
//}
|
||||
|
||||
//Return a project's relevant role members.
|
||||
//func (a HarborApi) ProjectsProjectIdMembersGet (projectId int32) ([]Role, error) {
|
||||
//}
|
||||
|
||||
//Add project role member accompany with relevant project and user.
|
||||
//func (a HarborApi) ProjectsProjectIdMembersPost (projectId int32, roles RoleParam) (error) {
|
||||
//}
|
||||
|
||||
//Delete project role members accompany with relevant project and user.
|
||||
//func (a HarborApi) ProjectsProjectIdMembersUserIdDelete (projectId int32, userId int32) (error) {
|
||||
//}
|
||||
|
||||
//Return role members accompany with relevant project and user.
|
||||
//func (a HarborApi) ProjectsProjectIdMembersUserIdGet (projectId int32, userId int32) ([]Role, error) {
|
||||
//}
|
||||
|
||||
//Update project role members accompany with relevant project and user.
|
||||
//func (a HarborApi) ProjectsProjectIdMembersUserIdPut (projectId int32, userId int32, roles RoleParam) (error) {
|
||||
//}
|
||||
|
||||
//Update properties for a selected project.
|
||||
//func (a HarborApi) ProjectsProjectIdPut (projectId int32, project Project) (error) {
|
||||
//}
|
||||
|
||||
//Get repositories accompany with relevant project and repo name.
|
||||
//func (a HarborApi) RepositoriesGet (projectId int32, q string) ([]Repository, error) {
|
||||
//}
|
||||
|
||||
//Get manifests of a relevant repository.
|
||||
//func (a HarborApi) RepositoriesManifestGet (repoName string, tag string) (error) {
|
||||
//}
|
||||
|
||||
//Get tags of a relevant repository.
|
||||
//func (a HarborApi) RepositoriesTagsGet (repoName string) (error) {
|
||||
//}
|
||||
|
||||
//Get registered users of Harbor.
|
||||
//func (a HarborApi) UsersGet (userName string) ([]User, error) {
|
||||
//}
|
||||
|
||||
//Creates a new user account.
|
||||
//func (a HarborApi) UsersPost (user User) (error) {
|
||||
//}
|
||||
|
||||
//Mark a registered user as be removed.
|
||||
//func (a HarborApi) UsersUserIdDelete (userId int32) (error) {
|
||||
//}
|
||||
|
||||
//Change the password on a user that already exists.
|
||||
//func (a HarborApi) UsersUserIdPasswordPut (userId int32, password Password) (error) {
|
||||
//}
|
||||
|
||||
//Update a registered user to change to be an administrator of Harbor.
|
||||
//func (a HarborApi) UsersUserIdPut (userId int32) (error) {
|
||||
//}
|
15
tests/apitests/apilib/harborlogout.go
Normal file
15
tests/apitests/apilib/harborlogout.go
Normal file
@ -0,0 +1,15 @@
|
||||
// HarborLogout.go
|
||||
package HarborAPI
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func (a HarborAPI) HarborLogout() (int, error) {
|
||||
|
||||
response, err := http.Get(a.basePath + "/logout")
|
||||
|
||||
defer response.Body.Close()
|
||||
|
||||
return response.StatusCode, err
|
||||
}
|
28
tests/apitests/apilib/harlogin.go
Normal file
28
tests/apitests/apilib/harlogin.go
Normal file
@ -0,0 +1,28 @@
|
||||
// HarborLogon.go
|
||||
package HarborAPI
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func (a HarborAPI) HarborLogin(user UsrInfo) (int, error) {
|
||||
|
||||
v := url.Values{}
|
||||
v.Set("principal", user.Name)
|
||||
v.Set("password", user.Passwd)
|
||||
|
||||
body := ioutil.NopCloser(strings.NewReader(v.Encode())) //endode v:[body struce]
|
||||
|
||||
client := &http.Client{}
|
||||
reqest, err := http.NewRequest("POST", a.basePath+"/login", body)
|
||||
|
||||
reqest.Header.Set("Content-Type", "application/x-www-form-urlencoded;param=value") //setting post head
|
||||
|
||||
resp, err := client.Do(reqest)
|
||||
defer resp.Body.Close() //close resp.Body
|
||||
|
||||
return resp.StatusCode, err
|
||||
}
|
15
tests/apitests/apilib/project.go
Normal file
15
tests/apitests/apilib/project.go
Normal file
@ -0,0 +1,15 @@
|
||||
package HarborAPI
|
||||
|
||||
import ()
|
||||
|
||||
type Project struct {
|
||||
ProjectId int32 `json:"id,omitempty"`
|
||||
OwnerId int32 `json:"owner_id,omitempty"`
|
||||
ProjectName string `json:"project_name,omitempty"`
|
||||
CreationTime string `json:"creation_time,omitempty"`
|
||||
Deleted int32 `json:"deleted,omitempty"`
|
||||
UserId int32 `json:"user_id,omitempty"`
|
||||
OwnerName string `json:"owner_name,omitempty"`
|
||||
Public bool `json:"public,omitempty"`
|
||||
Togglable bool `json:"togglable,omitempty"`
|
||||
}
|
9
tests/apitests/apilib/projecttemp4search.go
Normal file
9
tests/apitests/apilib/projecttemp4search.go
Normal file
@ -0,0 +1,9 @@
|
||||
package HarborAPI
|
||||
|
||||
import ()
|
||||
|
||||
type Project4Search struct {
|
||||
ProjectId int32 `json:"id,omitempty"`
|
||||
ProjectName string `json:"name,omitempty"`
|
||||
Public int32 `json:"public,omitempty"`
|
||||
}
|
16
tests/apitests/apilib/repository.go
Normal file
16
tests/apitests/apilib/repository.go
Normal file
@ -0,0 +1,16 @@
|
||||
package HarborAPI
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
type Repository struct {
|
||||
Id string `json:"id,omitempty"`
|
||||
Parent string `json:"parent,omitempty"`
|
||||
Created time.Time `json:"created,omitempty"`
|
||||
DurationDays string `json:"duration_days,omitempty"`
|
||||
Author string `json:"author,omitempty"`
|
||||
Architecture string `json:"architecture,omitempty"`
|
||||
DockerVersion string `json:"docker_version,omitempty"`
|
||||
Os string `json:"os,omitempty"`
|
||||
}
|
9
tests/apitests/apilib/repositorytemp4search.go
Normal file
9
tests/apitests/apilib/repositorytemp4search.go
Normal file
@ -0,0 +1,9 @@
|
||||
package HarborAPI
|
||||
|
||||
type Repository4Search struct {
|
||||
ProjectId int32 `json:"project_id,omitempty"`
|
||||
ProjectName string `json:"project_name,omitempty"`
|
||||
ProjectPublic int32 `json:"project_public,omitempty"`
|
||||
RepoName string `json:"repository_name,omitempty"`
|
||||
}
|
||||
|
7
tests/apitests/apilib/role.go
Normal file
7
tests/apitests/apilib/role.go
Normal file
@ -0,0 +1,7 @@
|
||||
package HarborAPI
|
||||
|
||||
type Role struct {
|
||||
RoleId int32 `json:"role_id,omitempty"`
|
||||
RoleCode string `json:"role_code,omitempty"`
|
||||
RoleName string `json:"role_name,omitempty"`
|
||||
}
|
6
tests/apitests/apilib/roleparam.go
Normal file
6
tests/apitests/apilib/roleparam.go
Normal file
@ -0,0 +1,6 @@
|
||||
package HarborAPI
|
||||
|
||||
type RoleParam struct {
|
||||
Roles []int32 `json:"roles,omitempty"`
|
||||
UserName string `json:"user_name,omitempty"`
|
||||
}
|
8
tests/apitests/apilib/search.go
Normal file
8
tests/apitests/apilib/search.go
Normal file
@ -0,0 +1,8 @@
|
||||
package HarborAPI
|
||||
|
||||
import ()
|
||||
|
||||
type Search struct {
|
||||
Projects []Project4Search `json:"project,omitempty"`
|
||||
Repositories []Repository4Search `json:"repository,omitempty"`
|
||||
}
|
11
tests/apitests/apilib/user.go
Normal file
11
tests/apitests/apilib/user.go
Normal file
@ -0,0 +1,11 @@
|
||||
package HarborAPI
|
||||
|
||||
type User struct {
|
||||
UserId int32 `json:"user_id,omitempty"`
|
||||
Username string `json:"username,omitempty"`
|
||||
Email string `json:"email,omitempty"`
|
||||
Password string `json:"password,omitempty"`
|
||||
Realname string `json:"realname,omitempty"`
|
||||
Comment string `json:"comment,omitempty"`
|
||||
Deleted int32 `json:"deleted,omitempty"`
|
||||
}
|
95
tests/apitests/hbapiaddprj_test.go
Normal file
95
tests/apitests/hbapiaddprj_test.go
Normal file
@ -0,0 +1,95 @@
|
||||
package HarborAPItest
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"testing"
|
||||
"github.com/vmware/harbor/tests/apitests/apilib"
|
||||
)
|
||||
|
||||
func TestAddProject(t *testing.T) {
|
||||
|
||||
fmt.Println("Test for Project Add (ProjectsPost) API\n")
|
||||
assert := assert.New(t)
|
||||
|
||||
apiTest := HarborAPI.NewHarborAPI()
|
||||
|
||||
//prepare for test
|
||||
adminEr := &HarborAPI.UsrInfo{"admin", "Harbor1234"}
|
||||
admin := &HarborAPI.UsrInfo{"admin", "Harbor12345"}
|
||||
|
||||
prjUsr := &HarborAPI.UsrInfo{"unknown", "unknown"}
|
||||
|
||||
var project HarborAPI.Project
|
||||
project.ProjectName = "testproject"
|
||||
project.Public = true
|
||||
|
||||
//case 1: admin login fail, expect project creation fail.
|
||||
fmt.Println("case 1: admin login fail, expect project creation fail.")
|
||||
resault, err := apiTest.HarborLogin(*adminEr)
|
||||
if err != nil {
|
||||
t.Error("Error while admin login", err.Error())
|
||||
t.Log(err)
|
||||
} else {
|
||||
assert.Equal(resault, int(401), "Admin login status should be 401")
|
||||
//t.Log(resault)
|
||||
}
|
||||
|
||||
resault, err = apiTest.ProjectsPost(*prjUsr, project)
|
||||
if err != nil {
|
||||
t.Error("Error while creat project", err.Error())
|
||||
t.Log(err)
|
||||
} else {
|
||||
assert.Equal(resault, int(401), "Case 1: Project creation status should be 401")
|
||||
//t.Log(resault)
|
||||
}
|
||||
|
||||
//case 2: admin successful login, expect project creation success.
|
||||
fmt.Println("case 2: admin successful login, expect project creation success.")
|
||||
resault, err = apiTest.HarborLogin(*admin)
|
||||
if err != nil {
|
||||
t.Error("Error while admin login", err.Error())
|
||||
t.Log(err)
|
||||
} else {
|
||||
assert.Equal(resault, int(200), "Admin login status should be 200")
|
||||
//t.Log(resault)
|
||||
}
|
||||
if resault != 200 {
|
||||
t.Log(resault)
|
||||
} else {
|
||||
prjUsr = admin
|
||||
}
|
||||
|
||||
resault, err = apiTest.ProjectsPost(*prjUsr, project)
|
||||
if err != nil {
|
||||
t.Error("Error while creat project", err.Error())
|
||||
t.Log(err)
|
||||
} else {
|
||||
assert.Equal(resault, int(201), "Case 2: Project creation status should be 201")
|
||||
//t.Log(resault)
|
||||
}
|
||||
|
||||
//case 3: duplicate project name, create project fail
|
||||
fmt.Println("case 3: duplicate project name, create project fail")
|
||||
resault, err = apiTest.ProjectsPost(*prjUsr, project)
|
||||
if err != nil {
|
||||
t.Error("Error while creat project", err.Error())
|
||||
t.Log(err)
|
||||
} else {
|
||||
assert.Equal(resault, int(409), "Case 3: Project creation status should be 409")
|
||||
//t.Log(resault)
|
||||
}
|
||||
|
||||
//resault1, err := apiTest.HarborLogout()
|
||||
//if err != nil {
|
||||
// t.Error("Error while admin logout", err.Error())
|
||||
// t.Log(err)
|
||||
//} else {
|
||||
// assert.Equal(resault1, int(200), "Admin logout status")
|
||||
// //t.Log(resault)
|
||||
//}
|
||||
//if resault1 != 200 {
|
||||
// t.Log(resault)
|
||||
//}
|
||||
|
||||
}
|
130
tests/apitests/hbapidelrpo_test.go
Normal file
130
tests/apitests/hbapidelrpo_test.go
Normal file
@ -0,0 +1,130 @@
|
||||
package HarborAPItest
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/vmware/harbor/tests/apitests/apilib"
|
||||
)
|
||||
|
||||
func TestRepositoryDelete(t *testing.T) {
|
||||
fmt.Println("Test for Project Delete (ProjectDelete) API\n")
|
||||
assert := assert.New(t)
|
||||
|
||||
//prepare for test
|
||||
adminEr := &HarborAPI.UsrInfo{"admin", "Harbor1234"}
|
||||
admin := &HarborAPI.UsrInfo{"admin", "Harbor12345"}
|
||||
prjUsr := &HarborAPI.UsrInfo{"unknown", "unknown"}
|
||||
|
||||
fmt.Println("Checking repository status...\n")
|
||||
apiTest := HarborAPI.NewHarborAPI()
|
||||
var searchResault HarborAPI.Search
|
||||
searchResault, err := apiTest.SearchGet("library")
|
||||
//fmt.Printf("%+v\n", resault)
|
||||
|
||||
if err != nil {
|
||||
t.Error("Error while search project or repository", err.Error())
|
||||
t.Log(err)
|
||||
} else {
|
||||
//assert.Equal(searchResault.Repositories[0].RepoName, "library/docker", "1st repo name should be")
|
||||
if !assert.Equal(searchResault.Repositories[0].RepoName, "library/docker", "1st repo name should be") {
|
||||
t.Error("fail to find repo 'library/docker'", err.Error())
|
||||
t.Log(err)
|
||||
} else {
|
||||
fmt.Println("repo 'library/docker' exit\n")
|
||||
}
|
||||
//assert.Equal(searchResault.Repositories[1].RepoName, "library/hello-world", "2nd repo name should be")
|
||||
if !assert.Equal(searchResault.Repositories[1].RepoName, "library/hello-world", "2nd repo name should be") {
|
||||
t.Error("fail to find repo 'library/hello-world'", err.Error())
|
||||
t.Log(err)
|
||||
} else {
|
||||
fmt.Println("repo 'library/hello-world' exit\n")
|
||||
}
|
||||
|
||||
//t.Log(resault)
|
||||
}
|
||||
|
||||
//case 1: admin login fail, expect repo delete fail.
|
||||
fmt.Println("case 1: admin login fail, expect repo delete fail.")
|
||||
|
||||
resault, err := apiTest.HarborLogin(*adminEr)
|
||||
if err != nil {
|
||||
t.Error("Error while admin login", err.Error())
|
||||
t.Log(err)
|
||||
} else {
|
||||
assert.Equal(resault, int(401), "Admin login status should be 401")
|
||||
//t.Log(resault)
|
||||
}
|
||||
if resault != 401 {
|
||||
t.Log(resault)
|
||||
} else {
|
||||
prjUsr = adminEr
|
||||
}
|
||||
|
||||
resault, err = apiTest.RepositoriesDelete(*prjUsr, "library/docker", "")
|
||||
if err != nil {
|
||||
t.Error("Error while delete repository", err.Error())
|
||||
t.Log(err)
|
||||
} else {
|
||||
assert.Equal(resault, int(401), "Case 1: Repository delete status should be 401")
|
||||
//t.Log(resault)
|
||||
}
|
||||
|
||||
//case 2: admin successful login, expect repository delete success.
|
||||
fmt.Println("case 2: admin successful login, expect repository delete success.")
|
||||
resault, err = apiTest.HarborLogin(*admin)
|
||||
if err != nil {
|
||||
t.Error("Error while admin login", err.Error())
|
||||
t.Log(err)
|
||||
} else {
|
||||
assert.Equal(resault, int(200), "Admin login status should be 200")
|
||||
//t.Log(resault)
|
||||
}
|
||||
if resault != 200 {
|
||||
t.Log(resault)
|
||||
} else {
|
||||
prjUsr = admin
|
||||
}
|
||||
|
||||
resault, err = apiTest.RepositoriesDelete(*prjUsr, "library/docker", "")
|
||||
if err != nil {
|
||||
t.Error("Error while delete repository", err.Error())
|
||||
t.Log(err)
|
||||
} else {
|
||||
if assert.Equal(resault, int(200), "Case 2: Repository delete status should be 200") {
|
||||
fmt.Println("Repository 'library/docker' delete success.")
|
||||
}
|
||||
//t.Log(resault)
|
||||
}
|
||||
|
||||
resault, err = apiTest.RepositoriesDelete(*prjUsr, "library/hello-world", "")
|
||||
if err != nil {
|
||||
t.Error("Error while delete repository", err.Error())
|
||||
t.Log(err)
|
||||
} else {
|
||||
if assert.Equal(resault, int(200), "Case 2: Repository delete status should be 200") {
|
||||
fmt.Println("Repository 'hello-world' delete success.")
|
||||
}
|
||||
//t.Log(resault)
|
||||
}
|
||||
|
||||
//case 3: delete one repo not exit, expect repo delete fail.
|
||||
fmt.Println("case 3: delete one repo not exit, expect repo delete fail.")
|
||||
|
||||
resault, err = apiTest.RepositoriesDelete(*prjUsr, "library/hello-world", "")
|
||||
if err != nil {
|
||||
t.Error("Error while delete repository", err.Error())
|
||||
t.Log(err)
|
||||
} else {
|
||||
if assert.Equal(resault, int(404), "Case 3: Repository delete status should be 404") {
|
||||
fmt.Println("Repository 'hello-world' not exit.")
|
||||
}
|
||||
//t.Log(resault)
|
||||
}
|
||||
|
||||
//if resault.Response.StatusCode != 200 {
|
||||
// t.Log(resault.Response)
|
||||
//}
|
||||
|
||||
}
|
31
tests/apitests/hbapisearch_test.go
Normal file
31
tests/apitests/hbapisearch_test.go
Normal file
@ -0,0 +1,31 @@
|
||||
package HarborAPItest
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"testing"
|
||||
"github.com/vmware/harbor/tests/apitests/apilib"
|
||||
)
|
||||
|
||||
func TestSearch(t *testing.T) {
|
||||
fmt.Println("Test for Search (SearchGet) API\n")
|
||||
assert := assert.New(t)
|
||||
|
||||
apiTest := HarborAPI.NewHarborAPI()
|
||||
var resault HarborAPI.Search
|
||||
resault, err := apiTest.SearchGet("library")
|
||||
//fmt.Printf("%+v\n", resault)
|
||||
if err != nil {
|
||||
t.Error("Error while search project or repository", err.Error())
|
||||
t.Log(err)
|
||||
} else {
|
||||
assert.Equal(resault.Projects[0].ProjectId, int32(1), "Project id should be equal")
|
||||
assert.Equal(resault.Projects[0].ProjectName, "library", "Project name should be library")
|
||||
assert.Equal(resault.Projects[0].Public, int32(1), "Project public status should be 1 (true)")
|
||||
//t.Log(resault)
|
||||
}
|
||||
//if resault.Response.StatusCode != 200 {
|
||||
// t.Log(resault.Response)
|
||||
//}
|
||||
|
||||
}
|
4
tests/hostcfg.sh
Executable file
4
tests/hostcfg.sh
Executable file
@ -0,0 +1,4 @@
|
||||
#!/bin/bash
|
||||
IP=`ip addr s eth0 |grep "inet "|awk '{print $2}' |awk -F "/" '{print $1}'`
|
||||
#echo $IP
|
||||
sudo sed "s/reg.mydomain.org/$IP/" -i Deploy/harbor.cfg
|
36
tests/startuptest.go
Normal file
36
tests/startuptest.go
Normal file
@ -0,0 +1,36 @@
|
||||
// Fetch prints the content found at a URL.
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func main() {
|
||||
time.Sleep(60*time.Second)
|
||||
for _, url := range os.Args[1:] {
|
||||
resp, err := http.Get(url)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "fetch: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
b, err := ioutil.ReadAll(resp.Body)
|
||||
resp.Body.Close()
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "fetch: reading %s: %v\n", url, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
// fmt.Printf("%s", b)
|
||||
if strings.Contains(string(b), "Harbor") {
|
||||
fmt.Printf("sucess!\n")
|
||||
} else {
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
10
tests/testprepare.sh
Executable file
10
tests/testprepare.sh
Executable file
@ -0,0 +1,10 @@
|
||||
docker pull hello-world
|
||||
docker pull docker
|
||||
docker login -u admin -p Harbor12345 127.0.0.1
|
||||
|
||||
docker tag hello-world 127.0.0.1/library/hello-world
|
||||
docker push 127.0.0.1/library/hello-world
|
||||
|
||||
docker tag docker 127.0.0.1/library/docker
|
||||
docker push 127.0.0.1/library/docker
|
||||
|
55
tests/userlogintest.go
Normal file
55
tests/userlogintest.go
Normal file
@ -0,0 +1,55 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"flag"
|
||||
)
|
||||
|
||||
func main() {
|
||||
usrNamePtr := flag.String("name","anaymous","user name")
|
||||
usrPasswdPtr := flag.String("passwd","anaymous","user password")
|
||||
flag.Parse()
|
||||
|
||||
v := url.Values{}
|
||||
v.Set("principal", *usrNamePtr)
|
||||
v.Set("password", *usrPasswdPtr)
|
||||
|
||||
body := ioutil.NopCloser(strings.NewReader(v.Encode())) //endode v:[body struce]
|
||||
fmt.Println(v)
|
||||
|
||||
client := &http.Client{}
|
||||
reqest, err := http.NewRequest("POST", "http://localhost/login", body)
|
||||
if err != nil {
|
||||
fmt.Println("Fatal error ", err.Error())
|
||||
}
|
||||
|
||||
reqest.Header.Set("Content-Type", "application/x-www-form-urlencoded;param=value") //setting post head
|
||||
|
||||
resp, err := client.Do(reqest)
|
||||
defer resp.Body.Close() //close resp.Body
|
||||
|
||||
fmt.Println("login status: ", resp.StatusCode) //print status code
|
||||
|
||||
//content_post, err := ioutil.ReadAll(resp.Body)
|
||||
//if err != nil {
|
||||
// fmt.Println("Fatal error ", err.Error())
|
||||
//}
|
||||
|
||||
//fmt.Println(string(content_post)) //print reply
|
||||
|
||||
response, err := http.Get("http://localhost/api/logout")
|
||||
if err != nil {
|
||||
fmt.Println("Fatal error ", err.Error())
|
||||
}
|
||||
|
||||
defer response.Body.Close()
|
||||
|
||||
fmt.Println("logout status: ", resp.StatusCode) //print status code
|
||||
//content_get, err := ioutil.ReadAll(response.Body)
|
||||
//fmt.Println(string(content_get))
|
||||
|
||||
}
|
12
ui/router.go
12
ui/router.go
@ -55,6 +55,7 @@ func initRouters() {
|
||||
beego.Router("/api/projects/:pid([0-9]+)/members/?:mid", &api.ProjectMemberAPI{})
|
||||
beego.Router("/api/projects/", &api.ProjectAPI{}, "get:List")
|
||||
beego.Router("/api/projects/?:id", &api.ProjectAPI{})
|
||||
beego.Router("/api/projects/:id/publicity", &api.ProjectAPI{}, "put:ToggleProjectPublic")
|
||||
beego.Router("/api/statistics", &api.StatisticAPI{})
|
||||
beego.Router("/api/projects/:id([0-9]+)/logs/filter", &api.ProjectAPI{}, "post:FilterAccessLog")
|
||||
beego.Router("/api/users/?:id", &api.UserAPI{})
|
||||
@ -64,10 +65,17 @@ func initRouters() {
|
||||
beego.Router("/api/repositories/manifests", &api.RepositoryAPI{}, "get:GetManifests")
|
||||
beego.Router("/api/jobs/replication/?:id([0-9]+)", &api.RepJobAPI{})
|
||||
beego.Router("/api/jobs/replication/:id([0-9]+)/log", &api.RepJobAPI{}, "get:GetLog")
|
||||
beego.Router("/api/policies/replication", &api.RepPolicyAPI{})
|
||||
beego.Router("/api/policies/replication/:id([0-9]+)", &api.RepPolicyAPI{})
|
||||
beego.Router("/api/policies/replication", &api.RepPolicyAPI{}, "get:List")
|
||||
beego.Router("/api/policies/replication", &api.RepPolicyAPI{}, "post:Post")
|
||||
beego.Router("/api/policies/replication/:id([0-9]+)/enablement", &api.RepPolicyAPI{}, "put:UpdateEnablement")
|
||||
beego.Router("/api/targets/?:id([0-9]+)", &api.TargetAPI{})
|
||||
beego.Router("/api/targets/", &api.TargetAPI{}, "get:List")
|
||||
beego.Router("/api/targets/", &api.TargetAPI{}, "post:Post")
|
||||
beego.Router("/api/targets/:id([0-9]+)", &api.TargetAPI{})
|
||||
beego.Router("/api/targets/ping", &api.TargetAPI{}, "post:Ping")
|
||||
beego.Router("/api/users/:id/sysadmin", &api.UserAPI{}, "put:ToggleUserAdminRole")
|
||||
beego.Router("/api/repositories/top", &api.RepositoryAPI{}, "get:GetTopRepos")
|
||||
beego.Router("api/logs", &api.LogAPI{})
|
||||
|
||||
//external service that hosted on harbor process:
|
||||
beego.Router("/service/notifications", &service.NotificationHandler{})
|
||||
|
@ -16,32 +16,32 @@
|
||||
<div class="col-sm-4"></div>
|
||||
<div class="col-sm-4">
|
||||
<div class="page-header">
|
||||
<h1>{{i18n .Lang "title_change_password"}}</h1>
|
||||
<h1>{{i18n .Lang "title_change_password"}}</h1>
|
||||
</div>
|
||||
<form class="form">
|
||||
<div class="alert alert-danger" role="alert" id="divErrMsg"></div>
|
||||
<div class="form-group has-feedback">
|
||||
<label for="OldPassword" class="control-label">{{i18n .Lang "old_password"}}</label>
|
||||
<input type="password" class="form-control" id="OldPassword">
|
||||
<span class="glyphicon form-control-feedback" aria-hidden="true"></span>
|
||||
</div>
|
||||
<div class="form-group has-feedback">
|
||||
<label for="Password" class="control-label">{{i18n .Lang "new_password"}}</label>
|
||||
<input type="password" class="form-control" id="Password">
|
||||
<span class="glyphicon form-control-feedback" aria-hidden="true"></span>
|
||||
<h6>{{i18n .Lang "password_description"}}</h6>
|
||||
</div>
|
||||
<div class="form-group has-feedback">
|
||||
<label for="ConfirmedPassword" class="control-label">{{i18n .Lang "confirm_password"}}</label>
|
||||
<input type="password" class="form-control" id="ConfirmedPassword">
|
||||
<span class="glyphicon form-control-feedback" aria-hidden="true"></span>
|
||||
<h6>{{i18n .Lang "password_description"}}</h6>
|
||||
</div>
|
||||
<div class="form-group has-feedback">
|
||||
<div class="text-center">
|
||||
<button type="button" class="btn btn-default" id="btnSubmit">{{i18n .Lang "button_submit"}}</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="alert alert-danger" role="alert" id="divErrMsg"></div>
|
||||
<div class="form-group has-feedback">
|
||||
<label for="OldPassword" class="control-label">{{i18n .Lang "old_password"}}</label>
|
||||
<input type="password" class="form-control" id="OldPassword">
|
||||
<span class="glyphicon form-control-feedback" aria-hidden="true"></span>
|
||||
</div>
|
||||
<div class="form-group has-feedback">
|
||||
<label for="Password" class="control-label">{{i18n .Lang "new_password"}}</label>
|
||||
<input type="password" class="form-control" id="Password">
|
||||
<span class="glyphicon form-control-feedback" aria-hidden="true"></span>
|
||||
<h6>{{i18n .Lang "password_description"}}</h6>
|
||||
</div>
|
||||
<div class="form-group has-feedback">
|
||||
<label for="ConfirmedPassword" class="control-label">{{i18n .Lang "confirm_password"}}</label>
|
||||
<input type="password" class="form-control" id="ConfirmedPassword">
|
||||
<span class="glyphicon form-control-feedback" aria-hidden="true"></span>
|
||||
<h6>{{i18n .Lang "password_description"}}</h6>
|
||||
</div>
|
||||
<div class="form-group has-feedback">
|
||||
<div class="text-center">
|
||||
<button type="button" class="btn btn-default" id="btnSubmit">{{i18n .Lang "button_submit"}}</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="col-sm-4"></div>
|
||||
|
@ -19,19 +19,19 @@
|
||||
<h1>{{i18n .Lang "title_forgot_password"}}</h1>
|
||||
</div>
|
||||
<form class="form">
|
||||
<div id="waiting1" class="waiting-nonfluid"></div>
|
||||
<div class="alert alert-danger" role="alert" id="divErrMsg"></div>
|
||||
<div class="form-group has-feedback">
|
||||
<label for="EmailF" class="control-label">{{i18n .Lang "email"}}</label>
|
||||
<input type="email" class="form-control" id="EmailF">
|
||||
<span class="glyphicon form-control-feedback" aria-hidden="true"></span>
|
||||
<h6>{{i18n .Lang "forgot_password_description"}}</h6>
|
||||
</div>
|
||||
<div class="form-group has-feedback">
|
||||
<div class="text-center">
|
||||
<button type="button" class="btn btn-default" id="btnSubmit">{{i18n .Lang "button_submit"}}</button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="waiting1" class="waiting-nonfluid"></div>
|
||||
<div class="alert alert-danger" role="alert" id="divErrMsg"></div>
|
||||
<div class="form-group has-feedback">
|
||||
<label for="EmailF" class="control-label">{{i18n .Lang "email"}}</label>
|
||||
<input type="email" class="form-control" id="EmailF">
|
||||
<span class="glyphicon form-control-feedback" aria-hidden="true"></span>
|
||||
<h6>{{i18n .Lang "forgot_password_description"}}</h6>
|
||||
</div>
|
||||
<div class="form-group has-feedback">
|
||||
<div class="text-center">
|
||||
<button type="button" class="btn btn-default" id="btnSubmit">{{i18n .Lang "button_submit"}}</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="col-sm-4"></div>
|
||||
|
@ -13,25 +13,25 @@
|
||||
limitations under the License.
|
||||
-->
|
||||
<!-- Main jumbotron for a primary marketing message or call to action -->
|
||||
<div class="jumbotron">
|
||||
<div class="container">
|
||||
<img class="pull-left" src="static/resources/image/Harbor_Logo_rec.png" alt="Harbor's Logo" height="180" width="360"/>
|
||||
<p class="pull-left" style="margin-top: 85px; color: #245580; font-size: 30pt; text-align: center; width: 60%;">{{i18n .Lang "index_title"}}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="jumbotron">
|
||||
<div class="container">
|
||||
<img class="pull-left" src="static/resources/image/Harbor_Logo_rec.png" alt="Harbor's Logo" height="180" width="360"/>
|
||||
<p class="pull-left" style="margin-top: 85px; color: #245580; font-size: 30pt; text-align: center; width: 60%;">{{i18n .Lang "index_title"}}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="container">
|
||||
<!-- Example row of columns -->
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<p>{{i18n .Lang "index_desc"}}</p>
|
||||
<p>{{i18n .Lang "index_desc_0"}}</p>
|
||||
<p>{{i18n .Lang "index_desc_1"}}</p>
|
||||
<p>{{i18n .Lang "index_desc_2"}}</p>
|
||||
<p>{{i18n .Lang "index_desc_3"}}</p>
|
||||
<p>{{i18n .Lang "index_desc_4"}}</p>
|
||||
<p>{{i18n .Lang "index_desc_5"}}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div> <!-- /container -->
|
||||
<div class="container">
|
||||
<!-- Example row of columns -->
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<p>{{i18n .Lang "index_desc"}}</p>
|
||||
<p>{{i18n .Lang "index_desc_0"}}</p>
|
||||
<p>{{i18n .Lang "index_desc_1"}}</p>
|
||||
<p>{{i18n .Lang "index_desc_2"}}</p>
|
||||
<p>{{i18n .Lang "index_desc_3"}}</p>
|
||||
<p>{{i18n .Lang "index_desc_4"}}</p>
|
||||
<p>{{i18n .Lang "index_desc_5"}}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div> <!-- /container -->
|
||||
<script src="static/resources/js/login.js"></script>
|
@ -18,7 +18,7 @@
|
||||
<li>{{.ProjectName}}</li>
|
||||
</ol>
|
||||
<div class="page-header" style="margin-top: -10px;">
|
||||
<h2>{{.ProjectName}} </h2></h4>{{i18n .Lang "owner"}}: {{.OwnerName}}</h4>
|
||||
<h2>{{.ProjectName}} </h2><h4>{{i18n .Lang "owner"}}: {{.OwnerName}}</h4>
|
||||
</div>
|
||||
<div row="tabpanel">
|
||||
<div class="row">
|
||||
@ -29,152 +29,150 @@
|
||||
<li role="presentation" style="visibility: hidden;"><a href="#tabOperationLog" aria-controls="tabOperationLog" role="tab" data-toggle="tab">{{i18n .Lang "logs"}}</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="col-md-10">
|
||||
<input type="hidden" id="projectId" value="{{.ProjectId}}">
|
||||
<input type="hidden" id="projectName" value="{{.ProjectName}}">
|
||||
<input type="hidden" id="userId" value="{{.UserId}}">
|
||||
<input type="hidden" id="ownerId" value="{{.OwnerId}}">
|
||||
<input type="hidden" id="roleId" value="{{.RoleId}}">
|
||||
<input type="hidden" id="harborRegUrl" value="{{.HarborRegUrl}}">
|
||||
<input type="hidden" id="public" value="{{.Public}}">
|
||||
<input type="hidden" id="repoName" value="{{.RepoName}}">
|
||||
<!-- tab panes -->
|
||||
<div class="tab-content">
|
||||
<div role="tabpanel" class="tab-pane" id="tabRepoInfo">
|
||||
<form class="form-inline">
|
||||
<div class="form-group">
|
||||
<label class="sr-only" for="txtRepoName">{{i18n .Lang "repo_name"}}:</label>
|
||||
<div class="input-group">
|
||||
<div class="input-group-addon">{{i18n .Lang "repo_name"}}:</div>
|
||||
<input type="text" class="form-control" id="txtRepoName">
|
||||
<span class="input-group-btn">
|
||||
<button id="btnSearchRepo" type="button" class="btn btn-primary"><span class="glyphicon glyphicon-search"></span></button>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<p>
|
||||
<div class="table-responsive div-height">
|
||||
<div class="alert alert-danger" role="alert" id="divErrMsg"><center></center></div>
|
||||
<div class="panel-group" id="accordionRepo" role="tablist" aria-multiselectable="true">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div role="tabpanel" class="tab-pane" id="tabUserInfo">
|
||||
<form class="form-inline">
|
||||
<div class="form-group">
|
||||
<div class="input-group">
|
||||
<label class="sr-only" for="txtSearchUser">{{i18n .Lang "username"}}:</label>
|
||||
<div class="input-group">
|
||||
<div class="input-group-addon">{{i18n .Lang "username"}}:</div>
|
||||
<input type="text" class="form-control" id="txtSearchUser">
|
||||
<div class="col-md-10">
|
||||
<input type="hidden" id="projectId" value="{{.ProjectId}}">
|
||||
<input type="hidden" id="projectName" value="{{.ProjectName}}">
|
||||
<input type="hidden" id="userId" value="{{.UserId}}">
|
||||
<input type="hidden" id="ownerId" value="{{.OwnerId}}">
|
||||
<input type="hidden" id="roleId" value="{{.RoleId}}">
|
||||
<input type="hidden" id="harborRegUrl" value="{{.HarborRegUrl}}">
|
||||
<input type="hidden" id="public" value="{{.Public}}">
|
||||
<input type="hidden" id="repoName" value="{{.RepoName}}">
|
||||
<!-- tab panes -->
|
||||
<div class="tab-content">
|
||||
<div role="tabpanel" class="tab-pane" id="tabRepoInfo">
|
||||
<form class="form-inline">
|
||||
<div class="form-group">
|
||||
<label class="sr-only" for="txtRepoName">{{i18n .Lang "repo_name"}}:</label>
|
||||
<div class="input-group">
|
||||
<div class="input-group-addon">{{i18n .Lang "repo_name"}}:</div>
|
||||
<input type="text" class="form-control" id="txtRepoName">
|
||||
<span class="input-group-btn">
|
||||
<button id="btnSearchUser" type="button" class="btn btn-primary"><span class="glyphicon glyphicon-search"></span></button>
|
||||
<button id="btnSearchRepo" type="button" class="btn btn-primary"><span class="glyphicon glyphicon-search"></span></button>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<p/>
|
||||
<div class="table-responsive div-height">
|
||||
<div class="alert alert-danger" role="alert" id="divErrMsg"><center></center></div>
|
||||
<div class="panel-group" id="accordionRepo" role="tablist" aria-multiselectable="true">
|
||||
</div>
|
||||
</div>
|
||||
<button type="button" class="btn btn-primary" data-toggle="modal" data-target="#dlgUser" id="btnAddUser">{{i18n .Lang "add_members"}}</button>
|
||||
</form>
|
||||
<p>
|
||||
<div class="table-responsive div-height">
|
||||
<table id="tblUser" class="table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{{i18n .Lang "username"}}</th>
|
||||
<th>{{i18n .Lang "role"}}</th>
|
||||
<th>{{i18n .Lang "operation"}}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div role="tabpanel" class="tab-pane" id="tabOperationLog">
|
||||
<form class="form-inline">
|
||||
<div class="form-group">
|
||||
<label for="txtUserName" class="sr-only">{{i18n .Lang "username"}}:</label>
|
||||
<div class="input-group">
|
||||
<div class="input-group-addon">{{i18n .Lang "username"}}:</div>
|
||||
<input type="text" class="form-control" id="txtSearchUserName">
|
||||
<span class="input-group-btn">
|
||||
<button id="btnFilterLog" type="button" class="btn btn-primary" data-toggle="modal" data-target="#dlgSearch"><span class="glyphicon glyphicon-search"></span></button>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="input-group">
|
||||
<button class="btn btn-link" type="button" data-toggle="collapse" data-target="#collapseAdvance" aria-expanded="false" aria-controls="collapseAdvance">{{i18n .Lang "advance"}}</button>
|
||||
</div>
|
||||
</div>
|
||||
<form>
|
||||
<p></p>
|
||||
<div class="collapse" id="collapseAdvance">
|
||||
<form class="form">
|
||||
<div role="tabpanel" class="tab-pane" id="tabUserInfo">
|
||||
<form class="form-inline">
|
||||
<div class="form-group">
|
||||
<label for="txtUserName" class="sr-only">{{i18n .Lang "operation"}}:</label>
|
||||
<div class="input-group">
|
||||
<label class="sr-only" for="txtSearchUser">{{i18n .Lang "username"}}:</label>
|
||||
<div class="input-group">
|
||||
<div class="input-group-addon">{{i18n .Lang "username"}}:</div>
|
||||
<input type="text" class="form-control" id="txtSearchUser">
|
||||
<span class="input-group-btn">
|
||||
<button id="btnSearchUser" type="button" class="btn btn-primary"><span class="glyphicon glyphicon-search"></span></button>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button type="button" class="btn btn-primary" data-toggle="modal" data-target="#dlgUser" id="btnAddUser">{{i18n .Lang "add_members"}}</button>
|
||||
</form>
|
||||
<p/>
|
||||
<div class="table-responsive div-height">
|
||||
<table id="tblUser" class="table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{{i18n .Lang "username"}}</th>
|
||||
<th>{{i18n .Lang "role"}}</th>
|
||||
<th>{{i18n .Lang "operation"}}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div role="tabpanel" class="tab-pane" id="tabOperationLog">
|
||||
<form class="form-inline">
|
||||
<div class="form-group">
|
||||
<label for="txtUserName" class="sr-only">{{i18n .Lang "username"}}:</label>
|
||||
<div class="input-group">
|
||||
<div class="input-group-addon">{{i18n .Lang "operation"}}:</div>
|
||||
<span class="input-group-addon" id="spnFilterOption">
|
||||
<input type="checkbox" name="chkAll" value="0"> {{i18n .Lang "all"}}
|
||||
<input type="checkbox" name="chkOperation" value="create"> Create
|
||||
<input type="checkbox" name="chkOperation" value="pull"> Pull
|
||||
<input type="checkbox" name="chkOperation" value="push"> Push
|
||||
<input type="checkbox" name="chkOperation" value="delete"> Delete
|
||||
<input type="checkbox" name="chkOperation" value="others"> {{i18n .Lang "others"}}:
|
||||
<input type="text" id="txtOthers" size="10">
|
||||
<div class="input-group-addon">{{i18n .Lang "username"}}:</div>
|
||||
<input type="text" class="form-control" id="txtSearchUserName">
|
||||
<span class="input-group-btn">
|
||||
<button id="btnFilterLog" type="button" class="btn btn-primary" data-toggle="modal" data-target="#dlgSearch"><span class="glyphicon glyphicon-search"></span></button>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<p></p>
|
||||
<div class="form-group">
|
||||
<label for="begindatepicker" class="sr-only">{{i18n .Lang "start_date"}}:</label>
|
||||
<div class="input-group">
|
||||
<div class="input-group-addon">{{i18n .Lang "start_date"}}:</div>
|
||||
<div class="input-group date" id="datetimepicker1">
|
||||
<input type="text" class="form-control" id="begindatepicker" readonly="readonly">
|
||||
<span class="input-group-addon">
|
||||
<span class="glyphicon glyphicon-calendar"></span>
|
||||
</span>
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<button class="btn btn-link" type="button" data-toggle="collapse" data-target="#collapseAdvance" aria-expanded="false" aria-controls="collapseAdvance">{{i18n .Lang "advance"}}</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="input-group">
|
||||
<div class="input-group-addon">{{i18n .Lang "end_date"}}:</div>
|
||||
<div class="input-group date" id="datetimepicker2">
|
||||
<input type="text" class="form-control" id="enddatepicker" readonly="readonly">
|
||||
<span class="input-group-addon">
|
||||
<span class="glyphicon glyphicon-calendar"></span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="table-responsive div-height">
|
||||
<table id="tblAccessLog" class="table table-hover" >
|
||||
<thead>
|
||||
<tr>
|
||||
<th width="15%">{{i18n .Lang "username"}}</th>
|
||||
<th width="30%">{{i18n .Lang "repo_name"}}</th>
|
||||
<th width="15%">{{i18n .Lang "repo_tag"}}</th>
|
||||
<th width="15%">{{i18n .Lang "operation"}}</th>
|
||||
<th width="15%">{{i18n .Lang "timestamp"}}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<p/>
|
||||
<div class="collapse" id="collapseAdvance">
|
||||
<form class="form">
|
||||
<div class="form-group">
|
||||
<label for="txtUserName" class="sr-only">{{i18n .Lang "operation"}}:</label>
|
||||
<div class="input-group">
|
||||
<div class="input-group-addon">{{i18n .Lang "operation"}}:</div>
|
||||
<span class="input-group-addon" id="spnFilterOption">
|
||||
<input type="checkbox" name="chkAll" value="0"> {{i18n .Lang "all"}}
|
||||
<input type="checkbox" name="chkOperation" value="create"> Create
|
||||
<input type="checkbox" name="chkOperation" value="pull"> Pull
|
||||
<input type="checkbox" name="chkOperation" value="push"> Push
|
||||
<input type="checkbox" name="chkOperation" value="delete"> Delete
|
||||
<input type="checkbox" name="chkOperation" value="others"> {{i18n .Lang "others"}}:
|
||||
<input type="text" id="txtOthers" size="10">
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<p></p>
|
||||
<div class="form-group">
|
||||
<label for="begindatepicker" class="sr-only">{{i18n .Lang "start_date"}}:</label>
|
||||
<div class="input-group">
|
||||
<div class="input-group-addon">{{i18n .Lang "start_date"}}:</div>
|
||||
<div class="input-group date" id="datetimepicker1">
|
||||
<input type="text" class="form-control" id="begindatepicker" readonly="readonly">
|
||||
<span class="input-group-addon">
|
||||
<span class="glyphicon glyphicon-calendar"></span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="input-group">
|
||||
<div class="input-group-addon">{{i18n .Lang "end_date"}}:</div>
|
||||
<div class="input-group date" id="datetimepicker2">
|
||||
<input type="text" class="form-control" id="enddatepicker" readonly="readonly">
|
||||
<span class="input-group-addon">
|
||||
<span class="glyphicon glyphicon-calendar"></span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="table-responsive div-height">
|
||||
<table id="tblAccessLog" class="table table-hover" >
|
||||
<thead>
|
||||
<tr>
|
||||
<th width="15%">{{i18n .Lang "username"}}</th>
|
||||
<th width="30%">{{i18n .Lang "repo_name"}}</th>
|
||||
<th width="15%">{{i18n .Lang "repo_tag"}}</th>
|
||||
<th width="15%">{{i18n .Lang "operation"}}</th>
|
||||
<th width="15%">{{i18n .Lang "timestamp"}}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal fade" id="dlgUser" tabindex="-1" role="dialog" aria-labelledby="User" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
|
@ -25,24 +25,24 @@
|
||||
<!-- tab panes -->
|
||||
<div class="tab-content">
|
||||
<div role="tabpanel" class="tab-pane" id="tabMyProject" style="margin-top: 15px;">
|
||||
<form class="form-inline">
|
||||
<label class="sr-only" for="txtProjectName">{{i18n .Lang "project_name"}}:</label>
|
||||
<div class="input-group">
|
||||
<div class="input-group-addon">{{i18n .Lang "project_name"}}:</div>
|
||||
<input type="text" class="form-control" id="txtSearchProject">
|
||||
<span class="input-group-btn">
|
||||
<button id="btnSearch" type="button" class="btn btn-primary"><span class="glyphicon glyphicon-search"></span></button>
|
||||
</span>
|
||||
</div>
|
||||
<button type="button" class="btn btn-primary" data-toggle="modal" data-target="#dlgAddProject" id="btnAddProject">{{i18n .Lang "add_project"}}</button>
|
||||
</form>
|
||||
<form class="form-inline">
|
||||
<label class="sr-only" for="txtProjectName">{{i18n .Lang "project_name"}}:</label>
|
||||
<div class="input-group">
|
||||
<div class="input-group-addon">{{i18n .Lang "project_name"}}:</div>
|
||||
<input type="text" class="form-control" id="txtSearchProject">
|
||||
<span class="input-group-btn">
|
||||
<button id="btnSearch" type="button" class="btn btn-primary"><span class="glyphicon glyphicon-search"></span></button>
|
||||
</span>
|
||||
</div>
|
||||
<button type="button" class="btn btn-primary" data-toggle="modal" data-target="#dlgAddProject" id="btnAddProject">{{i18n .Lang "add_project"}}</button>
|
||||
</form>
|
||||
<div class="table-responsive div-height">
|
||||
<table id="tblProject" class="table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th width="35%">{{i18n .Lang "project_name"}}</th>
|
||||
<th width="45%">{{i18n .Lang "creation_time"}}</th>
|
||||
<th width="20%">{{i18n .Lang "publicity"}}</th>
|
||||
<th width="20%">{{i18n .Lang "publicity"}}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@ -51,65 +51,65 @@
|
||||
</div>
|
||||
</div>
|
||||
<div role="tabpanel" class="tab-pane" id="tabAdminOption" style="visibility: hidden; margin-top: 15px;">
|
||||
<form class="form-inline">
|
||||
<label class="sr-only" for="txtProjectName">{{i18n .Lang "username"}}:</label>
|
||||
<div class="input-group">
|
||||
<div class="input-group-addon">{{i18n .Lang "username"}}:</div>
|
||||
<input type="text" class="form-control" id="txtSearchUsername">
|
||||
<span class="input-group-btn">
|
||||
<button id="btnSearchUsername" type="button" class="btn btn-primary"><span class="glyphicon glyphicon-search"></span></button>
|
||||
</span>
|
||||
</div>
|
||||
</form>
|
||||
<div class="table-responsive div-height">
|
||||
<table id="tblUser" class="table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th width="35%">{{i18n .Lang "username"}}</th>
|
||||
<th width="45%">{{i18n .Lang "email"}}</th>
|
||||
<th width="20%">{{i18n .Lang "system_admin"}}</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
</tbody>
|
||||
</table>
|
||||
<form class="form-inline">
|
||||
<label class="sr-only" for="txtProjectName">{{i18n .Lang "username"}}:</label>
|
||||
<div class="input-group">
|
||||
<div class="input-group-addon">{{i18n .Lang "username"}}:</div>
|
||||
<input type="text" class="form-control" id="txtSearchUsername">
|
||||
<span class="input-group-btn">
|
||||
<button id="btnSearchUsername" type="button" class="btn btn-primary"><span class="glyphicon glyphicon-search"></span></button>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal fade" id="dlgAddProject" tabindex="-1" role="dialog" aria-labelledby="Add Project" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<a type="button" class="close" data-dismiss="modal" aria-label="Close" id="btnCancel">
|
||||
<span aria-hidden="true">×</span>
|
||||
</a>
|
||||
<h4 class="modal-title" id="dlgAddProjectTitle">{{i18n .Lang "add_project"}}</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form role="form">
|
||||
<div class="alert alert-danger" role="alert" id="divErrMsg"></div>
|
||||
<div class="form-group has-feedback">
|
||||
<label for="projectName" class="control-label">{{i18n .Lang "project_name"}}:</label>
|
||||
<input type="text" class="form-control" id="projectName">
|
||||
<span class="glyphicon form-control-feedback" aria-hidden="true"></span>
|
||||
</div>
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
<input type="checkbox" id="isPublic" checked=false> {{i18n .Lang "check_for_publicity"}}
|
||||
</label>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-primary" id="btnSave">{{i18n .Lang "button_save"}}</button>
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal">{{i18n .Lang "button_cancel"}}</button>
|
||||
</form>
|
||||
<div class="table-responsive div-height">
|
||||
<table id="tblUser" class="table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th width="35%">{{i18n .Lang "username"}}</th>
|
||||
<th width="45%">{{i18n .Lang "email"}}</th>
|
||||
<th width="20%">{{i18n .Lang "system_admin"}}</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal fade" id="dlgAddProject" tabindex="-1" role="dialog" aria-labelledby="Add Project" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<a type="button" class="close" data-dismiss="modal" aria-label="Close" id="btnCancel">
|
||||
<span aria-hidden="true">×</span>
|
||||
</a>
|
||||
<h4 class="modal-title" id="dlgAddProjectTitle">{{i18n .Lang "add_project"}}</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form role="form">
|
||||
<div class="alert alert-danger" role="alert" id="divErrMsg"></div>
|
||||
<div class="form-group has-feedback">
|
||||
<label for="projectName" class="control-label">{{i18n .Lang "project_name"}}:</label>
|
||||
<input type="text" class="form-control" id="projectName">
|
||||
<span class="glyphicon form-control-feedback" aria-hidden="true"></span>
|
||||
</div>
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
<input type="checkbox" id="isPublic" checked=false> {{i18n .Lang "check_for_publicity"}}
|
||||
</label>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-primary" id="btnSave">{{i18n .Lang "button_save"}}</button>
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal">{{i18n .Lang "button_cancel"}}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script src="static/resources/js/validate-options.js"></script>
|
||||
<script src="static/resources/js/project.js"></script>
|
||||
|
@ -16,65 +16,65 @@
|
||||
<div class="col-sm-4"></div>
|
||||
<div class="col-sm-4">
|
||||
<div class="page-header">
|
||||
{{ if eq .IsAdmin true }}
|
||||
<h1>{{i18n .Lang "add_user" }}</h1>
|
||||
{{ else }}
|
||||
<h1>{{i18n .Lang "registration"}}</h1>
|
||||
{{ end }}
|
||||
{{ if eq .IsAdmin true }}
|
||||
<h1>{{i18n .Lang "add_user" }}</h1>
|
||||
{{ else }}
|
||||
<h1>{{i18n .Lang "registration"}}</h1>
|
||||
{{ end }}
|
||||
</div>
|
||||
<form class="form">
|
||||
<div class="alert alert-danger" role="alert" id="divErrMsg"></div>
|
||||
<div class="form-group has-feedback">
|
||||
<label for="username" class="control-label">{{i18n .Lang "username"}}</label>
|
||||
<p style="display:inline; color: red; font-size: 12pt;">*</p>
|
||||
<input type="text" class="form-control" id="Username">
|
||||
<span class="glyphicon form-control-feedback" aria-hidden="true"></span>
|
||||
<h6>{{i18n .Lang "username_description"}}</h6>
|
||||
</div>
|
||||
<div class="form-group has-feedback">
|
||||
<label for="Email" class="control-label">{{i18n .Lang "email"}}</label>
|
||||
<p style="display:inline; color: red; font-size: 12pt;">*</p>
|
||||
<input type="email" class="form-control" id="Email">
|
||||
<span class="glyphicon form-control-feedback" aria-hidden="true"></span>
|
||||
<h6>{{i18n .Lang "email_description"}}</h6>
|
||||
</div>
|
||||
<div class="form-group has-feedback">
|
||||
<label for="Realname" class="control-label">{{i18n .Lang "full_name"}}</label>
|
||||
<p style="display:inline; color: red; font-size: 12pt;">*</p>
|
||||
<input type="text" class="form-control" id="Realname">
|
||||
<span class="glyphicon form-control-feedback" aria-hidden="true"></span>
|
||||
<h6>{{i18n .Lang "full_name_description"}}</h6>
|
||||
</div>
|
||||
<div class="form-group has-feedback">
|
||||
<label for="Password" class="control-label">{{i18n .Lang "password"}}</label>
|
||||
<p style="display:inline; color: red; font-size: 12pt;">*</p>
|
||||
<input type="password" class="form-control" id="Password">
|
||||
<span class="glyphicon form-control-feedback" aria-hidden="true"></span>
|
||||
<h6>{{i18n .Lang "password_description"}}</h6>
|
||||
</div>
|
||||
<div class="form-group has-feedback">
|
||||
<label for="ConfirmedPassword" class="control-label">{{i18n .Lang "confirm_password"}}</label>
|
||||
<p style="display:inline; color: red; font-size: 12pt;">*</p>
|
||||
<input type="password" class="form-control" id="ConfirmedPassword">
|
||||
<span class="glyphicon form-control-feedback" aria-hidden="true"></span>
|
||||
<h6>{{i18n .Lang "password_description"}}</h6>
|
||||
</div>
|
||||
<div class="form-group has-feedback">
|
||||
<label for="Comment" class="control-label">{{i18n .Lang "note_to_the_admin"}}</label>
|
||||
<input type="text" class="form-control" id="Comment">
|
||||
<span class="glyphicon form-control-feedback" aria-hidden="true"></span>
|
||||
</div>
|
||||
<div class="form-group has-feedback">
|
||||
<div class="text-center">
|
||||
<button type="button" class="btn btn-default" id="btnPageSignUp">
|
||||
{{ if eq .IsAdmin true }}
|
||||
{{i18n .Lang "add_user" }}
|
||||
{{ else }}
|
||||
{{i18n .Lang "sign_up"}}
|
||||
{{ end }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="alert alert-danger" role="alert" id="divErrMsg"></div>
|
||||
<div class="form-group has-feedback">
|
||||
<label for="username" class="control-label">{{i18n .Lang "username"}}</label>
|
||||
<p style="display:inline; color: red; font-size: 12pt;">*</p>
|
||||
<input type="text" class="form-control" id="Username">
|
||||
<span class="glyphicon form-control-feedback" aria-hidden="true"></span>
|
||||
<h6>{{i18n .Lang "username_description"}}</h6>
|
||||
</div>
|
||||
<div class="form-group has-feedback">
|
||||
<label for="Email" class="control-label">{{i18n .Lang "email"}}</label>
|
||||
<p style="display:inline; color: red; font-size: 12pt;">*</p>
|
||||
<input type="email" class="form-control" id="Email">
|
||||
<span class="glyphicon form-control-feedback" aria-hidden="true"></span>
|
||||
<h6>{{i18n .Lang "email_description"}}</h6>
|
||||
</div>
|
||||
<div class="form-group has-feedback">
|
||||
<label for="Realname" class="control-label">{{i18n .Lang "full_name"}}</label>
|
||||
<p style="display:inline; color: red; font-size: 12pt;">*</p>
|
||||
<input type="text" class="form-control" id="Realname">
|
||||
<span class="glyphicon form-control-feedback" aria-hidden="true"></span>
|
||||
<h6>{{i18n .Lang "full_name_description"}}</h6>
|
||||
</div>
|
||||
<div class="form-group has-feedback">
|
||||
<label for="Password" class="control-label">{{i18n .Lang "password"}}</label>
|
||||
<p style="display:inline; color: red; font-size: 12pt;">*</p>
|
||||
<input type="password" class="form-control" id="Password">
|
||||
<span class="glyphicon form-control-feedback" aria-hidden="true"></span>
|
||||
<h6>{{i18n .Lang "password_description"}}</h6>
|
||||
</div>
|
||||
<div class="form-group has-feedback">
|
||||
<label for="ConfirmedPassword" class="control-label">{{i18n .Lang "confirm_password"}}</label>
|
||||
<p style="display:inline; color: red; font-size: 12pt;">*</p>
|
||||
<input type="password" class="form-control" id="ConfirmedPassword">
|
||||
<span class="glyphicon form-control-feedback" aria-hidden="true"></span>
|
||||
<h6>{{i18n .Lang "password_description"}}</h6>
|
||||
</div>
|
||||
<div class="form-group has-feedback">
|
||||
<label for="Comment" class="control-label">{{i18n .Lang "note_to_the_admin"}}</label>
|
||||
<input type="text" class="form-control" id="Comment">
|
||||
<span class="glyphicon form-control-feedback" aria-hidden="true"></span>
|
||||
</div>
|
||||
<div class="form-group has-feedback">
|
||||
<div class="text-center">
|
||||
<button type="button" class="btn btn-default" id="btnPageSignUp">
|
||||
{{ if eq .IsAdmin true }}
|
||||
{{i18n .Lang "add_user" }}
|
||||
{{ else }}
|
||||
{{i18n .Lang "sign_up"}}
|
||||
{{ end }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="col-sm-4"></div>
|
||||
|
@ -14,8 +14,8 @@
|
||||
-->
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<body>
|
||||
<p>{{.Hint}}:</p>
|
||||
<a href="{{.URL}}/resetPassword?reset_uuid={{.UUID}}">{{.URL}}/resetPassword?reset_uuid={{.UUID}}</a>
|
||||
</body>
|
||||
<body>
|
||||
<p>{{.Hint}}:</p>
|
||||
<a href="{{.URL}}/resetPassword?reset_uuid={{.UUID}}">{{.URL}}/resetPassword?reset_uuid={{.UUID}}</a>
|
||||
</body>
|
||||
</html>
|
@ -17,27 +17,27 @@
|
||||
<div class="col-sm-4"></div>
|
||||
<div class="col-sm-4">
|
||||
<div class="page-header">
|
||||
<h1>{{i18n .Lang "title_reset_password"}}</h1>
|
||||
<h1>{{i18n .Lang "title_reset_password"}}</h1>
|
||||
</div>
|
||||
<form class="form">
|
||||
<div class="alert alert-danger" role="alert" id="divErrMsg"></div>
|
||||
<div class="form-group has-feedback">
|
||||
<label for="Password" class="control-label">{{i18n .Lang "password"}}</label>
|
||||
<input type="password" class="form-control" id="Password">
|
||||
<span class="glyphicon form-control-feedback" aria-hidden="true"></span>
|
||||
<h6>{{i18n .Lang "password_description"}}</h6>
|
||||
</div>
|
||||
<div class="form-group has-feedback">
|
||||
<label for="ConfirmedPassword" class="control-label">{{i18n .Lang "confirm_password"}}</label>
|
||||
<input type="password" class="form-control" id="ConfirmedPassword">
|
||||
<span class="glyphicon form-control-feedback" aria-hidden="true"></span>
|
||||
<h6>{{i18n .Lang "password_description"}}</h6>
|
||||
</div>
|
||||
<div class="form-group has-feedback">
|
||||
<div class="text-center">
|
||||
<button type="button" class="btn btn-default" id="btnSubmit">{{i18n .Lang "button_submit"}}</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="alert alert-danger" role="alert" id="divErrMsg"></div>
|
||||
<div class="form-group has-feedback">
|
||||
<label for="Password" class="control-label">{{i18n .Lang "password"}}</label>
|
||||
<input type="password" class="form-control" id="Password">
|
||||
<span class="glyphicon form-control-feedback" aria-hidden="true"></span>
|
||||
<h6>{{i18n .Lang "password_description"}}</h6>
|
||||
</div>
|
||||
<div class="form-group has-feedback">
|
||||
<label for="ConfirmedPassword" class="control-label">{{i18n .Lang "confirm_password"}}</label>
|
||||
<input type="password" class="form-control" id="ConfirmedPassword">
|
||||
<span class="glyphicon form-control-feedback" aria-hidden="true"></span>
|
||||
<h6>{{i18n .Lang "password_description"}}</h6>
|
||||
</div>
|
||||
<div class="form-group has-feedback">
|
||||
<div class="text-center">
|
||||
<button type="button" class="btn btn-default" id="btnSubmit">{{i18n .Lang "button_submit"}}</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="col-sm-4"></div>
|
||||
|
@ -18,15 +18,15 @@
|
||||
<li><a href="/">{{i18n .Lang "home"}}</a></li>
|
||||
<li>{{i18n .Lang "search"}}</li>
|
||||
</ol>
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading" id="panelCommonSearchProjectsHeader">{{i18n .Lang "projects"}}</div>
|
||||
<div class="panel-body" id="panelCommonSearchProjectsBody">
|
||||
</div>
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading" id="panelCommonSearchProjectsHeader">{{i18n .Lang "projects"}}</div>
|
||||
<div class="panel-body" id="panelCommonSearchProjectsBody">
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading" id="panelCommonSearchRepositoriesHeader">{{i18n .Lang "repositories"}}</div>
|
||||
<div class="panel-body" id="panelCommonSearchRepositoriesBody">
|
||||
</div>
|
||||
<div class="panel-heading" id="panelCommonSearchRepositoriesHeader">{{i18n .Lang "repositories"}}</div>
|
||||
<div class="panel-body" id="panelCommonSearchRepositoriesBody">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script src="static/resources/js/search.js"></script>
|
@ -14,15 +14,15 @@
|
||||
-->
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
{{.HeaderInc}}
|
||||
<title>{{.PageTitle}}</title>
|
||||
</head>
|
||||
<body>
|
||||
{{.HeaderContent}}
|
||||
{{.BodyContent}}
|
||||
{{.FooterInc}}
|
||||
{{.ModalDialog}}
|
||||
{{.FootContent}}
|
||||
</body>
|
||||
<head>
|
||||
{{.HeaderInc}}
|
||||
<title>{{.PageTitle}}</title>
|
||||
</head>
|
||||
<body>
|
||||
{{.HeaderContent}}
|
||||
{{.BodyContent}}
|
||||
{{.FooterInc}}
|
||||
{{.ModalDialog}}
|
||||
{{.FootContent}}
|
||||
</body>
|
||||
</html>
|
@ -13,11 +13,11 @@
|
||||
limitations under the License.
|
||||
-->
|
||||
<footer class="footer">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-md-5 col-md-offset-4">
|
||||
<p class="text-muted">{{i18n .Lang "copyright"}} © 2015-2016 VMware, Inc. {{i18n .Lang "all_rights_reserved"}}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-md-5 col-md-offset-4">
|
||||
<p class="text-muted">{{i18n .Lang "copyright"}} © 2015-2016 VMware, Inc. {{i18n .Lang "all_rights_reserved"}}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
@ -11,79 +11,83 @@
|
||||
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.
|
||||
-->
|
||||
-->
|
||||
|
||||
<input type="hidden" id="currentLanguage" value="{{.Lang}}">
|
||||
<input type="hidden" id="isAdmin" value="{{.IsAdmin}}">
|
||||
<nav class="navbar navbar-default" role="navigation" style="margin-bottom: 0;">
|
||||
<div class="navbar-header">
|
||||
<button aria-controls="navbar" aria-expanded="false" data-target="#navbar" data-toggle="collapse" class="navbar-toggle collapsed" type="button">
|
||||
<span class="sr-only">Toggle navigation</span>
|
||||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
</button>
|
||||
<span class="sr-only">Toggle navigation</span>
|
||||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
</button>
|
||||
<a class="navbar-brand" href="/"><img src="static/resources/image/Harbor_Logo_rec.png" height="40px" width="80px"/></a>
|
||||
</div>
|
||||
</div>
|
||||
<div id="navbar" class="navbar-collapse collapse">
|
||||
<form class="navbar-form navbar-right">
|
||||
<div class="form-group">
|
||||
<div class="input-group">
|
||||
|
||||
<ul class="nav navbar-nav">
|
||||
<li class="dropdown">
|
||||
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">
|
||||
<span class="glyphicon glyphicon-globe"></span>
|
||||
{{i18n .Lang "language"}}
|
||||
<span class="caret"></span></a>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a href="/language?lang=en-US">{{i18n .Lang "language_en-US"}}</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=ru-RU">{{i18n .Lang "language_ru-RU"}}</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="input-group" >
|
||||
<span class="input-group-addon"><span class="input-group glyphicon glyphicon-search"></span></span>
|
||||
<input type="text" class="form-control" id="txtCommonSearch" size="50" placeholder="{{i18n .Lang "search_placeholder"}}">
|
||||
</div>
|
||||
</div>
|
||||
{{ if .Username }}
|
||||
<div class="input-group">
|
||||
<ul class="nav navbar-nav">
|
||||
<li class="dropdown">
|
||||
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false"><span class="glyphicon glyphicon-user"></span> {{.Username}}<span class="caret"></span></a>
|
||||
<ul class="dropdown-menu">
|
||||
{{ if eq .AuthMode "db_auth" }}
|
||||
<li><a id="aChangePassword" href="/changePassword" target="_blank"><span class="glyphicon glyphicon-pencil"></span> {{i18n .Lang "change_password"}}</a></li>
|
||||
<li role="separator" class="divider"></li>
|
||||
{{ end }}
|
||||
{{ if eq .IsLdapAdminUser true }}
|
||||
<li><a id="aChangePassword" href="/changePassword" target="_blank"><span class="glyphicon glyphicon-pencil"></span> {{i18n .Lang "change_password"}}</a></li>
|
||||
<li role="separator" class="divider"></li>
|
||||
{{ end }}
|
||||
{{ if eq .AuthMode "db_auth" }}
|
||||
{{ if eq .IsAdmin true }}
|
||||
<li><a id="aAddUser" href="/addUser" target="_blank"><span class="glyphicon glyphicon-plus"></span> {{i18n .Lang "add_user"}}</a></li>
|
||||
{{ end }}
|
||||
{{ end}}
|
||||
<li><a id="aLogout" href="#"><span class="glyphicon glyphicon-log-in"></span> {{i18n .Lang "log_out"}}</a></li>
|
||||
<li class="dropdown">
|
||||
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">
|
||||
<span class="glyphicon glyphicon-globe"></span>
|
||||
{{i18n .Lang "language"}}
|
||||
<span class="caret"></span></a>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a href="/language?lang=en-US">{{i18n .Lang "language_en-US"}}</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=ru-RU">{{i18n .Lang "language_ru-RU"}}</a></li>
|
||||
<li><a href="/language?lang=ja-JP">{{i18n .Lang "language_ja-JP"}}</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
{{ else if eq .AuthMode "db_auth" }}
|
||||
<div class="input-group">
|
||||
<button type="button" class="btn btn-default" id="btnSignIn">{{i18n .Lang "sign_in"}}</button>
|
||||
{{ if eq .SelfRegistration true }}
|
||||
<button type="button" class="btn btn-success" id="btnSignUp">{{i18n .Lang "sign_up"}}</button>
|
||||
</div>
|
||||
|
||||
<div class="input-group" >
|
||||
<span class="input-group-addon"><span class="input-group glyphicon glyphicon-search"></span></span>
|
||||
<input type="text" class="form-control" id="txtCommonSearch" size="50" placeholder="{{i18n .Lang "search_placeholder"}}">
|
||||
</div>
|
||||
</div>
|
||||
{{ if .Username }}
|
||||
<div class="input-group">
|
||||
<ul class="nav navbar-nav">
|
||||
<li class="dropdown">
|
||||
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false"><span class="glyphicon glyphicon-user"></span> {{.Username}}<span class="caret"></span></a>
|
||||
<ul class="dropdown-menu">
|
||||
{{ if eq .AuthMode "db_auth" }}
|
||||
<li><a id="aChangePassword" href="/changePassword" target="_blank"><span class="glyphicon glyphicon-pencil"></span> {{i18n .Lang "change_password"}}</a></li>
|
||||
<li role="separator" class="divider"></li>
|
||||
{{ end }}
|
||||
{{ if eq .IsLdapAdminUser true }}
|
||||
<li><a id="aChangePassword" href="/changePassword" target="_blank"><span class="glyphicon glyphicon-pencil"></span> {{i18n .Lang "change_password"}}</a></li>
|
||||
<li role="separator" class="divider"></li>
|
||||
{{ end }}
|
||||
{{ if eq .AuthMode "db_auth" }}
|
||||
{{ if eq .IsAdmin true }}
|
||||
<li><a id="aAddUser" href="/addUser" target="_blank"><span class="glyphicon glyphicon-plus"></span> {{i18n .Lang "add_user"}}</a></li>
|
||||
{{ end }}
|
||||
{{ end}}
|
||||
<li><a id="aLogout" href="#"><span class="glyphicon glyphicon-log-in"></span> {{i18n .Lang "log_out"}}</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
{{ else if eq .AuthMode "db_auth" }}
|
||||
<div class="input-group">
|
||||
<button type="button" class="btn btn-default" id="btnSignIn">{{i18n .Lang "sign_in"}}</button>
|
||||
{{ if eq .SelfRegistration true }}
|
||||
<button type="button" class="btn btn-success" id="btnSignUp">{{i18n .Lang "sign_up"}}</button>
|
||||
{{ end }}
|
||||
</div>
|
||||
{{ else }}
|
||||
<div class="input-group">
|
||||
<button type="button" class="btn btn-default" id="btnSignIn">{{i18n .Lang "sign_in"}}</button>
|
||||
</div>
|
||||
{{ end }}
|
||||
</div>
|
||||
{{ else }}
|
||||
<div class="input-group">
|
||||
<button type="button" class="btn btn-default" id="btnSignIn">{{i18n .Lang "sign_in"}}</button>
|
||||
</div>
|
||||
{{ end }}
|
||||
</form>
|
||||
</div>
|
||||
</nav>
|
||||
</form>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
|
@ -13,26 +13,26 @@
|
||||
limitations under the License.
|
||||
-->
|
||||
<style>
|
||||
.center {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
top: 10%;
|
||||
}
|
||||
.center {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
top: 10%;
|
||||
}
|
||||
</style>
|
||||
<!-- Modal -->
|
||||
<div class="center modal fade" id="dlgModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||
<h4 class="modal-title" id="dlgLabel"></h4>
|
||||
</div>
|
||||
<div class="modal-body" id="dlgBody">
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-primary" id="dlgConfirm" data-dismiss="modal">{{i18n .Lang "dlg_button_ok"}}</button>
|
||||
<button type="button" class="btn btn-primary" id="dlgCancel" data-dismiss="modal" style="display: none;">{{i18n .Lang "dlg_button_cancel"}}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||
<h4 class="modal-title" id="dlgLabel"></h4>
|
||||
</div>
|
||||
<div class="modal-body" id="dlgBody">
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-primary" id="dlgConfirm" data-dismiss="modal">{{i18n .Lang "dlg_button_ok"}}</button>
|
||||
<button type="button" class="btn btn-primary" id="dlgCancel" data-dismiss="modal" style="display: none;">{{i18n .Lang "dlg_button_cancel"}}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@ -13,28 +13,28 @@
|
||||
limitations under the License.
|
||||
-->
|
||||
<div class="container">
|
||||
<form class="form-signin form-horizontal">
|
||||
<div class="form-group">
|
||||
<label for="Principal" class="col-md-4 control-label">{{i18n .Lang "username_email"}}</label>
|
||||
<div class="col-md-8">
|
||||
<input type="text" id="Principal" class="form-control" placeholder="{{i18n .Lang "username_email"}}">
|
||||
<form class="form-signin form-horizontal">
|
||||
<div class="form-group">
|
||||
<label for="Principal" class="col-md-4 control-label">{{i18n .Lang "username_email"}}</label>
|
||||
<div class="col-md-8">
|
||||
<input type="text" id="Principal" class="form-control" placeholder="{{i18n .Lang "username_email"}}">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="Password" class="col-md-4 control-label">{{i18n .Lang "password"}}</label>
|
||||
<div class="col-md-8">
|
||||
<input type="password" id="Password" class="form-control" placeholder="{{i18n .Lang "password"}}">
|
||||
<div class="form-group">
|
||||
<label for="Password" class="col-md-4 control-label">{{i18n .Lang "password"}}</label>
|
||||
<div class="col-md-8">
|
||||
<input type="password" id="Password" class="form-control" placeholder="{{i18n .Lang "password"}}">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button class="btn btn-lg btn-primary btn-block" type="button" id="btnPageSignIn">{{i18n .Lang "sign_in"}}</button>
|
||||
{{ if eq .AuthMode "db_auth" }}
|
||||
<div class="form-group">
|
||||
<div class="col-md-12">
|
||||
<button type="button" class="btn btn-link pull-right" id="btnForgot">{{i18n .Lang "forgot_password"}}</button>
|
||||
</div>
|
||||
</div>
|
||||
{{ end }}
|
||||
</form>
|
||||
<button class="btn btn-lg btn-primary btn-block" type="button" id="btnPageSignIn">{{i18n .Lang "sign_in"}}</button>
|
||||
{{ if eq .AuthMode "db_auth" }}
|
||||
<div class="form-group">
|
||||
<div class="col-md-12">
|
||||
<button type="button" class="btn btn-link pull-right" id="btnForgot">{{i18n .Lang "forgot_password"}}</button>
|
||||
</div>
|
||||
</div>
|
||||
{{ end }}
|
||||
</form>
|
||||
</div>
|
||||
<link href="static/resources/css/sign-in.css" type="text/css" rel="stylesheet">
|
||||
<script src="static/resources/js/sign-in.js"></script>
|
Loading…
Reference in New Issue
Block a user