Merge remote-tracking branch 'upstream/job-service' into develop

This commit is contained in:
wemeya 2016-06-14 18:47:33 +08:00
commit a0e9d4e790
61 changed files with 2198 additions and 1354 deletions

View File

@ -1,3 +1,5 @@
sudo: true
language: go
go:
@ -5,36 +7,75 @@ 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:
- ./tests/hostcfg.sh
- cd Deploy
- ./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
- 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
- go test -v ./tests/apitests

View File

@ -19,6 +19,7 @@ import (
"encoding/json"
"fmt"
"net/http"
"strconv"
"github.com/astaxie/beego/validation"
"github.com/vmware/harbor/auth"
@ -120,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
}

View File

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

View File

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

View File

@ -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()
@ -122,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 {
@ -135,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
@ -147,18 +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(pa.policyID, "stop"); err != nil {
log.Errorf("failed to stop replication of %d: %v", pa.policyID, err)
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", pa.policyID)
log.Infof("try to stop replication of %d", id)
}
}()
}

View File

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

View File

@ -120,7 +120,7 @@ func (t *TargetAPI) Ping() {
// Get ...
func (t *TargetAPI) Get() {
id := t.getIDFromURL()
id := t.GetIDFromURL()
target, err := dao.GetRepTarget(id)
if err != nil {
@ -205,22 +205,22 @@ func (t *TargetAPI) Post() {
// Put ...
func (t *TargetAPI) Put() {
id := t.getIDFromURL()
id := t.GetIDFromURL()
originTarget, err := dao.GetRepTarget(id)
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 originTarget == nil {
if originalTarget == nil {
t.CustomAbort(http.StatusNotFound, http.StatusText(http.StatusNotFound))
}
target := &models.RepTarget{}
t.DecodeJSONReqAndValidate(target)
if target.Name != originTarget.Name {
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)
@ -246,7 +246,7 @@ func (t *TargetAPI) Put() {
// Delete ...
func (t *TargetAPI) Delete() {
id := t.getIDFromURL()
id := t.GetIDFromURL()
target, err := dao.GetRepTarget(id)
if err != nil {
@ -263,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 {
t.CustomAbort(http.StatusBadRequest, "invalid target ID")
}
id, err := strconv.ParseInt(idStr, 10, 64)
if err != nil || id <= 0 {
t.CustomAbort(http.StatusBadRequest, "invalid target ID")
}
return id
}

View File

@ -866,12 +866,6 @@ func TestUpdateRepTarget(t *testing.T) {
}
}
func TestGetAllRepTargets(t *testing.T) {
if _, err := GetAllRepTargets(); err != nil {
t.Fatalf("failed to get all targets: %v", err)
}
}
func TestFilterRepTargets(t *testing.T) {
targets, err := FilterRepTargets("test")
if err != nil {
@ -1155,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 {
@ -1163,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 {

View File

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

View File

@ -51,27 +51,22 @@ func UpdateRepTarget(target models.RepTarget) error {
return err
}
// GetAllRepTargets ...
func GetAllRepTargets() ([]*models.RepTarget, error) {
o := GetOrmer()
qs := o.QueryTable(&models.RepTarget{})
var targets []*models.RepTarget
_, err := qs.All(&targets)
return targets, err
}
// FilterRepTargets filters targets by name
func FilterRepTargets(name string) ([]*models.RepTarget, error) {
if len(name) == 0 {
return GetAllRepTargets()
}
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
sql := "select * from replication_target where name like ?"
if _, err := o.Raw(sql, "%"+name+"%").QueryRows(&targets); err != nil {
if _, err := o.Raw(sql, args).QueryRows(&targets); err != nil {
return nil, err
}
@ -103,31 +98,84 @@ func AddRepPolicy(policy models.RepPolicy) (int64, error) {
// GetRepPolicy ...
func GetRepPolicy(id int64) (*models.RepPolicy, error) {
o := GetOrmer()
p := models.RepPolicy{ID: id}
err := o.Read(&p)
if err == orm.ErrNoRows {
return nil, nil
sql := `select * from replication_policy where id = ?`
var policy models.RepPolicy
if err := o.Raw(sql, id).QueryRow(&policy); err != 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()
p := models.RepPolicy{Name: name}
err := o.Read(&p, "Name")
if err == orm.ErrNoRows {
return nil, nil
sql := `select * from replication_policy where name = ?`
var policy models.RepPolicy
if err := o.Raw(sql, name).QueryRow(&policy); err != nil {
return nil, err
}
return &p, err
return &policy, nil
}
// GetRepPolicyByProject ...
func GetRepPolicyByProject(projectID int64) ([]*models.RepPolicy, error) {
var res []*models.RepPolicy
o := GetOrmer()
_, err := o.QueryTable("replication_policy").Filter("project_id", projectID).All(&res)
return res, err
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 ...

View File

@ -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
@ -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:
@ -567,7 +567,7 @@ paths:
schema:
type: array
items:
$ref: '#/definitions/Repository'
type: string
400:
description: Invalid project ID.
403:
@ -719,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:
@ -736,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:
@ -794,6 +832,7 @@ definitions:
user_id:
type: integer
format: int32
description: The ID of the user.
username:
type: string
email:
@ -816,7 +855,7 @@ definitions:
new_password:
type: string
description: New password for marking as to be updated.
AccessLog:
AccessLogFilter:
type: object
properties:
username:
@ -825,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:
@ -855,7 +912,7 @@ 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:

View File

@ -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:"pk;column(log_id)" json:"LogId"`
UserID int `orm:"column(user_id)" json:"UserId"`
ProjectID int64 `orm:"column(project_id)" json:"ProjectId"`
RepoName string `orm:"column(repo_name)"`
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"`
}

View File

@ -21,19 +21,19 @@ import (
// Project holds the details of a project.
type Project struct {
ProjectID int64 `orm:"pk;column(project_id)" json:"ProjectId"`
OwnerID int `orm:"column(owner_id)" json:"OwnerId"`
Name string `orm:"column(name)"`
CreationTime time.Time `orm:"column(creation_time)"`
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"`
}

View File

@ -31,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"`

View File

@ -21,20 +21,21 @@ import (
// User holds the details of a user.
type User struct {
UserID int `orm:"pk;column(user_id)" json:"UserId"`
Username string `orm:"column(username)" json:"username"`
Email string `orm:"column(email)" json:"email"`
Password string `orm:"column(password)" json:"password"`
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 `orm:"rel(m2m)"`
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"`
}

View File

@ -13,7 +13,6 @@
limitations under the License.
*/
.footer {
margin-top: 60px;
width: 100%;
/* Set the fixed height of the footer here */
height: 60px;

View File

@ -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();

View File

@ -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,39 +32,39 @@ 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 = {
@ -134,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"){
@ -142,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(""));
@ -154,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');

View File

@ -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;
}
});
}
}
});

View File

@ -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";
});
});

View File

@ -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, "------").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(/[-]{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, "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>&nbsp;' +
'<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>&nbsp;' +
'<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");
}
});
})
}
});
})

