mirror of
https://github.com/goharbor/harbor.git
synced 2025-02-08 16:02:15 +01:00
Merge remote-tracking branch 'upstream/master'
This commit is contained in:
commit
8ae98de7fa
@ -101,3 +101,12 @@ create table access_log (
|
|||||||
FOREIGN KEY (user_id) REFERENCES user(user_id),
|
FOREIGN KEY (user_id) REFERENCES user(user_id),
|
||||||
FOREIGN KEY (project_id) REFERENCES project (project_id)
|
FOREIGN KEY (project_id) REFERENCES project (project_id)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
create table properties (
|
||||||
|
k varchar(64) NOT NULL,
|
||||||
|
v varchar(128) NOT NULL,
|
||||||
|
primary key (k)
|
||||||
|
);
|
||||||
|
|
||||||
|
insert into properties (k, v) values
|
||||||
|
('schema_version', '0.1.1');
|
||||||
|
@ -1,62 +1,69 @@
|
|||||||
log:
|
version: '2'
|
||||||
build: ./log/
|
services:
|
||||||
volumes:
|
log:
|
||||||
- /var/log/harbor/:/var/log/docker/
|
build: ./log/
|
||||||
ports:
|
volumes:
|
||||||
- 1514:514
|
- /var/log/harbor/:/var/log/docker/
|
||||||
registry:
|
ports:
|
||||||
image: library/registry:2.3.0
|
- 1514:514
|
||||||
volumes:
|
registry:
|
||||||
- /data/registry:/storage
|
image: library/registry:2.3.0
|
||||||
- ./config/registry/:/etc/registry/
|
volumes:
|
||||||
ports:
|
- /data/registry:/storage
|
||||||
- 5001:5001
|
- ./config/registry/:/etc/registry/
|
||||||
command:
|
ports:
|
||||||
/etc/registry/config.yml
|
- 5001:5001
|
||||||
links:
|
command:
|
||||||
- log
|
/etc/registry/config.yml
|
||||||
log_driver: "syslog"
|
depends_on:
|
||||||
log_opt:
|
- log
|
||||||
syslog-address: "tcp://127.0.0.1:1514"
|
logging:
|
||||||
syslog-tag: "registry"
|
driver: "syslog"
|
||||||
mysql:
|
options:
|
||||||
build: ./db/
|
syslog-address: "tcp://127.0.0.1:1514"
|
||||||
volumes:
|
syslog-tag: "registry"
|
||||||
- /data/database:/var/lib/mysql
|
mysql:
|
||||||
env_file:
|
build: ./db/
|
||||||
- ./config/db/env
|
volumes:
|
||||||
links:
|
- /data/database:/var/lib/mysql
|
||||||
- log
|
env_file:
|
||||||
log_driver: "syslog"
|
- ./config/db/env
|
||||||
log_opt:
|
depends_on:
|
||||||
syslog-address: "tcp://127.0.0.1:1514"
|
- log
|
||||||
syslog-tag: "mysql"
|
logging:
|
||||||
ui:
|
driver: "syslog"
|
||||||
build: ../
|
options:
|
||||||
env_file:
|
syslog-address: "tcp://127.0.0.1:1514"
|
||||||
- ./config/ui/env
|
syslog-tag: "mysql"
|
||||||
volumes:
|
ui:
|
||||||
- ./config/ui/app.conf:/etc/ui/app.conf
|
build:
|
||||||
- ./config/ui/private_key.pem:/etc/ui/private_key.pem
|
context: ../
|
||||||
links:
|
dockerfile: Dockerfile.ui
|
||||||
- registry
|
env_file:
|
||||||
- mysql
|
- ./config/ui/env
|
||||||
- log
|
volumes:
|
||||||
log_driver: "syslog"
|
- ./config/ui/app.conf:/etc/ui/app.conf
|
||||||
log_opt:
|
- ./config/ui/private_key.pem:/etc/ui/private_key.pem
|
||||||
syslog-address: "tcp://127.0.0.1:1514"
|
depends_on:
|
||||||
syslog-tag: "ui"
|
- log
|
||||||
proxy:
|
logging:
|
||||||
image: library/nginx:1.9
|
driver: "syslog"
|
||||||
volumes:
|
options:
|
||||||
- ./config/nginx:/etc/nginx
|
syslog-address: "tcp://127.0.0.1:1514"
|
||||||
links:
|
syslog-tag: "ui"
|
||||||
- ui
|
proxy:
|
||||||
- registry
|
image: library/nginx:1.9
|
||||||
- log
|
volumes:
|
||||||
ports:
|
- ./config/nginx:/etc/nginx
|
||||||
- 80:80
|
ports:
|
||||||
log_driver: "syslog"
|
- 80:80
|
||||||
log_opt:
|
depends_on:
|
||||||
syslog-address: "tcp://127.0.0.1:1514"
|
- mysql
|
||||||
syslog-tag: "proxy"
|
- registry
|
||||||
|
- ui
|
||||||
|
- log
|
||||||
|
logging:
|
||||||
|
driver: "syslog"
|
||||||
|
options:
|
||||||
|
syslog-address: "tcp://127.0.0.1:1514"
|
||||||
|
syslog-tag: "proxy"
|
||||||
|
@ -1,24 +1,35 @@
|
|||||||
## CONFIGURATIONS
|
## Configuration file of Harbor
|
||||||
#The endpoint for user to access UI and registry service
|
|
||||||
hostname = mydomain.com
|
#The IP address or hostname to access admin UI and registry service.
|
||||||
#The protocol for accessing the UI and token/notification service, by default it is http
|
#DO NOT use localhost or 127.0.0.1, because Harbor needs to be accessed by external clients.
|
||||||
#User can set it to https if ssl is setup on nginx
|
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
|
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 = smtp.mydomain.com
|
||||||
email_server_port = 25
|
email_server_port = 25
|
||||||
email_username = sample_admin@mydomain.com
|
email_username = sample_admin@mydomain.com
|
||||||
email_password = abc
|
email_password = abc
|
||||||
email_from = admin <sample_admin@mydomain.com>
|
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
|
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
|
auth_mode = db_auth
|
||||||
#The url for ldap endpoint
|
|
||||||
|
#The url for an ldap endpoint.
|
||||||
ldap_url = ldaps://ldap.mydomain.com
|
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
|
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
|
db_password = root123
|
||||||
|
#Switch for self-registration feature
|
||||||
|
self_registration = on
|
||||||
#####
|
#####
|
||||||
|
@ -25,6 +25,7 @@ auth_mode = cp.get("configuration", "auth_mode")
|
|||||||
ldap_url = cp.get("configuration", "ldap_url")
|
ldap_url = cp.get("configuration", "ldap_url")
|
||||||
ldap_basedn = cp.get("configuration", "ldap_basedn")
|
ldap_basedn = cp.get("configuration", "ldap_basedn")
|
||||||
db_password = cp.get("configuration", "db_password")
|
db_password = cp.get("configuration", "db_password")
|
||||||
|
self_registration = cp.get("configuration", "self_registration")
|
||||||
########
|
########
|
||||||
|
|
||||||
base_dir = os.path.dirname(__file__)
|
base_dir = os.path.dirname(__file__)
|
||||||
@ -60,11 +61,13 @@ for f in conf_files:
|
|||||||
render(os.path.join(templates_dir, "ui", "env"),
|
render(os.path.join(templates_dir, "ui", "env"),
|
||||||
ui_conf_env,
|
ui_conf_env,
|
||||||
hostname=hostname,
|
hostname=hostname,
|
||||||
|
db_password=db_password,
|
||||||
ui_url=ui_url,
|
ui_url=ui_url,
|
||||||
auth_mode=auth_mode,
|
auth_mode=auth_mode,
|
||||||
admin_pwd=harbor_admin_password,
|
admin_pwd=harbor_admin_password,
|
||||||
ldap_url=ldap_url,
|
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"),
|
render(os.path.join(templates_dir, "ui", "app.conf"),
|
||||||
ui_conf,
|
ui_conf,
|
||||||
|
@ -27,7 +27,7 @@ notifications:
|
|||||||
endpoints:
|
endpoints:
|
||||||
- name: harbor
|
- name: harbor
|
||||||
disabled: false
|
disabled: false
|
||||||
url: $ui_url/service/notifications
|
url: http://ui/service/notifications
|
||||||
timeout: 500
|
timeout: 500
|
||||||
threshold: 5
|
threshold: 5
|
||||||
backoff: 1000
|
backoff: 1000
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
MYSQL_HOST=mysql
|
MYSQL_HOST=mysql
|
||||||
|
MYSQL_PORT=3306
|
||||||
MYSQL_USR=root
|
MYSQL_USR=root
|
||||||
|
MYSQL_PWD=$db_password
|
||||||
REGISTRY_URL=http://registry:5000
|
REGISTRY_URL=http://registry:5000
|
||||||
CONFIG_PATH=/etc/ui/app.conf
|
CONFIG_PATH=/etc/ui/app.conf
|
||||||
HARBOR_REG_URL=$hostname
|
HARBOR_REG_URL=$hostname
|
||||||
@ -8,4 +10,5 @@ HARBOR_URL=$ui_url
|
|||||||
AUTH_MODE=$auth_mode
|
AUTH_MODE=$auth_mode
|
||||||
LDAP_URL=$ldap_url
|
LDAP_URL=$ldap_url
|
||||||
LDAP_BASE_DN=$ldap_basedn
|
LDAP_BASE_DN=$ldap_basedn
|
||||||
|
SELF_REGISTRATION=$self_registration
|
||||||
LOG_LEVEL=debug
|
LOG_LEVEL=debug
|
||||||
|
@ -9,30 +9,28 @@ RUN apt-get update \
|
|||||||
COPY . /go/src/github.com/vmware/harbor
|
COPY . /go/src/github.com/vmware/harbor
|
||||||
#golang.org is blocked in China
|
#golang.org is blocked in China
|
||||||
COPY ./vendor/golang.org /go/src/golang.org
|
COPY ./vendor/golang.org /go/src/golang.org
|
||||||
WORKDIR /go/src/github.com/vmware/harbor
|
WORKDIR /go/src/github.com/vmware/harbor/ui
|
||||||
|
|
||||||
ENV GO15VENDOREXPERIMENT 1
|
ENV GO15VENDOREXPERIMENT 1
|
||||||
RUN go get -d github.com/docker/distribution \
|
RUN go get -d github.com/docker/distribution \
|
||||||
&& go get -d github.com/docker/libtrust \
|
&& go get -d github.com/docker/libtrust \
|
||||||
&& go get -d github.com/go-sql-driver/mysql \
|
&& go get -d github.com/go-sql-driver/mysql \
|
||||||
&& go install -v -a
|
&& go build -v -a -o /go/bin/harbor_ui
|
||||||
|
|
||||||
ENV MYSQL_USR root \
|
ENV MYSQL_USR root \
|
||||||
MYSQL_PWD root \
|
MYSQL_PWD root \
|
||||||
MYSQL_PORT_3306_TCP_ADDR localhost \
|
|
||||||
MYSQL_PORT_3306_TCP_PORT 3306 \
|
|
||||||
REGISTRY_URL localhost:5000
|
REGISTRY_URL localhost:5000
|
||||||
|
|
||||||
COPY views /go/bin/views
|
COPY views /go/bin/views
|
||||||
COPY static /go/bin/static
|
COPY static /go/bin/static
|
||||||
COPY favicon.ico /go/bin/favicon.ico
|
COPY favicon.ico /go/bin/favicon.ico
|
||||||
|
|
||||||
RUN chmod u+x /go/bin/harbor \
|
RUN chmod u+x /go/bin/harbor_ui \
|
||||||
&& sed -i 's/TLS_CACERT/#TLS_CAERT/g' /etc/ldap/ldap.conf \
|
&& sed -i 's/TLS_CACERT/#TLS_CAERT/g' /etc/ldap/ldap.conf \
|
||||||
&& sed -i '$a\TLS_REQCERT allow' /etc/ldap/ldap.conf
|
&& sed -i '$a\TLS_REQCERT allow' /etc/ldap/ldap.conf
|
||||||
|
|
||||||
WORKDIR /go/bin/
|
WORKDIR /go/bin/
|
||||||
ENTRYPOINT ["/go/bin/harbor"]
|
ENTRYPOINT ["/go/bin/harbor_ui"]
|
||||||
|
|
||||||
EXPOSE 80
|
EXPOSE 80
|
||||||
|
|
1
contrib/README.md
Normal file
1
contrib/README.md
Normal file
@ -0,0 +1 @@
|
|||||||
|
The `contrib` directory contains documents, scripts, and other helpful things which are contributed by community.
|
@ -16,12 +16,14 @@
|
|||||||
package controllers
|
package controllers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/vmware/harbor/utils/log"
|
|
||||||
"github.com/astaxie/beego"
|
"github.com/astaxie/beego"
|
||||||
"github.com/beego/i18n"
|
"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 ...
|
// 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 {
|
type BaseController struct {
|
||||||
beego.Controller
|
beego.Controller
|
||||||
i18n.Locale
|
i18n.Locale
|
||||||
|
SelfRegistration bool
|
||||||
|
IsAdmin bool
|
||||||
|
AuthMode string
|
||||||
}
|
}
|
||||||
|
|
||||||
type langType struct {
|
type langType struct {
|
||||||
@ -93,15 +98,35 @@ func (b *BaseController) Prepare() {
|
|||||||
b.Data["CurLang"] = curLang.Name
|
b.Data["CurLang"] = curLang.Name
|
||||||
b.Data["RestLangs"] = restLangs
|
b.Data["RestLangs"] = restLangs
|
||||||
|
|
||||||
sessionUserID := b.GetSession("userId")
|
authMode := strings.ToLower(os.Getenv("AUTH_MODE"))
|
||||||
if sessionUserID != nil {
|
|
||||||
b.Data["Username"] = b.GetSession("username")
|
|
||||||
}
|
|
||||||
authMode := os.Getenv("AUTH_MODE")
|
|
||||||
if authMode == "" {
|
if authMode == "" {
|
||||||
authMode = "db_auth"
|
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.
|
// ForwardTo setup layout and template for content for a page.
|
||||||
|
@ -17,11 +17,11 @@ package controllers
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/vmware/harbor/dao"
|
"github.com/vmware/harbor/dao"
|
||||||
"github.com/vmware/harbor/models"
|
"github.com/vmware/harbor/models"
|
||||||
|
|
||||||
"github.com/vmware/harbor/utils/log"
|
"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
|
// Get renders the Sign In page, it only works if the auth mode is set to db_auth
|
||||||
func (rc *RegisterController) Get() {
|
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")
|
rc.ForwardTo("page_title_registration", "register")
|
||||||
} else {
|
} 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.
|
// SignUp insert data into DB based on data in form.
|
||||||
func (rc *CommonController) SignUp() {
|
func (cc *CommonController) SignUp() {
|
||||||
username := strings.TrimSpace(rc.GetString("username"))
|
|
||||||
email := strings.TrimSpace(rc.GetString("email"))
|
if !(cc.AuthMode == "db_auth") {
|
||||||
realname := strings.TrimSpace(rc.GetString("realname"))
|
cc.CustomAbort(http.StatusForbidden, "")
|
||||||
password := strings.TrimSpace(rc.GetString("password"))
|
}
|
||||||
comment := strings.TrimSpace(rc.GetString("comment"))
|
|
||||||
|
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}
|
user := models.User{Username: username, Email: email, Realname: realname, Password: password, Comment: comment}
|
||||||
|
|
||||||
_, err := dao.Register(user)
|
_, err := dao.Register(user)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("Error occurred in Register: %v", err)
|
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.
|
// UserExists checks if user exists when user input value in sign in form.
|
||||||
func (rc *CommonController) UserExists() {
|
func (cc *CommonController) UserExists() {
|
||||||
target := rc.GetString("target")
|
target := cc.GetString("target")
|
||||||
value := rc.GetString("value")
|
value := cc.GetString("value")
|
||||||
|
|
||||||
user := models.User{}
|
user := models.User{}
|
||||||
switch target {
|
switch target {
|
||||||
@ -73,8 +108,8 @@ func (rc *CommonController) UserExists() {
|
|||||||
exist, err := dao.UserExists(user, target)
|
exist, err := dao.UserExists(user, target)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("Error occurred in UserExists: %v", err)
|
log.Errorf("Error occurred in UserExists: %v", err)
|
||||||
rc.CustomAbort(http.StatusInternalServerError, "Internal error.")
|
cc.CustomAbort(http.StatusInternalServerError, "Internal error.")
|
||||||
}
|
}
|
||||||
rc.Data["json"] = exist
|
cc.Data["json"] = exist
|
||||||
rc.ServeJSON()
|
cc.ServeJSON()
|
||||||
}
|
}
|
||||||
|
13
dao/base.go
13
dao/base.go
@ -66,18 +66,11 @@ func GenerateRandomString() (string, error) {
|
|||||||
func InitDB() {
|
func InitDB() {
|
||||||
orm.RegisterDriver("mysql", orm.DRMySQL)
|
orm.RegisterDriver("mysql", orm.DRMySQL)
|
||||||
addr := os.Getenv("MYSQL_HOST")
|
addr := os.Getenv("MYSQL_HOST")
|
||||||
if len(addr) == 0 {
|
port := os.Getenv("MYSQL_PORT")
|
||||||
addr = os.Getenv("MYSQL_PORT_3306_TCP_ADDR")
|
|
||||||
}
|
|
||||||
|
|
||||||
port := os.Getenv("MYSQL_PORT_3306_TCP_PORT")
|
|
||||||
username := os.Getenv("MYSQL_USR")
|
username := os.Getenv("MYSQL_USR")
|
||||||
|
password := os.Getenv("MYSQL_PWD")
|
||||||
|
|
||||||
password := os.Getenv("MYSQL_ENV_MYSQL_ROOT_PASSWORD")
|
log.Debugf("db url: %s:%s, db user: %s", addr, port, username)
|
||||||
if len(password) == 0 {
|
|
||||||
password = os.Getenv("MYSQL_PWD")
|
|
||||||
}
|
|
||||||
|
|
||||||
dbStr := username + ":" + password + "@tcp(" + addr + ":" + port + ")/registry"
|
dbStr := username + ":" + password + "@tcp(" + addr + ":" + port + ")/registry"
|
||||||
ch := make(chan int, 1)
|
ch := make(chan int, 1)
|
||||||
go func() {
|
go func() {
|
||||||
|
@ -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)
|
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_HOST", dbHost)
|
||||||
os.Setenv("MYSQL_PORT_3306_TCP_PORT", dbPort)
|
os.Setenv("MYSQL_PORT", dbPort)
|
||||||
os.Setenv("MYSQL_USR", dbUser)
|
os.Setenv("MYSQL_USR", dbUser)
|
||||||
os.Setenv("MYSQL_PWD", dbPassword)
|
os.Setenv("MYSQL_PWD", dbPassword)
|
||||||
os.Setenv("AUTH_MODE", "db_auth")
|
os.Setenv("AUTH_MODE", "db_auth")
|
||||||
|
@ -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.
|
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.
|
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.
|
* 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/
|
* 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
|
### Configuration of Harbor
|
||||||
@ -139,4 +139,4 @@ Removing harbor_mysql_1 ... done
|
|||||||
|
|
||||||
### Persistent data and log files
|
### Persistent data and log files
|
||||||
By default, the data of database and image files in the registry are persisted in the directory **/data/** of the target machine. When Harbor's containers are removed and recreated, the data remain unchanged.
|
By default, the data of database and image files in the registry are persisted in the directory **/data/** of the target machine. When Harbor's containers are removed and recreated, the data remain unchanged.
|
||||||
Harbor leverages rsyslog to collect the logs of each container, by default the log files are stored in the directory **/var/log/harbor/** on Harbor's host.
|
Harbor leverages rsyslog to collect the logs of each container, by default the log files are stored in the directory **/var/log/harbor/** on Harbor's host.
|
||||||
|
@ -42,13 +42,14 @@ func (a *TokenHandler) Get() {
|
|||||||
username, password, _ := request.BasicAuth()
|
username, password, _ := request.BasicAuth()
|
||||||
authenticated := authenticate(username, password)
|
authenticated := authenticate(username, password)
|
||||||
service := a.GetString("service")
|
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")
|
log.Info("login request with invalid credentials")
|
||||||
a.CustomAbort(http.StatusUnauthorized, "")
|
a.CustomAbort(http.StatusUnauthorized, "")
|
||||||
}
|
}
|
||||||
access := svc_utils.GetResourceActions(scope)
|
access := svc_utils.GetResourceActions(scopes)
|
||||||
for _, a := range access {
|
for _, a := range access {
|
||||||
svc_utils.FilterAccess(username, authenticated, a)
|
svc_utils.FilterAccess(username, authenticated, a)
|
||||||
}
|
}
|
||||||
|
@ -38,17 +38,19 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// GetResourceActions ...
|
// GetResourceActions ...
|
||||||
func GetResourceActions(scope string) []*token.ResourceActions {
|
func GetResourceActions(scopes []string) []*token.ResourceActions {
|
||||||
var res []*token.ResourceActions
|
var res []*token.ResourceActions
|
||||||
if scope == "" {
|
for _, s := range scopes {
|
||||||
return res
|
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
|
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
|
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, "/")]
|
projectName := a.Name[0:strings.LastIndex(a.Name, "/")]
|
||||||
var permission string
|
var permission string
|
||||||
var err error
|
|
||||||
if authenticated {
|
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)
|
exist, err := dao.ProjectExists(projectName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("Error occurred in CheckExistProject: %v", err)
|
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.
|
// 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) {
|
func GenTokenForUI(username string, service string, scopes []string) (string, error) {
|
||||||
access := GetResourceActions(scope)
|
access := GetResourceActions(scopes)
|
||||||
for _, a := range access {
|
for _, a := range access {
|
||||||
FilterAccess(username, true, a)
|
FilterAccess(username, true, a)
|
||||||
}
|
}
|
||||||
|
@ -63,14 +63,15 @@ func RegistryAPIGet(url, username string) ([]byte, error) {
|
|||||||
authenticate := response.Header.Get("WWW-Authenticate")
|
authenticate := response.Header.Get("WWW-Authenticate")
|
||||||
log.Debugf("authenticate header: %s", authenticate)
|
log.Debugf("authenticate header: %s", authenticate)
|
||||||
var service string
|
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=\"(.*?)\"`)
|
re := regexp.MustCompile(`service=\"(.*?)\".*scope=\"(.*?)\"`)
|
||||||
res := re.FindStringSubmatch(authenticate)
|
res := re.FindStringSubmatch(authenticate)
|
||||||
if len(res) > 2 {
|
if len(res) > 2 {
|
||||||
service = res[1]
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@ page_title_sign_in = Sign In - Harbor
|
|||||||
page_title_project = Project - Harbor
|
page_title_project = Project - Harbor
|
||||||
page_title_item_details = Details - Harbor
|
page_title_item_details = Details - Harbor
|
||||||
page_title_registration = Sign Up - Harbor
|
page_title_registration = Sign Up - Harbor
|
||||||
|
page_title_add_user = Add User - Harbor
|
||||||
page_title_forgot_password = Forgot Password - Harbor
|
page_title_forgot_password = Forgot Password - Harbor
|
||||||
title_forgot_password = Forgot Password
|
title_forgot_password = Forgot Password
|
||||||
page_title_reset_password = Reset Password - Harbor
|
page_title_reset_password = Reset Password - Harbor
|
||||||
@ -12,6 +13,7 @@ title_change_password = Change Password
|
|||||||
page_title_search = Search - Harbor
|
page_title_search = Search - Harbor
|
||||||
sign_in = Sign In
|
sign_in = Sign In
|
||||||
sign_up = Sign Up
|
sign_up = Sign Up
|
||||||
|
add_user = Add User
|
||||||
log_out = Log Out
|
log_out = Log Out
|
||||||
search_placeholder = projects or repositories
|
search_placeholder = projects or repositories
|
||||||
change_password = Change Password
|
change_password = Change Password
|
||||||
|
@ -177,6 +177,10 @@ var global_messages = {
|
|||||||
"en-US": "Sign Up",
|
"en-US": "Sign Up",
|
||||||
"zh-CN": "注册"
|
"zh-CN": "注册"
|
||||||
},
|
},
|
||||||
|
"title_add_user": {
|
||||||
|
"en-US": "Add User",
|
||||||
|
"zh-CN": "新增用户"
|
||||||
|
},
|
||||||
"registered_successfully": {
|
"registered_successfully": {
|
||||||
"en-US": "Signed up successfully.",
|
"en-US": "Signed up successfully.",
|
||||||
"zh-CN": "注册成功。"
|
"zh-CN": "注册成功。"
|
||||||
@ -185,6 +189,14 @@ var global_messages = {
|
|||||||
"en-US": "Failed to sign up.",
|
"en-US": "Failed to sign up.",
|
||||||
"zh-CN": "注册失败。"
|
"zh-CN": "注册失败。"
|
||||||
},
|
},
|
||||||
|
"added_user_successfully": {
|
||||||
|
"en-US": "Added user successfully.",
|
||||||
|
"zh-CN": "新增用户成功。"
|
||||||
|
},
|
||||||
|
"added_user_failed": {
|
||||||
|
"en-US": "Added user failed.",
|
||||||
|
"zh-CN": "新增用户失败。"
|
||||||
|
},
|
||||||
"projects" : {
|
"projects" : {
|
||||||
"en-US": "Projects",
|
"en-US": "Projects",
|
||||||
"zh-CN": "项目"
|
"zh-CN": "项目"
|
||||||
|
@ -3,6 +3,7 @@ page_title_sign_in = 登录 - Harbor
|
|||||||
page_title_project = 项目 - Harbor
|
page_title_project = 项目 - Harbor
|
||||||
page_title_item_details = 详细信息 - Harbor
|
page_title_item_details = 详细信息 - Harbor
|
||||||
page_title_registration = 注册 - Harbor
|
page_title_registration = 注册 - Harbor
|
||||||
|
page_title_add_user = 新增用户 - Harbor
|
||||||
page_title_forgot_password = 忘记密码 - Harbor
|
page_title_forgot_password = 忘记密码 - Harbor
|
||||||
title_forgot_password = 忘记密码
|
title_forgot_password = 忘记密码
|
||||||
page_title_reset_password = 重置密码 - Harbor
|
page_title_reset_password = 重置密码 - Harbor
|
||||||
@ -12,6 +13,7 @@ title_change_password = 修改密码
|
|||||||
page_title_search = 搜索 - Harbor
|
page_title_search = 搜索 - Harbor
|
||||||
sign_in = 登录
|
sign_in = 登录
|
||||||
sign_up = 注册
|
sign_up = 注册
|
||||||
|
add_user = 新增用户
|
||||||
log_out = 注销
|
log_out = 注销
|
||||||
search_placeholder = 项目或镜像名称
|
search_placeholder = 项目或镜像名称
|
||||||
change_password = 修改密码
|
change_password = 修改密码
|
||||||
|
@ -30,12 +30,14 @@ jQuery(function(){
|
|||||||
|
|
||||||
$("#btnPageSignUp").on("click", function(){
|
$("#btnPageSignUp").on("click", function(){
|
||||||
validateOptions.Validate(function() {
|
validateOptions.Validate(function() {
|
||||||
var username = $.trim($("#Username").val());
|
var username = $.trim($("#Username").val());
|
||||||
var email = $.trim($("#Email").val());
|
var email = $.trim($("#Email").val());
|
||||||
var password = $.trim($("#Password").val());
|
var password = $.trim($("#Password").val());
|
||||||
var confirmedPassword = $.trim($("#ConfirmedPassword").val());
|
var confirmedPassword = $.trim($("#ConfirmedPassword").val());
|
||||||
var realname = $.trim($("#Realname").val());
|
var realname = $.trim($("#Realname").val());
|
||||||
var comment = $.trim($("#Comment").val());
|
var comment = $.trim($("#Comment").val());
|
||||||
|
var isAdmin = $("#isAdmin").val();
|
||||||
|
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url : "/api/users",
|
url : "/api/users",
|
||||||
data: JSON.stringify({username: username, password: password, realname: realname, comment: comment, email: email}),
|
data: JSON.stringify({username: username, password: password, realname: realname, comment: comment, email: email}),
|
||||||
@ -48,10 +50,14 @@ jQuery(function(){
|
|||||||
if(xhr && xhr.status == 200){
|
if(xhr && xhr.status == 200){
|
||||||
$("#dlgModal")
|
$("#dlgModal")
|
||||||
.dialogModal({
|
.dialogModal({
|
||||||
"title": i18n.getMessage("title_sign_up"),
|
"title": isAdmin == "true" ? i18n.getMessage("title_add_user") : i18n.getMessage("title_sign_up"),
|
||||||
"content": i18n.getMessage("registered_successfully"),
|
"content": isAdmin == "true" ? i18n.getMessage("added_user_successfully") : i18n.getMessage("registered_successfully"),
|
||||||
"callback": function(){
|
"callback": function(){
|
||||||
document.location = "/signIn";
|
if(isAdmin == "true") {
|
||||||
|
document.location = "/registry/project";
|
||||||
|
}else{
|
||||||
|
document.location = "/signIn";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -24,7 +24,6 @@ import (
|
|||||||
_ "github.com/vmware/harbor/auth/ldap"
|
_ "github.com/vmware/harbor/auth/ldap"
|
||||||
"github.com/vmware/harbor/dao"
|
"github.com/vmware/harbor/dao"
|
||||||
"github.com/vmware/harbor/models"
|
"github.com/vmware/harbor/models"
|
||||||
_ "github.com/vmware/harbor/routers"
|
|
||||||
|
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
@ -71,5 +70,6 @@ func main() {
|
|||||||
if err := updateInitPassword(adminUserID, os.Getenv("HARBOR_ADMIN_PASSWORD")); err != nil {
|
if err := updateInitPassword(adminUserID, os.Getenv("HARBOR_ADMIN_PASSWORD")); err != nil {
|
||||||
log.Error(err)
|
log.Error(err)
|
||||||
}
|
}
|
||||||
|
initRouters()
|
||||||
beego.Run()
|
beego.Run()
|
||||||
}
|
}
|
@ -13,7 +13,7 @@
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package routers
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/vmware/harbor/api"
|
"github.com/vmware/harbor/api"
|
||||||
@ -23,7 +23,7 @@ import (
|
|||||||
"github.com/astaxie/beego"
|
"github.com/astaxie/beego"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func initRouters() {
|
||||||
|
|
||||||
beego.SetStaticPath("registry/static/i18n", "static/i18n")
|
beego.SetStaticPath("registry/static/i18n", "static/i18n")
|
||||||
beego.SetStaticPath("registry/static/resources", "static/resources")
|
beego.SetStaticPath("registry/static/resources", "static/resources")
|
||||||
@ -41,6 +41,7 @@ func init() {
|
|||||||
beego.Router("/", &controllers.IndexController{})
|
beego.Router("/", &controllers.IndexController{})
|
||||||
beego.Router("/signIn", &controllers.SignInController{})
|
beego.Router("/signIn", &controllers.SignInController{})
|
||||||
beego.Router("/register", &controllers.RegisterController{})
|
beego.Router("/register", &controllers.RegisterController{})
|
||||||
|
beego.Router("/addUser", &controllers.AddUserController{})
|
||||||
beego.Router("/forgotPassword", &controllers.ForgotPasswordController{})
|
beego.Router("/forgotPassword", &controllers.ForgotPasswordController{})
|
||||||
beego.Router("/resetPassword", &controllers.ResetPasswordController{})
|
beego.Router("/resetPassword", &controllers.ResetPasswordController{})
|
||||||
beego.Router("/changePassword", &controllers.ChangePasswordController{})
|
beego.Router("/changePassword", &controllers.ChangePasswordController{})
|
@ -263,12 +263,12 @@ func line(calldepth int) string {
|
|||||||
line = 0
|
line = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
for i := len(file) - 1; i > 0; i-- {
|
for i := len(file) - 2; i > 0; i-- {
|
||||||
if file[i] == '/' {
|
if file[i] == os.PathSeparator {
|
||||||
file = file[i+1:]
|
file = file[i+1:]
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return fmt.Sprintf("%s:%d", file, line)
|
return fmt.Sprintf("[%s:%d]:", file, line)
|
||||||
}
|
}
|
||||||
|
@ -16,7 +16,11 @@
|
|||||||
<div class="col-sm-4"></div>
|
<div class="col-sm-4"></div>
|
||||||
<div class="col-sm-4">
|
<div class="col-sm-4">
|
||||||
<div class="page-header">
|
<div class="page-header">
|
||||||
|
{{ if eq .IsAdmin true }}
|
||||||
|
<h1>{{i18n .Lang "add_user" }}</h1>
|
||||||
|
{{ else }}
|
||||||
<h1>{{i18n .Lang "registration"}}</h1>
|
<h1>{{i18n .Lang "registration"}}</h1>
|
||||||
|
{{ end }}
|
||||||
</div>
|
</div>
|
||||||
<form class="form">
|
<form class="form">
|
||||||
<div class="alert alert-danger" role="alert" id="divErrMsg"></div>
|
<div class="alert alert-danger" role="alert" id="divErrMsg"></div>
|
||||||
@ -62,7 +66,13 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="form-group has-feedback">
|
<div class="form-group has-feedback">
|
||||||
<div class="text-center">
|
<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>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
@ -13,6 +13,7 @@
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
-->
|
-->
|
||||||
<input type="hidden" id="currentLanguage" value="{{.Lang}}">
|
<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;">
|
<nav class="navbar navbar-default" role="navigation" style="margin-bottom: 0;">
|
||||||
<div class="navbar-header">
|
<div class="navbar-header">
|
||||||
<button aria-controls="navbar" aria-expanded="false" data-target="#navbar" data-toggle="collapse" class="navbar-toggle collapsed" type="button">
|
<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> {{i18n .Lang "change_password"}}</a></li>
|
<li><a id="aChangePassword" href="/changePassword" target="_blank"><span class="glyphicon glyphicon-pencil"></span> {{i18n .Lang "change_password"}}</a></li>
|
||||||
<li role="separator" class="divider"></li>
|
<li role="separator" class="divider"></li>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
{{ if eq .AuthMode "db_auth" }}
|
||||||
|
{{ if eq .IsAdmin true }}
|
||||||
|
<li><a id="aAddUser" href="/addUser" target="_blank"><span class="glyphicon glyphicon-plus"></span> {{i18n .Lang "add_user"}}</a></li>
|
||||||
|
{{ end }}
|
||||||
|
{{ end}}
|
||||||
<li><a id="aLogout" href="#"><span class="glyphicon glyphicon-log-in"></span> {{i18n .Lang "log_out"}}</a></li>
|
<li><a id="aLogout" href="#"><span class="glyphicon glyphicon-log-in"></span> {{i18n .Lang "log_out"}}</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
@ -63,7 +69,9 @@
|
|||||||
{{ else if eq .AuthMode "db_auth" }}
|
{{ else if eq .AuthMode "db_auth" }}
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<button type="button" class="btn btn-default" id="btnSignIn">{{i18n .Lang "sign_in"}}</button>
|
<button type="button" class="btn btn-default" id="btnSignIn">{{i18n .Lang "sign_in"}}</button>
|
||||||
|
{{ if eq .SelfRegistration true }}
|
||||||
<button type="button" class="btn btn-success" id="btnSignUp">{{i18n .Lang "sign_up"}}</button>
|
<button type="button" class="btn btn-success" id="btnSignUp">{{i18n .Lang "sign_up"}}</button>
|
||||||
|
{{ end }}
|
||||||
</div>
|
</div>
|
||||||
{{ else }}
|
{{ else }}
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
|
Loading…
Reference in New Issue
Block a user