Merge remote-tracking branch 'upstream/master'

This commit is contained in:
perhapszzy@sina.com 2016-04-06 22:46:20 +08:00
commit 9fe1ef41b6
41 changed files with 299 additions and 162 deletions

View File

@ -1,62 +1,67 @@
log:
build: ./log/
volumes:
- /var/log/harbor/:/var/log/docker/
ports:
- 1514:514
registry:
image: library/registry:2.3.0
volumes:
- /data/registry:/storage
- ./config/registry/:/etc/registry/
ports:
- 5001:5001
command:
/etc/registry/config.yml
links:
- log
log_driver: "syslog"
log_opt:
syslog-address: "tcp://127.0.0.1:1514"
syslog-tag: "registry"
mysql:
build: ./db/
volumes:
- /data/database:/var/lib/mysql
env_file:
- ./config/db/env
links:
- log
log_driver: "syslog"
log_opt:
syslog-address: "tcp://127.0.0.1:1514"
syslog-tag: "mysql"
ui:
build: ../
env_file:
- ./config/ui/env
volumes:
- ./config/ui/app.conf:/etc/ui/app.conf
- ./config/ui/private_key.pem:/etc/ui/private_key.pem
links:
- registry
- mysql
- log
log_driver: "syslog"
log_opt:
syslog-address: "tcp://127.0.0.1:1514"
syslog-tag: "ui"
proxy:
image: library/nginx:1.9
volumes:
- ./config/nginx:/etc/nginx
links:
- ui
- registry
- log
ports:
- 80:80
log_driver: "syslog"
log_opt:
syslog-address: "tcp://127.0.0.1:1514"
syslog-tag: "proxy"
version: '2'
services:
log:
build: ./log/
volumes:
- /var/log/harbor/:/var/log/docker/
ports:
- 1514:514
registry:
image: library/registry:2.3.0
volumes:
- /data/registry:/storage
- ./config/registry/:/etc/registry/
ports:
- 5001:5001
command:
/etc/registry/config.yml
depends_on:
- log
logging:
driver: "syslog"
options:
syslog-address: "tcp://127.0.0.1:1514"
syslog-tag: "registry"
mysql:
build: ./db/
volumes:
- /data/database:/var/lib/mysql
env_file:
- ./config/db/env
depends_on:
- log
logging:
driver: "syslog"
options:
syslog-address: "tcp://127.0.0.1:1514"
syslog-tag: "mysql"
ui:
build: ../
env_file:
- ./config/ui/env
volumes:
- ./config/ui/app.conf:/etc/ui/app.conf
- ./config/ui/private_key.pem:/etc/ui/private_key.pem
depends_on:
- log
logging:
driver: "syslog"
options:
syslog-address: "tcp://127.0.0.1:1514"
syslog-tag: "ui"
proxy:
image: library/nginx:1.9
volumes:
- ./config/nginx:/etc/nginx
ports:
- 80:80
depends_on:
- mysql
- registry
- ui
- log
logging:
driver: "syslog"
options:
syslog-address: "tcp://127.0.0.1:1514"
syslog-tag: "proxy"

View File

@ -1,24 +1,35 @@
## CONFIGURATIONS
#The endpoint for user to access UI and registry service
hostname = mydomain.com
#The protocol for accessing the UI and token/notification service, by default it is http
#User can set it to https if ssl is setup on nginx
## Configuration file of Harbor
#The IP address or hostname to access admin UI and registry service.
#DO NOT use localhost or 127.0.0.1, because Harbor needs to be accessed by external clients.
hostname = reg.mydomain.com
#The protocol for accessing the UI and token/notification service, by default it is http.
#It can be set to https if ssl is enabled on nginx.
ui_url_protocol = http
#Email settings for ui to send password resetting emails
#Email account settings for sending out password resetting emails.
email_server = smtp.mydomain.com
email_server_port = 25
email_username = sample_admin@mydomain.com
email_password = abc
email_from = admin <sample_admin@mydomain.com>
##The password of harbor admin
##The password of Harbor admin, change this before any production use.
harbor_admin_password= Harbor12345
##By default the auth mode is db_auth, i.e. the creadentials are stored in a databse
#please set it to ldap_auth if you want to verify user's credentials against an ldap server.
##By default the auth mode is db_auth, i.e. the credentials are stored in a local database.
#Set it to ldap_auth if you want to verify a user's credentials against an LDAP server.
auth_mode = db_auth
#The url for ldap endpoint
#The url for an ldap endpoint.
ldap_url = ldaps://ldap.mydomain.com
#The basedn template for verifying the user's password
#The basedn template to look up a user in LDAP and verify the user's password.
ldap_basedn = uid=%s,ou=people,dc=mydomain,dc=com
#The password for root user of db
#The password for the root user of mysql db, change this before any production use.
db_password = root123
#Switch for self-registration feature
self_registration = on
#####

