Merge remote-tracking branch 'upstream/new-version-of-ui' into new-version-of-ui

This commit is contained in:
xiahaoshawn 2016-05-23 11:18:57 +08:00
commit a205487857
58 changed files with 955 additions and 833 deletions

View File

@ -2,8 +2,8 @@ appname = registry
runmode = dev
[lang]
types = en-US|zh-CN|de-DE|ru-RU
names = en-US|zh-CN|de-DE|ru-RU
types = en-US|zh-CN
names = English|中文
[dev]
httpport = 80

View File

@ -113,23 +113,50 @@ func (p *ProjectAPI) Head() {
// Get ...
func (p *ProjectAPI) Get() {
queryProject := models.Project{UserID: p.userID}
var projectList []models.Project
projectName := p.GetString("project_name")
if len(projectName) > 0 {
queryProject.Name = "%" + projectName + "%"
projectName = "%" + projectName + "%"
}
var public int
var err error
isPublic := p.GetString("is_public")
if len(isPublic) > 0 {
public, err = strconv.Atoi(isPublic)
if err != nil {
log.Errorf("Error parsing public property: %d, error: %v", isPublic, err)
p.CustomAbort(http.StatusBadRequest, "invalid project Id")
}
}
isAdmin := false
if public == 1 {
projectList, err = dao.GetPublicProjects(projectName)
} else {
isAdmin, err = dao.IsAdminRole(p.userID)
if err != nil {
log.Errorf("Error occured in check admin, error: %v", err)
p.CustomAbort(http.StatusInternalServerError, "Internal error.")
}
if isAdmin {
projectList, err = dao.GetAllProjects(projectName)
} else {
projectList, err = dao.GetUserRelevantProjects(p.userID, projectName)
}
}
public, _ := p.GetInt("is_public")
queryProject.Public = public
projectList, err := dao.QueryProject(queryProject)
if err != nil {
log.Errorf("Error occurred in QueryProject, error: %v", err)
log.Errorf("Error occured in get projects info, error: %v", err)
p.CustomAbort(http.StatusInternalServerError, "Internal error.")
}
for i := 0; i < len(projectList); i++ {
if isProjectAdmin(p.userID, projectList[i].ProjectID) {
projectList[i].Togglable = true
if public != 1 {
if isAdmin {
projectList[i].Role = models.PROJECTADMIN
}
if projectList[i].Role == models.PROJECTADMIN {
projectList[i].Togglable = true
}
}
projectList[i].RepoCount = getRepoCountByProject(projectList[i].Name)
}
p.Data["json"] = projectList
p.ServeJSON()

View File

@ -55,13 +55,13 @@ func (s *SearchAPI) Get() {
var projects []models.Project
if isSysAdmin {
projects, err = dao.GetAllProjects()
projects, err = dao.GetAllProjects("")
if err != nil {
log.Errorf("failed to get all projects: %v", err)
s.CustomAbort(http.StatusInternalServerError, "internal error")
}
} else {
projects, err = dao.GetUserRelevantProjects(userID)
projects, err = dao.SearchProjects(userID)
if err != nil {
log.Errorf("failed to get user %d 's relevant projects: %v", userID, err)
s.CustomAbort(http.StatusInternalServerError, "internal error")

117
api/statistic.go Normal file
View File

@ -0,0 +1,117 @@
/*
Copyright (c) 2016 VMware, Inc. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package api
import (
"net/http"
"strings"
"github.com/vmware/harbor/dao"
"github.com/vmware/harbor/models"
svc_utils "github.com/vmware/harbor/service/utils"
"github.com/vmware/harbor/utils/log"
)
// StatisticAPI handles request to /api/statistics/
type StatisticAPI struct {
BaseAPI
userID int
}
//Prepare validates the URL and the user
func (s *StatisticAPI) Prepare() {
s.userID = s.ValidateUser()
}
// Get total projects and repos of the user
func (s *StatisticAPI) Get() {
isAdmin, err := dao.IsAdminRole(s.userID)
if err != nil {
log.Errorf("Error occured in check admin, error: %v", err)
s.CustomAbort(http.StatusInternalServerError, "Internal error.")
}
var projectList []models.Project
if isAdmin {
projectList, err = dao.GetAllProjects("")
} else {
projectList, err = dao.GetUserRelevantProjects(s.userID, "")
}
if err != nil {
log.Errorf("Error occured in QueryProject, error: %v", err)
s.CustomAbort(http.StatusInternalServerError, "Internal error.")
}
proMap := map[string]int{}
proMap["my_project_count"] = 0
proMap["my_repo_count"] = 0
proMap["public_project_count"] = 0
proMap["public_repo_count"] = 0
var publicProjects []models.Project
publicProjects, err = dao.GetPublicProjects("")
if err != nil {
log.Errorf("Error occured in QueryPublicProject, error: %v", err)
s.CustomAbort(http.StatusInternalServerError, "Internal error.")
}
proMap["public_project_count"] = len(publicProjects)
for i := 0; i < len(publicProjects); i++ {
proMap["public_repo_count"] += getRepoCountByProject(publicProjects[i].Name)
}
if isAdmin {
proMap["total_project_count"] = len(projectList)
proMap["total_repo_count"] = getTotalRepoCount()
}
for i := 0; i < len(projectList); i++ {
if isAdmin {
projectList[i].Role = models.PROJECTADMIN
}
if projectList[i].Role == models.PROJECTADMIN || projectList[i].Role == models.DEVELOPER ||
projectList[i].Role == models.GUEST {
proMap["my_project_count"]++
proMap["my_repo_count"] += getRepoCountByProject(projectList[i].Name)
}
}
s.Data["json"] = proMap
s.ServeJSON()
}
//getReposByProject returns repo numbers of specified project
func getRepoCountByProject(projectName string) int {
repoList, err := svc_utils.GetRepoFromCache()
if err != nil {
log.Errorf("Failed to get repo from cache, error: %v", err)
return 0
}
var resp int
if len(projectName) > 0 {
for _, r := range repoList {
if strings.Contains(r, "/") && r[0:strings.LastIndex(r, "/")] == projectName {
resp++
}
}
return resp
}
return 0
}
//getTotalRepoCount returns total repo count
func getTotalRepoCount() int {
repoList, err := svc_utils.GetRepoFromCache()
if err != nil {
log.Errorf("Failed to get repo from cache, error: %v", err)
return 0
}
return len(repoList)
}

View File

@ -8,7 +8,6 @@ import (
"github.com/astaxie/beego"
"github.com/beego/i18n"
"github.com/vmware/harbor/dao"
"github.com/vmware/harbor/utils/log"
)
@ -74,6 +73,7 @@ func (b *BaseController) Prepare() {
b.Data["Lang"] = curLang.Lang
b.Data["CurLang"] = curLang.Name
b.Data["RestLangs"] = restLangs
b.Data["SupportLanguages"] = supportLanguages
authMode := strings.ToLower(os.Getenv("AUTH_MODE"))
if authMode == "" {
@ -82,28 +82,6 @@ func (b *BaseController) Prepare() {
b.AuthMode = authMode
b.Data["AuthMode"] = b.AuthMode
selfRegistration := strings.ToLower(os.Getenv("SELF_REGISTRATION"))
if selfRegistration == "on" {
b.SelfRegistration = true
}
sessionUserID := b.GetSession("userId")
if sessionUserID != nil {
b.Data["Username"] = b.GetSession("username")
b.Data["UserId"] = sessionUserID.(int)
var err error
b.IsAdmin, err = dao.IsAdminRole(sessionUserID.(int))
if err != nil {
log.Errorf("Error occurred in IsAdminRole:%v", err)
b.CustomAbort(http.StatusInternalServerError, "Internal error.")
}
}
b.Data["IsAdmin"] = b.IsAdmin
b.Data["SelfRegistration"] = b.SelfRegistration
}
func (bc *BaseController) Forward(title, templateName string) {
@ -120,6 +98,27 @@ func (bc *BaseController) Forward(title, templateName string) {
var langTypes []*langType
type CommonController struct {
BaseController
}
func (cc *CommonController) Render() error {
return nil
}
func (cc *CommonController) LogOut() {
cc.DestroySession()
}
func (cc *CommonController) SwitchLanguage() {
lang := cc.GetString("lang")
if _, exist := supportLanguages[lang]; exist {
cc.SetSession("lang", lang)
cc.Data["Lang"] = lang
}
cc.Redirect(cc.Ctx.Request.Header.Get("Referer"), http.StatusFound)
}
func init() {
//conf/app.conf -> os.Getenv("config_path")

View File

@ -0,0 +1,37 @@
package ng
import (
"net/http"
"github.com/vmware/harbor/dao"
"github.com/vmware/harbor/models"
"github.com/vmware/harbor/utils/log"
)
type NavigationHeaderController struct {
BaseController
}
func (nhc *NavigationHeaderController) Get() {
sessionUserID := nhc.GetSession("userId")
var hasLoggedIn bool
var isAdmin int
if sessionUserID != nil {
hasLoggedIn = true
userID := sessionUserID.(int)
u, err := dao.GetUser(models.User{UserID: userID})
if err != nil {
log.Errorf("Error occurred in GetUser, error: %v", err)
nhc.CustomAbort(http.StatusInternalServerError, "Internal error.")
}
if u == nil {
log.Warningf("User was deleted already, user id: %d, canceling request.", userID)
nhc.CustomAbort(http.StatusUnauthorized, "")
}
isAdmin = u.HasAdminRole
}
nhc.Data["HasLoggedIn"] = hasLoggedIn
nhc.Data["IsAdmin"] = isAdmin
nhc.TplName = "ng/navigation-header.htm"
nhc.Render()
}

View File

@ -0,0 +1,37 @@
package ng
import (
"net/http"
"github.com/vmware/harbor/dao"
"github.com/vmware/harbor/models"
"github.com/vmware/harbor/utils/log"
)
type OptionalMenuController struct {
BaseController
}
func (omc *OptionalMenuController) Get() {
sessionUserID := omc.GetSession("userId")
var hasLoggedIn bool
if sessionUserID != nil {
hasLoggedIn = true
userID := sessionUserID.(int)
u, err := dao.GetUser(models.User{UserID: userID})
if err != nil {
log.Errorf("Error occurred in GetUser, error: %v", err)
omc.CustomAbort(http.StatusInternalServerError, "Internal error.")
}
if u == nil {
log.Warningf("User was deleted already, user id: %d, canceling request.", userID)
omc.CustomAbort(http.StatusUnauthorized, "")
}
omc.Data["Username"] = u.Username
}
omc.Data["HasLoggedIn"] = hasLoggedIn
omc.TplName = "ng/optional-menu.htm"
omc.Render()
}

View File

@ -14,18 +14,6 @@ import (
"github.com/vmware/harbor/utils/log"
)
type CommonController struct {
BaseController
}
func (cc *CommonController) Render() error {
return nil
}
func (cc *CommonController) LogOut() {
cc.DestroySession()
}
type messageDetail struct {
Hint string
URL string

9
controllers/ng/search.go Normal file
View File

@ -0,0 +1,9 @@
package ng
type SearchController struct {
BaseController
}
func (sc *SearchController) Get() {
sc.Forward("Search", "search.htm")
}

View File

@ -353,7 +353,7 @@ func TestChangeUserPasswordWithIncorrectOldPassword(t *testing.T) {
}
func TestQueryRelevantProjectsWhenNoProjectAdded(t *testing.T) {
projects, err := GetUserRelevantProjects(currentUser.UserID)
projects, err := SearchProjects(currentUser.UserID)
if err != nil {
t.Errorf("Error occurred in QueryRelevantProjects: %v", err)
}
@ -572,39 +572,6 @@ func TestIsProjectPublic(t *testing.T) {
}
}
func TestQueryProject(t *testing.T) {
query1 := models.Project{
UserID: 1,
}
projects, err := QueryProject(query1)
if err != nil {
t.Errorf("Error in Query Project: %v, query: %+v", err, query1)
}
if len(projects) != 2 {
t.Errorf("Expecting get 2 projects, but actual: %d, the list: %+v", len(projects), projects)
}
query2 := models.Project{
Public: 1,
}
projects, err = QueryProject(query2)
if err != nil {
t.Errorf("Error in Query Project: %v, query: %+v", err, query2)
}
if len(projects) != 1 {
t.Errorf("Expecting get 1 project, but actual: %d, the list: %+v", len(projects), projects)
}
query3 := models.Project{
UserID: 9,
}
projects, err = QueryProject(query3)
if err != nil {
t.Errorf("Error in Query Project: %v, query: %+v", err, query3)
}
if len(projects) != 0 {
t.Errorf("Expecting get 0 project, but actual: %d, the list: %+v", len(projects), projects)
}
}
func TestGetUserProjectRoles(t *testing.T) {
r, err := GetUserProjectRoles(currentUser.UserID, currentProject.ProjectID)
if err != nil {
@ -632,20 +599,20 @@ func TestProjectPermission(t *testing.T) {
}
func TestGetUserRelevantProjects(t *testing.T) {
projects, err := GetUserRelevantProjects(currentUser.UserID)
projects, err := GetUserRelevantProjects(currentUser.UserID, "")
if err != nil {
t.Errorf("Error occurred in GetUserRelevantProjects: %v", err)
}
if len(projects) != 2 {
t.Errorf("Expected length of relevant projects is 2, but actual: %d, the projects: %+v", len(projects), projects)
if len(projects) != 1 {
t.Errorf("Expected length of relevant projects is 1, but actual: %d, the projects: %+v", len(projects), projects)
}
if projects[1].Name != projectName {
if projects[0].Name != projectName {
t.Errorf("Expected project name in the list: %s, actual: %s", projectName, projects[1].Name)
}
}
func TestGetAllProjects(t *testing.T) {
projects, err := GetAllProjects()
projects, err := GetAllProjects("")
if err != nil {
t.Errorf("Error occurred in GetAllProjects: %v", err)
}
@ -657,6 +624,19 @@ func TestGetAllProjects(t *testing.T) {
}
}
func TestGetPublicProjects(t *testing.T) {
projects, err := GetPublicProjects("")
if err != nil {
t.Errorf("Error occurred in getProjects: %v", err)
}
if len(projects) != 1 {
t.Errorf("Expected length of projects is 1, but actual: %d, the projects: %+v", len(projects), projects)
}
if projects[0].Name != "library" {
t.Errorf("Expected project name in the list: %s, actual: %s", "library", projects[0].Name)
}
}
func TestAddProjectMember(t *testing.T) {
err := AddProjectMember(currentProject.ProjectID, 1, models.DEVELOPER)
if err != nil {

View File

@ -79,42 +79,6 @@ func IsProjectPublic(projectName string) bool {
return project.Public == 1
}
// QueryProject querys the projects based on publicity and user, disregarding the names etc.
func QueryProject(query models.Project) ([]models.Project, error) {
o := orm.NewOrm()
sql := `select distinct
p.project_id, p.owner_id, p.name,p.creation_time, p.update_time, p.public
from project p
left join project_member pm on p.project_id = pm.project_id
where p.deleted = 0 `
queryParam := make([]interface{}, 1)
if query.Public == 1 {
sql += ` and p.public = ?`
queryParam = append(queryParam, query.Public)
} else if isAdmin, _ := IsAdminRole(query.UserID); isAdmin == false {
sql += ` and (pm.user_id = ?) `
queryParam = append(queryParam, query.UserID)
}
if query.Name != "" {
sql += " and p.name like ? "
queryParam = append(queryParam, query.Name)
}
sql += " order by p.name "
var r []models.Project
_, err := o.Raw(sql, queryParam).QueryRows(&r)
if err != nil {
return nil, err
}
return r, nil
}
//ProjectExists returns whether the project exists according to its name of ID.
func ProjectExists(nameOrID interface{}) (bool, error) {
o := orm.NewOrm()
@ -208,11 +172,11 @@ func ToggleProjectPublicity(projectID int64, publicity int) error {
return err
}
// GetUserRelevantProjects returns a project list,
// SearchProjects returns a project list,
// which satisfies the following conditions:
// 1. the project is not deleted
// 2. the prject is public or the user is a member of the project
func GetUserRelevantProjects(userID int) ([]models.Project, error) {
func SearchProjects(userID int) ([]models.Project, error) {
o := orm.NewOrm()
sql := `select distinct p.project_id, p.name, p.public
from project p
@ -228,14 +192,66 @@ func GetUserRelevantProjects(userID int) ([]models.Project, error) {
return projects, nil
}
// GetAllProjects returns all projects which are not deleted
func GetAllProjects() ([]models.Project, error) {
// GetUserRelevantProjects returns the projects of the user which are not deleted and name like projectName
func GetUserRelevantProjects(userID int, projectName string) ([]models.Project, error) {
o := orm.NewOrm()
sql := `select project_id, name, public
sql := `select distinct
p.project_id, p.owner_id, p.name,p.creation_time, p.update_time, p.public, pm.role role
from project p
left join project_member pm on p.project_id = pm.project_id
where p.deleted = 0 and pm.user_id= ?`
queryParam := make([]interface{}, 1)
queryParam = append(queryParam, userID)
if projectName != "" {
sql += " and p.name like ? "
queryParam = append(queryParam, projectName)
}
sql += " order by p.name "
var r []models.Project
_, err := o.Raw(sql, queryParam).QueryRows(&r)
if err != nil {
return nil, err
}
return r, nil
}
//GetPublicProjects returns all public projects whose name like projectName
func GetPublicProjects(projectName string) ([]models.Project, error) {
publicProjects, err := getProjects(1, projectName)
if err != nil {
return nil, err
}
return publicProjects, nil
}
// GetAllProjects returns all projects which are not deleted and name like projectName
func GetAllProjects(projectName string) ([]models.Project, error) {
allProjects, err := getProjects(0, projectName)
if err != nil {
return nil, err
}
return allProjects, nil
}
func getProjects(public int, projectName string) ([]models.Project, error) {
o := orm.NewOrm()
sql := `select project_id, owner_id, creation_time, update_time, name, public
from project
where deleted = 0`
queryParam := make([]interface{}, 1)
if public == 1 {
sql += " and public = ? "
queryParam = append(queryParam, public)
}
if len(projectName) > 0 {
sql += " and name like ? "
queryParam = append(queryParam, projectName)
}
sql += " order by name "
var projects []models.Project
if _, err := o.Raw(sql).QueryRows(&projects); err != nil {
log.Debugf("sql xxx", sql)
if _, err := o.Raw(sql, queryParam).QueryRows(&projects); err != nil {
return nil, err
}
return projects, nil

View File

@ -34,4 +34,6 @@ type Project struct {
Togglable bool
UpdateTime time.Time `orm:"update_time" json:"update_time"`
Role int `json:"role_id"`
RepoCount int `json:"repo_count"`
}

View File

@ -12,7 +12,3 @@
text-align: left;
}
.single {
margin-right: 0;
}

View File

@ -21,11 +21,7 @@ nav .container-custom {
}
.navbar-brand > img {
height: 60px;
width: 100px;
margin-top: -30px;
filter: brightness(0) invert(1);
-webkit-filter: brightness(0) invert(1);
}
.navbar-form {
@ -40,21 +36,21 @@ nav .container-custom {
}
.nav-custom li {
float: left;
padding: 10px 0 0 0;
margin-right: 12px;
list-style: none;
display: inline-block;
float: left;
padding: 10px 0 0 0;
margin-right: 12px;
list-style: none;
display: inline-block;
}
.nav-custom li a {
font-size: 14px;
color: #FFFFFF;
text-decoration: none;
font-size: 14px;
color: #FFFFFF;
text-decoration: none;
}
.nav-custom .active {
border-bottom: 3px solid #EFEFEF;
border-bottom: 3px solid #EFEFEF;
}
.dropdown {

View File

@ -0,0 +1,9 @@
.search-result {
min-height: 200px;
max-height: 200px;
overflow-y: auto;
}
.search-result li {
margin-bottom: 15px;
}

BIN
static/ng/resources/img/Harbor_Logo_rec.png Executable file → Normal file

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.4 KiB

After

Width:  |  Height:  |  Size: 3.6 KiB

View File

@ -1,10 +1,10 @@
<div class="switch-pane-drop-down" ng-show="vm.isOpen">
<div id="retrievePane" class="switch-pane-drop-down" ng-show="vm.isOpen">
<div class="row">
<div class="col-xs-12 col-md-12">
<div class="panel panel-default">
<div class="form-inline search-projects">
<div class="input-group">
<input type="text" class="form-control search-icon" placeholder="" ng-model="vm.filterInput" size="30">
<input type="text" id="retrieveFilter" class="form-control search-icon" placeholder="" ng-model="vm.filterInput" size="30">
</div>
</div>
<h5 class="page-header">//vm.projectType | tr//: <span class="badge">//vm.resultCount//</span></h5>

View File

@ -12,6 +12,7 @@
var vm = this;
vm.projectName = '';
vm.isOpen = false;
if($route.current.params.is_public) {
vm.isPublic = $route.current.params.is_public === 'true' ? 1 : 0;
@ -19,8 +20,6 @@
}
vm.retrieve = retrieve;
vm.retrieve();
vm.checkProjectMember = checkProjectMember;
$scope.$watch('vm.selectedProject', function(current, origin) {
@ -33,7 +32,7 @@
vm.selectItem = selectItem;
$scope.$watch('vm.publicity', function(current, origin) {
vm.publicity = current ? 1 : 0;
vm.publicity = current ? true : false;
vm.isPublic = vm.publicity ? 1 : 0;
vm.projectType = (vm.isPublic === 1) ? 'public_projects' : 'my_projects';
vm.retrieve();
@ -64,9 +63,8 @@
}
});
}
$location.search('project_id', vm.selectedProject.ProjectId);
vm.checkProjectMember(vm.selectedProject.ProjectId);
$location.search('project_id', vm.selectedProject.ProjectId);
vm.resultCount = vm.projects.length;
$scope.$watch('vm.filterInput', function(current, origin) {
@ -114,6 +112,7 @@
'publicity': '=',
'isProjectMember': '='
},
link: link,
replace: true,
controller: RetrieveProjectsController,
bindToController: true,
@ -122,6 +121,22 @@
return directive;
function link(scope, element, attrs, ctrl) {
$(document).on('click', clickHandler);
function clickHandler(e) {
var targetId = $(e.target).attr('id');
if(targetId === 'switchPane' ||
targetId === 'retrievePane' ||
targetId === 'retrieveFilter') {
return;
}else{
ctrl.isOpen = false;
scope.$apply();
}
}
}
}
})();

View File

@ -1,5 +1,5 @@
<div class="switch-pane-projects" ng-switch="vm.isOpen">
<a href="javascript:void(0);" ng-click="vm.switchPane()">//vm.projectName//</a>
<a id="switchPane" href="javascript:void(0);" ng-click="vm.switchPane()" >//vm.projectName//</a>
<span ng-switch-default class="glyphicon glyphicon-triangle-right" style="font-size: 12px;"></span>
<span ng-switch-when="true" class="glyphicon glyphicon-triangle-bottom" style="font-size: 12px;"></span>
</div>

View File

@ -10,11 +10,7 @@
function SwitchPaneProjectsController($scope) {
var vm = this;
$scope.$on('isOpen', function(e, val){
vm.isOpen = val;
});
$scope.$watch('vm.selectedProject', function(current, origin) {
if(current){
vm.projectName = current.Name;
@ -40,21 +36,16 @@
templateUrl: '/static/ng/resources/js/components/details/switch-pane-projects.directive.html',
replace: true,
scope: {
'selectedProject': '=',
'isOpen': '='
'isOpen': '=',
'selectedProject': '='
},
link: link,
controller: SwitchPaneProjectsController,
controllerAs: 'vm',
bindToController: true
}
return directive;
function link(scope, element, attrs, ctrl) {
}
}
})();

View File

@ -6,28 +6,32 @@
.module('harbor.optional.menu')
.directive('optionalMenu', optionalMenu);
OptionalMenuController.$inject = ['$scope', '$window', '$cookies', 'I18nService', 'LogOutService'];
OptionalMenuController.$inject = ['$window', 'I18nService', 'LogOutService', 'currentUser', '$timeout'];
function OptionalMenuController($scope, $window, $cookies, I18nService, LogOutService) {
function OptionalMenuController($window, I18nService, LogOutService, currentUser, $timeout) {
var vm = this;
vm.currentLanguage = I18nService().getCurrentLanguage();
vm.setLanguage = setLanguage;
vm.languageName = I18nService().getLanguageName(vm.currentLanguage);
console.log('current language:' + I18nService().getCurrentLanguage());
vm.user = currentUser.get();
vm.setLanguage = setLanguage;
vm.logOut = logOut;
function setLanguage(name) {
I18nService().setCurrentLanguage(name);
$window.location.reload();
function setLanguage(language) {
I18nService().setCurrentLanguage(language);
$window.location.href = '/ng/language?lang=' + language;
}
function logOut() {
LogOutService()
.success(logOutSuccess)
.error(logOutFailed);
}
function logOutSuccess(data, status) {
currentUser.unset();
I18nService().unset();
$window.location.href= '/ng';
}
function logOutFailed(data, status) {
@ -38,24 +42,13 @@
function optionalMenu() {
var directive = {
'restrict': 'E',
'templateUrl': '/static/ng/resources/js/components/optional-menu/optional-menu.directive.html',
'link': link,
'templateUrl': '/ng/optional_menu',
'scope': true,
'controller': OptionalMenuController,
'controllerAs': 'vm',
'bindToController': true
};
return directive;
function link(scope, element, attrs, ctrl) {
ctrl.isLoggedIn = false;
scope.$on('currentUser', function(e, val) {
if(val != null) {
ctrl.isLoggedIn = true;
ctrl.username = val.username;
}
scope.$apply();
});
}
}
})();

View File

@ -74,16 +74,11 @@
'projectId': '=',
'reload': '&'
},
'link': link,
'controller': EditProjectMemberController,
'controllerAs': 'vm',
'bindToController': true
};
return directive;
function link(scope, element, attrs, ctrl) {
}
}
})();

View File

@ -18,7 +18,7 @@
<th width="30%">// 'username' | tr //</th><th width="40%">// 'role' | tr //</th><th width="30%">// 'operation' | tr //</th>
</thead>
<tbody>
<tr ng-repeat="pr in vm.projectMembers" edit-project-member username="pr.username" project-id="vm.projectId" user-id="pr.UserId" current-user-id="vm.currentUser.UserId" role-name="pr.Rolename" reload='vm.search({projectId: vm.projectId, username: vm.username})'></tr>
<tr ng-repeat="pr in vm.projectMembers" edit-project-member username="pr.username" project-id="vm.projectId" user-id="pr.UserId" current-user-id="vm.user.UserId" role-name="pr.Rolename" reload='vm.search({projectId: vm.projectId, username: vm.username})'></tr>
</tbody>
</table>
</div>

View File

@ -10,24 +10,19 @@
function ListProjectMemberController($scope, ListProjectMemberService, $routeParams, currentUser) {
var vm = this;
vm.isOpen = false;
vm.isOpen = false;
vm.search = search;
vm.addProjectMember = addProjectMember;
vm.retrieve = retrieve;
vm.projectId = $routeParams.project_id;
vm.username = "";
vm.currentUser = {};
vm.retrieve();
function search(e) {
vm.projectId = e.projectId;
vm.username = e.username;
console.log('project_id:' + e.projectId);
retrieve();
}
@ -46,7 +41,7 @@
}
function getProjectMemberComplete(response) {
vm.currentUser = currentUser.get();
vm.user = currentUser.get();
vm.projectMembers = response.data;
}
@ -62,17 +57,11 @@
templateUrl: '/static/ng/resources/js/components/project-member/list-project-member.directive.html',
replace: true,
scope: true,
link: link,
controller: ListProjectMemberController,
controllerAs: 'vm',
bindToController: true
}
}
return directive;
function link(scope, element, attrs, ctrl) {
}
}
})();

View File

@ -5,12 +5,23 @@
.module('harbor.repository')
.directive('listRepository', listRepository);
ListRepositoryController.$inject = ['$scope', 'ListRepositoryService', 'DeleteRepositoryService', '$routeParams', '$filter', 'trFilter'];
ListRepositoryController.$inject = ['$scope', 'ListRepositoryService', 'DeleteRepositoryService', '$routeParams', '$filter', 'trFilter', '$location'];
function ListRepositoryController($scope, ListRepositoryService, DeleteRepositoryService, $routeParams, $filter, trFilter) {
function ListRepositoryController($scope, ListRepositoryService, DeleteRepositoryService, $routeParams, $filter, trFilter, $location) {
var vm = this;
vm.filterInput = '';
var hashValue = $location.hash();
if(hashValue) {
var slashIndex = hashValue.indexOf('/');
if(slashIndex >=0) {
vm.filterInput = hashValue.substring(slashIndex + 1);
}else{
vm.filterInput = hashValue;
}
}
vm.filterInput = "";
vm.retrieve = retrieve;
vm.projectId = $routeParams.project_id;
vm.tagCount = {};
@ -84,18 +95,13 @@
restrict: 'E',
templateUrl: '/static/ng/resources/js/components/repository/list-repository.directive.html',
replace: true,
link: 'link',
controller: ListRepositoryController,
controllerAs: 'vm',
bindToController: true
}
return directive;
function link(scope, element, attrs, ctrl) {
}
}
})();

View File

@ -0,0 +1,5 @@
<form class="navbar-form pull-right" role="search">
<div class="form-group">
<input type="text" class="form-control search-icon" ng-model="vm.searchInput" placeholder="// 'projects_or_repositories' | tr //" size="35">
</div>
</form>

View File

@ -0,0 +1,54 @@
(function() {
'use strict';
angular
.module('harbor.search')
.directive('searchInput', searchInput);
SearchInputController.$inject = ['$scope', '$location', '$window'];
function SearchInputController($scope, $location, $window) {
var vm = this;
vm.searchFor = searchFor;
function searchFor(searchContent) {
$location
.path('/ng/search')
.search({'q': searchContent});
$window.location.href = $location.url();
}
}
function searchInput() {
var directive = {
'restrict': 'E',
'templateUrl': '/static/ng/resources/js/components/search/search-input.directive.html',
'scope': {
'searchInput': '=',
},
'link': link,
'controller': SearchInputController,
'controllerAs': 'vm',
'bindToController': true
};
return directive;
function link(scope, element, attrs, ctrl) {
element
.find('input[type="text"]')
.on('keydown', keydownHandler);
function keydownHandler(e) {
if(e.keyCode === 13) {
ctrl.searchFor($(this).val());
}
}
}
}
})();

View File

@ -1,9 +1,9 @@
<form name="form" class="form-horizontal css-form" ng-submit="form.$valid" novalidate>
<div class="form-group">
<div class="col-sm-offset-1 col-sm-10">
<input id="username" type="text" class="form-control" placeholder="// 'username_email' | tr //" name="uPrincipal" ng-model-options="{ debounce: 250 }" ng-change="vm.reset()" ng-model="user.principal" required>
<input id="username" type="text" class="form-control" placeholder="// 'username_email' | tr //" name="uPrincipal" ng-change="vm.reset()" ng-model="user.principal" required>
<div class="error-message">
<div ng-messages="form.uPrincipal.$error" ng-if="form.uPrincipal.$touched">
<div ng-messages="form.uPrincipal.$error" ng-if="form.uPrincipal.$touched || form.$submitted">
<span ng-message="required">// 'username_is_required' | tr //</span>
</div>
</div>
@ -11,9 +11,9 @@
</div>
<div class="form-group">
<div class="col-sm-offset-1 col-sm-10">
<input type="password" class="form-control" placeholder="// 'password' | tr //" name="uPassword" ng-model-options="{ debounce: 250 }" ng-change="vm.reset()" ng-model="user.password" required>
<input type="password" class="form-control" placeholder="// 'password' | tr //" name="uPassword" ng-change="vm.reset()" ng-model="user.password" required>
<div class="error-message">
<div ng-messages="form.uPassword.$error" ng-if="form.uPassword.$touched">
<div ng-messages="form.uPassword.$error" ng-if="form.uPassword.$touched || form.$submitted">
<span ng-message="required">// 'password_is_required' | tr //</span>
</div>
<span ng-show="vm.hasError">// vm.errorMessage | tr //</span>
@ -23,7 +23,7 @@
<div class="form-group">
<div class="col-sm-offset-1 col-sm-10">
<div class="pull-right">
<button class="btn btn-default" ng-disabled="form.$invalid" ng-click="vm.doSignIn(user)">// 'sign_in' | tr //</button>
<button class="btn btn-default" ng-click="vm.doSignIn(user)">// 'sign_in' | tr //</button>
<button class="btn btn-success" ng-click="vm.doSignUp()">// 'sign_up' | tr //</button>
</div>
</div>

View File

@ -9,9 +9,22 @@
.config(function($httpProvider) {
$httpProvider.defaults.headers.common = {'Accept': 'application/json, text/javascript, */*; q=0.01'};
})
.factory('getParameterByName', getParameterByName)
.filter('dateL', localizeDate)
.filter('tr', tr);
function getParameterByName() {
return get;
function get(name, url) {
name = name.replace(/[\[\]]/g, "\\$&");
var regex = new RegExp("[?&]" + name + "(=([^&#]*)|&|#|$)"),
results = regex.exec(url);
if (!results) return null;
if (!results[2]) return '';
return decodeURIComponent(results[2].replace(/\+/g, " "));
}
}
function localizeDate() {
return filter;

View File

@ -1,36 +1,40 @@
(function() {
'use strict';
angular
.module('harbor.app')
.module('harbor.app')
.factory('currentUser', currentUser)
.factory('projectMember', projectMember);
function currentUser() {
var currentUser;
.factory('currentProjectMember', currentProjectMember);
currentUser.$inject = ['$cookies', '$timeout'];
function currentUser($cookies, $timeout) {
return {
set: function(user) {
currentUser = user;
console.log('set currentUser:' + currentUser);
$cookies.putObject('user', user, {'path': '/'});
},
get: function() {
console.log('get currentUser:' + currentUser);
return currentUser;
return $cookies.getObject('user');
},
unset: function() {
$cookies.remove('user', {'path': '/'});
}
}
}
function projectMember() {
var projectMember;
currentProjectMember.$inject = ['$cookies'];
function currentProjectMember($cookies) {
return {
set: function(member) {
projectMember = member;
console.log('set projectMember:');
console.log(projectMember);
$cookies.putObject('member', member, {'path': '/'});
},
get: function() {
console.log('get projectMember:');
console.log(projectMember);
return projectMember;
return $cookies.getObject('member');
},
unset: function() {
$cookies.remove('member', {'path': '/'});
}
}
}

View File

@ -21,6 +21,7 @@
'harbor.layout.system.management',
'harbor.layout.log',
'harbor.layout.admin.option',
'harbor.layout.search',
'harbor.services.i18n',
'harbor.services.project',
'harbor.services.user',

View File

@ -5,23 +5,17 @@
angular
.module('harbor.details')
.controller('DetailsController', DetailsController);
DetailsController.$inject = ['$scope', '$location', '$routeParams'];
function DetailsController($scope, $location, $routeParams) {
function DetailsController() {
var vm = this;
vm.isOpen = false;
vm.publicity = false;
vm.isProjectMember = true;
vm.closeRetrievePane = closeRetrievePane;
vm.isProjectMember = false;
vm.togglePublicity = togglePublicity;
function closeRetrievePane() {
$scope.$broadcast('isOpen', false);
}
function togglePublicity(e) {
vm.publicity = e.publicity;
console.log('current project publicity:' + vm.publicity);
}
}

View File

@ -6,10 +6,14 @@
.module('harbor.layout.header')
.controller('HeaderController', HeaderController);
HeaderController.$inject = ['$scope', 'I18nService', '$cookies', '$window'];
HeaderController.$inject = ['$scope', '$window', 'getParameterByName'];
function HeaderController($scope, I18nService, $cookies, $window) {
function HeaderController($scope, $window, getParameterByName) {
var vm = this;
if($window.location.search) {
vm.searchInput = getParameterByName('q', $window.location.search);
console.log('vm.searchInput at header:' + vm.searchInput);
}
}
})();

View File

@ -18,14 +18,6 @@
});
vm.url = $location.url();
vm.clickTab = clickTab;
function clickTab() {
console.log("triggered clickTab of Controller.");
vm.isOpen = false;
$scope.$apply();
}
}
function navigationDetails() {
@ -34,7 +26,6 @@
templateUrl: '/static/ng/resources/js/layout/navigation/navigation-details.directive.html',
link: link,
scope: {
'isOpen': '=',
'selectedProject': '='
},
replace: true,
@ -66,7 +57,6 @@
function click(event) {
element.find('a').removeClass('active');
$(event.target).addClass('active');
ctrl.clickTab();
}
}

View File

@ -6,24 +6,17 @@
.module('harbor.layout.navigation')
.directive('navigationHeader', navigationHeader);
NavigationHeaderController.$inject = ['$window', '$scope'];
NavigationHeaderController.$inject = ['$window', '$scope', 'currentUser', '$timeout'];
function NavigationHeaderController($window, $scope) {
function NavigationHeaderController($window, $scope, currentUser, $timeout) {
var vm = this;
vm.url = $window.location.pathname;
vm.isAdmin = false;
$scope.$on('currentUser', function(e, val) {
if(val.HasAdminRole === 1) {
vm.isAdmin = true;
}
$scope.$apply();
});
vm.url = $window.location.pathname;
}
function navigationHeader() {
var directive = {
restrict: 'E',
templateUrl: '/static/ng/resources/js/layout/navigation/navigation-header.directive.html',
templateUrl: '/ng/navigation_header',
link: link,
scope: true,
controller: NavigationHeaderController,
@ -34,12 +27,11 @@
return directive;
function link(scope, element, attrs, ctrl) {
element.find('a').removeClass('active');
var visited = ctrl.url;
if (visited != "/ng") {
element.find('a[href*="' + visited + '"]').addClass('active');
}
element.on('click', click);
element.find('a').on('click', click);
function click(event) {
element.find('a').removeClass('active');
$(event.target).not('span').addClass('active');

View File

@ -5,9 +5,7 @@
angular
.module('harbor.layout.project.member')
.controller('ProjectMemberController', ProjectMemberController);
ProjectMemberController.$inject = ['$scope'];
function ProjectMemberController($scope) {
}

View File

@ -6,9 +6,9 @@
.module('harbor.layout.project')
.controller('ProjectController', ProjectController);
ProjectController.$inject = ['$scope', 'ListProjectService'];
ProjectController.$inject = ['$scope', 'ListProjectService', '$timeout', 'currentUser'];
function ProjectController($scope, ListProjectService) {
function ProjectController($scope, ListProjectService, $timeout, currentUser) {
var vm = this;
vm.isOpen = false;
@ -21,8 +21,8 @@
vm.showAddButton = showAddButton;
vm.togglePublicity = togglePublicity;
$scope.$on('currentUser', function(e, val) {
vm.currentUser = val;
$timeout(function() {
vm.user = currentUser.get();
});
vm.retrieve();

View File

@ -6,18 +6,9 @@
.module('harbor.layout.reset.password')
.controller('ResetPasswordController', ResetPasswordController);
ResetPasswordController.$inject = ['$location', 'ResetPasswordService', '$window'];
ResetPasswordController.$inject = ['$location', 'ResetPasswordService', '$window', 'getParameterByName'];
function getParameterByName(name, url) {
name = name.replace(/[\[\]]/g, "\\$&");
var regex = new RegExp("[?&]" + name + "(=([^&#]*)|&|#|$)"),
results = regex.exec(url);
if (!results) return null;
if (!results[2]) return '';
return decodeURIComponent(results[2].replace(/\+/g, " "));
}
function ResetPasswordController($location, ResetPasswordService, $window) {
function ResetPasswordController($location, ResetPasswordService, $window, getParameterByName) {
var vm = this;
vm.resetUuid = getParameterByName('reset_uuid', $location.absUrl());

View File

@ -0,0 +1,31 @@
(function() {
'use strict';
angular
.module('harbor.layout.search')
.controller('SearchController', SearchController);
SearchController.$inject = ['$window', 'SearchService'];
function SearchController($window, SearchService) {
var vm = this;
if($window.location.search) {
vm.q = $window.location.search.split('=')[1];
console.log('vm.q:' + vm.q);
SearchService(vm.q)
.success(searchSuccess)
.error(searchFailed);
}
function searchSuccess(data, status) {
vm.repository = data['repository'];
vm.project = data['project'];
}
function searchFailed(data, status) {
console.log('Failed search:' + data);
}
}
})();

View File

@ -0,0 +1,9 @@
(function() {
'use strict';
angular
.module('harbor.layout.search', []);
})();

View File

@ -1,468 +0,0 @@
var global_messages = {
'sign_in': {
'en-US': 'Sign In',
'zh-CN': '登录'
},
'sign_up': {
'en-US': 'Sign Up',
'zh-CN': '注册'
},
'forgot_password': {
'en-US': 'Forgot Password',
'zh-CN': '忘记密码'
},
'login_now': {
'en-US': 'Login Now',
'zh-CN': '立刻登录'
},
'its_easy_to_get_started': {
'en-US': 'It\'s easy to get started ...',
'zh-CN': '这很容易上手...'
},
'anybody_can_read_public_projects': {
'en-US': 'Anybody can read public projects',
'zh-CN': '任何人都可以访问公开的项目'
},
'create_projects_and_connect_repositories': {
'en-US': 'Create projects and connect repositories',
'zh-CN': '创建项目并关联镜像仓库'
},
'user_management_and_role_assignment': {
'en-US': 'User management and role assignment',
'zh-CN': '用户管理和角色分派'
},
'why_use_harbor': {
'en-US': 'Why use Harbor?',
'zh-CN': '为什么要使用Harbor'
},
'index_desc': {
'en-US': 'Project Harbor is to build an enterprise-class, reliable registry server. Enterprises can set up a private registry server in their own environment to improve productivity as well as security. Project Harbor can be used in both development and production environment.',
'zh-CN': 'Harbor是可靠的企业级Registry服务器。企业用户可使用Harbor搭建私有容器Registry服务提高生产效率和安全度既可应用于生产环境也可以在开发环境中使用。'
},
'index_desc_1': {
'en-US': 'Security: Keep their intellectual properties within their organizations.',
'zh-CN': '安全: 确保知识产权在自己组织内部的管控之下。'
},
'index_desc_2': {
'en-US': 'Efficiency: A private registry server is set up within the organization\'s network and can reduce significantly the internet traffic to the public service. ',
'zh-CN': '效率: 搭建组织内部的私有容器Registry服务可显著降低访问公共Registry服务的网络需求。'
},
'index_desc_3': {
'en-US': 'Access Control: RBAC (Role Based Access Control) is provided. User management can be integrated with existing enterprise identity services like AD/LDAP. ',
'zh-CN': '访问控制: 提供基于角色的访问控制,可集成企业目前拥有的用户管理系统(如:AD/LDAP。 '
},
'index_desc_4': {
'en-US': 'Audit: All access to the registry are logged and can be used for audit purpose.',
'zh-CN': '审计: 所有访问Registry服务的操作均被记录便于日后审计。'
},
'index_desc_5': {
'en-US': 'GUI: User friendly single-pane-of-glass management console.',
'zh-CN': '管理界面: 具有友好易用图形管理界面。'
},
'learn_more': {
'en-US': 'Learn more...',
'zh-CN': '更多...'
},
'repositories': {
'en-US': 'Repositories',
'zh-CN': '镜像仓库'
},
'project_repo_name': {
'en-US': 'Project/Repository Name',
'zh-CN': '项目/镜像仓库名称'
},
'creation_time': {
'en-US': 'Creation Time',
'zh-CN': '创建时间'
},
'author': {
'en-US': 'Author',
'zh-CN': '作者'
},
'username': {
'en-US': 'Username',
'zh-CN': '用户名'
},
'username_is_required' : {
'en-US': 'Username is required.',
'zh-CN': '用户名为必填项。'
},
'username_has_been_taken' : {
'en-US': 'Username has been taken.',
'zh-CN': '用户名已被占用。'
},
'username_is_too_long' : {
'en-US': 'Username is too long. (maximum 20 characters)',
'zh-CN': '用户名长度超出限制。最长为20个字符'
},
'username_contains_illegal_chars': {
'en-US': 'Username contains illegal character(s).',
'zh-CN': '用户名包含不合法的字符。'
},
'email': {
'en-US': 'Email',
'zh-CN': '邮箱'
},
'email_desc': {
'en-US': 'The Email address will be used for resetting password.',
'zh-CN': '此邮箱将用于重置密码。'
},
'email_is_required' : {
'en-US': 'Email is required.',
'zh-CN': '邮箱为必填项。'
},
'email_has_been_taken' : {
'en-US': 'Email has been taken.',
'zh-CN': '邮箱已被占用。'
},
'email_content_illegal' : {
'en-US': 'Email format is illegal.',
'zh-CN': '邮箱格式不合法。'
},
'email_does_not_exist' : {
'en-US': 'Email does not exist.',
'zh-CN': '邮箱不存在。'
},
'email_is_too_long': {
'en-US': 'Email is to long. (maximum 50 characters)',
'zh-CN': '邮箱名称长度超出限制。最长为50个字符'
},
'full_name': {
'en-US': 'Full Name',
'zh-CN': '全名'
},
'full_name_desc': {
'en-US': 'First name & Last name',
'zh-CN': '请输入全名。'
},
'full_name_is_required' : {
'en-US': 'Full name is required.',
'zh-CN': '全名为必填项。'
},
'full_name_is_too_long' : {
'en-US': 'Full name is too long. (maximum 20 characters)',
'zh-CN': '全名长度超出限制。最长为20个字符'
},
'full_name_contains_illegal_chars' : {
'en-US': 'Full name contains illegal character(s).',
'zh-CN': '全名包含不合法的字符。'
},
'password': {
'en-US': 'Password',
'zh-CN': '密码'
},
'password_desc': {
'en-US': 'At least 7 characters with 1 lowercase letter, 1 capital letter and 1 numeric character.',
'zh-CN': '至少输入 7个字符且包含 1个小写字母 1个大写字母和 1个数字。'
},
'password_is_required' : {
'en-US': 'Password is required.',
'zh-CN': '密码为必填项。'
},
'password_is_invalid' : {
'en-US': 'Password is invalid. At least 7 characters with 1 lowercase letter, 1 capital letter and 1 numeric character.',
'zh-CN': '密码无效。至少输入 7个字符且包含 1个小写字母1个大写字母和 1个数字。'
},
'confirm_password': {
'en-US': 'Confirm Password',
'zh-CN': '确认密码'
},
'password_does_not_match' : {
'en-US': 'Passwords do not match.',
'zh-CN': '两次密码输入不一致。'
},
'comments': {
'en-US': 'Comments',
'zh-CN': '备注'
},
'comment_is_too_long' : {
'en-US': 'Comment is too long. (maximum 20 characters)',
'zh-CN': '备注长度超出限制。最长为20个字符'
},
'forgot_password_description': {
'en-US': 'Please input the Email used when you signed up, a reset password Email will be sent to you.',
'zh-CN': '重置邮件将发送到此邮箱。'
},
'email_does_not_exist': {
'en-US': 'Email does not exist',
'zh-CN': '邮箱不存在。'
},
'reset_password': {
'en-US': 'Reset Password',
'zh-CN': '重置密码'
},
'summary': {
'en-US': 'Summary',
'zh-CN': '摘要'
},
'projects': {
'en-US': 'Projects',
'zh-CN': '项目'
},
'public_projects': {
'en-US': 'Public Projects',
'zh-CN': '公开项目'
},
'public': {
'en-US': 'Public',
'zh-CN': '公开'
},
'total_projects': {
'en-US': 'Total Projects',
'zh-CN': '全部项目'
},
'public_repositories': {
'en-US': 'Public Repositories',
'zh-CN': '公开镜像仓库'
},
'total_repositories': {
'en-US': 'Total Repositories',
'zh-CN': '全部镜像仓库'
},
'top_10_repositories': {
'en-US': 'Top 10 Repositories',
'zh-CN': 'Top 10 镜像仓库'
},
'repository_name': {
'en-US': 'Repository Name',
'zh-CN': '镜像仓库名'
},
'size': {
'en-US': 'Size',
'zh-CN': '规格'
},
'creator': {
'en-US': 'Creator',
'zh-CN': '创建者'
},
'logs': {
'en-US': 'Logs',
'zh-CN': '日志'
},
'task_name': {
'en-US': 'Task Name',
'zh-CN': '任务名称'
},
'details': {
'en-US': 'Details',
'zh-CN': '详细信息'
},
'user': {
'en-US': 'User',
'zh-CN': '用户'
},
'users': {
'en-US': 'Users',
'zh-CN': '用户'
},
'my_projects': {
'en-US': 'My Projects',
'zh-CN': '我的项目'
},
'project_name': {
'en-US': 'Project Name',
'zh-CN': '项目名称'
},
'role': {
'en-US': 'Role',
'zh-CN': '角色'
},
'publicity': {
'en-US': 'Publicity',
'zh-CN': '公开'
},
'button_on': {
'en-US': 'On',
'zh-CN': '打开'
},
'button_off': {
'en-US': 'Off',
'zh-CN': '关闭'
},
'new_project': {
'en-US': 'New Project',
'zh-CN': '新增项目'
},
'save': {
'en-US': 'Save',
'zh-CN': '保存'
},
'cancel': {
'en-US': 'Cancel',
'zh-CN': '取消'
},
'items': {
'en-US': 'items',
'zh-CN': '条记录'
},
'add_member': {
'en-US': 'Add Member',
'zh-CN': '新增成员'
},
'operation': {
'en-US': 'Operation',
'zh-CN': '操作'
},
'advanced_search': {
'en-US': 'Advanced Search',
'zh-CN': '高级搜索'
},
'all': {
'en-US': 'All',
'zh-CN': '全部'
},
'others': {
'en-US': 'Others',
'zh-CN': '其他'
},
'search': {
'en-US': 'Search',
'zh-CN': '搜索'
},
'duration': {
'en-US': 'Duration',
'zh-CN': '持续时间'
},
'from': {
'en-US': 'From',
'zh-CN': '起始'
},
'to': {
'en-US': 'To',
'zh-CN': '结束'
},
'timestamp': {
'en-US': 'Timestamp',
'zh-CN': '时间戳'
},
'dashboard': {
'en-US': 'Dashboard',
'zh-CN': '消息中心'
},
'admin_options': {
'en-US': 'Admin Options',
'zh-CN': '管理员选项'
},
'account_setting': {
'en-US': 'Account Setting',
'zh-CN': '个人设置'
},
'log_out': {
'en-US': 'Log Out',
'zh-CN': '注销'
},
'registration_time': {
'en-US': 'Registration Time',
'zh-CN': '注册时间'
},
'system_management': {
'en-US': 'System Management',
'zh-CN': '系统管理'
},
'change_password': {
'en-US': 'Change Password',
'zh-CN': '修改密码'
},
'old_password': {
'en-US': 'Old Password',
'zh-CN': '原密码'
},
'old_password_is_required': {
'en-US': 'Old password is required.',
'zh-CN': '原密码为必填项。'
},
'old_password_is_incorrect': {
'en-US': 'Old password is incorrect.',
'zh-CN': '原密码不正确。'
},
'new_password_is_required': {
'en-US': 'New password is required.',
'zh-CN': '新密码为必填项。'
},
'new_password_is_invalid': {
'en-US': 'New password is invalid. At least 7 characters with 1 lowercase letter, 1 capital letter and 1 numeric character.',
'zh-CN': '新密码无效。至少输入 7个字符且包含 1个小写字母1个大写字母和 1个数字。'
},
'new_password': {
'en-US': 'New Password',
'zh-CN': '新密码'
},
'username_already_exist': {
'en-US': 'Username already exist.',
'zh-CN': '用户名已存在。'
},
'username_does_not_exist': {
'en-US': 'Username does not exist.',
'zh-CN': '用户名不存在。'
},
'username_or_password_is_incorrect': {
'en-US': 'Username or password is incorrect',
'zh-CN': '用户名或密码不正确。'
},
'username_email': {
'en-US': 'Username/Email',
'zh-CN': '用户名/邮箱'
},
'project_name_is_required': {
'en-US': 'Project name is required',
'zh-CN': '项目名称为必填项。'
},
'project_already_exist': {
'en-US': 'Project already exist',
'zh-CN': '项目已存在。'
},
'project_name_is_invalid': {
'en-US': 'Project name is invalid',
'zh-CN': '项目名称无效。'
},
'projects_or_repositories': {
'en-US': 'Projects or repositories',
'zh-CN': '项目和镜像资源'
},
'tag': {
'en-US': 'Tag',
'zh-CN': '标签'
},
'image_details': {
'en-US': 'Image Details',
'zh-CN': '镜像明细'
},
'pull_command': {
'en-US': 'Pull Command',
'zh-CN': 'Pull 命令'
},
'alert_delete_repo_title': {
'en-US': 'Delete repository - $0',
'zh-CN': '删除镜像仓库 - $0'
},
'alert_delete_repo': {
'en-US': 'After deleting the associated tags with the repository will be deleted together.<br/>' +
'And the corresponding image will be removed from the system.<br/>' +
'<br/>Delete this "$0" repository now?',
'zh-CN': '删除镜像仓库也会删除其所有相关联的镜像标签,<br/>并且其对应镜像资源文件也会被从系统删除。<br/>' +
'<br/>是否删除镜像仓库 "$0" ?'
},
'alert_delete_last_tag': {
'en-US': 'After deleting the associated repository with the tag will be deleted together,<br/>' +
'because a repository contains at least one tag. And the corresponding image will be removed from the system.<br/>' +
'<br/>Delete this "$0" tag now?',
'zh-CN': '当删除只包含一个镜像标签的镜像仓库时,其对应的镜像仓库也会被从系统中删除。<br/>' +
'<br/>删除镜像标签 "$0" ?'
},
'alert_delete_tag_title': {
'en-US': 'Delete tag - $0',
'zh-CN': '删除镜像标签 - $0'
},
'alert_delete_tag': {
'en-US': 'Delete this "$0" tag now?',
'zh-CN': '删除镜像标签 "$0" ?'
},
'close': {
'en-US': 'Close',
'zh-CN': '关闭'
},
'ok': {
'en-US': 'OK',
'zh-CN': '确认'
}
};

View File

@ -0,0 +1,121 @@
var locale_messages = {
'sign_in': 'Sign In',
'sign_up': 'Sign Up',
'forgot_password': 'Forgot Password',
'login_now': 'Login Now',
'its_easy_to_get_started': 'It\'s easy to get started ...',
'anybody_can_read_public_projects': 'Anybody can read public projects',
'create_projects_and_connect_repositories': 'Create projects and connect repositories',
'user_management_and_role_assignment': 'User management and role assignment',
'why_use_harbor': 'Why use Harbor?',
'index_desc': 'Project Harbor is to build an enterprise-class, reliable registry server. Enterprises can set up a private registry server in their own environment to improve productivity as well as security. Project Harbor can be used in both development and production environment.',
'index_desc_1': 'Security: Keep their intellectual properties within their organizations.',
'index_desc_2': 'Efficiency: A private registry server is set up within the organization\'s network and can reduce significantly the internet traffic to the public service. ',
'index_desc_3': 'Access Control: RBAC (Role Based Access Control) is provided. User management can be integrated with existing enterprise identity services like AD/LDAP. ',
'index_desc_4': 'Audit: All access to the registry are logged and can be used for audit purpose.',
'index_desc_5': 'GUI: User friendly single-pane-of-glass management console.',
'learn_more': 'Learn more...',
'repositories': 'Repositories',
'project_repo_name': 'Project/Repository Name',
'creation_time': 'Creation Time',
'author': 'Author',
'username': 'Username',
'username_is_required': 'Username is required.',
'username_has_been_taken': 'Username has been taken.',
'username_is_too_long': 'Username is too long. (maximum 20 characters)',
'username_contains_illegal_chars': 'Username contains illegal character(s).',
'email': 'Email',
'email_desc': 'The Email address will be used for resetting password.',
'email_is_required': 'Email is required.',
'email_has_been_taken': 'Email has been taken.',
'email_content_illegal': 'Email format is illegal.',
'email_does_not_exist': 'Email does not exist.',
'email_is_too_long': 'Email is to long. (maximum 50 characters)',
'full_name': 'Full Name',
'full_name_desc': 'First name & Last name',
'full_name_is_required': 'Full name is required.',
'full_name_is_too_long': 'Full name is too long. (maximum 20 characters)',
'full_name_contains_illegal_chars': 'Full name contains illegal character(s).',
'password': 'Password',
'password_desc': 'At least 7 characters with 1 lowercase letter, 1 capital letter and 1 numeric character.',
'password_is_required': 'Password is required.',
'password_is_invalid': 'Password is invalid. At least 7 characters with 1 lowercase letter, 1 capital letter and 1 numeric character.',
'confirm_password': 'Confirm Password',
'password_does_not_match': 'Passwords do not match.',
'comments': 'Comments',
'comment_is_too_long': 'Comment is too long. (maximum 20 characters)',
'forgot_password_description': 'Please input the Email used when you signed up, a reset password Email will be sent to you.',
'email_does_not_exist': 'Email does not exist',
'reset_password': 'Reset Password',
'summary': 'Summary',
'projects': 'Projects',
'public_projects': 'Public Projects',
'public': 'Public',
'total_projects': 'Total Projects',
'public_repositories': 'Public Repositories',
'total_repositories': 'Total Repositories',
'top_10_repositories': 'Top 10 Repositories',
'repository_name': 'Repository Name',
'size': 'Size',
'creator': 'Creator',
'logs': 'Logs',
'task_name': 'Task Name',
'details': 'Details',
'user': 'User',
'users': 'Users',
'my_projects': 'My Projects',
'project_name': 'Project Name',
'role': 'Role',
'publicity': 'Publicity',
'button_on': 'On',
'button_off': 'Off',
'new_project': 'New Project',
'save': 'Save',
'cancel': 'Cancel',
'items': 'items',
'add_member': 'Add Member',
'operation': 'Operation',
'advanced_search': 'Advanced Search',
'all': 'All',
'others': 'Others',
'search': 'Search',
'duration': 'Duration',
'from': 'From',
'to': 'To',
'timestamp': 'Timestamp',
'dashboard': 'Dashboard',
'admin_options': 'Admin Options',
'account_setting': 'Account Setting',
'log_out': 'Log Out',
'registration_time': 'Registration Time',
'system_management': 'System Management',
'change_password': 'Change Password',
'old_password': 'Old Password',
'old_password_is_required': 'Old password is required.',
'old_password_is_incorrect': 'Old password is incorrect.',
'new_password_is_required': 'New password is required.',
'new_password_is_invalid': 'New password is invalid. At least 7 characters with 1 lowercase letter, 1 capital letter and 1 numeric character.',
'new_password': 'New Password',
'username_already_exist': 'Username already exist.',
'username_does_not_exist': 'Username does not exist.',
'username_or_password_is_incorrect': 'Username or password is incorrect',
'username_email': 'Username/Email',
'project_name_is_required': 'Project name is required',
'project_already_exist': 'Project already exist',
'project_name_is_invalid': 'Project name is invalid',
'projects_or_repositories': 'Projects or repositories',
'tag': 'Tag',
'image_details': 'Image Details',
'pull_command': 'Pull Command',
'alert_delete_repo_title': 'Delete repository - $0',
'alert_delete_repo': 'After deleting the associated tags with the repository will be deleted together.<br/>' +
'And the corresponding image will be removed from the system.<br/>' +
'<br/>Delete this "$0" repository now?',
'alert_delete_last_tag': 'After deleting the associated repository with the tag will be deleted together,<br/>' +
'because a repository contains at least one tag. And the corresponding image will be removed from the system.<br/>' +
'<br/>Delete this "$0" tag now?',
'alert_delete_tag_title': 'Delete tag - $0',
'alert_delete_tag': 'Delete this "$0" tag now?',
'close': 'Close',
'ok': 'OK'
};

View File

@ -0,0 +1,119 @@
var locale_messages = {
'sign_in': '登录',
'sign_up': '注册',
'forgot_password': '忘记密码',
'login_now': '立刻登录',
'its_easy_to_get_started': '这很容易上手...',
'anybody_can_read_public_projects': '任何人都可以访问公开的项目',
'create_projects_and_connect_repositories': '创建项目并关联镜像仓库',
'user_management_and_role_assignment': '用户管理和角色分派',
'why_use_harbor': '为什么要使用Harbor',
'index_desc': 'Harbor是可靠的企业级Registry服务器。企业用户可使用Harbor搭建私有容器Registry服务提高生产效率和安全度既可应用于生产环境也可以在开发环境中使用。',
'index_desc_1': '安全: 确保知识产权在自己组织内部的管控之下。',
'index_desc_2': '效率: 搭建组织内部的私有容器Registry服务可显著降低访问公共Registry服务的网络需求。',
'index_desc_3': '访问控制: 提供基于角色的访问控制,可集成企业目前拥有的用户管理系统(如:AD/LDAP。',
'index_desc_4': '审计: 所有访问Registry服务的操作均被记录便于日后审计。',
'index_desc_5': '管理界面: 具有友好易用图形管理界面。',
'learn_more': '更多...',
'repositories': '镜像仓库',
'project_repo_name': '项目/镜像仓库名称',
'creation_time': '创建时间',
'author': '作者',
'username': '用户名',
'username_is_required' : '用户名为必填项。',
'username_has_been_taken' : '用户名已被占用。',
'username_is_too_long' : '用户名长度超出限制。最长为20个字符',
'username_contains_illegal_chars': '用户名包含不合法的字符。',
'email': '邮箱',
'email_desc': '此邮箱将用于重置密码。',
'email_is_required' : '邮箱为必填项。',
'email_has_been_taken' : '邮箱已被占用。',
'email_content_illegal' : '邮箱格式不合法。',
'email_does_not_exist' : '邮箱不存在。',
'email_is_too_long': '邮箱名称长度超出限制。最长为50个字符',
'full_name': '全名',
'full_name_desc': '请输入全名。',
'full_name_is_required' : '全名为必填项。',
'full_name_is_too_long' : '全名长度超出限制。最长为20个字符',
'full_name_contains_illegal_chars' : '全名包含不合法的字符。',
'password': '密码',
'password_desc': '至少输入 7个字符且包含 1个小写字母 1个大写字母和 1个数字。',
'password_is_required' : '密码为必填项。',
'password_is_invalid' : '密码无效。至少输入 7个字符且包含 1个小写字母1个大写字母和 1个数字。',
'confirm_password': '确认密码',
'password_does_not_match' : '两次密码输入不一致。',
'comments': '备注',
'comment_is_too_long' : '备注长度超出限制。最长为20个字符',
'forgot_password_description': '重置邮件将发送到此邮箱。',
'email_does_not_exist': '邮箱不存在。',
'reset_password': '重置密码',
'summary': '摘要',
'projects': '项目',
'public_projects': '公开项目',
'public': '公开',
'total_projects': '全部项目',
'public_repositories': '公开镜像仓库',
'total_repositories': '全部镜像仓库',
'top_10_repositories': 'Top 10 镜像仓库',
'repository_name': '镜像仓库名',
'size': '规格',
'creator': '创建者',
'logs': '日志',
'task_name': '任务名称',
'details': '详细信息',
'user': '用户',
'users': '用户',
'my_projects': '我的项目',
'project_name': '项目名称',
'role': '角色',
'publicity': '公开',
'button_on': '打开',
'button_off': '关闭',
'new_project': '新增项目',
'save': '保存',
'cancel': '取消',
'items': '条记录',
'add_member': '新增成员',
'operation': '操作',
'advanced_search': '高级搜索',
'all': '全部',
'others': '其他',
'search':'搜索',
'duration': '持续时间',
'from': '起始',
'to': '结束',
'timestamp': '时间戳',
'dashboard': '消息中心',
'admin_options': '管理员选项',
'account_setting': '个人设置',
'log_out': '注销',
'registration_time': '注册时间',
'system_management': '系统管理',
'change_password': '修改密码',
'old_password': '原密码',
'old_password_is_required': '原密码为必填项。',
'old_password_is_incorrect': '原密码不正确。',
'new_password_is_required': '新密码为必填项。',
'new_password_is_invalid': '新密码无效。至少输入 7个字符且包含 1个小写字母1个大写字母和 1个数字。',
'new_password': '新密码',
'username_already_exist': '用户名已存在。',
'username_does_not_exist': '用户名不存在。',
'username_or_password_is_incorrect': '用户名或密码不正确。',
'username_email': '用户名/邮箱',
'project_name_is_required': '项目名称为必填项。',
'project_already_exist': '项目已存在。',
'project_name_is_invalid': '项目名称无效。',
'projects_or_repositories': '项目和镜像资源',
'tag': '标签',
'image_details': '镜像明细',
'pull_command': 'Pull 命令',
'alert_delete_repo_title': '删除镜像仓库 - $0',
'alert_delete_repo': '删除镜像仓库也会删除其所有相关联的镜像标签,<br/>并且其对应镜像资源文件也会被从系统删除。<br/>' +
'<br/>是否删除镜像仓库 "$0" ?',
'alert_delete_last_tag': '当删除只包含一个镜像标签的镜像仓库时,其对应的镜像仓库也会被从系统中删除。<br/>' +
'<br/>删除镜像标签 "$0" ?',
'alert_delete_tag_title': '删除镜像标签 - $0',
'alert_delete_tag': '删除镜像标签 "$0" ?',
'close': '关闭',
'ok': '确认'
};

View File

@ -9,29 +9,48 @@
I18nService.$inject = ['$cookies', '$window'];
function I18nService($cookies, $window) {
var languages = $.extend(true, {}, global_messages);
var cookieOptions = {'path': '/'};
var messages = $.extend(true, {}, eval('locale_messages'));
var defaultLanguage = navigator.language || 'en-US';
var languageNames = {
var supportLanguages = {
'en-US': 'English',
'zh-CN': '中文'
};
};
var isSupportLanguage = function(language) {
for (var i in supportLanguages) {
if(language == i) {
return true;
}
}
return false;
}
return tr;
function tr() {
return {
'setCurrentLanguage': function(language) {
if(!language){
'setCurrentLanguage': function(language) {
if(!angular.isDefined(language) || !isSupportLanguage(language)) {
language = defaultLanguage;
}
$cookies.put('language', language, {'path': '/'});
$cookies.put('language', language, cookieOptions);
},
'setDefaultLanguage': function() {
$cookies.put('language', defaultLanguage, cookieOptions);
},
'getCurrentLanguage': function() {
return $cookies.get('language') || defaultLanguage;
},
'getLanguageName': function(crrentLanguage) {
return languageNames[crrentLanguage];
'getLanguageName': function(language) {
if(!angular.isDefined(language) || !isSupportLanguage(language)) {
language = defaultLanguage;
}
return supportLanguages[language];
},
'getValue': function(key, currentLanguage) {
return languages[key][currentLanguage];
'unset': function(){
$cookies.put('language', defaultLanguage, cookieOptions);
},
'getValue': function(key) {
return messages[key];
}
}
}

View File

@ -10,21 +10,15 @@
function SearchService($http, $log) {
return Search;
return search;
function Search(keywords) {
return $http({
method: 'GET',
url: '/api/search',
transformRequest: function(obj) {
var str = [];
for(var p in obj)
str.push(encodeURIComponent(p) + "=" + encodeURIComponent(obj[p]));
return str.join("&");
},
data: {'q': keywords}
});
function search(keywords) {
return $http
.get('/api/search',{
params: {
'q': keywords
}
});
}
}

View File

@ -6,34 +6,38 @@
.module('harbor.session')
.controller('CurrentUserController', CurrentUserController);
CurrentUserController.$inject = ['CurrentUserService', 'currentUser', '$scope', '$timeout', '$window'];
CurrentUserController.$inject = ['CurrentUserService', 'currentUser', '$window'];
function CurrentUserController(CurrentUserService, currentUser, $scope, $timeout, $window) {
function CurrentUserController(CurrentUserService, currentUser, $window) {
var vm = this;
CurrentUserService()
.then(getCurrentUserComplete)
.catch(getCurrentUserFailed);
function getCurrentUserComplete(response) {
console.log('Successful logged in.');
$timeout(function(){
$scope.$broadcast('currentUser', response.data);
currentUser.set(response.data);
}, 50);
if(angular.isDefined(response)) {
currentUser.set(response.data);
}
}
function getCurrentUserFailed(e){
var url = location.pathname;
var exclusions = ['/ng', '/ng/forgot_password', '/ng/sign_up', '/ng/reset_password'];
var exclusions = [
'/ng',
'/ng/forgot_password',
'/ng/sign_up',
'/ng/reset_password',
'/ng/search'
];
for(var i = 0; i < exclusions.length; i++) {
if(exclusions[i]===url) {
return;
}
}
$window.location.href = '/ng';
}
}
}
})();

View File

@ -16,8 +16,13 @@ func initNgRouters() {
beego.Router("/ng/admin_option", &ng.AdminOptionController{})
beego.Router("/ng/forgot_password", &ng.ForgotPasswordController{})
beego.Router("/ng/reset_password", &ng.ResetPasswordController{})
beego.Router("/ng/search", &ng.SearchController{})
beego.Router("/ng/log_out", &ng.CommonController{}, "get:LogOut")
beego.Router("/ng/reset", &ng.CommonController{}, "post:ResetPassword")
beego.Router("/ng/sendEmail", &ng.CommonController{}, "get:SendEmail")
beego.Router("/ng/language", &ng.CommonController{}, "get:SwitchLanguage")
beego.Router("/ng/optional_menu", &ng.OptionalMenuController{})
beego.Router("/ng/navigation_header", &ng.NavigationHeaderController{})
}

View File

@ -54,6 +54,7 @@ func initRouters() {
beego.Router("/api/search", &api.SearchAPI{})
beego.Router("/api/projects/:pid/members/?:mid", &api.ProjectMemberAPI{})
beego.Router("/api/projects/?:id", &api.ProjectAPI{})
beego.Router("/api/statistics", &api.StatisticAPI{})
beego.Router("/api/projects/:id/logs/filter", &api.ProjectAPI{}, "post:FilterAccessLog")
beego.Router("/api/users", &api.UserAPI{})
beego.Router("/api/users/?:id", &api.UserAPI{})

View File

@ -5,7 +5,7 @@
<div class="row">
<div class="up-section">
<h4 class="page-header title-color underlined">// 'summary' | tr //</h4>
<dl class="page-content dl-horizontal">
<dl class="page-content dl-horizontal">
<dt>// 'projects' | tr //:</dt><dd>//vm.statProjects['projects']//</dd>
<dt>// 'public_projects' | tr //:</dt><dd>//vm.statProjects['public_projects']//</dd>
<dt>// 'total_projects' | tr //:</dt><dd>//vm.statProjects['total_projects']//</dd>
@ -38,7 +38,7 @@
<div class="container">
<div class="row row-custom">
<div class="col-xs-12 col-md-12">
<div class="down-section single">
<div class="down-section">
<h4 class="page-header title-color underlined">// 'logs' | tr //</h4>
<div style="padding: 15px;">
<table class="table">

View File

@ -5,7 +5,7 @@
<h1 class="col-md-12 col-md-offset-2 main-title title-color">// 'forgot_password' | tr //</h1>
<div class="row">
<div class="col-md-12 col-md-offset-2 main-content">
<form name="form" class="form-horizontal css-form" ng-submit="form.$valid " >
<form name="form" class="form-horizontal css-form" ng-submit="form.$valid" >
<div class="form-group">
<label for="email" class="col-sm-3 control-label">// 'email' | tr //:</label>
<div class="col-sm-7">

View File

@ -1,5 +1,9 @@
{{ if eq .HasLoggedIn true }}
<ul class="nav-custom">
<li><a tag="dashboard" href="/ng/dashboard">// 'dashboard' | tr //</a></li>
<li><a tag="project" href="/ng/project">// 'projects' | tr //</a></li>
<li><a ng-show="vm.isAdmin" tag="admin_option" href="/ng/admin_option#/all_user">// 'admin_options' | tr //</a></li>
</ul>
{{ if eq .IsAdmin 1 }}
<li><a tag="admin_option" href="/ng/admin_option#/all_user">// 'admin_options' | tr //</a></li>
{{ end }}
</ul>
{{ end }}

View File

@ -1,27 +1,31 @@
<div ng-if="!vm.isLoggedIn" class="dropdown">
<a id="dLabel" role="button" data-toggle="dropdown" class="btn btn-link" data-target="#" href="">
<span class="glyphicon glyphicon-globe"></span> //vm.languageName//
</a>
<ul class="dropdown-menu multi-level" role="menu" aria-labelledby="dropdownMenu">
<li><a href="javascript:void(0);" ng-click="vm.setLanguage('en-US')">English</a></li>
<li><a href="javascript:void(0);" ng-click="vm.setLanguage('zh-CN')">中文</a></li>
</ul>
</div>
<div ng-if="vm.isLoggedIn" class="dropdown">
<a id="dLabel" role="button" data-toggle="dropdown" class="btn btn-link" data-target="#" href="">
<span class="glyphicon glyphicon-user"></span> //vm.username//
{{if eq .HasLoggedIn true }}
<div class="dropdown">
<a role="button" data-toggle="dropdown" class="btn btn-link" data-target="#" href="">
<span class="glyphicon glyphicon-user"></span> {{ .Username }}
</a>
<ul class="dropdown-menu multi-level" role="menu" aria-labelledby="dropdownMenu">
<li><a href="/ng/account_setting"><span class="glyphicon glyphicon-pencil"></span> // 'account_setting' | tr //</a></li>
<li class="dropdown-submenu">
<a tabindex="-1" href="#"><span class="glyphicon glyphicon-globe"></span> //vm.languageName//</a>
<ul class="dropdown-menu">
<li><a href="javascript:void(0);" ng-click="vm.setLanguage('en-US')">English</a></li>
<li><a href="javascript:void(0);" ng-click="vm.setLanguage('zh-CN')">中文</a></li>
{{ range $key, $value := .SupportLanguages }}
<li><a href="javascript:void(0);" ng-click="vm.setLanguage('{{ $key }}')">{{ $value.Name }}</a></li>
{{ end }}
</ul>
</li>
<li class="divider"></li>
<li><a href="javascript:void(0)" ng-click="vm.logOut()"><span class="glyphicon glyphicon-log-out"></span> // 'log_out' | tr //</a></li>
</ul>
</div>
</div>
{{ else }}
<div class="dropdown">
<a role="button" data-toggle="dropdown" class="btn btn-link" data-target="#" href="">
<span class="glyphicon glyphicon-globe"></span> //vm.languageName//
</a>
<ul class="dropdown-menu multi-level" role="menu" aria-labelledby="dropdownMenu">
{{ range $key, $value := .SupportLanguages }}
<li><a href="javascript:void(0);" ng-click="vm.setLanguage('{{ $key }}')">{{ $value.Name }}</a></li>
{{ end }}
</ul>
</div>
{{ end }}

View File

@ -35,7 +35,7 @@
<td>N/A</td>
<td>N/A</td>
<td>//p.CreationTime | dateL : 'YYYY-MM-DD HH:mm:ss'//</td>
<td><publicity-button is-public="p.Public" owned="p.OwnerId == vm.currentUser.UserId" project-id="p.ProjectId"></publicity-button></td>
<td><publicity-button is-public="p.Public" owned="p.OwnerId == vm.user.UserId" project-id="p.ProjectId"></publicity-button></td>
</tr>
</tbody>
</table>

View File

@ -10,12 +10,12 @@
<span ng-show="vm.publicity">// 'public_projects' | tr //</span>
<a ng-show="!vm.publicity" href="#/repositories?project_id=//vm.selectedProject.ProjectId//" class="title-color" ng-click="vm.togglePublicity({publicity: true})">// 'public_projects' | tr //</a></h4>
<div class="switch-pane">
<switch-pane-projects is-open="vm.open" selected-project="vm.selectedProject"></switch-pane-projects>
<switch-pane-projects is-open="vm.isOpen" selected-project="vm.selectedProject"></switch-pane-projects>
<span>
<navigation-details is-open="vm.open" selected-project="vm.selectedProject" ng-show="vm.isProjectMember"></navigation-details>
<navigation-details selected-project="vm.selectedProject" ng-show="vm.isProjectMember"></navigation-details>
</span>
</div>
<retrieve-projects is-open="vm.open" selected-project="vm.selectedProject" is-project-member="vm.isProjectMember" publicity="vm.publicity"></retrieve-projects>
<retrieve-projects is-open="vm.isOpen" selected-project="vm.selectedProject" is-project-member="vm.isProjectMember" publicity="vm.publicity"></retrieve-projects>
<!-- Tab panes -->
<div class="tab-content" ng-click="vm.closeRetrievePane()">
<input type="hidden" id="HarborRegUrl" value="{{.HarborRegUrl}}">

24
views/ng/search.htm Normal file
View File

@ -0,0 +1,24 @@
<div class="container-fluid container-fluid-custom" ng-controller="SearchController as vm">
<div class="container container-custom">
<div class="row extend-height">
<div class="section">
<div class="row">
<div class="col-md-12 col-md-offset-2 main-content">
<h4 class="page-header title-color underlined">Projects&nbsp;&nbsp;<span class="badge">//vm.project.length//</span></h4>
<div class="search-result">
<ul>
<li ng-repeat="p in vm.project"><a href="/ng/repository#/repositories?project_id=//p.id//&is_public=//p.public === 1//">//p.name//</a></li>
</ul>
</div>
<h4 class="page-header title-color underlined">Repositories&nbsp;&nbsp;<span class="badge">//vm.repository.length//</span></h4>
<div class="search-result">
<ul>
<li ng-repeat="r in vm.repository"><a href="/ng/repository#/repositories?project_id=//r.project_id//&is_public=//r.project_public === 1//#//r.repository_name//">//r.repository_name//</a></li>
</ul>
</div>
</div>
</div>
</div>
</div>
</div>
</div>

View File

@ -10,17 +10,13 @@
</div>
<!-- Collect the nav links, forms, and other content for toggling -->
<div class="collapse navbar-collapse" id="bs-harbor-navbar-collapse-1">
<optional-menu></optional-menu>
<optional-menu login-status="//vm.loginStatus//"></optional-menu>
<ul class="nav navbar-nav navbar-right">
<li>
<navigation-header></navigation-header>
</li>
<li>
<form class="navbar-form pull-right" role="search">
<div class="form-group">
<input type="text" class="form-control search-icon" placeholder="// 'projects_or_repositories' | tr //" size="35">
</div>
</form>
<search-input search-input='vm.searchInput'></search-input>
</li>
</ul>
</div><!-- /.navbar-collapse -->

View File

@ -13,15 +13,15 @@
<script src="/static/ng/vendors/zc/v2.2.0/ZeroClipboard.js"></script>
<!-- Latest compiled and minified CSS -->
<!--<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css" integrity="sha384-1q8mTJOASx8j1Au+a5WDVnPi2lkFfwwEAa8hDDdjZlpLegxhjVME1fgjWPGmkzs7" crossorigin="anonymous">-->
<!--link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css" integrity="sha384-1q8mTJOASx8j1Au+a5WDVnPi2lkFfwwEAa8hDDdjZlpLegxhjVME1fgjWPGmkzs7" crossorigin="anonymous"-->
<link rel="stylesheet" href="https://cdn.bootcss.com/bootstrap/3.3.6/css/bootstrap.min.css" integrity="sha384-1q8mTJOASx8j1Au+a5WDVnPi2lkFfwwEAa8hDDdjZlpLegxhjVME1fgjWPGmkzs7" crossorigin="anonymous">
<!-- Optional theme -->
<!--<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap-theme.min.css" integrity="sha384-fLW2N01lMqjakBkx3l/M9EahuwpSfeNvV63J5ezn3uZzapT0u7EYsXMjQV+0En5r" crossorigin="anonymous">-->
<!--link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap-theme.min.css" integrity="sha384-fLW2N01lMqjakBkx3l/M9EahuwpSfeNvV63J5ezn3uZzapT0u7EYsXMjQV+0En5r" crossorigin="anonymous"-->
<link rel="stylesheet" href="https://cdn.bootcss.com/bootstrap/3.3.6/css/bootstrap-theme.min.css" integrity="sha384-fLW2N01lMqjakBkx3l/M9EahuwpSfeNvV63J5ezn3uZzapT0u7EYsXMjQV+0En5r" crossorigin="anonymous">
<!-- Latest compiled and minified JavaScript -->
<!--<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js" integrity="sha384-0mSbJDEHialfmuBBQP6A4Qrprq5OVfW37PRR3j5ELqxss1yVqOtnepnHVP9aJ7xS" crossorigin="anonymous"></script>-->
<!--script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js" integrity="sha384-0mSbJDEHialfmuBBQP6A4Qrprq5OVfW37PRR3j5ELqxss1yVqOtnepnHVP9aJ7xS" crossorigin="anonymous"></script-->
<script src="https://cdn.bootcss.com/bootstrap/3.3.6/js/bootstrap.min.js"></script>
<link rel="stylesheet" href="/static/ng/resources/css/header.css">
@ -31,6 +31,7 @@
<link rel="stylesheet" href="/static/ng/resources/css/project.css">
<link rel="stylesheet" href="/static/ng/resources/css/repository.css">
<link rel="stylesheet" href="/static/ng/resources/css/sign-up.css">
<link rel="stylesheet" href="/static/ng/resources/css/search.css">
<script src="/static/ng/vendors/angularjs/angular.js"></script>
<script src="/static/ng/vendors/angularjs/angular-route.js"></script>
@ -93,8 +94,17 @@
<script src="/static/ng/resources/js/layout/admin-option/admin-option.controller.js"></script>
<script src="/static/ng/resources/js/layout/admin-option/admin-option.config.js"></script>
<script src="/static/ng/resources/js/layout/search/search.module.js"></script>
<script src="/static/ng/resources/js/layout/search/search.controller.js"></script>
<script src="/static/ng/resources/js/services/i18n/services.i18n.module.js"></script>
<script src="/static/ng/resources/js/services/i18n/locale_messages.js"></script>
{{ if eq .Lang "zh-CN" }}
<script src="/static/ng/resources/js/services/i18n/locale_messages_zh-CN.js"></script>
{{ else if eq .Lang "en-US"}}
<script src="/static/ng/resources/js/services/i18n/locale_messages_en-US.js"></script>
{{ end }}
<script src="/static/ng/resources/js/services/i18n/services.i18n.js"></script>
<script src="/static/ng/resources/js/services/search/services.search.module.js"></script>
@ -156,6 +166,7 @@
<script src="/static/ng/resources/js/components/search/search.module.js"></script>
<script src="/static/ng/resources/js/components/search/search.directive.js"></script>
<script src="/static/ng/resources/js/components/search/search-input.directive.js"></script>
<script src="/static/ng/resources/js/components/sign-in/sign-in.module.js"></script>
<script src="/static/ng/resources/js/components/sign-in/sign-in.directive.js"></script>

View File

@ -5,7 +5,7 @@
<h1 class="col-md-12 col-md-offset-2 main-title title-color">// 'sign_up' | tr //</h1>
<div class="row">
<div class="col-md-12 col-md-offset-2 main-content">
<form name="form" class="form-horizontal css-form" ng-submit="form.$valid" novalidate>
<form name="form" class="form-horizontal css-form" ng-submit="form.$valid">
<div class="form-group">
<label for="username" class="col-sm-3 control-label">// 'username' | tr //:</label>
<div class="col-sm-7">