mirror of
https://github.com/goharbor/harbor.git
synced 2024-09-30 22:37:43 +02:00
Merge remote-tracking branch 'upstream/dev' into dev-revised
This commit is contained in:
commit
6acf2af7c0
@ -74,8 +74,8 @@ before_script:
|
|||||||
- sudo chmod 777 /tmp/registry.db
|
- sudo chmod 777 /tmp/registry.db
|
||||||
|
|
||||||
script:
|
script:
|
||||||
- sudo mkdir -p /harbor_storage/ca_download
|
- sudo mkdir -p /etc/ui/ca/
|
||||||
- sudo mv ./tests/ca.crt /harbor_storage/ca_download
|
- sudo mv ./tests/ca.crt /etc/ui/ca/
|
||||||
- sudo mkdir -p /harbor
|
- sudo mkdir -p /harbor
|
||||||
- sudo mv ./VERSION /harbor/VERSION
|
- sudo mv ./VERSION /harbor/VERSION
|
||||||
- sudo service mysql stop
|
- sudo service mysql stop
|
||||||
|
@ -33,7 +33,6 @@ UI_SECRET=$ui_secret
|
|||||||
JOBSERVICE_SECRET=$jobservice_secret
|
JOBSERVICE_SECRET=$jobservice_secret
|
||||||
TOKEN_EXPIRATION=$token_expiration
|
TOKEN_EXPIRATION=$token_expiration
|
||||||
CFG_EXPIRATION=5
|
CFG_EXPIRATION=5
|
||||||
USE_COMPRESSED_JS=$use_compressed_js
|
|
||||||
GODEBUG=netdns=cgo
|
GODEBUG=netdns=cgo
|
||||||
ADMIRAL_URL=$admiral_url
|
ADMIRAL_URL=$admiral_url
|
||||||
WITH_NOTARY=$with_notary
|
WITH_NOTARY=$with_notary
|
||||||
|
@ -21,6 +21,12 @@ http {
|
|||||||
server ui:80;
|
server ui:80;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log_format timed_combined '$$remote_addr - '
|
||||||
|
'"$$request" $$status $$body_bytes_sent '
|
||||||
|
'"$$http_referer" "$$http_user_agent" '
|
||||||
|
'$$request_time $$upstream_response_time $$pipe';
|
||||||
|
|
||||||
|
access_log /dev/stdout timed_combined;
|
||||||
|
|
||||||
server {
|
server {
|
||||||
listen 80;
|
listen 80;
|
||||||
|
@ -21,6 +21,13 @@ http {
|
|||||||
server ui:80;
|
server ui:80;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log_format timed_combined '$$remote_addr - '
|
||||||
|
'"$$request" $$status $$body_bytes_sent '
|
||||||
|
'"$$http_referer" "$$http_user_agent" '
|
||||||
|
'$$request_time $$upstream_response_time $$pipe';
|
||||||
|
|
||||||
|
access_log /dev/stdout timed_combined;
|
||||||
|
|
||||||
include /etc/nginx/conf.d/*.server.conf;
|
include /etc/nginx/conf.d/*.server.conf;
|
||||||
|
|
||||||
server {
|
server {
|
||||||
|
@ -69,6 +69,7 @@ services:
|
|||||||
- ../common/config/ui/app.conf:/etc/ui/app.conf
|
- ../common/config/ui/app.conf:/etc/ui/app.conf
|
||||||
- ../common/config/ui/private_key.pem:/etc/ui/private_key.pem
|
- ../common/config/ui/private_key.pem:/etc/ui/private_key.pem
|
||||||
- /data/secretkey:/etc/ui/key
|
- /data/secretkey:/etc/ui/key
|
||||||
|
- /data/ca_download/:/etc/ui/ca/
|
||||||
depends_on:
|
depends_on:
|
||||||
- log
|
- log
|
||||||
- adminserver
|
- adminserver
|
||||||
|
@ -76,6 +76,7 @@ services:
|
|||||||
- ./common/config/ui/app.conf:/etc/ui/app.conf
|
- ./common/config/ui/app.conf:/etc/ui/app.conf
|
||||||
- ./common/config/ui/private_key.pem:/etc/ui/private_key.pem
|
- ./common/config/ui/private_key.pem:/etc/ui/private_key.pem
|
||||||
- /data/secretkey:/etc/ui/key
|
- /data/secretkey:/etc/ui/key
|
||||||
|
- /data/ca_download/:/etc/ui/ca/
|
||||||
networks:
|
networks:
|
||||||
- harbor
|
- harbor
|
||||||
depends_on:
|
depends_on:
|
||||||
|
@ -11,10 +11,6 @@ ui_url_protocol = http
|
|||||||
#The password for the root user of mysql db, change this before any production use.
|
#The password for the root user of mysql db, change this before any production use.
|
||||||
db_password = root123
|
db_password = root123
|
||||||
|
|
||||||
#Determine whether the UI should use compressed js files.
|
|
||||||
#For production, set it to on. For development, set it to off.
|
|
||||||
use_compressed_js = on
|
|
||||||
|
|
||||||
#Maximum number of job workers in job service
|
#Maximum number of job workers in job service
|
||||||
max_job_workers = 3
|
max_job_workers = 3
|
||||||
|
|
||||||
|
@ -138,7 +138,6 @@ ldap_scope = rcp.get("configuration", "ldap_scope")
|
|||||||
ldap_timeout = rcp.get("configuration", "ldap_timeout")
|
ldap_timeout = rcp.get("configuration", "ldap_timeout")
|
||||||
db_password = rcp.get("configuration", "db_password")
|
db_password = rcp.get("configuration", "db_password")
|
||||||
self_registration = rcp.get("configuration", "self_registration")
|
self_registration = rcp.get("configuration", "self_registration")
|
||||||
use_compressed_js = rcp.get("configuration", "use_compressed_js")
|
|
||||||
if protocol == "https":
|
if protocol == "https":
|
||||||
cert_path = rcp.get("configuration", "ssl_cert")
|
cert_path = rcp.get("configuration", "ssl_cert")
|
||||||
cert_key_path = rcp.get("configuration", "ssl_cert_key")
|
cert_key_path = rcp.get("configuration", "ssl_cert_key")
|
||||||
@ -223,8 +222,7 @@ render(os.path.join(templates_dir, "adminserver", "env"),
|
|||||||
jobservice_secret=jobservice_secret,
|
jobservice_secret=jobservice_secret,
|
||||||
token_expiration=token_expiration,
|
token_expiration=token_expiration,
|
||||||
admiral_url=admiral_url,
|
admiral_url=admiral_url,
|
||||||
with_notary=args.notary_mode,
|
with_notary=args.notary_mode
|
||||||
use_compressed_js=use_compressed_js
|
|
||||||
)
|
)
|
||||||
|
|
||||||
render(os.path.join(templates_dir, "ui", "env"),
|
render(os.path.join(templates_dir, "ui", "env"),
|
||||||
|
@ -98,10 +98,6 @@ var (
|
|||||||
env: "TOKEN_EXPIRATION",
|
env: "TOKEN_EXPIRATION",
|
||||||
parse: parseStringToInt,
|
parse: parseStringToInt,
|
||||||
},
|
},
|
||||||
common.UseCompressedJS: &parser{
|
|
||||||
env: "USE_COMPRESSED_JS",
|
|
||||||
parse: parseStringToBool,
|
|
||||||
},
|
|
||||||
common.CfgExpiration: &parser{
|
common.CfgExpiration: &parser{
|
||||||
env: "CFG_EXPIRATION",
|
env: "CFG_EXPIRATION",
|
||||||
parse: parseStringToInt,
|
parse: parseStringToInt,
|
||||||
@ -132,11 +128,6 @@ var (
|
|||||||
env: "MAX_JOB_WORKERS",
|
env: "MAX_JOB_WORKERS",
|
||||||
parse: parseStringToInt,
|
parse: parseStringToInt,
|
||||||
},
|
},
|
||||||
// TODO remove this config?
|
|
||||||
common.UseCompressedJS: &parser{
|
|
||||||
env: "USE_COMPRESSED_JS",
|
|
||||||
parse: parseStringToBool,
|
|
||||||
},
|
|
||||||
common.CfgExpiration: &parser{
|
common.CfgExpiration: &parser{
|
||||||
env: "CFG_EXPIRATION",
|
env: "CFG_EXPIRATION",
|
||||||
parse: parseStringToInt,
|
parse: parseStringToInt,
|
||||||
|
@ -58,7 +58,6 @@ const (
|
|||||||
TokenExpiration = "token_expiration"
|
TokenExpiration = "token_expiration"
|
||||||
CfgExpiration = "cfg_expiration"
|
CfgExpiration = "cfg_expiration"
|
||||||
JobLogDir = "job_log_dir"
|
JobLogDir = "job_log_dir"
|
||||||
UseCompressedJS = "use_compressed_js"
|
|
||||||
AdminInitialPassword = "admin_initial_password"
|
AdminInitialPassword = "admin_initial_password"
|
||||||
AdmiralEndpoint = "admiral_url"
|
AdmiralEndpoint = "admiral_url"
|
||||||
WithNotary = "with_notary"
|
WithNotary = "with_notary"
|
||||||
|
@ -92,7 +92,6 @@ type SystemCfg struct {
|
|||||||
MaxJobWorkers int `json:"max_job_workers"`
|
MaxJobWorkers int `json:"max_job_workers"`
|
||||||
JobLogDir string `json:"job_log_dir"`
|
JobLogDir string `json:"job_log_dir"`
|
||||||
InitialAdminPwd string `json:"initial_admin_pwd,omitempty"`
|
InitialAdminPwd string `json:"initial_admin_pwd,omitempty"`
|
||||||
CompressJS bool `json:"compress_js"` //TODO remove
|
|
||||||
TokenExpiration int `json:"token_expiration"` // in minute
|
TokenExpiration int `json:"token_expiration"` // in minute
|
||||||
SecretKey string `json:"secret_key,omitempty"`
|
SecretKey string `json:"secret_key,omitempty"`
|
||||||
CfgExpiration int `json:"cfg_expiration"`
|
CfgExpiration int `json:"cfg_expiration"`
|
||||||
|
@ -16,87 +16,34 @@
|
|||||||
package email
|
package email
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
tlspkg "crypto/tls"
|
tlspkg "crypto/tls"
|
||||||
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"strconv"
|
"net/smtp"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"net/smtp"
|
|
||||||
"text/template"
|
|
||||||
|
|
||||||
"github.com/vmware/harbor/src/common/models"
|
|
||||||
"github.com/vmware/harbor/src/common/utils/log"
|
"github.com/vmware/harbor/src/common/utils/log"
|
||||||
"github.com/vmware/harbor/src/ui/config"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Mail holds information about content of Email
|
// Send ...
|
||||||
type Mail struct {
|
func Send(addr, identity, username, password string,
|
||||||
From string
|
timeout int, tls, insecure bool, from string,
|
||||||
To []string
|
to []string, subject, message string) error {
|
||||||
Subject string
|
|
||||||
Message string
|
|
||||||
}
|
|
||||||
|
|
||||||
var mc *models.Email
|
client, err := newClient(addr, identity, username,
|
||||||
|
password, timeout, tls, insecure)
|
||||||
// SendMail sends Email according to the configurations
|
|
||||||
func (m Mail) SendMail() error {
|
|
||||||
var err error
|
|
||||||
mc, err = config.Email()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
mailTemplate, err := template.ParseFiles("views/mail.tpl")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
mailContent := new(bytes.Buffer)
|
|
||||||
err = mailTemplate.Execute(mailContent, m)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
content := mailContent.Bytes()
|
|
||||||
|
|
||||||
auth := smtp.PlainAuth(mc.Identity, mc.Username, mc.Password, mc.Host)
|
|
||||||
if mc.SSL {
|
|
||||||
err = sendMailWithTLS(m, auth, content)
|
|
||||||
} else {
|
|
||||||
err = sendMail(m, auth, content)
|
|
||||||
}
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func sendMail(m Mail, auth smtp.Auth, content []byte) error {
|
|
||||||
return smtp.SendMail(mc.Host+":"+strconv.Itoa(mc.Port), auth, m.From, m.To, content)
|
|
||||||
}
|
|
||||||
|
|
||||||
func sendMailWithTLS(m Mail, auth smtp.Auth, content []byte) error {
|
|
||||||
conn, err := tlspkg.Dial("tcp", mc.Host+":"+strconv.Itoa(mc.Port), nil)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
client, err := smtp.NewClient(conn, mc.Host)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer client.Close()
|
defer client.Close()
|
||||||
|
|
||||||
if ok, _ := client.Extension("AUTH"); ok {
|
if err = client.Mail(from); err != nil {
|
||||||
if err = client.Auth(auth); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = client.Mail(m.From); err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, to := range m.To {
|
for _, t := range to {
|
||||||
if err = client.Rcpt(to); err != nil {
|
if err = client.Rcpt(t); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -106,7 +53,11 @@ func sendMailWithTLS(m Mail, auth smtp.Auth, content []byte) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = w.Write(content)
|
template := "From: %s\r\nTo: %s\r\nSubject: %s\r\nMIME-version: 1.0;\r\nContent-Type: text/html; charset=\"UTF-8\"\r\n\n%s\r\n"
|
||||||
|
data := fmt.Sprintf(template, from,
|
||||||
|
strings.Join(to, ","), subject, message)
|
||||||
|
|
||||||
|
_, err = w.Write([]byte(data))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -126,18 +77,29 @@ func sendMailWithTLS(m Mail, auth smtp.Auth, content []byte) error {
|
|||||||
// Ping doesn't verify the server's certificate and hostname when
|
// Ping doesn't verify the server's certificate and hostname when
|
||||||
// needed if the parameter insecure is ture
|
// needed if the parameter insecure is ture
|
||||||
func Ping(addr, identity, username, password string,
|
func Ping(addr, identity, username, password string,
|
||||||
timeout int, tls, insecure bool) (err error) {
|
timeout int, tls, insecure bool) error {
|
||||||
|
client, err := newClient(addr, identity, username, password,
|
||||||
|
timeout, tls, insecure)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer client.Close()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// caller needs to close the client
|
||||||
|
func newClient(addr, identity, username, password string,
|
||||||
|
timeout int, tls, insecure bool) (*smtp.Client, error) {
|
||||||
log.Debugf("establishing TCP connection with %s ...", addr)
|
log.Debugf("establishing TCP connection with %s ...", addr)
|
||||||
conn, err := net.DialTimeout("tcp", addr,
|
conn, err := net.DialTimeout("tcp", addr,
|
||||||
time.Duration(timeout)*time.Second)
|
time.Duration(timeout)*time.Second)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return nil, err
|
||||||
}
|
}
|
||||||
defer conn.Close()
|
|
||||||
|
|
||||||
host, _, err := net.SplitHostPort(addr)
|
host, _, err := net.SplitHostPort(addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if tls {
|
if tls {
|
||||||
@ -147,9 +109,8 @@ func Ping(addr, identity, username, password string,
|
|||||||
InsecureSkipVerify: insecure,
|
InsecureSkipVerify: insecure,
|
||||||
})
|
})
|
||||||
if err = tlsConn.Handshake(); err != nil {
|
if err = tlsConn.Handshake(); err != nil {
|
||||||
return
|
return nil, err
|
||||||
}
|
}
|
||||||
defer tlsConn.Close()
|
|
||||||
|
|
||||||
conn = tlsConn
|
conn = tlsConn
|
||||||
}
|
}
|
||||||
@ -157,9 +118,8 @@ func Ping(addr, identity, username, password string,
|
|||||||
log.Debugf("creating SMTP client for %s ...", host)
|
log.Debugf("creating SMTP client for %s ...", host)
|
||||||
client, err := smtp.NewClient(conn, host)
|
client, err := smtp.NewClient(conn, host)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return nil, err
|
||||||
}
|
}
|
||||||
defer client.Close()
|
|
||||||
|
|
||||||
//try to swith to SSL/TLS
|
//try to swith to SSL/TLS
|
||||||
if !tls {
|
if !tls {
|
||||||
@ -169,7 +129,7 @@ func Ping(addr, identity, username, password string,
|
|||||||
ServerName: host,
|
ServerName: host,
|
||||||
InsecureSkipVerify: insecure,
|
InsecureSkipVerify: insecure,
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
return
|
return nil, err
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
log.Debugf("the email server %s does not support STARTTLS", addr)
|
log.Debugf("the email server %s does not support STARTTLS", addr)
|
||||||
@ -181,14 +141,14 @@ func Ping(addr, identity, username, password string,
|
|||||||
// only support plain auth
|
// only support plain auth
|
||||||
if err = client.Auth(smtp.PlainAuth(identity,
|
if err = client.Auth(smtp.PlainAuth(identity,
|
||||||
username, password, host)); err != nil {
|
username, password, host)); err != nil {
|
||||||
return
|
return nil, err
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
log.Debugf("the email server %s does not support AUTH, skip",
|
log.Debugf("the email server %s does not support AUTH, skip",
|
||||||
addr)
|
addr)
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debug("ping email server successfully")
|
log.Debug("create smtp client successfully")
|
||||||
|
|
||||||
return
|
return client, nil
|
||||||
}
|
}
|
||||||
|
@ -18,20 +18,81 @@ package email
|
|||||||
import (
|
import (
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func TestSend(t *testing.T) {
|
||||||
|
addr := "smtp.gmail.com:465"
|
||||||
|
identity := ""
|
||||||
|
username := "harbortestonly@gmail.com"
|
||||||
|
password := "harborharbor"
|
||||||
|
timeout := 60
|
||||||
|
tls := true
|
||||||
|
insecure := false
|
||||||
|
from := "from"
|
||||||
|
to := []string{username}
|
||||||
|
subject := "subject"
|
||||||
|
message := "message"
|
||||||
|
|
||||||
|
// tls connection
|
||||||
|
tls = true
|
||||||
|
err := Send(addr, identity, username, password,
|
||||||
|
timeout, tls, insecure, from, to,
|
||||||
|
subject, message)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
/*not work on travis
|
||||||
|
// non-tls connection
|
||||||
|
addr = "smtp.gmail.com:25"
|
||||||
|
tls = false
|
||||||
|
err = Send(addr, identity, username, password,
|
||||||
|
timeout, tls, insecure, from, to,
|
||||||
|
subject, message)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
*/
|
||||||
|
|
||||||
|
//invalid username/password
|
||||||
|
username = "invalid_username"
|
||||||
|
err = Send(addr, identity, username, password,
|
||||||
|
timeout, tls, insecure, from, to,
|
||||||
|
subject, message)
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("there should be an auth error")
|
||||||
|
} else {
|
||||||
|
if !strings.Contains(err.Error(), "535") {
|
||||||
|
t.Errorf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestPing(t *testing.T) {
|
func TestPing(t *testing.T) {
|
||||||
addr := "smtp.gmail.com:465"
|
addr := "smtp.gmail.com:465"
|
||||||
identity := ""
|
identity := ""
|
||||||
username := "wrong_username"
|
username := "harbortestonly@gmail.com"
|
||||||
password := "wrong_password"
|
password := "harborharbor"
|
||||||
timeout := 60
|
timeout := 0
|
||||||
tls := true
|
tls := true
|
||||||
insecure := false
|
insecure := false
|
||||||
|
|
||||||
// test secure connection
|
// tls connection
|
||||||
err := Ping(addr, identity, username, password,
|
err := Ping(addr, identity, username, password,
|
||||||
timeout, tls, insecure)
|
timeout, tls, insecure)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
/*not work on travis
|
||||||
|
// non-tls connection
|
||||||
|
addr = "smtp.gmail.com:25"
|
||||||
|
tls = false
|
||||||
|
err = Ping(addr, identity, username, password,
|
||||||
|
timeout, tls, insecure)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
*/
|
||||||
|
|
||||||
|
//invalid username/password
|
||||||
|
username = "invalid_username"
|
||||||
|
err = Ping(addr, identity, username, password,
|
||||||
|
timeout, tls, insecure)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Errorf("there should be an auth error")
|
t.Errorf("there should be an auth error")
|
||||||
} else {
|
} else {
|
||||||
|
@ -63,7 +63,6 @@ var adminServerLdapTestConfig = map[string]interface{}{
|
|||||||
// config.TokenExpiration: 30,
|
// config.TokenExpiration: 30,
|
||||||
common.CfgExpiration: 5,
|
common.CfgExpiration: 5,
|
||||||
// config.JobLogDir: "/var/log/jobs",
|
// config.JobLogDir: "/var/log/jobs",
|
||||||
// config.UseCompressedJS: true,
|
|
||||||
common.AdminInitialPassword: "password",
|
common.AdminInitialPassword: "password",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -57,7 +57,6 @@ var adminServerDefaultConfig = map[string]interface{}{
|
|||||||
common.MaxJobWorkers: 3,
|
common.MaxJobWorkers: 3,
|
||||||
common.TokenExpiration: 30,
|
common.TokenExpiration: 30,
|
||||||
common.CfgExpiration: 5,
|
common.CfgExpiration: 5,
|
||||||
common.UseCompressedJS: true,
|
|
||||||
common.AdminInitialPassword: "password",
|
common.AdminInitialPassword: "password",
|
||||||
common.AdmiralEndpoint: "http://www.vmware.com",
|
common.AdmiralEndpoint: "http://www.vmware.com",
|
||||||
common.WithNotary: false,
|
common.WithNotary: false,
|
||||||
|
@ -63,7 +63,6 @@ var (
|
|||||||
common.TokenExpiration,
|
common.TokenExpiration,
|
||||||
common.CfgExpiration,
|
common.CfgExpiration,
|
||||||
common.JobLogDir,
|
common.JobLogDir,
|
||||||
common.UseCompressedJS,
|
|
||||||
common.AdminInitialPassword,
|
common.AdminInitialPassword,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -81,7 +80,6 @@ var (
|
|||||||
common.EmailSSL,
|
common.EmailSSL,
|
||||||
common.SelfRegistration,
|
common.SelfRegistration,
|
||||||
common.VerifyRemoteCert,
|
common.VerifyRemoteCert,
|
||||||
common.UseCompressedJS,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
passwordKeys = []string{
|
passwordKeys = []string{
|
||||||
|
@ -20,7 +20,7 @@ type SystemInfoAPI struct {
|
|||||||
isAdmin bool
|
isAdmin bool
|
||||||
}
|
}
|
||||||
|
|
||||||
const defaultRootCert = "/harbor_storage/ca_download/ca.crt"
|
const defaultRootCert = "/etc/ui/ca/ca.crt"
|
||||||
const harborVersionFile = "/harbor/VERSION"
|
const harborVersionFile = "/harbor/VERSION"
|
||||||
|
|
||||||
//SystemInfo models for system info.
|
//SystemInfo models for system info.
|
||||||
|
@ -48,7 +48,6 @@ var adminServerLdapTestConfig = map[string]interface{}{
|
|||||||
// config.TokenExpiration: 30,
|
// config.TokenExpiration: 30,
|
||||||
common.CfgExpiration: 5,
|
common.CfgExpiration: 5,
|
||||||
// config.JobLogDir: "/var/log/jobs",
|
// config.JobLogDir: "/var/log/jobs",
|
||||||
// config.UseCompressedJS: true,
|
|
||||||
common.AdminInitialPassword: "password",
|
common.AdminInitialPassword: "password",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,19 +3,21 @@ package controllers
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"html/template"
|
"html/template"
|
||||||
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
"github.com/astaxie/beego"
|
"github.com/astaxie/beego"
|
||||||
"github.com/beego/i18n"
|
"github.com/beego/i18n"
|
||||||
"github.com/vmware/harbor/src/common"
|
|
||||||
"github.com/vmware/harbor/src/common/dao"
|
"github.com/vmware/harbor/src/common/dao"
|
||||||
"github.com/vmware/harbor/src/common/models"
|
"github.com/vmware/harbor/src/common/models"
|
||||||
"github.com/vmware/harbor/src/common/utils"
|
"github.com/vmware/harbor/src/common/utils"
|
||||||
"github.com/vmware/harbor/src/common/utils/email"
|
email_util "github.com/vmware/harbor/src/common/utils/email"
|
||||||
"github.com/vmware/harbor/src/common/utils/log"
|
"github.com/vmware/harbor/src/common/utils/log"
|
||||||
"github.com/vmware/harbor/src/ui/auth"
|
"github.com/vmware/harbor/src/ui/auth"
|
||||||
|
"github.com/vmware/harbor/src/ui/config"
|
||||||
)
|
)
|
||||||
|
|
||||||
// CommonController handles request from UI that doesn't expect a page, such as /SwitchLanguage /logout ...
|
// CommonController handles request from UI that doesn't expect a page, such as /SwitchLanguage /logout ...
|
||||||
@ -87,72 +89,81 @@ func (cc *CommonController) UserExists() {
|
|||||||
// SendEmail verifies the Email address and contact SMTP server to send reset password Email.
|
// SendEmail verifies the Email address and contact SMTP server to send reset password Email.
|
||||||
func (cc *CommonController) SendEmail() {
|
func (cc *CommonController) SendEmail() {
|
||||||
|
|
||||||
emailStr := cc.GetString("email")
|
email := cc.GetString("email")
|
||||||
|
|
||||||
pass, _ := regexp.MatchString(`^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$`, emailStr)
|
|
||||||
|
|
||||||
if !pass {
|
|
||||||
cc.CustomAbort(http.StatusBadRequest, "email_content_illegal")
|
|
||||||
} else {
|
|
||||||
|
|
||||||
queryUser := models.User{Email: emailStr}
|
|
||||||
exist, err := dao.UserExists(queryUser, "email")
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("Error occurred in UserExists: %v", err)
|
|
||||||
cc.CustomAbort(http.StatusInternalServerError, "Internal error.")
|
|
||||||
}
|
|
||||||
if !exist {
|
|
||||||
cc.CustomAbort(http.StatusNotFound, "email_does_not_exist")
|
|
||||||
}
|
|
||||||
|
|
||||||
messageTemplate, err := template.ParseFiles("views/reset-password-mail.tpl")
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("Parse email template file failed: %v", err)
|
|
||||||
cc.CustomAbort(http.StatusInternalServerError, err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
message := new(bytes.Buffer)
|
|
||||||
|
|
||||||
harborURL := common.ExtEndpoint
|
|
||||||
if harborURL == "" {
|
|
||||||
harborURL = "localhost"
|
|
||||||
}
|
|
||||||
uuid := utils.GenerateRandomString()
|
|
||||||
err = messageTemplate.Execute(message, messageDetail{
|
|
||||||
Hint: cc.Tr("reset_email_hint"),
|
|
||||||
URL: harborURL,
|
|
||||||
UUID: uuid,
|
|
||||||
})
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("Message template error: %v", err)
|
|
||||||
cc.CustomAbort(http.StatusInternalServerError, "internal_error")
|
|
||||||
}
|
|
||||||
|
|
||||||
config, err := beego.AppConfig.GetSection("mail")
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("Can not load app.conf: %v", err)
|
|
||||||
cc.CustomAbort(http.StatusInternalServerError, "internal_error")
|
|
||||||
}
|
|
||||||
|
|
||||||
mail := email.Mail{
|
|
||||||
From: config["from"],
|
|
||||||
To: []string{emailStr},
|
|
||||||
Subject: cc.Tr("reset_email_subject"),
|
|
||||||
Message: message.String()}
|
|
||||||
|
|
||||||
err = mail.SendMail()
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("Send email failed: %v", err)
|
|
||||||
cc.CustomAbort(http.StatusInternalServerError, "send_email_failed")
|
|
||||||
}
|
|
||||||
|
|
||||||
user := models.User{ResetUUID: uuid, Email: emailStr}
|
|
||||||
dao.UpdateUserResetUUID(user)
|
|
||||||
|
|
||||||
|
valid, err := regexp.MatchString(`^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$`, email)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("failed to match regexp: %v", err)
|
||||||
|
cc.CustomAbort(http.StatusInternalServerError, "Internal error.")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !valid {
|
||||||
|
cc.CustomAbort(http.StatusBadRequest, "invalid email")
|
||||||
|
}
|
||||||
|
|
||||||
|
queryUser := models.User{Email: email}
|
||||||
|
exist, err := dao.UserExists(queryUser, "email")
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("Error occurred in UserExists: %v", err)
|
||||||
|
cc.CustomAbort(http.StatusInternalServerError, "Internal error.")
|
||||||
|
}
|
||||||
|
if !exist {
|
||||||
|
log.Debugf("email %s not found", email)
|
||||||
|
cc.CustomAbort(http.StatusNotFound, "email_does_not_exist")
|
||||||
|
}
|
||||||
|
|
||||||
|
uuid := utils.GenerateRandomString()
|
||||||
|
user := models.User{ResetUUID: uuid, Email: email}
|
||||||
|
if err = dao.UpdateUserResetUUID(user); err != nil {
|
||||||
|
log.Errorf("failed to update user reset UUID: %v", err)
|
||||||
|
cc.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
|
||||||
|
}
|
||||||
|
|
||||||
|
messageTemplate, err := template.ParseFiles("views/reset-password-mail.tpl")
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("Parse email template file failed: %v", err)
|
||||||
|
cc.CustomAbort(http.StatusInternalServerError, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
message := new(bytes.Buffer)
|
||||||
|
|
||||||
|
harborURL, err := config.ExtEndpoint()
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("failed to get domain name: %v", err)
|
||||||
|
cc.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
|
||||||
|
}
|
||||||
|
|
||||||
|
err = messageTemplate.Execute(message, messageDetail{
|
||||||
|
Hint: cc.Tr("reset_email_hint"),
|
||||||
|
URL: harborURL,
|
||||||
|
UUID: uuid,
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("Message template error: %v", err)
|
||||||
|
cc.CustomAbort(http.StatusInternalServerError, "internal_error")
|
||||||
|
}
|
||||||
|
|
||||||
|
settings, err := config.Email()
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("failed to get email configurations: %v", err)
|
||||||
|
cc.CustomAbort(http.StatusInternalServerError, "internal_error")
|
||||||
|
}
|
||||||
|
|
||||||
|
addr := net.JoinHostPort(settings.Host, strconv.Itoa(settings.Port))
|
||||||
|
err = email_util.Send(addr,
|
||||||
|
settings.Identity,
|
||||||
|
settings.Username,
|
||||||
|
settings.Password,
|
||||||
|
60, settings.SSL,
|
||||||
|
false, settings.From,
|
||||||
|
[]string{email},
|
||||||
|
cc.Tr("reset_email_subject"),
|
||||||
|
message.String())
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("Send email failed: %v", err)
|
||||||
|
cc.CustomAbort(http.StatusInternalServerError, "send_email_failed")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ResetPassword handles request from the reset page and reset password
|
// ResetPassword handles request from the reset page and reset password
|
||||||
|
@ -13,7 +13,8 @@
|
|||||||
<label for="account_settings_email" aria-haspopup="true" role="tooltip" class="tooltip tooltip-validation tooltip-md tooltip-top-left" [class.invalid]='!getValidationState("account_settings_email")'>
|
<label for="account_settings_email" aria-haspopup="true" role="tooltip" class="tooltip tooltip-validation tooltip-md tooltip-top-left" [class.invalid]='!getValidationState("account_settings_email")'>
|
||||||
<input name="account_settings_email" type="text" #eamilInput="ngModel" [(ngModel)]="account.email"
|
<input name="account_settings_email" type="text" #eamilInput="ngModel" [(ngModel)]="account.email"
|
||||||
required
|
required
|
||||||
pattern='^(([^<>()[\]\.,;:\s@\"]+(\.[^<>()[\]\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$' id="account_settings_email" size="30"
|
email
|
||||||
|
id="account_settings_email" size="30"
|
||||||
(input)='handleValidation("account_settings_email", false)'
|
(input)='handleValidation("account_settings_email", false)'
|
||||||
(focusout)='handleValidation("account_settings_email", true)'>
|
(focusout)='handleValidation("account_settings_email", true)'>
|
||||||
<span class="tooltip-content">
|
<span class="tooltip-content">
|
||||||
|
@ -28,7 +28,7 @@
|
|||||||
required
|
required
|
||||||
pattern="^(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?!.*\s).{8,20}$"
|
pattern="^(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?!.*\s).{8,20}$"
|
||||||
name="reNewPassword"
|
name="reNewPassword"
|
||||||
[(ngModel)]="ngModel"
|
[(ngModel)]="confirmPwd"
|
||||||
#reNewPassInput
|
#reNewPassInput
|
||||||
size="25"
|
size="25"
|
||||||
(input)='handleValidation("reNewPassword", true)'
|
(input)='handleValidation("reNewPassword", true)'
|
||||||
|
@ -21,6 +21,7 @@ export class ResetPasswordComponent implements OnInit {
|
|||||||
};
|
};
|
||||||
private resetUuid: string = "";
|
private resetUuid: string = "";
|
||||||
private resetOk: boolean = false;
|
private resetOk: boolean = false;
|
||||||
|
confirmPwd: string = "";
|
||||||
|
|
||||||
@ViewChild("resetPwdForm") resetPwdForm: NgForm;
|
@ViewChild("resetPwdForm") resetPwdForm: NgForm;
|
||||||
@ViewChild(InlineAlertComponent)
|
@ViewChild(InlineAlertComponent)
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
<div class="login-wrapper login-wrapper-override">
|
<div class="login-wrapper login-wrapper-override">
|
||||||
<form #signInForm="ngForm" class="login">
|
<form #signInForm="ngForm" class="login">
|
||||||
<label class="title">
|
<label class="title">{{appTitle | translate}}<span class="trademark">™</span>
|
||||||
VMware Harbor<span class="trademark">™</span>
|
|
||||||
</label>
|
</label>
|
||||||
<div class="login-group">
|
<div class="login-group">
|
||||||
<label for="username" aria-haspopup="true" role="tooltip" class="tooltip tooltip-validation tooltip-md tooltip-top-left" [class.invalid]="userNameInput.invalid && (userNameInput.dirty || userNameInput.touched)">
|
<label for="username" aria-haspopup="true" role="tooltip" class="tooltip tooltip-validation tooltip-md tooltip-top-left" [class.invalid]="userNameInput.invalid && (userNameInput.dirty || userNameInput.touched)">
|
||||||
|
@ -80,6 +80,15 @@ export class SignInComponent implements AfterViewChecked, OnInit {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//App title
|
||||||
|
public get appTitle(): string {
|
||||||
|
if(this.appConfig && this.appConfig.with_admiral){
|
||||||
|
return "APP_TITLE.VIC";
|
||||||
|
}
|
||||||
|
|
||||||
|
return "APP_TITLE.VMW_HARBOR";
|
||||||
|
}
|
||||||
|
|
||||||
//For template accessing
|
//For template accessing
|
||||||
public get isError(): boolean {
|
public get isError(): boolean {
|
||||||
return this.signInStatus === signInStatusError;
|
return this.signInStatus === signInStatusError;
|
||||||
@ -153,7 +162,8 @@ export class SignInComponent implements AfterViewChecked, OnInit {
|
|||||||
private handleUserCreation(user: User): void {
|
private handleUserCreation(user: User): void {
|
||||||
if (user) {
|
if (user) {
|
||||||
this.currentForm.setValue({
|
this.currentForm.setValue({
|
||||||
"login_username": user.username
|
"login_username": user.username,
|
||||||
|
"login_password": ""
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<global-search></global-search>
|
<global-search></global-search>
|
||||||
<div class="header-actions">
|
<div class="header-actions">
|
||||||
<clr-dropdown class="dropdown bottom-left">
|
<clr-dropdown class="dropdown bottom-left" *ngIf="!isIntegrationMode">
|
||||||
<button class="nav-icon" clrDropdownToggle style="width: 98px;">
|
<button class="nav-icon" clrDropdownToggle style="width: 98px;">
|
||||||
<clr-icon shape="world" style="left:-8px;"></clr-icon>
|
<clr-icon shape="world" style="left:-8px;"></clr-icon>
|
||||||
<span style="padding-right: 8px;">{{currentLang}}</span>
|
<span style="padding-right: 8px;">{{currentLang}}</span>
|
||||||
|
@ -15,5 +15,5 @@
|
|||||||
display: inline-block;
|
display: inline-block;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
width: 80%;
|
width: 80%;
|
||||||
white-space: pre;
|
white-space: pre-wrap;
|
||||||
}
|
}
|
@ -7,7 +7,7 @@
|
|||||||
<div class="confirmation-content">{{dialogContent}}</div>
|
<div class="confirmation-content">{{dialogContent}}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<button type="button" class="btn btn-outline" (click)="cancel()">{{'BUTTON.NEGATIVE' | translate}}</button>
|
<button type="button" class="btn btn-outline" (click)="cancel()">{{'BUTTON.NO' | translate}}</button>
|
||||||
<button type="button" class="btn btn-primary" (click)="confirm()">{{'BUTTON.CONFIRM' | translate}}</button>
|
<button type="button" class="btn btn-primary" (click)="confirm()">{{'BUTTON.YES' | translate}}</button>
|
||||||
</div>
|
</div>
|
||||||
</clr-modal>
|
</clr-modal>
|
33
src/ui_ng/src/app/shared/email.directive.ts
Normal file
33
src/ui_ng/src/app/shared/email.directive.ts
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
import { Directive } from '@angular/core';
|
||||||
|
import { ValidatorFn, AbstractControl, Validator, NG_VALIDATORS, Validators } from '@angular/forms';
|
||||||
|
|
||||||
|
const emailPattern = /^(([^<>()[\]\.,;:\s@\"]+(\.[^<>()[\]\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
|
||||||
|
|
||||||
|
export function emailValidator(): ValidatorFn {
|
||||||
|
return (control: AbstractControl): { [key: string]: any } => {
|
||||||
|
const value: string = control.value
|
||||||
|
if (!value) {
|
||||||
|
return { 'email': false };
|
||||||
|
}
|
||||||
|
|
||||||
|
const regExp = new RegExp(emailPattern);
|
||||||
|
if(!regExp.test(value)){
|
||||||
|
return { 'email': false };
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Directive({
|
||||||
|
selector: '[email]',
|
||||||
|
providers: [{ provide: NG_VALIDATORS, useExisting: EmailValidatorDirective, multi: true }]
|
||||||
|
})
|
||||||
|
|
||||||
|
export class EmailValidatorDirective implements Validator {
|
||||||
|
valFn = emailValidator();
|
||||||
|
|
||||||
|
validate(control: AbstractControl): { [key: string]: any } {
|
||||||
|
return this.valFn(control);
|
||||||
|
}
|
||||||
|
}
|
@ -1,4 +1,9 @@
|
|||||||
.alert-text-blink {
|
.alert-text-blink {
|
||||||
color: red;
|
color: red;
|
||||||
font-weight: bolder;
|
font-weight: bolder;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-btn-link {
|
||||||
|
padding: 0px !important;
|
||||||
|
min-width: 30px !important;
|
||||||
}
|
}
|
@ -4,8 +4,8 @@
|
|||||||
{{errorMessage}}
|
{{errorMessage}}
|
||||||
</span>
|
</span>
|
||||||
<div class="alert-actions" *ngIf="showCancelAction">
|
<div class="alert-actions" *ngIf="showCancelAction">
|
||||||
<button class="btn alert-action" (click)="close()">{{'BUTTON.NO' | translate}}</button>
|
<button class="btn btn-sm btn-link alert-btn-link" (click)="close()">{{'BUTTON.NO' | translate}}</button>
|
||||||
<button class="btn alert-action" (click)="confirmCancel()">{{'BUTTON.YES' | translate}}</button>
|
<button class="btn btn-sm btn-link alert-btn-link" (click)="confirmCancel()">{{'BUTTON.YES' | translate}}</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</clr-alert>
|
</clr-alert>
|
@ -17,7 +17,8 @@
|
|||||||
<label for="email" aria-haspopup="true" role="tooltip" class="tooltip tooltip-validation tooltip-md tooltip-bottom-left" [class.invalid]='getValidationState("email")'>
|
<label for="email" aria-haspopup="true" role="tooltip" class="tooltip tooltip-validation tooltip-md tooltip-bottom-left" [class.invalid]='getValidationState("email")'>
|
||||||
<input name="email" type="text" #eamilInput="ngModel" [(ngModel)]="newUser.email"
|
<input name="email" type="text" #eamilInput="ngModel" [(ngModel)]="newUser.email"
|
||||||
required
|
required
|
||||||
pattern='^(([^<>()[\]\.,;:\s@\"]+(\.[^<>()[\]\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$' id="email" size="36"
|
email
|
||||||
|
id="email" size="36"
|
||||||
(input)='handleValidation("email", false)'
|
(input)='handleValidation("email", false)'
|
||||||
(focusout)='handleValidation("email", true)'>
|
(focusout)='handleValidation("email", true)'>
|
||||||
<span class="tooltip-content">
|
<span class="tooltip-content">
|
||||||
|
@ -38,6 +38,7 @@ import { ListProjectROComponent } from './list-project-ro/list-project-ro.compon
|
|||||||
import { ListRepositoryROComponent } from './list-repository-ro/list-repository-ro.component';
|
import { ListRepositoryROComponent } from './list-repository-ro/list-repository-ro.component';
|
||||||
|
|
||||||
import { MessageHandlerService } from './message-handler/message-handler.service';
|
import { MessageHandlerService } from './message-handler/message-handler.service';
|
||||||
|
import { EmailValidatorDirective } from './email.directive';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
@ -60,7 +61,8 @@ import { MessageHandlerService } from './message-handler/message-handler.service
|
|||||||
StatisticsComponent,
|
StatisticsComponent,
|
||||||
StatisticsPanelComponent,
|
StatisticsPanelComponent,
|
||||||
ListProjectROComponent,
|
ListProjectROComponent,
|
||||||
ListRepositoryROComponent
|
ListRepositoryROComponent,
|
||||||
|
EmailValidatorDirective
|
||||||
],
|
],
|
||||||
exports: [
|
exports: [
|
||||||
CoreModule,
|
CoreModule,
|
||||||
@ -79,7 +81,8 @@ import { MessageHandlerService } from './message-handler/message-handler.service
|
|||||||
StatisticsComponent,
|
StatisticsComponent,
|
||||||
StatisticsPanelComponent,
|
StatisticsPanelComponent,
|
||||||
ListProjectROComponent,
|
ListProjectROComponent,
|
||||||
ListRepositoryROComponent
|
ListRepositoryROComponent,
|
||||||
|
EmailValidatorDirective
|
||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
SessionService,
|
SessionService,
|
||||||
|
@ -30,4 +30,8 @@
|
|||||||
right: 2px;
|
right: 2px;
|
||||||
top: 12px;
|
top: 12px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hide-create {
|
||||||
|
visibility: hidden !important;
|
||||||
}
|
}
|
@ -3,7 +3,7 @@
|
|||||||
<h2 class="custom-h2">{{'SIDE_NAV.SYSTEM_MGMT.USER' | translate}}</h2>
|
<h2 class="custom-h2">{{'SIDE_NAV.SYSTEM_MGMT.USER' | translate}}</h2>
|
||||||
<div class="action-panel-pos">
|
<div class="action-panel-pos">
|
||||||
<span>
|
<span>
|
||||||
<button *ngIf="canCreateUser" type="submit" class="btn btn-link custom-add-button" (click)="addNewUser()"><clr-icon shape="add"></clr-icon> {{'USER.ADD_ACTION' | translate}}</button>
|
<button [class.hide-create]="!canCreateUser" type="submit" class="btn btn-link custom-add-button" (click)="addNewUser()"><clr-icon shape="add"></clr-icon> {{'USER.ADD_ACTION' | translate}}</button>
|
||||||
</span>
|
</span>
|
||||||
<grid-filter class="filter-pos" filterPlaceholder='{{"USER.FILTER_PLACEHOLDER" | translate}}' (filter)="doFilter($event)"></grid-filter>
|
<grid-filter class="filter-pos" filterPlaceholder='{{"USER.FILTER_PLACEHOLDER" | translate}}' (filter)="doFilter($event)"></grid-filter>
|
||||||
<span class="refresh-btn" (click)="refreshUser()">
|
<span class="refresh-btn" (click)="refreshUser()">
|
||||||
|
@ -130,9 +130,8 @@ export class UserComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//Value copy
|
//Value copy
|
||||||
let updatedUser: User = {
|
let updatedUser: User = new User();
|
||||||
user_id: user.user_id
|
updatedUser.user_id = user.user_id;
|
||||||
};
|
|
||||||
|
|
||||||
if (user.has_admin_role === 0) {
|
if (user.has_admin_role === 0) {
|
||||||
updatedUser.has_admin_role = 1;//Set as admin
|
updatedUser.has_admin_role = 1;//Set as admin
|
||||||
@ -206,6 +205,9 @@ export class UserComponent implements OnInit, OnDestroy {
|
|||||||
|
|
||||||
//Add new user
|
//Add new user
|
||||||
addNewUser(): void {
|
addNewUser(): void {
|
||||||
|
if (!this.canCreateUser) {
|
||||||
|
return;// No response to this hacking action
|
||||||
|
}
|
||||||
this.newUserDialog.open();
|
this.newUserDialog.open();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"APP_TITLE": {
|
"APP_TITLE": {
|
||||||
|
"VMW_HARBOR": "VMware Harbor",
|
||||||
"HARBOR": "Harbor",
|
"HARBOR": "Harbor",
|
||||||
"VIC": "vSphere Integrated Containers",
|
"VIC": "vSphere Integrated Containers",
|
||||||
"MGMT": "Management",
|
"MGMT": "Management",
|
||||||
@ -110,8 +111,8 @@
|
|||||||
"COLUMN_REG_NAME": "Registration time",
|
"COLUMN_REG_NAME": "Registration time",
|
||||||
"IS_ADMIN": "Yes",
|
"IS_ADMIN": "Yes",
|
||||||
"IS_NOT_ADMIN": "No",
|
"IS_NOT_ADMIN": "No",
|
||||||
"ADD_USER_TITLE": "Add User",
|
"ADD_USER_TITLE": "New User",
|
||||||
"SAVE_SUCCESS": "New user added successfully",
|
"SAVE_SUCCESS": "New user created successfully",
|
||||||
"DELETION_TITLE": "Confirm user deletion",
|
"DELETION_TITLE": "Confirm user deletion",
|
||||||
"DELETION_SUMMARY": "Do you want to delete user {{param}}?",
|
"DELETION_SUMMARY": "Do you want to delete user {{param}}?",
|
||||||
"DELETE_SUCCESS": "User deleted successfully",
|
"DELETE_SUCCESS": "User deleted successfully",
|
||||||
@ -324,7 +325,7 @@
|
|||||||
"COPY": "Copy"
|
"COPY": "Copy"
|
||||||
},
|
},
|
||||||
"ALERT": {
|
"ALERT": {
|
||||||
"FORM_CHANGE_CONFIRMATION": "Some changes are not saved yet, do you really want to cancel?"
|
"FORM_CHANGE_CONFIRMATION": "Some changes are not saved yet, do you want to cancel?"
|
||||||
},
|
},
|
||||||
"RESET_PWD": {
|
"RESET_PWD": {
|
||||||
"TITLE": "Reset Password",
|
"TITLE": "Reset Password",
|
||||||
@ -391,7 +392,7 @@
|
|||||||
"TEST_MAIL_FAILED": "Failed to verify mail server with error: {{param}}",
|
"TEST_MAIL_FAILED": "Failed to verify mail server with error: {{param}}",
|
||||||
"TEST_LDAP_FAILED": "Failed to verify LDAP server with error: {{param}}",
|
"TEST_LDAP_FAILED": "Failed to verify LDAP server with error: {{param}}",
|
||||||
"LEAVING_CONFIRMATION_TITLE": "Confirm to leave",
|
"LEAVING_CONFIRMATION_TITLE": "Confirm to leave",
|
||||||
"LEAVING_CONFIRMATION_SUMMARY": "Changes have not been saved yet, do you really want to leave currnet page?"
|
"LEAVING_CONFIRMATION_SUMMARY": "Changes have not been saved yet, do you want to leave currnet page?"
|
||||||
},
|
},
|
||||||
"PAGE_NOT_FOUND": {
|
"PAGE_NOT_FOUND": {
|
||||||
"MAIN_TITLE": "Page not found",
|
"MAIN_TITLE": "Page not found",
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"APP_TITLE": {
|
"APP_TITLE": {
|
||||||
|
"VMW_HARBOR": "VMware Harbor",
|
||||||
"HARBOR": "Harbor",
|
"HARBOR": "Harbor",
|
||||||
"VIC": "vSphere Integrated Containers",
|
"VIC": "vSphere Integrated Containers",
|
||||||
"MGMT": "Management",
|
"MGMT": "Management",
|
||||||
@ -110,8 +111,8 @@
|
|||||||
"COLUMN_REG_NAME": "注册时间",
|
"COLUMN_REG_NAME": "注册时间",
|
||||||
"IS_ADMIN": "是",
|
"IS_ADMIN": "是",
|
||||||
"IS_NOT_ADMIN": "否",
|
"IS_NOT_ADMIN": "否",
|
||||||
"ADD_USER_TITLE": "添加用户",
|
"ADD_USER_TITLE": "创建用户",
|
||||||
"SAVE_SUCCESS": "添加用户成功",
|
"SAVE_SUCCESS": "创建用户成功",
|
||||||
"DELETION_TITLE": "删除用户确认",
|
"DELETION_TITLE": "删除用户确认",
|
||||||
"DELETION_SUMMARY": "你确认删除用户 {{param}}?",
|
"DELETION_SUMMARY": "你确认删除用户 {{param}}?",
|
||||||
"DELETE_SUCCESS": "删除用户成功",
|
"DELETE_SUCCESS": "删除用户成功",
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
set -e
|
set -e
|
||||||
cp tests/docker-compose.test.yml make/.
|
cp tests/docker-compose.test.yml make/.
|
||||||
|
|
||||||
mkdir /etc/ui
|
mkdir -p /etc/ui
|
||||||
cp make/common/config/ui/private_key.pem /etc/ui/.
|
cp make/common/config/ui/private_key.pem /etc/ui/.
|
||||||
|
|
||||||
mkdir conf
|
mkdir conf
|
||||||
|
Loading…
Reference in New Issue
Block a user