View File

@ -19,7 +19,7 @@ http:
auth:
token:
issuer: registry-token-issuer
realm: http://localhost/service/token
realm: http://registry/service/token
rootcertbundle: /etc/registry/root.crt
service: token-service
@ -27,7 +27,7 @@ notifications:
endpoints:
- name: harbor
disabled: false
url: http://localhost/service/notifications
url: http://registry/service/notifications
timeout: 500
threshold: 5
backoff: 1000

View File

@ -7,9 +7,9 @@ metadata:
spec:
type: LoadBalancer
ports:
- name: bbb
- name: http
port: 80
- name: aaa
- name: https
port: 443
selector:
name: proxy

View File

@ -20,6 +20,7 @@ spec:
ports:
- containerPort: 5000
- containerPort: 5001
- containerPort: 53
volumeMounts:
- name: storage
mountPath: /storage

View File

@ -11,5 +11,7 @@ spec:
port: 5000
- name: external
port: 5001
- name: aa
port: 53
selector:
name: registry

View File

@ -25,6 +25,7 @@ auth_mode = cp.get("configuration", "auth_mode")
ldap_url = cp.get("configuration", "ldap_url")
ldap_basedn = cp.get("configuration", "ldap_basedn")
db_password = cp.get("configuration", "db_password")
self_registration = cp.get("configuration", "self_registration")
########
base_dir = os.path.dirname(__file__)
@ -60,11 +61,13 @@ for f in conf_files:
render(os.path.join(templates_dir, "ui", "env"),
ui_conf_env,
hostname=hostname,
db_password=db_password,
ui_url=ui_url,
auth_mode=auth_mode,
admin_pwd=harbor_admin_password,
ldap_url=ldap_url,
ldap_basedn=ldap_basedn)
ldap_basedn=ldap_basedn,
self_registration=self_registration)
render(os.path.join(templates_dir, "ui", "app.conf"),
ui_conf,

View File

@ -27,7 +27,7 @@ notifications:
endpoints:
- name: harbor
disabled: false
url: $ui_url/service/notifications
url: http://ui/service/notifications
timeout: 500
threshold: 5
backoff: 1000

View File

@ -1,5 +1,7 @@
MYSQL_HOST=mysql
MYSQL_PORT=3306
MYSQL_USR=root
MYSQL_PWD=$db_password
REGISTRY_URL=http://registry:5000
CONFIG_PATH=/etc/ui/app.conf
HARBOR_REG_URL=$hostname
@ -8,4 +10,5 @@ HARBOR_URL=$ui_url
AUTH_MODE=$auth_mode
LDAP_URL=$ldap_url
LDAP_BASE_DN=$ldap_basedn
SELF_REGISTRATION=$self_registration
LOG_LEVEL=debug

View File

@ -16,12 +16,14 @@
package controllers
import (
"net/http"
"os"
"strings"
"github.com/vmware/harbor/utils/log"
"github.com/astaxie/beego"
"github.com/beego/i18n"
"github.com/vmware/harbor/dao"
"github.com/vmware/harbor/utils/log"
)
// CommonController handles request from UI that doesn't expect a page, such as /login /logout ...
@ -38,6 +40,9 @@ func (c *CommonController) Render() error {
type BaseController struct {
beego.Controller
i18n.Locale
SelfRegistration bool
IsAdmin bool
AuthMode string
}
type langType struct {
@ -93,15 +98,35 @@ func (b *BaseController) Prepare() {
b.Data["CurLang"] = curLang.Name
b.Data["RestLangs"] = restLangs
sessionUserID := b.GetSession("userId")
if sessionUserID != nil {
b.Data["Username"] = b.GetSession("username")
}
authMode := os.Getenv("AUTH_MODE")
authMode := strings.ToLower(os.Getenv("AUTH_MODE"))
if authMode == "" {
authMode = "db_auth"
}
b.Data["AuthMode"] = authMode
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
}
// ForwardTo setup layout and template for content for a page.

View File

@ -17,11 +17,11 @@ package controllers
import (
"net/http"
"os"
"strings"
"github.com/vmware/harbor/dao"
"github.com/vmware/harbor/models"
"github.com/vmware/harbor/utils/log"
)
@ -32,35 +32,70 @@ type RegisterController struct {
// Get renders the Sign In page, it only works if the auth mode is set to db_auth
func (rc *RegisterController) Get() {
authMode := os.Getenv("AUTH_MODE")
if authMode == "" || authMode == "db_auth" {
if !rc.SelfRegistration {
log.Warning("Registration is disabled when self-registration is off.")
rc.Redirect("/signIn", http.StatusFound)
}
if rc.AuthMode == "db_auth" {
rc.ForwardTo("page_title_registration", "register")
} else {
rc.Redirect("/signIn", http.StatusNotFound)
rc.Redirect("/signIn", http.StatusFound)
}
}
// AddUserController handles request for adding user with an admin role user
type AddUserController struct {
BaseController
}
// Get renders the Sign In page, it only works if the auth mode is set to db_auth
func (ac *AddUserController) Get() {
if !ac.IsAdmin {
log.Warning("Add user can only be used by admin role user.")
ac.Redirect("/signIn", http.StatusFound)
}
if ac.AuthMode == "db_auth" {
ac.ForwardTo("page_title_add_user", "register")
} else {
ac.Redirect("/signIn", http.StatusFound)
}
}
// SignUp insert data into DB based on data in form.
func (rc *CommonController) SignUp() {
username := strings.TrimSpace(rc.GetString("username"))
email := strings.TrimSpace(rc.GetString("email"))
realname := strings.TrimSpace(rc.GetString("realname"))
password := strings.TrimSpace(rc.GetString("password"))
comment := strings.TrimSpace(rc.GetString("comment"))
func (cc *CommonController) SignUp() {
if !(cc.AuthMode == "db_auth") {
cc.CustomAbort(http.StatusForbidden, "")
}
if !(cc.SelfRegistration || cc.IsAdmin) {
log.Warning("Registration can only be used by admin role user when self-registration is off.")
cc.CustomAbort(http.StatusForbidden, "")
}
username := strings.TrimSpace(cc.GetString("username"))
email := strings.TrimSpace(cc.GetString("email"))
realname := strings.TrimSpace(cc.GetString("realname"))
password := strings.TrimSpace(cc.GetString("password"))
comment := strings.TrimSpace(cc.GetString("comment"))
user := models.User{Username: username, Email: email, Realname: realname, Password: password, Comment: comment}
_, err := dao.Register(user)
if err != nil {
log.Errorf("Error occurred in Register: %v", err)
rc.CustomAbort(http.StatusInternalServerError, "Internal error.")
cc.CustomAbort(http.StatusInternalServerError, "Internal error.")
}
}
// UserExists checks if user exists when user input value in sign in form.
func (rc *CommonController) UserExists() {
target := rc.GetString("target")
value := rc.GetString("value")
func (cc *CommonController) UserExists() {
target := cc.GetString("target")
value := cc.GetString("value")
user := models.User{}
switch target {
@ -73,8 +108,8 @@ func (rc *CommonController) UserExists() {
exist, err := dao.UserExists(user, target)
if err != nil {
log.Errorf("Error occurred in UserExists: %v", err)
rc.CustomAbort(http.StatusInternalServerError, "Internal error.")
cc.CustomAbort(http.StatusInternalServerError, "Internal error.")
}
rc.Data["json"] = exist
rc.ServeJSON()
cc.Data["json"] = exist
cc.ServeJSON()
}

View File

@ -66,18 +66,11 @@ func GenerateRandomString() (string, error) {
func InitDB() {
orm.RegisterDriver("mysql", orm.DRMySQL)
addr := os.Getenv("MYSQL_HOST")
if len(addr) == 0 {
addr = os.Getenv("MYSQL_PORT_3306_TCP_ADDR")
}
port := os.Getenv("MYSQL_PORT_3306_TCP_PORT")
port := os.Getenv("MYSQL_PORT")
username := os.Getenv("MYSQL_USR")
password := os.Getenv("MYSQL_PWD")
password := os.Getenv("MYSQL_ENV_MYSQL_ROOT_PASSWORD")
if len(password) == 0 {
password = os.Getenv("MYSQL_PWD")
}
log.Debugf("db url: %s:%s, db user: %s", addr, port, username)
dbStr := username + ":" + password + "@tcp(" + addr + ":" + port + ")/registry"
ch := make(chan int, 1)
go func() {

View File

@ -128,8 +128,8 @@ func TestMain(m *testing.M) {
log.Infof("DB_HOST: %s, DB_USR: %s, DB_PORT: %s, DB_PWD: %s\n", dbHost, dbUser, dbPort, dbPassword)
os.Setenv("MYSQL_PORT_3306_TCP_ADDR", dbHost)
os.Setenv("MYSQL_PORT_3306_TCP_PORT", dbPort)
os.Setenv("MYSQL_HOST", dbHost)
os.Setenv("MYSQL_PORT", dbPort)
os.Setenv("MYSQL_USR", dbUser)
os.Setenv("MYSQL_PWD", dbPassword)
os.Setenv("AUTH_MODE", "db_auth")

View File

@ -5,7 +5,7 @@ Harbor can be installed from the source code by using "docker-compose up" comman
Harbor is deployed as several Docker containers. Hence, it can be deployed on any Linux distribution that supports Docker.
Before deploying Harbor, the target machine requires Python, Docker, Docker Compose to be installed.
* Python should be version 2.7 or higher. Some Linux distributions (Gentoo, Arch) may not have a Python interpreter installed by default. On those systems, you need to install Python manually.
* The Docker engine should be version 1.8 or higher. For the details to install Docker engine, please refer to: https://docs.docker.com/engine/installation/
* The Docker engine should be version 1.10 or higher. For the details to install Docker engine, please refer to: https://docs.docker.com/engine/installation/
* The Docker Compose needs to be version 1.6.0 or higher. For the details to install Docker compose, please refer to: https://docs.docker.com/compose/install/
### Configuration of Harbor

View File

@ -41,6 +41,7 @@ func init() {
beego.Router("/", &controllers.IndexController{})
beego.Router("/signIn", &controllers.SignInController{})
beego.Router("/register", &controllers.RegisterController{})
beego.Router("/addUser", &controllers.AddUserController{})
beego.Router("/forgotPassword", &controllers.ForgotPasswordController{})
beego.Router("/resetPassword", &controllers.ResetPasswordController{})
beego.Router("/changePassword", &controllers.ChangePasswordController{})

View File

@ -42,13 +42,14 @@ func (a *TokenHandler) Get() {
username, password, _ := request.BasicAuth()
authenticated := authenticate(username, password)
service := a.GetString("service")
scope := a.GetString("scope")
scopes := a.GetStrings("scope")
log.Debugf("scopes: %+v", scopes)
if len(scope) == 0 && !authenticated {
if len(scopes) == 0 && !authenticated {
log.Info("login request with invalid credentials")
a.CustomAbort(http.StatusUnauthorized, "")
}
access := svc_utils.GetResourceActions(scope)
access := svc_utils.GetResourceActions(scopes)
for _, a := range access {
svc_utils.FilterAccess(username, authenticated, a)
}

View File

@ -38,17 +38,19 @@ const (
)
// GetResourceActions ...
func GetResourceActions(scope string) []*token.ResourceActions {
func GetResourceActions(scopes []string) []*token.ResourceActions {
var res []*token.ResourceActions
if scope == "" {
return res
for _, s := range scopes {
if s == "" {
continue
}
items := strings.Split(s, ":")
res = append(res, &token.ResourceActions{
Type: items[0],
Name: items[1],
Actions: strings.Split(items[2], ","),
})
}
items := strings.Split(scope, ":")
res = append(res, &token.ResourceActions{
Type: items[0],
Name: items[1],
Actions: strings.Split(items[2], ","),
})
return res
}
@ -66,9 +68,12 @@ func FilterAccess(username string, authenticated bool, a *token.ResourceActions)
if strings.Contains(a.Name, "/") { //Only check the permission when the requested image has a namespace, i.e. project
projectName := a.Name[0:strings.LastIndex(a.Name, "/")]
var permission string
var err error
if authenticated {
if username == "admin" {
isAdmin, err := dao.IsAdminRole(username)
if err != nil {
log.Errorf("Error occurred in IsAdminRole: %v", err)
}
if isAdmin {
exist, err := dao.ProjectExists(projectName)
if err != nil {
log.Errorf("Error occurred in CheckExistProject: %v", err)
@ -100,8 +105,8 @@ func FilterAccess(username string, authenticated bool, a *token.ResourceActions)
}
// GenTokenForUI is for the UI process to call, so it won't establish a https connection from UI to proxy.
func GenTokenForUI(username, service, scope string) (string, error) {
access := GetResourceActions(scope)
func GenTokenForUI(username string, service string, scopes []string) (string, error) {
access := GetResourceActions(scopes)
for _, a := range access {
FilterAccess(username, true, a)
}

View File

@ -63,14 +63,15 @@ func RegistryAPIGet(url, username string) ([]byte, error) {
authenticate := response.Header.Get("WWW-Authenticate")
log.Debugf("authenticate header: %s", authenticate)
var service string
var scope string
var scopes []string
//Disregard the case for hanlding multiple scopes for http call initiated from UI, as there's refactor planned.
re := regexp.MustCompile(`service=\"(.*?)\".*scope=\"(.*?)\"`)
res := re.FindStringSubmatch(authenticate)
if len(res) > 2 {
service = res[1]
scope = res[2]
scopes = append(scopes, res[2])
}
token, err := GenTokenForUI(username, service, scope)
token, err := GenTokenForUI(username, service, scopes)
if err != nil {
return nil, err
}

View File

@ -3,6 +3,7 @@ page_title_sign_in = Sign In - Harbor
page_title_project = Project - Harbor
page_title_item_details = Details - Harbor
page_title_registration = Sign Up - Harbor
page_title_add_user = Add User - Harbor
page_title_forgot_password = Forgot Password - Harbor
title_forgot_password = Forgot Password
page_title_reset_password = Reset Password - Harbor
@ -12,6 +13,7 @@ title_change_password = Change Password
page_title_search = Search - Harbor
sign_in = Sign In
sign_up = Sign Up
add_user = Add User
log_out = Log Out
search_placeholder = projects or repositories
change_password = Change Password

View File

@ -177,6 +177,10 @@ var global_messages = {
"en-US": "Sign Up",
"zh-CN": "注册"
},
"title_add_user": {
"en-US": "Add User",
"zh-CN": "新增用户"
},
"registered_successfully": {
"en-US": "Signed up successfully.",
"zh-CN": "注册成功。"
@ -185,6 +189,14 @@ var global_messages = {
"en-US": "Failed to sign up.",
"zh-CN": "注册失败。"
},
"added_user_successfully": {
"en-US": "Added user successfully.",
"zh-CN": "新增用户成功。"
},
"added_user_failed": {
"en-US": "Added user failed.",
"zh-CN": "新增用户失败。"
},
"projects" : {
"en-US": "Projects",
"zh-CN": "项目"

View File

@ -3,6 +3,7 @@ page_title_sign_in = 登录 - Harbor
page_title_project = 项目 - Harbor
page_title_item_details = 详细信息 - Harbor
page_title_registration = 注册 - Harbor
page_title_add_user = 新增用户 - Harbor
page_title_forgot_password = 忘记密码 - Harbor
title_forgot_password = 忘记密码
page_title_reset_password = 重置密码 - Harbor
@ -12,6 +13,7 @@ title_change_password = 修改密码
page_title_search = 搜索 - Harbor
sign_in = 登录
sign_up = 注册
add_user = 新增用户
log_out = 注销
search_placeholder = 项目或镜像名称
change_password = 修改密码

View File

@ -30,12 +30,14 @@ jQuery(function(){
$("#btnPageSignUp").on("click", function(){
validateOptions.Validate(function() {
var username = $.trim($("#Username").val());
var email = $.trim($("#Email").val());
var password = $.trim($("#Password").val());
var confirmedPassword = $.trim($("#ConfirmedPassword").val());
var realname = $.trim($("#Realname").val());
var comment = $.trim($("#Comment").val());
var username = $.trim($("#Username").val());
var email = $.trim($("#Email").val());
var password = $.trim($("#Password").val());
var confirmedPassword = $.trim($("#ConfirmedPassword").val());
var realname = $.trim($("#Realname").val());
var comment = $.trim($("#Comment").val());
var isAdmin = $("#isAdmin").val();
$.ajax({
url : '/signUp',
data:{username: username, password: password, realname: realname, comment: comment, email: email},
@ -47,10 +49,14 @@ jQuery(function(){
if(xhr && xhr.status == 200){
$("#dlgModal")
.dialogModal({
"title": i18n.getMessage("title_sign_up"),
"content": i18n.getMessage("registered_successfully"),
"title": isAdmin == "true" ? i18n.getMessage("title_add_user") : i18n.getMessage("title_sign_up"),
"content": isAdmin == "true" ? i18n.getMessage("added_user_successfully") : i18n.getMessage("registered_successfully"),
"callback": function(){
document.location = "/signIn";
if(isAdmin == "true") {
document.location = "/registry/project";
}else{
document.location = "/signIn";
}
}
});
}

View File

@ -27,6 +27,8 @@ import (
var logger = New(os.Stdout, NewTextFormatter(), WarningLevel)
func init() {
logger.callDepth = 3
// TODO add item in configuaration file
lvl := os.Getenv("LOG_LEVEL")
if len(lvl) == 0 {
@ -46,18 +48,20 @@ func init() {
// Logger provides a struct with fields that describe the details of logger.
type Logger struct {
out io.Writer
fmtter Formatter
lvl Level
mu sync.Mutex
out io.Writer
fmtter Formatter
lvl Level
callDepth int
mu sync.Mutex
}
// New returns a customized Logger
func New(out io.Writer, fmtter Formatter, lvl Level) *Logger {
return &Logger{
out: out,
fmtter: fmtter,
lvl: lvl,
out: out,
fmtter: fmtter,
lvl: lvl,
callDepth: 2,
}
}
@ -117,7 +121,7 @@ func (l *Logger) output(record *Record) (err error) {
// Debug ...
func (l *Logger) Debug(v ...interface{}) {
if l.lvl <= DebugLevel {
line := line(2)
line := line(l.callDepth)
record := NewRecord(time.Now(), fmt.Sprint(v...), line, DebugLevel)
l.output(record)
}
@ -126,7 +130,7 @@ func (l *Logger) Debug(v ...interface{}) {
// Debugf ...
func (l *Logger) Debugf(format string, v ...interface{}) {
if l.lvl <= DebugLevel {
line := line(2)
line := line(l.callDepth)
record := NewRecord(time.Now(), fmt.Sprintf(format, v...), line, DebugLevel)
l.output(record)
}
@ -167,7 +171,7 @@ func (l *Logger) Warningf(format string, v ...interface{}) {
// Error ...
func (l *Logger) Error(v ...interface{}) {
if l.lvl <= ErrorLevel {
line := line(2)
line := line(l.callDepth)
record := NewRecord(time.Now(), fmt.Sprint(v...), line, ErrorLevel)
l.output(record)
}
@ -176,7 +180,7 @@ func (l *Logger) Error(v ...interface{}) {
// Errorf ...
func (l *Logger) Errorf(format string, v ...interface{}) {
if l.lvl <= ErrorLevel {
line := line(2)
line := line(l.callDepth)
record := NewRecord(time.Now(), fmt.Sprintf(format, v...), line, ErrorLevel)
l.output(record)
}
@ -185,7 +189,7 @@ func (l *Logger) Errorf(format string, v ...interface{}) {
// Fatal ...
func (l *Logger) Fatal(v ...interface{}) {
if l.lvl <= FatalLevel {
line := line(2)
line := line(l.callDepth)
record := NewRecord(time.Now(), fmt.Sprint(v...), line, FatalLevel)
l.output(record)
}
@ -195,7 +199,7 @@ func (l *Logger) Fatal(v ...interface{}) {
// Fatalf ...
func (l *Logger) Fatalf(format string, v ...interface{}) {
if l.lvl <= FatalLevel {
line := line(2)
line := line(l.callDepth)
record := NewRecord(time.Now(), fmt.Sprintf(format, v...), line, FatalLevel)
l.output(record)
}
@ -259,5 +263,12 @@ func line(calldepth int) string {
line = 0
}
return fmt.Sprintf("%s:%d", file, line)
for i := len(file) - 2; i > 0; i-- {
if file[i] == os.PathSeparator {
file = file[i+1:]
break
}
}
return fmt.Sprintf("[%s:%d]:", file, line)
}

View File

@ -16,7 +16,11 @@
<div class="col-sm-4"></div>
<div class="col-sm-4">
<div class="page-header">
{{ if eq .IsAdmin true }}
<h1>{{i18n .Lang "add_user" }}</h1>
{{ else }}
<h1>{{i18n .Lang "registration"}}</h1>
{{ end }}
</div>
<form class="form">
<div class="alert alert-danger" role="alert" id="divErrMsg"></div>
@ -62,7 +66,13 @@
</div>
<div class="form-group has-feedback">
<div class="text-center">
<button type="button" class="btn btn-default" id="btnPageSignUp">{{i18n .Lang "sign_up"}}</button>
<button type="button" class="btn btn-default" id="btnPageSignUp">
{{ if eq .IsAdmin true }}
{{i18n .Lang "add_user" }}
{{ else }}
{{i18n .Lang "sign_up"}}
{{ end }}
</button>
</div>
</div>
</form>

View File

@ -13,6 +13,7 @@
limitations under the License.
-->
<input type="hidden" id="currentLanguage" value="{{.Lang}}">
<input type="hidden" id="isAdmin" value="{{.IsAdmin}}">
<nav class="navbar navbar-default" role="navigation" style="margin-bottom: 0;">
<div class="navbar-header">
<button aria-controls="navbar" aria-expanded="false" data-target="#navbar" data-toggle="collapse" class="navbar-toggle collapsed" type="button">
@ -55,6 +56,11 @@
<li><a id="aChangePassword" href="/changePassword" target="_blank"><span class="glyphicon glyphicon-pencil"></span>&nbsp;&nbsp;{{i18n .Lang "change_password"}}</a></li>
<li role="separator" class="divider"></li>
{{ end }}
{{ if eq .AuthMode "db_auth" }}
{{ if eq .IsAdmin true }}
<li><a id="aAddUser" href="/addUser" target="_blank"><span class="glyphicon glyphicon-plus"></span>&nbsp;&nbsp;{{i18n .Lang "add_user"}}</a></li>
{{ end }}
{{ end}}
<li><a id="aLogout" href="#"><span class="glyphicon glyphicon-log-in"></span>&nbsp;&nbsp;{{i18n .Lang "log_out"}}</a></li>
</ul>
</li>
@ -63,7 +69,9 @@
{{ else if eq .AuthMode "db_auth" }}
<div class="input-group">
&nbsp;<button type="button" class="btn btn-default" id="btnSignIn">{{i18n .Lang "sign_in"}}</button>
{{ if eq .SelfRegistration true }}
&nbsp;<button type="button" class="btn btn-success" id="btnSignUp">{{i18n .Lang "sign_up"}}</button>
{{ end }}
</div>
{{ else }}
<div class="input-group">