merge with upstream

This commit is contained in:
xiahaoshawn 2016-05-26 14:42:49 +08:00
commit 626aec5c79
37 changed files with 274 additions and 2724 deletions

37
controllers/ng/signin.go Normal file
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 SignInController struct {
BaseController
}
func (sic *SignInController) Get() {
sessionUserID := sic.GetSession("userId")
var hasLoggedIn bool
var username string
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)
sic.CustomAbort(http.StatusInternalServerError, "Internal error.")
}
if u == nil {
log.Warningf("User was deleted already, user id: %d, canceling request.", userID)
sic.CustomAbort(http.StatusUnauthorized, "")
}
username = u.Username
}
sic.Data["Username"] = username
sic.Data["HasLoggedIn"] = hasLoggedIn
sic.TplName = "ng/sign-in.htm"
sic.Render()
}

View File

@ -20,13 +20,11 @@ import (
"github.com/vmware/harbor/models"
"github.com/vmware/harbor/utils/log"
"github.com/astaxie/beego/orm"
)
// AddAccessLog persists the access logs
func AddAccessLog(accessLog models.AccessLog) error {
o := orm.NewOrm()
o := GetOrmer()
p, err := o.Raw(`insert into access_log
(user_id, project_id, repo_name, repo_tag, guid, operation, op_time)
values (?, ?, ?, ?, ?, ?, now())`).Prepare()
@ -43,7 +41,7 @@ func AddAccessLog(accessLog models.AccessLog) error {
//GetAccessLogs gets access logs according to different conditions
func GetAccessLogs(accessLog models.AccessLog) ([]models.AccessLog, error) {
o := orm.NewOrm()
o := GetOrmer()
sql := `select a.log_id, u.username, a.repo_name, a.repo_tag, a.operation, a.op_time
from access_log a left join user u on a.user_id = u.user_id
where a.project_id = ? `
@ -106,7 +104,7 @@ func GetAccessLogs(accessLog models.AccessLog) ([]models.AccessLog, error) {
// AccessLog ...
func AccessLog(username, projectName, repoName, repoTag, action string) error {
o := orm.NewOrm()
o := GetOrmer()
sql := "insert into access_log (user_id, project_id, repo_name, repo_tag, operation, op_time) " +
"select (select user_id as user_id from user where username=?), " +
"(select project_id as project_id from project where name=?), ?, ?, ?, now() "

View File

@ -22,6 +22,7 @@ import (
"os"
"strings"
"sync"
"time"
"github.com/astaxie/beego/orm"
@ -97,3 +98,14 @@ func InitDB() {
panic(err)
}
}
var globalOrm orm.Ormer
var once sync.Once
// GetOrmer :set ormer singleton
func GetOrmer() orm.Ormer {
once.Do(func() {
globalOrm = orm.NewOrm()
})
return globalOrm
}

View File

@ -715,3 +715,10 @@ func TestDeleteUser(t *testing.T) {
t.Errorf("user is not nil after deletion, user: %+v", user)
}
}
func TestGetOrmer(t *testing.T) {
o := GetOrmer()
if o == nil {
t.Errorf("Error get ormer.")
}
}

View File

@ -22,7 +22,6 @@ import (
"fmt"
"time"
"github.com/astaxie/beego/orm"
"github.com/vmware/harbor/utils/log"
)
@ -38,7 +37,7 @@ func AddProject(project models.Project) (int64, error) {
return 0, errors.New("project name contains illegal characters")
}
o := orm.NewOrm()
o := GetOrmer()
p, err := o.Raw("insert into project (owner_id, name, creation_time, update_time, deleted, public) values (?, ?, ?, ?, ?, ?)").Prepare()
if err != nil {
@ -81,7 +80,7 @@ func IsProjectPublic(projectName string) bool {
//ProjectExists returns whether the project exists according to its name of ID.
func ProjectExists(nameOrID interface{}) (bool, error) {
o := orm.NewOrm()
o := GetOrmer()
type dummy struct{}
sql := `select project_id from project where deleted = 0 and `
switch nameOrID.(type) {
@ -104,7 +103,7 @@ func ProjectExists(nameOrID interface{}) (bool, error) {
// GetProjectByID ...
func GetProjectByID(id int64) (*models.Project, error) {
o := orm.NewOrm()
o := GetOrmer()
sql := `select p.project_id, p.name, u.username as owner_name, p.owner_id, p.creation_time, p.update_time, p.public
from project p left join user u on p.owner_id = u.user_id where p.deleted = 0 and p.project_id = ?`
@ -127,7 +126,7 @@ func GetProjectByID(id int64) (*models.Project, error) {
// GetProjectByName ...
func GetProjectByName(name string) (*models.Project, error) {
o := orm.NewOrm()
o := GetOrmer()
var p []models.Project
n, err := o.Raw(`select * from project where name = ? and deleted = 0`, name).QueryRows(&p)
if err != nil {
@ -143,7 +142,7 @@ func GetProjectByName(name string) (*models.Project, error) {
// GetPermission gets roles that the user has according to the project.
func GetPermission(username, projectName string) (string, error) {
o := orm.NewOrm()
o := GetOrmer()
sql := `select r.role_code from role as r
inner join project_member as pm on r.role_id = pm.role
@ -166,7 +165,7 @@ func GetPermission(username, projectName string) (string, error) {
// ToggleProjectPublicity toggles the publicity of the project.
func ToggleProjectPublicity(projectID int64, publicity int) error {
o := orm.NewOrm()
o := GetOrmer()
sql := "update project set public = ? where project_id = ?"
_, err := o.Raw(sql, publicity, projectID).Exec()
return err
@ -177,7 +176,7 @@ func ToggleProjectPublicity(projectID int64, publicity int) error {
// 1. the project is not deleted
// 2. the prject is public or the user is a member of the project
func SearchProjects(userID int) ([]models.Project, error) {
o := orm.NewOrm()
o := GetOrmer()
sql := `select distinct p.project_id, p.name, p.public
from project p
left join project_member pm on p.project_id = pm.project_id
@ -194,7 +193,7 @@ func SearchProjects(userID int) ([]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()
o := GetOrmer()
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
@ -235,7 +234,7 @@ func GetAllProjects(projectName string) ([]models.Project, error) {
}
func getProjects(public int, projectName string) ([]models.Project, error) {
o := orm.NewOrm()
o := GetOrmer()
sql := `select project_id, owner_id, creation_time, update_time, name, public
from project
where deleted = 0`

View File

@ -16,13 +16,12 @@
package dao
import (
"github.com/astaxie/beego/orm"
"github.com/vmware/harbor/models"
)
// AddProjectMember inserts a record to table project_member
func AddProjectMember(projectID int64, userID int, role int) error {
o := orm.NewOrm()
o := GetOrmer()
sql := "insert into project_member (project_id, user_id , role) values (?, ?, ?)"
@ -33,7 +32,7 @@ func AddProjectMember(projectID int64, userID int, role int) error {
// UpdateProjectMember updates the record in table project_member
func UpdateProjectMember(projectID int64, userID int, role int) error {
o := orm.NewOrm()
o := GetOrmer()
sql := "update project_member set role = ? where project_id = ? and user_id = ?"
@ -44,7 +43,7 @@ func UpdateProjectMember(projectID int64, userID int, role int) error {
// DeleteProjectMember delete the record from table project_member
func DeleteProjectMember(projectID int64, userID int) error {
o := orm.NewOrm()
o := GetOrmer()
sql := "delete from project_member where project_id = ? and user_id = ?"
@ -57,7 +56,7 @@ func DeleteProjectMember(projectID int64, userID int) error {
// GetUserByProject gets all members of the project.
func GetUserByProject(projectID int64, queryUser models.User) ([]models.User, error) {
o := orm.NewOrm()
o := GetOrmer()
u := []models.User{}
sql := `select u.user_id, u.username, r.name rolename, r.role_id
from user u

View File

@ -22,8 +22,6 @@ import (
"github.com/vmware/harbor/models"
"github.com/vmware/harbor/utils"
"github.com/astaxie/beego/orm"
)
// Register is used for user to register, the password is encrypted before the record is inserted into database.
@ -34,7 +32,7 @@ func Register(user models.User) (int64, error) {
return 0, err
}
o := orm.NewOrm()
o := GetOrmer()
p, err := o.Raw("insert into user (username, password, realname, email, comment, salt, sysadmin_flag, creation_time, update_time) values (?, ?, ?, ?, ?, ?, ?, ?, ?)").Prepare()
if err != nil {
@ -108,7 +106,7 @@ func UserExists(user models.User, target string) (bool, error) {
return false, errors.New("User name and email are blank.")
}
o := orm.NewOrm()
o := GetOrmer()
sql := `select user_id from user where 1=1 `
queryParam := make([]interface{}, 1)

View File

@ -18,14 +18,13 @@ package dao
import (
"fmt"
"github.com/astaxie/beego/orm"
"github.com/vmware/harbor/models"
)
// GetUserProjectRoles returns roles that the user has according to the project.
func GetUserProjectRoles(userID int, projectID int64) ([]models.Role, error) {
o := orm.NewOrm()
o := GetOrmer()
sql := `select *
from role
@ -76,7 +75,7 @@ func IsAdminRole(userIDOrUsername interface{}) (bool, error) {
// GetRoleByID ...
func GetRoleByID(id int) (*models.Role, error) {
o := orm.NewOrm()
o := GetOrmer()
sql := `select *
from role

View File

@ -22,14 +22,13 @@ import (
"github.com/vmware/harbor/models"
"github.com/vmware/harbor/utils"
"github.com/astaxie/beego/orm"
"github.com/vmware/harbor/utils/log"
)
// GetUser ...
func GetUser(query models.User) (*models.User, error) {
o := orm.NewOrm()
o := GetOrmer()
sql := `select user_id, username, email, realname, comment, reset_uuid, salt,
sysadmin_flag, creation_time, update_time
@ -66,7 +65,7 @@ func GetUser(query models.User) (*models.User, error) {
// LoginByDb is used for user to login with database auth mode.
func LoginByDb(auth models.AuthModel) (*models.User, error) {
o := orm.NewOrm()
o := GetOrmer()
var users []models.User
n, err := o.Raw(`select * from user where (username = ? or email = ?) and deleted = 0`,
@ -91,7 +90,7 @@ func LoginByDb(auth models.AuthModel) (*models.User, error) {
// ListUsers lists all users according to different conditions.
func ListUsers(query models.User) ([]models.User, error) {
o := orm.NewOrm()
o := GetOrmer()
u := []models.User{}
sql := `select user_id, username, email, realname, comment, reset_uuid, salt,
sysadmin_flag, creation_time, update_time
@ -111,7 +110,7 @@ func ListUsers(query models.User) ([]models.User, error) {
// ToggleUserAdminRole gives a user admin role.
func ToggleUserAdminRole(u models.User) error {
o := orm.NewOrm()
o := GetOrmer()
sql := `update user set sysadmin_flag =not sysadmin_flag where user_id = ?`
@ -133,7 +132,7 @@ func ChangeUserPassword(u models.User, oldPassword ...string) (err error) {
return errors.New("Wrong numbers of params.")
}
o := orm.NewOrm()
o := GetOrmer()
var r sql.Result
if len(oldPassword) == 0 {
@ -159,7 +158,7 @@ func ChangeUserPassword(u models.User, oldPassword ...string) (err error) {
// ResetUserPassword ...
func ResetUserPassword(u models.User) error {
o := orm.NewOrm()
o := GetOrmer()
r, err := o.Raw(`update user set password=?, reset_uuid=? where reset_uuid=?`, utils.Encrypt(u.Password, u.Salt), "", u.ResetUUID).Exec()
if err != nil {
return err
@ -176,7 +175,7 @@ func ResetUserPassword(u models.User) error {
// UpdateUserResetUUID ...
func UpdateUserResetUUID(u models.User) error {
o := orm.NewOrm()
o := GetOrmer()
_, err := o.Raw(`update user set reset_uuid=? where email=?`, u.ResetUUID, u.Email).Exec()
return err
}
@ -207,7 +206,7 @@ func CheckUserPassword(query models.User) (*models.User, error) {
queryParam = append(queryParam, currentUser.Username)
queryParam = append(queryParam, utils.Encrypt(query.Password, currentUser.Salt))
}
o := orm.NewOrm()
o := GetOrmer()
var user []models.User
n, err := o.Raw(sql, queryParam).QueryRows(&user)
@ -226,7 +225,7 @@ func CheckUserPassword(query models.User) (*models.User, error) {
// DeleteUser ...
func DeleteUser(userID int) error {
o := orm.NewOrm()
o := GetOrmer()
_, err := o.Raw(`update user set deleted = 1 where user_id = ?`, userID).Exec()
return err
}

View File

@ -6,6 +6,29 @@ body {
position: fixed;
}
.has-logged-in {
position: relative;
top: 50px;
}
.has-logged-in h4 {
float: left;
width: 100%;
line-height: 2em;
text-align: center;
}
.has-logged-in .last-logged-in-time {
text-align: center;
}
.has-logged-in .control-button {
height: 2em;
width: 100%;
padding-right: 10%;
clear: both;
}
.container-fluid-custom {
background-color: #EFEFEF;
max-height: 100%;

View File

@ -56,4 +56,9 @@
.well-custom {
position: relative;
width: 100%;
}
.empty-hint {
text-align: center;
vertical-align: middle;
}

View File

@ -108,4 +108,23 @@
.popover{
max-width: 500px;
}
}
.popover-header {
padding:8px 14px;
background-color:#f7f7f7;
border-bottom:1px solid #ebebeb;
-webkit-border-radius:5px 5px 0 0;
-moz-border-radius:5px 5px 0 0;
border-radius:5px 5px 0 0;
}
.popover-title {
height: 2.5em;
padding: 8px 14px;
margin: 0;
font-size: 14px;
background-color: #f7f7f7;
border-bottom: 1px solid #ebebeb;
border-radius: 5px 5px 0 0;
}

View File

@ -58,7 +58,7 @@
if($routeParams.project_id){
angular.forEach(vm.projects, function(value, index) {
if(value['ProjectId'] === $routeParams.project_id) {
if(value['ProjectId'] === Number($routeParams.project_id)) {
vm.selectedProject = value;
}
});
@ -123,8 +123,15 @@
function link(scope, element, attrs, ctrl) {
$(document).on('click', clickHandler);
function clickHandler(e) {
$('[data-toggle="popover"]').each(function () {
//the 'is' for buttons that trigger popups
//the 'has' for icons within a button that triggers a popup
if (!$(this).is(e.target) && $(this).has(e.target).length === 0 && $('.popover').has(e.target).length === 0) {
$(this).parent().popover('hide');
}
});
var targetId = $(e.target).attr('id');
if(targetId === 'switchPane' ||
targetId === 'retrievePane' ||

View File

@ -31,9 +31,6 @@
AddProjectMemberService(vm.projectId, vm.optRole, pm.username)
.success(addProjectMemberComplete)
.error(addProjectMemberFailed);
vm.username = '';
vm.optRole = 1;
vm.reload();
}
}
@ -48,6 +45,7 @@
function addProjectMemberComplete(data, status, header) {
console.log('addProjectMemberComplete: status:' + status + ', data:' + data);
vm.reload();
}
function addProjectMemberFailed(data, status, headers) {

View File

@ -28,7 +28,6 @@
function updateProjectMember(e) {
if(vm.editMode) {
vm.editMode = false;
console.log('update project member, roleId:' + e.roleId);
EditProjectMemberService(e.projectId, e.userId, e.roleId)
.success(editProjectMemberComplete)
@ -42,12 +41,13 @@
DeleteProjectMemberService(e.projectId, e.userId)
.success(editProjectMemberComplete)
.error(editProjectMemberFailed);
vm.reload();
}
function editProjectMemberComplete(data, status, headers) {
console.log('edit project member complete: ' + status);
vm.lastRoleName = vm.roleName;
vm.editMode = false;
vm.reload();
}
function editProjectMemberFailed(e) {
@ -56,7 +56,6 @@
function cancelUpdate() {
vm.editMode = false;
console.log('lastRoleName:' + vm.lastRoleName);
vm.roleName = vm.lastRoleName;
}

View File

@ -16,6 +16,7 @@
vm.currentRole = getRole({'key': 'roleName', 'value': current});
}
});
vm.selectRole = selectRole;
function selectRole(role) {

View File

@ -8,7 +8,10 @@
</span>
</div>
</div>
<div class="panel-group" id="accordion" role="tablist" aria-multiselectable="true">
<div ng-if="vm.repositories.length === 0" class="empty-hint">
<h3 style="margin-top: 200px;" class="text-muted">// 'no_repositories' | tr //</h3>
</div>
<div ng-if="vm.repositories.length > 0" class="panel-group" id="accordion" role="tablist" aria-multiselectable="true">
<modal-dialog action="vm.deleteImage()" content-type="text/html" title="//vm.modalTitle//" message="//vm.modalMessage//"></modal-dialog>
<div class="panel panel-default" ng-repeat="repo in vm.repositories">
<div class="panel-heading" role="tab" id="heading//$index + 1//">

View File

@ -25,9 +25,14 @@
vm.retrieve = retrieve;
vm.projectId = $routeParams.project_id;
vm.tagCount = {};
vm.retrieve();
$scope.$watch('vm.repositories', function(current) {
if(current) {
vm.repositories = current || [];
}
});
$scope.$on('repoName', function(e, val) {
vm.repoName = val;
});
@ -58,7 +63,7 @@
}
function getRepositoryComplete(data, status) {
vm.repositories = data;
vm.repositories = data || [];
}
function getRepositoryFailed(response) {

View File

@ -10,7 +10,7 @@
<tbody>
<tr ng-repeat="tag in vm.tags">
<td>//tag//</td>
<td style="text-align: center;"><popup-details repo-name="//vm.repoName//" tag="//tag//"></popup-details></td>
<td style="text-align: center;"><popup-details repo-name="//vm.repoName//" tag="//tag//" index="//$index//"></popup-details></td>
<td>
<pull-command repo-name="//vm.repoName//" tag="//tag//"></pull-command>
</td>

View File

@ -61,11 +61,18 @@
'repoName': '='
},
'replace': true,
'link': link,
'controller': ListTagController,
'controllerAs': 'vm',
'bindToController': true
};
return directive;
function link(scope, element, attrs, ctrl) {
}
}
})();

View File

@ -1,4 +1,4 @@
<a href="javascript:void(0);">
<span tabindex="0" class="glyphicon glyphicon-info-sign" role="button" data-trigger="focus" data-toggle="popover" data-placement="right">
<a href="javascript:void(0)" >
<span class="glyphicon glyphicon-info-sign" role="button" data-trigger="click" data-toggle="popover" data-placement="right">
</span>
</a>
</a>

View File

@ -10,8 +10,9 @@
function PopupDetailsController(ListManifestService, $filter, dateLFilter) {
var vm = this;
vm.retrieve = retrieve;
function retrieve() {
ListManifestService(vm.repoName, vm.tag)
.success(getManifestSuccess)
@ -35,8 +36,10 @@
'templateUrl': '/static/ng/resources/js/components/repository/popup-details.directive.html',
'scope': {
'repoName': '@',
'tag': '@'
'tag': '@',
'index': '@'
},
'replace': true,
'link': link,
'controller': PopupDetailsController,
'controllerAs': 'vm',
@ -46,21 +49,35 @@
function link(scope, element, attrs, ctrl) {
ctrl.retrieve();
scope.$watch('vm.manifest', function(current, origin) {
scope.$watch('vm.manifest', function(current) {
if(current) {
element.find('span').popover({
'content': generateContent,
'html': true
});
element
.popover({
'template': '<div class="popover" role="tooltip"><div class="arrow"></div><div class="popover-title"></div><div class="popover-content"></div></div>',
'title': '<div class="pull-right clearfix"><a href="javascript:void(0);"><span class="glyphicon glyphicon-remove-circle"></span></a></div>',
'content': generateContent,
'html': true
})
.on('shown.bs.popover', function(e){
var self = jQuery(this);
$('[type="text"]:input', self.parent())
.select()
.end()
.on('click', function() {
$(this).select();
});
self.parent().find('.glyphicon.glyphicon-remove-circle').on('click', function() {
element.trigger('click');
});
});
}
});
function generateContent() {
var content =
'<form class="form-horizontal">' +
var content = '<form class="form-horizontal">' +
'<div class="form-group">' +
'<label class="col-sm-3 control-label">Id</label>' +
'<div class="col-sm-9"><p class="form-control-static long-line">' + ctrl.manifest['Id'] + '</p></div></div>' +
'<div class="col-sm-9"><p class="form-control-static long-line"><input type="text" id="txtImageId" value="' + ctrl.manifest['Id'] + '" readonly size="40"></p></div></div>' +
'<div class="form-group"><label class="col-sm-3 control-label">Parent</label>' +
'<div class="col-sm-9"><p class="form-control-static long-line">' + ctrl.manifest['Parent'] + '</p></div></div>' +
'<div class="form-group"><label class="col-sm-3 control-label">Created</label>' +

View File

@ -1,9 +1,9 @@
<form class="form-inline">
<div class="form-group">
<div class="input-group">
<input type="text" class="form-control" id="//vm.repoName//-//vm.tag//" value="docker pull //vm.harborRegUrl////vm.repoName//://vm.tag//" readonly="readonly" size="60">
<input type="text" class="form-control" value="docker pull //vm.harborRegUrl////vm.repoName//://vm.tag//" readonly="readonly" size="60">
<div class="input-group-addon">
<a href="javascript:void(0);" data-clipboard-target="//vm.repoName//-//vm.tag//"><span class="glyphicon glyphicon-duplicate" data-toggle="tooltip" data-placement="right" title="Copied!"></span></a>
<a href="javascript:void(0);"><span class="glyphicon glyphicon-duplicate"></span></a>
</div>
</div>
</div>

View File

@ -29,26 +29,10 @@
ctrl.harborRegUrl = $('#HarborRegUrl').val() + '/';
ZeroClipboard.config( { swfPath: "/static/ng/vendors/zc/v2.2.0/ZeroClipboard.swf" } );
var clip = new ZeroClipboard(element.find('a'));
element.find('span').tooltip({'trigger': 'click'});
clip.on("ready", function() {
console.log("Flash movie loaded and ready.");
this.on("aftercopy", function(event) {
console.log("Copied text to clipboard: " + event.data["text/plain"]);
element.find('span').tooltip('show');
setTimeout(function(){
element.find('span').tooltip('hide');
}, 1000);
});
});
clip.on("error", function(event) {
console.log('error[name="' + event.name + '"]: ' + event.message);
ZeroClipboard.destroy();
element.find('span').tooltip('destroy');
});
element.find('a').on('click', clickHandler);
function clickHandler(e) {
element.find('input[type="text"]').select();
}
}

View File

@ -6,8 +6,8 @@
.module('harbor.sign.in')
.directive('signIn', signIn);
SignInController.$inject = ['SignInService', '$window', '$scope'];
function SignInController(SignInService, $window, $scope) {
SignInController.$inject = ['SignInService', 'LogOutService', 'currentUser', 'I18nService', '$window', '$scope'];
function SignInController(SignInService, LogOutService, currentUser, I18nService, $window, $scope) {
var vm = this;
vm.hasError = false;
@ -18,6 +18,9 @@
vm.doSignUp = doSignUp;
vm.doForgotPassword = doForgotPassword;
vm.doContinue = doContinue;
vm.doLogOut = doLogOut;
function reset() {
vm.hasError = false;
vm.errorMessage = '';
@ -32,7 +35,7 @@
}
function signedInSuccess(data, status) {
$window.location.href = "/ng/project";
$window.location.href = "/ng/dashboard";
}
function signedInFailed(data, status) {
@ -50,12 +53,32 @@
function doForgotPassword() {
$window.location.href = '/ng/forgot_password';
}
function doContinue() {
$window.location.href = '/ng/dashboard';
}
function doLogOut() {
LogOutService()
.success(logOutSuccess)
.error(logOutFailed);
}
function logOutSuccess(data, status) {
currentUser.unset();
I18nService().unset();
$window.location.href= '/ng';
}
function logOutFailed(data, status) {
console.log('Failed to log out:' + data);
}
}
function signIn() {
var directive = {
'restrict': 'E',
'templateUrl': '/static/ng/resources/js/components/sign-in/sign-in.directive.html',
'templateUrl': '/ng/sign_in',
'scope': true,
'controller': SignInController,
'controllerAs': 'vm',

View File

@ -38,22 +38,23 @@
function link(scope, element, attrs, ctrl) {
var visited = ctrl.url.substring(1);
var visited = ctrl.url.substring(1);
if(visited.indexOf('?') >= 0) {
visited = ctrl.url.substring(1, ctrl.url.indexOf('?') - 1);
visited = ctrl.url.substring(1, ctrl.url.indexOf('?'));
}
scope.$watch('vm.selectedProject', function(current) {
if(current) {
element.find('a').removeClass('active');
element.find('a:first').addClass('active');
if(visited) {
element.find('a[tag="' + visited + '"]').addClass('active');
}else{
element.find('a:first').addClass('active');
}
}
});
element.find('a[tag*="' + visited + '"]').addClass('active');
element.find('a').on('click', click);
element.find('a').on('click', click);
function click(event) {
element.find('a').removeClass('active');
$(event.target).addClass('active');

View File

@ -27,11 +27,7 @@
vm.searchProject = searchProject;
vm.showAddButton = showAddButton;
vm.togglePublicity = togglePublicity;
$timeout(function() {
vm.user = currentUser.get();
});
vm.user = currentUser.get();
vm.retrieve();
function retrieve() {
@ -45,7 +41,9 @@
data.forEach(function(data){
data.role = vm.MAP[data.role_id];
});
vm.projects = data;
vm.projects = data || [];
}
function listProjectFailed(e) {

View File

@ -117,5 +117,10 @@ var locale_messages = {
'alert_delete_tag_title': 'Delete tag - $0',
'alert_delete_tag': 'Delete this "$0" tag now?',
'close': 'Close',
'ok': 'OK'
'ok': 'OK',
'welcome': 'Welcome ',
'to_harbor': ' to Harbor!',
'continue' : 'Continue',
'no_projects_add_new_project': 'No projects, add new project now.',
'no_repositories': 'No repositories found, please use "docker push" to upload images.'
};

View File

@ -115,5 +115,10 @@ var locale_messages = {
'alert_delete_tag_title': '删除镜像标签 - $0',
'alert_delete_tag': '删除镜像标签 "$0" ?',
'close': '关闭',
'ok': '确认'
'ok': '确认',
'welcome': '欢迎 ',
'to_harbor': ' 使用Harbor!',
'continue' : '继续',
'no_projects_add_new_project': '当前没有项目,请新增项目。',
'no_repositories': '未发现镜像,请用"docker push"命令上传镜像。'
};

File diff suppressed because it is too large Load Diff

Binary file not shown.

View File

@ -25,4 +25,5 @@ func initNgRouters() {
beego.Router("/ng/optional_menu", &ng.OptionalMenuController{})
beego.Router("/ng/navigation_header", &ng.NavigationHeaderController{})
beego.Router("/ng/sign_in", &ng.SignInController{})
}

View File

@ -17,15 +17,15 @@
<div class="page-content">
<div style="margin-left: auto; margin-right: auto; width: 100%;">
<div class="thumbnail display-inline-block">
<img src="static/ng/resources/img/Step1.png" alt="Step 1">
<img src="/static/ng/resources/img/Step1.png" alt="Step 1">
<h5 class="step-content">// 'anybody_can_read_public_projects' | tr //</h5>
</div>
<div class="thumbnail display-inline-block">
<img src="static/ng/resources/img/Step2.png" alt="Step 2">
<img src="/static/ng/resources/img/Step2.png" alt="Step 2">
<h5 class="step-content">// 'create_projects_and_connect_repositories' | tr //</h5>
</div>
<div class="thumbnail display-inline-block">
<img src="static/ng/resources/img/Step3.png" alt="Step 3">
<img src="/static/ng/resources/img/Step3.png" alt="Step 3">
<h5 class="step-content">// 'user_management_and_role_assignment' | tr //</h5>
</div>
</div>

View File

@ -30,13 +30,16 @@
<th>// 'project_name' | tr //</th><th>// 'repositories' | tr //</th><th>// 'role' | tr //</th><th>// 'creation_time' | tr //</th><th>// 'publicity' | tr //</th>
</thead>
<tbody>
<tr ng-repeat="p in vm.projects">
<tr>
<td colspan="5" height="320px" class="empty-hint" ng-if="vm.projects.length === 0"><h3 class="text-muted">// 'no_projects_add_new_project' | tr //</h3></td>
</tr>
<tr ng-if="vm.projects.length > 0" ng-repeat="p in vm.projects">
<td><a href="/ng/repository#/repositories?project_id=//p.ProjectId//&is_public=//p.Public//">//p.Name//</a></td>
<td>//p.count//</td>
<td>//p.role//</td>
<td>//p.CreationTime | dateL : 'YYYY-MM-DD HH:mm:ss'//</td>
<td><publicity-button is-public="p.Public" owned="p.OwnerId == vm.user.UserId" project-id="p.ProjectId"></publicity-button></td>
</tr>
</tr>
</tbody>
</table>
</div>

View File

@ -6,7 +6,7 @@
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="#"><img class="img-responsive" src="/static/ng/resources/img/Harbor_Logo_rec.png" alt="Harbor's Logo"/></a>
<a class="navbar-brand" href="/ng"><img class="img-responsive" src="/static/ng/resources/img/Harbor_Logo_rec.png" alt="Harbor's Logo"/></a>
</div>
<!-- Collect the nav links, forms, and other content for toggling -->
<div class="collapse navbar-collapse" id="bs-harbor-navbar-collapse-1">

View File

@ -10,7 +10,6 @@
<link rel="stylesheet" href="/static/ng/vendors/eonasdan-bootstrap-datetimepicker/build/css/bootstrap-datetimepicker.min.css" />
<script src="/static/ng/vendors/spinner/spinner.min.js"></script>
<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"-->

View File

@ -1,3 +1,11 @@
{{ if eq .HasLoggedIn true }}
<div class="has-logged-in">
<h4>// 'welcome' | tr // <strong>{{ .Username }}</strong> // 'to_harbor' | tr //</h4>
<!--p class="text-muted last-logged-in-time">Last login time: //vm.lastLoggedInTime//</p-->
<p class="control-button"><input type="button" class="btn btn-default pull-right" value="// 'continue' | tr //" ng-click="vm.doContinue()"></p>
<p class="control-button"><input type="button" class="btn btn-link pull-right" value="// 'log_out' | tr //" ng-click="vm.doLogOut()"></p>
</div>
{{ else }}
<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">
@ -35,4 +43,5 @@
</div>
</div>
</div>
</form>
</form>
{{ end }}