View File

@ -24,7 +24,7 @@ jQuery(function(){
},
error: function(jqXhr){
if(jqXhr.status == 401)
return false;
return false;
}
}).exec();

View File

@ -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) + "&timestamp=" + 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) + "&timestamp=" + 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);
});
}
})
})

View File

@ -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();

View File

@ -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;
}
});
}
}
});

View File

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

View File

@ -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)
});
}
});
});

View File

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

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

View 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&#39;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) {
//}

View 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
}

View 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
}

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

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

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

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

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

View File

@ -0,0 +1,6 @@
package HarborAPI
type RoleParam struct {
Roles []int32 `json:"roles,omitempty"`
UserName string `json:"user_name,omitempty"`
}

View File

@ -0,0 +1,8 @@
package HarborAPI
import ()
type Search struct {
Projects []Project4Search `json:"project,omitempty"`
Repositories []Repository4Search `json:"repository,omitempty"`
}

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

View 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)
//}
}

View 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
View File

@ -0,0 +1,4 @@
#!/bin/bash
IP=`ip addr s eth0 |grep "inet "|awk '{print $2}' |awk -F "/" '{print $1}'`
#echo $IP
sed "s/reg.mydomain.com/$IP/" -i Deploy/harbor.cfg

36
tests/startuptest.go Normal file
View 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)
}
}
}

11
tests/testprepare.sh Executable file
View File

@ -0,0 +1,11 @@
IP=`ip addr s eth0 |grep "inet "|awk '{print $2}' |awk -F "/" '{print $1}'`
#echo $IP
docker pull hello-world
docker pull docker
#docker login -u admin -p Harbor12345 $IP
docker tag hello-world $IP/library/hello-world
docker push $IP/library/hello-world
docker tag docker $IP/library/docker
docker push $IP/library/docker

55
tests/userlogintest.go Normal file
View 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))
}

View File

@ -65,7 +65,8 @@ 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/:id([0-9]+)/enablement", &api.RepPolicyAPI{}, "put:UpdateEnablement")
beego.Router("/api/targets/", &api.TargetAPI{}, "get:List")
beego.Router("/api/targets/:id([0-9]+)", &api.TargetAPI{})

View File

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

View File

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

View File

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

View File

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

View File

@ -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">&times;</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">&times;</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>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -11,80 +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>
<li><a href="/language?lang=ja-JP">{{i18n .Lang "language_ja-JP"}}</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>&nbsp;&nbsp;{{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>&nbsp;&nbsp;{{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>&nbsp;&nbsp;{{i18n .Lang "add_user"}}</a></li>
{{ end }}
{{ end}}
<li><a id="aLogout" href="#"><span class="glyphicon glyphicon-log-in"></span>&nbsp;&nbsp;{{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">
&nbsp;<button type="button" class="btn btn-default" id="btnSignIn">{{i18n .Lang "sign_in"}}</button>
{{ if eq .SelfRegistration true }}
&nbsp;<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>&nbsp;&nbsp;{{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>&nbsp;&nbsp;{{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>&nbsp;&nbsp;{{i18n .Lang "add_user"}}</a></li>
{{ end }}
{{ end}}
<li><a id="aLogout" href="#"><span class="glyphicon glyphicon-log-in"></span>&nbsp;&nbsp;{{i18n .Lang "log_out"}}</a></li>
</ul>
</li>
</ul>
</div>
{{ else if eq .AuthMode "db_auth" }}
<div class="input-group">
&nbsp;<button type="button" class="btn btn-default" id="btnSignIn">{{i18n .Lang "sign_in"}}</button>
{{ if eq .SelfRegistration true }}
&nbsp;<button type="button" class="btn btn-success" id="btnSignUp">{{i18n .Lang "sign_up"}}</button>
{{ end }}
</div>
{{ else }}
<div class="input-group">
&nbsp;<button type="button" class="btn btn-default" id="btnSignIn">{{i18n .Lang "sign_in"}}</button>
</div>
{{ end }}
</div>
{{ else }}
<div class="input-group">
&nbsp;<button type="button" class="btn btn-default" id="btnSignIn">{{i18n .Lang "sign_in"}}</button>
</div>
{{ end }}
</form>
</div>
</nav>
</form>
</div>
</nav>

View File

@ -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">&times;</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">&times;</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>

View File

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