mirror of
https://github.com/goharbor/harbor.git
synced 2025-01-03 14:37:44 +01:00
Merge remote-tracking branch 'upstream/master' into batchDelection
This commit is contained in:
commit
29e53817b9
@ -35,6 +35,7 @@ import (
|
||||
const (
|
||||
defaultJSONCfgStorePath string = "/etc/adminserver/config/config.json"
|
||||
defaultKeyPath string = "/etc/adminserver/key"
|
||||
ldapScopeKey string = "ldap_scope"
|
||||
)
|
||||
|
||||
var (
|
||||
@ -274,6 +275,11 @@ func initCfgStore() (err error) {
|
||||
log.Errorf("Failed to read old configuration from %s", path)
|
||||
return err
|
||||
}
|
||||
// Update LDAP Scope for migration
|
||||
// only used when migrating harbor release before v1.3
|
||||
// after v1.3 there is always a db configuration before migrate.
|
||||
validLdapScope(jsonconfig, true)
|
||||
|
||||
err = CfgStore.Write(jsonconfig)
|
||||
if err != nil {
|
||||
log.Error("Failed to update old configuration to database")
|
||||
@ -336,7 +342,7 @@ func LoadFromEnv(cfgs map[string]interface{}, all bool) error {
|
||||
|
||||
return fmt.Errorf("%v is not string or parse type", v)
|
||||
}
|
||||
|
||||
validLdapScope(cfgs, false)
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -356,3 +362,18 @@ func GetDatabaseFromCfg(cfg map[string]interface{}) *models.Database {
|
||||
database.SQLite = sqlite
|
||||
return database
|
||||
}
|
||||
|
||||
// Valid LDAP Scope
|
||||
func validLdapScope(cfg map[string]interface{}, isMigrate bool) {
|
||||
ldapScope := cfg[ldapScopeKey].(int)
|
||||
if isMigrate && ldapScope > 0 && ldapScope < 3 {
|
||||
ldapScope = ldapScope - 1
|
||||
}
|
||||
if ldapScope >= 3 {
|
||||
ldapScope = 2
|
||||
}
|
||||
if ldapScope < 0 {
|
||||
ldapScope = 0
|
||||
}
|
||||
cfg[ldapScopeKey] = ldapScope
|
||||
}
|
||||
|
@ -127,17 +127,54 @@ func TestLoadFromEnv(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestGetDatabaseFromCfg(t *testing.T) {
|
||||
cfg :=map[string]interface{} {
|
||||
common.DatabaseType:"mysql",
|
||||
common.MySQLDatabase:"registry",
|
||||
common.MySQLHost:"127.0.0.1",
|
||||
common.MySQLPort:3306,
|
||||
common.MySQLPassword:"1234",
|
||||
common.MySQLUsername:"root",
|
||||
common.SQLiteFile:"/tmp/sqlite.db",
|
||||
cfg := map[string]interface{}{
|
||||
common.DatabaseType: "mysql",
|
||||
common.MySQLDatabase: "registry",
|
||||
common.MySQLHost: "127.0.0.1",
|
||||
common.MySQLPort: 3306,
|
||||
common.MySQLPassword: "1234",
|
||||
common.MySQLUsername: "root",
|
||||
common.SQLiteFile: "/tmp/sqlite.db",
|
||||
}
|
||||
|
||||
database := GetDatabaseFromCfg(cfg)
|
||||
|
||||
assert.Equal(t,"mysql",database.Type)
|
||||
assert.Equal(t, "mysql", database.Type)
|
||||
}
|
||||
|
||||
func TestValidLdapScope(t *testing.T) {
|
||||
ldapScopeKey := "ldap_scope"
|
||||
testCfgs := []struct {
|
||||
config map[string]interface{}
|
||||
migrate bool
|
||||
ldapScopeResult int
|
||||
}{
|
||||
{map[string]interface{}{
|
||||
ldapScopeKey: 1,
|
||||
}, true, 0},
|
||||
{map[string]interface{}{
|
||||
ldapScopeKey: 2,
|
||||
}, true, 1},
|
||||
{map[string]interface{}{
|
||||
ldapScopeKey: 3,
|
||||
}, true, 2},
|
||||
{map[string]interface{}{
|
||||
ldapScopeKey: -1,
|
||||
}, true, 0},
|
||||
{map[string]interface{}{
|
||||
ldapScopeKey: 100,
|
||||
}, false, 2},
|
||||
{map[string]interface{}{
|
||||
ldapScopeKey: -100,
|
||||
}, false, 0},
|
||||
}
|
||||
|
||||
for i, item := range testCfgs {
|
||||
validLdapScope(item.config, item.migrate)
|
||||
if item.config[ldapScopeKey].(int) != item.ldapScopeResult {
|
||||
t.Fatalf("Failed to update ldapScope expected %v, actual %v at index %v", item.ldapScopeResult, item.config[ldapScopeKey], i)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -41,6 +41,8 @@ const (
|
||||
UsersURLSuffix = "/Users"
|
||||
)
|
||||
|
||||
var uaaTransport = &http.Transport{}
|
||||
|
||||
// Client provides funcs to interact with UAA.
|
||||
type Client interface {
|
||||
//PasswordAuth accepts username and password, return a token if it's valid.
|
||||
@ -49,6 +51,8 @@ type Client interface {
|
||||
GetUserInfo(token string) (*UserInfo, error)
|
||||
//SearchUser searches a user based on user name.
|
||||
SearchUser(name string) ([]*SearchUserEntry, error)
|
||||
//UpdateConfig updates the config of the current client
|
||||
UpdateConfig(cfg *ClientConfig) error
|
||||
}
|
||||
|
||||
// ClientConfig values to initialize UAA Client
|
||||
@ -169,13 +173,13 @@ func (dc *defaultClient) prepareCtx() context.Context {
|
||||
return context.WithValue(context.Background(), oauth2.HTTPClient, dc.httpClient)
|
||||
}
|
||||
|
||||
// NewDefaultClient creates an instance of defaultClient.
|
||||
func NewDefaultClient(cfg *ClientConfig) (Client, error) {
|
||||
func (dc *defaultClient) UpdateConfig(cfg *ClientConfig) error {
|
||||
url := cfg.Endpoint
|
||||
if !strings.Contains(url, "://") {
|
||||
url = "https://" + url
|
||||
}
|
||||
url = strings.TrimSuffix(url, "/")
|
||||
dc.endpoint = url
|
||||
tc := &tls.Config{
|
||||
InsecureSkipVerify: cfg.SkipTLSVerify,
|
||||
}
|
||||
@ -183,7 +187,7 @@ func NewDefaultClient(cfg *ClientConfig) (Client, error) {
|
||||
if _, err := os.Stat(cfg.CARootPath); !os.IsNotExist(err) {
|
||||
content, err := ioutil.ReadFile(cfg.CARootPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return err
|
||||
}
|
||||
pool := x509.NewCertPool()
|
||||
//Do not throw error if the certificate is malformed, so we can put a place holder.
|
||||
@ -196,11 +200,9 @@ func NewDefaultClient(cfg *ClientConfig) (Client, error) {
|
||||
log.Warningf("The root certificate file %s is not found, skip configuring root cert in UAA client.", cfg.CARootPath)
|
||||
}
|
||||
}
|
||||
hc := &http.Client{
|
||||
Transport: &http.Transport{
|
||||
TLSClientConfig: tc,
|
||||
},
|
||||
}
|
||||
uaaTransport.TLSClientConfig = tc
|
||||
dc.httpClient.Transport = uaaTransport
|
||||
//dc.httpClient.Transport = transport.
|
||||
|
||||
oc := &oauth2.Config{
|
||||
ClientID: cfg.ClientID,
|
||||
@ -216,11 +218,17 @@ func NewDefaultClient(cfg *ClientConfig) (Client, error) {
|
||||
ClientSecret: cfg.ClientSecret,
|
||||
TokenURL: url + TokenURLSuffix,
|
||||
}
|
||||
|
||||
return &defaultClient{
|
||||
httpClient: hc,
|
||||
oauth2Cfg: oc,
|
||||
twoLegCfg: cc,
|
||||
endpoint: url,
|
||||
}, nil
|
||||
dc.oauth2Cfg = oc
|
||||
dc.twoLegCfg = cc
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewDefaultClient creates an instance of defaultClient.
|
||||
func NewDefaultClient(cfg *ClientConfig) (Client, error) {
|
||||
hc := &http.Client{}
|
||||
c := &defaultClient{httpClient: hc}
|
||||
if err := c.UpdateConfig(cfg); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return c, nil
|
||||
}
|
||||
|
@ -19,6 +19,8 @@ import (
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
||||
const fakeToken = "The Fake Token"
|
||||
|
||||
// FakeClient is for test only
|
||||
type FakeClient struct {
|
||||
Username string
|
||||
@ -28,14 +30,26 @@ type FakeClient struct {
|
||||
// PasswordAuth ...
|
||||
func (fc *FakeClient) PasswordAuth(username, password string) (*oauth2.Token, error) {
|
||||
if username == fc.Username && password == fc.Password {
|
||||
return &oauth2.Token{}, nil
|
||||
return &oauth2.Token{AccessToken: fakeToken}, nil
|
||||
}
|
||||
return nil, fmt.Errorf("Invalide username and password")
|
||||
}
|
||||
|
||||
// GetUserInfo ...
|
||||
func (fc *FakeClient) GetUserInfo(token string) (*UserInfo, error) {
|
||||
return nil, nil
|
||||
if token != fakeToken {
|
||||
return nil, fmt.Errorf("Unexpected token: %s, expected: %s", token, fakeToken)
|
||||
}
|
||||
info := &UserInfo{
|
||||
Name: "fakeName",
|
||||
Email: "fake@fake.com",
|
||||
}
|
||||
return info, nil
|
||||
}
|
||||
|
||||
// UpdateConfig ...
|
||||
func (fc *FakeClient) UpdateConfig(cfg *ClientConfig) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// SearchUser ...
|
||||
|
@ -110,7 +110,7 @@ func Login(m models.AuthModel) (*models.User, error) {
|
||||
time.Sleep(frozenTime)
|
||||
}
|
||||
|
||||
authenticator.PostAuthenticate(user)
|
||||
err = authenticator.PostAuthenticate(user)
|
||||
|
||||
return user, err
|
||||
}
|
||||
|
@ -23,27 +23,12 @@ import (
|
||||
"github.com/vmware/harbor/src/common"
|
||||
"github.com/vmware/harbor/src/common/dao"
|
||||
"github.com/vmware/harbor/src/common/models"
|
||||
"github.com/vmware/harbor/src/common/utils/log"
|
||||
"github.com/vmware/harbor/src/common/utils/uaa"
|
||||
"github.com/vmware/harbor/src/ui/auth"
|
||||
"github.com/vmware/harbor/src/ui/config"
|
||||
)
|
||||
|
||||
//CreateClient create a UAA Client instance based on system configuration.
|
||||
func CreateClient() (uaa.Client, error) {
|
||||
UAASettings, err := config.UAASettings()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cfg := &uaa.ClientConfig{
|
||||
ClientID: UAASettings.ClientID,
|
||||
ClientSecret: UAASettings.ClientSecret,
|
||||
Endpoint: UAASettings.Endpoint,
|
||||
SkipTLSVerify: !UAASettings.VerifyCert,
|
||||
CARootPath: os.Getenv("UAA_CA_ROOT"),
|
||||
}
|
||||
return uaa.NewDefaultClient(cfg)
|
||||
}
|
||||
|
||||
// Auth is the implementation of AuthenticateHelper to access uaa for authentication.
|
||||
type Auth struct {
|
||||
sync.Mutex
|
||||
@ -58,12 +43,17 @@ func (u *Auth) Authenticate(m models.AuthModel) (*models.User, error) {
|
||||
}
|
||||
t, err := u.client.PasswordAuth(m.Principal, m.Password)
|
||||
if t != nil && err == nil {
|
||||
//TODO: See if it's possible to get more information from token.
|
||||
user := &models.User{
|
||||
Username: m.Principal,
|
||||
}
|
||||
err = u.OnBoardUser(user)
|
||||
return user, err
|
||||
info, err2 := u.client.GetUserInfo(t.AccessToken)
|
||||
if err2 != nil {
|
||||
log.Warningf("Failed to extract user info from UAA, error: %v", err2)
|
||||
} else {
|
||||
user.Email = info.Email
|
||||
user.Realname = info.Name
|
||||
}
|
||||
return user, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
@ -89,6 +79,28 @@ func (u *Auth) OnBoardUser(user *models.User) error {
|
||||
return dao.OnBoardUser(user)
|
||||
}
|
||||
|
||||
// PostAuthenticate will check if user exists in DB, if not on Board user, if he does, update the profile.
|
||||
func (u *Auth) PostAuthenticate(user *models.User) error {
|
||||
dbUser, err := dao.GetUser(models.User{Username: user.Username})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if dbUser == nil {
|
||||
return u.OnBoardUser(user)
|
||||
}
|
||||
if user.Email != "" {
|
||||
dbUser.Email = user.Email
|
||||
}
|
||||
if user.Realname != "" {
|
||||
dbUser.Realname = user.Realname
|
||||
}
|
||||
if err2 := dao.ChangeUserProfile(*user, "Email", "Realname"); err2 != nil {
|
||||
log.Warningf("Failed to update user profile, user: %s, error: %v", user.Username, err2)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// SearchUser search user on uaa server, transform it to Harbor's user model
|
||||
func (u *Auth) SearchUser(username string) (*models.User, error) {
|
||||
if err := u.ensureClient(); err != nil {
|
||||
@ -116,13 +128,27 @@ func (u *Auth) SearchUser(username string) (*models.User, error) {
|
||||
}
|
||||
|
||||
func (u *Auth) ensureClient() error {
|
||||
if u.client != nil {
|
||||
return nil
|
||||
var cfg *uaa.ClientConfig
|
||||
UAASettings, err := config.UAASettings()
|
||||
// log.Debugf("Uaa settings: %+v", UAASettings)
|
||||
if err != nil {
|
||||
log.Warningf("Failed to get UAA setting from Admin Server, error: %v", err)
|
||||
} else {
|
||||
cfg = &uaa.ClientConfig{
|
||||
ClientID: UAASettings.ClientID,
|
||||
ClientSecret: UAASettings.ClientSecret,
|
||||
Endpoint: UAASettings.Endpoint,
|
||||
SkipTLSVerify: !UAASettings.VerifyCert,
|
||||
CARootPath: os.Getenv("UAA_CA_ROOT"),
|
||||
}
|
||||
}
|
||||
if u.client != nil && cfg != nil {
|
||||
return u.client.UpdateConfig(cfg)
|
||||
}
|
||||
u.Lock()
|
||||
defer u.Unlock()
|
||||
if u.client == nil {
|
||||
c, err := CreateClient()
|
||||
c, err := uaa.NewDefaultClient(cfg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -102,11 +102,12 @@ func TestMain(m *testing.M) {
|
||||
os.Exit(rc)
|
||||
}
|
||||
|
||||
func TestCreateClient(t *testing.T) {
|
||||
func TestEnsureClient(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
c, err := CreateClient()
|
||||
auth := Auth{client: nil}
|
||||
err := auth.ensureClient()
|
||||
assert.Nil(err)
|
||||
assert.NotNil(c)
|
||||
assert.NotNil(auth.client)
|
||||
}
|
||||
|
||||
func TestAuthenticate(t *testing.T) {
|
||||
@ -123,6 +124,7 @@ func TestAuthenticate(t *testing.T) {
|
||||
u1, err1 := auth.Authenticate(m1)
|
||||
assert.Nil(err1)
|
||||
assert.NotNil(u1)
|
||||
assert.Equal("fake@fake.com", u1.Email)
|
||||
m2 := models.AuthModel{
|
||||
Principal: "wrong",
|
||||
Password: "wrong",
|
||||
@ -153,6 +155,30 @@ func TestOnBoardUser(t *testing.T) {
|
||||
assert.Equal("test", user.Realname)
|
||||
assert.Equal("test", user.Username)
|
||||
assert.Equal("test@uaa.placeholder", user.Email)
|
||||
err3 := dao.ClearTable(models.UserTable)
|
||||
assert.Nil(err3)
|
||||
}
|
||||
|
||||
func TestPostAuthenticate(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
auth := Auth{}
|
||||
um := &models.User{
|
||||
Username: "test",
|
||||
}
|
||||
err := auth.PostAuthenticate(um)
|
||||
assert.Nil(err)
|
||||
user, _ := dao.GetUser(models.User{Username: "test"})
|
||||
assert.Equal("test@uaa.placeholder", user.Email)
|
||||
um.Email = "newEmail@new.com"
|
||||
um.Realname = "newName"
|
||||
err2 := auth.PostAuthenticate(um)
|
||||
assert.Nil(err2)
|
||||
user2, _ := dao.GetUser(models.User{Username: "test"})
|
||||
assert.Equal("newEmail@new.com", user2.Email)
|
||||
assert.Equal("newName", user2.Realname)
|
||||
err3 := dao.ClearTable(models.UserTable)
|
||||
assert.Nil(err3)
|
||||
|
||||
}
|
||||
|
||||
func TestSearchUser(t *testing.T) {
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "harbor-ui",
|
||||
"version": "0.6.6",
|
||||
"version": "0.6.22",
|
||||
"description": "Harbor shared UI components based on Clarity and Angular4",
|
||||
"scripts": {
|
||||
"start": "ng serve --host 0.0.0.0 --port 4500 --proxy-config proxy.config.json",
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "harbor-ui",
|
||||
"version": "0.6.6",
|
||||
"version": "0.6.22",
|
||||
"description": "Harbor shared UI components based on Clarity and Angular4",
|
||||
"author": "VMware",
|
||||
"module": "index.js",
|
||||
|
@ -20,7 +20,7 @@ import { ConfirmationState, ConfirmationTargets, ConfirmationButtons } from '../
|
||||
|
||||
import { CONFIRMATION_DIALOG_TEMPLATE } from './confirmation-dialog.component.html';
|
||||
import { CONFIRMATION_DIALOG_STYLE } from './confirmation-dialog.component.css';
|
||||
import {BatchInfo} from "./confirmation-batch-message";
|
||||
import {BatchInfo} from './confirmation-batch-message';
|
||||
|
||||
@Component({
|
||||
selector: 'confirmation-dialog',
|
||||
@ -29,16 +29,16 @@ import {BatchInfo} from "./confirmation-batch-message";
|
||||
})
|
||||
|
||||
export class ConfirmationDialogComponent {
|
||||
opened: boolean = false;
|
||||
dialogTitle: string = "";
|
||||
dialogContent: string = "";
|
||||
opened = false;
|
||||
dialogTitle = '';
|
||||
dialogContent = '';
|
||||
message: ConfirmationMessage;
|
||||
buttons: ConfirmationButtons;
|
||||
|
||||
@Output() confirmAction = new EventEmitter<ConfirmationAcknowledgement>();
|
||||
@Output() cancelAction = new EventEmitter<ConfirmationAcknowledgement>();
|
||||
@Input() batchInfors: BatchInfo[] = [];
|
||||
isDelete: boolean = false;
|
||||
isDelete = false;
|
||||
|
||||
constructor(
|
||||
private translate: TranslateService) {}
|
||||
@ -49,7 +49,7 @@ export class ConfirmationDialogComponent {
|
||||
this.message = msg;
|
||||
this.translate.get(this.dialogTitle).subscribe((res: string) => this.dialogTitle = res);
|
||||
this.translate.get(this.dialogContent, { 'param': msg.param }).subscribe((res: string) => this.dialogContent = res);
|
||||
//Open dialog
|
||||
// Open dialog
|
||||
this.buttons = msg.buttons;
|
||||
this.opened = true;
|
||||
}
|
||||
@ -81,7 +81,8 @@ export class ConfirmationDialogComponent {
|
||||
}
|
||||
|
||||
cancel(): void {
|
||||
if(!this.message){//Inproper condition
|
||||
if (!this.message) {
|
||||
// Inproper condition
|
||||
this.close();
|
||||
return;
|
||||
}
|
||||
|
@ -31,7 +31,7 @@
|
||||
"clarity-icons": "^0.10.17",
|
||||
"clarity-ui": "^0.10.17",
|
||||
"core-js": "^2.4.1",
|
||||
"harbor-ui": "0.6.21",
|
||||
"harbor-ui": "0.6.22",
|
||||
"intl": "^1.2.5",
|
||||
"mutationobserver-shim": "^0.3.2",
|
||||
"ngx-cookie": "^1.0.0",
|
||||
|
@ -1,12 +1,20 @@
|
||||
<clr-modal [(clrModalOpen)]="opened" [clrModalStaticBackdrop]="staticBackdrop" [clrModalClosable]="false">
|
||||
<h3 class="modal-title">{{'PROFILE.TITLE' | translate}}</h3>
|
||||
<inline-alert class="modal-title" (confirmEvt)="confirmCancel($event)"></inline-alert>
|
||||
<inline-alert class="modal-title" (confirmEvt)="confirm($event)"></inline-alert>
|
||||
<div class="modal-body" style="overflow-y: hidden;">
|
||||
<form #accountSettingsFrom="ngForm" class="form">
|
||||
<section class="form-block">
|
||||
<div class="form-group form-group-override">
|
||||
<label for="account_settings_username" class="form-group-label-override">{{'PROFILE.USER_NAME' | translate}}</label>
|
||||
<label for="account_settings_username" aria-haspopup="true" class="form-group-label-override">{{'PROFILE.USER_NAME' | translate}}</label>
|
||||
<input type="text" name="account_settings_username" [(ngModel)]="account.username" disabled id="account_settings_username" size="33">
|
||||
<clr-tooltip *ngIf="renamable">
|
||||
<button (dblclick)="openRenameAlert()" class="btn btn-link">
|
||||
<clr-icon clrTooltipTrigger shape="info-circle" size="24"></clr-icon>
|
||||
</button>
|
||||
<clr-tooltip-content clrPosition="bottom-left" clrSize="md" *clrIfOpen>
|
||||
<span (click)="openRenameAlert()"> {{'PROFILE.ADMIN_RENAME_TIP'}}</span>
|
||||
</clr-tooltip-content>
|
||||
</clr-tooltip>
|
||||
</div>
|
||||
<div class="form-group form-group-override">
|
||||
<label for="account_settings_email" class="required form-group-label-override">{{'PROFILE.EMAIL' | translate}}</label>
|
||||
|
@ -42,6 +42,8 @@ export class AccountSettingsModalComponent implements OnInit, AfterViewChecked {
|
||||
formValueChanged: boolean = false;
|
||||
checkOnGoing: boolean = false;
|
||||
|
||||
RenameOnGoing: boolean = false;
|
||||
|
||||
accountFormRef: NgForm;
|
||||
@ViewChild("accountSettingsFrom") accountForm: NgForm;
|
||||
@ViewChild(InlineAlertComponent)
|
||||
@ -133,6 +135,29 @@ export class AccountSettingsModalComponent implements OnInit, AfterViewChecked {
|
||||
return this.checkOnGoing;
|
||||
}
|
||||
|
||||
public get renamable(): boolean {
|
||||
return this.account && this.account.has_admin_role && this.account.username === 'admin' && this.account.user_id === 1;
|
||||
}
|
||||
|
||||
openRenameAlert(): void {
|
||||
this.RenameOnGoing = true;
|
||||
this.inlineAlert.showInlineConfirmation({
|
||||
message: 'PROFILE.RENAME_CONFIRM_INFO'
|
||||
});
|
||||
}
|
||||
|
||||
confirmRename(): void {
|
||||
if (this.renamable) {
|
||||
this.session.renameAdmin(this.account)
|
||||
.then(() => {
|
||||
this.msgHandler.showSuccess('PROFILE.RENAME_SUCCESS');
|
||||
})
|
||||
.catch(error => {
|
||||
this.msgHandler.handleError(error);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
ngAfterViewChecked(): void {
|
||||
if (this.accountFormRef != this.accountForm) {
|
||||
this.accountFormRef = this.accountForm;
|
||||
@ -215,7 +240,11 @@ export class AccountSettingsModalComponent implements OnInit, AfterViewChecked {
|
||||
});
|
||||
}
|
||||
|
||||
confirmCancel($event: any): void {
|
||||
confirm($event: any): void {
|
||||
if(this.RenameOnGoing) {
|
||||
this.confirmRename();
|
||||
this.RenameOnGoing = false;
|
||||
}
|
||||
this.inlineAlert.close();
|
||||
this.opened = false;
|
||||
}
|
||||
|
@ -13,7 +13,7 @@
|
||||
<button id="config-system" class="btn btn-link nav-link" aria-controls="system_settings" [class.active]='isCurrentTabLink("config-system")' type="button" (click)='tabLinkClick("config-system")'>{{'CONFIG.SYSTEM' | translate }}</button>
|
||||
</li>
|
||||
<li role="presentation" class="nav-item" *ngIf="withClair">
|
||||
<button id="config-vulnerability" class="btn btn-link nav-link" aria-controls="vulnerability" [class.active]='isCurrentTabLink("config-vulnerability")' type="button" (click)='tabLinkClick("config-vulnerability")'>{{'VULNERABILITY.SINGULAR' | translate}}</button>
|
||||
<button id="config-vulnerability" class="btn btn-link nav-link" aria-controls="vulnerability" [class.active]='isCurrentTabLink("config-vulnerability")' type="button" (click)='tabLinkClick("config-vulnerability")'>{{'CONFIG.VULNERABILITY' | translate}}</button>
|
||||
</li>
|
||||
</ul>
|
||||
<section id="authentication" role="tabpanel" aria-labelledby="config-auth" [hidden]='!isCurrentTabContent("authentication")'>
|
||||
|
@ -28,6 +28,7 @@ const signOffEndpoint = "/log_out";
|
||||
const accountEndpoint = "/api/users/:id";
|
||||
const langEndpoint = "/language";
|
||||
const userExistsEndpoint = "/userExists";
|
||||
const renameAdminEndpoint = 'api/internal/renameadmin';
|
||||
const langMap = {
|
||||
"zh": "zh-CN",
|
||||
"en": "en-US"
|
||||
@ -129,6 +130,25 @@ export class SessionService {
|
||||
.catch(error => this.handleError(error))
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Update accpunt settings
|
||||
*
|
||||
* @param {SessionUser} account
|
||||
* @returns {Promise<any>}
|
||||
*
|
||||
* @memberOf SessionService
|
||||
*/
|
||||
renameAdmin(account: SessionUser): Promise<any> {
|
||||
if (!account) {
|
||||
return Promise.reject("Invalid account settings");
|
||||
}
|
||||
return this.http.post(renameAdminEndpoint, JSON.stringify({}), HTTP_JSON_OPTIONS)
|
||||
.toPromise()
|
||||
.then(() => null)
|
||||
.catch(error => this.handleError(error));
|
||||
}
|
||||
|
||||
/**
|
||||
* Switch the backend language profile
|
||||
*/
|
||||
|
@ -31,26 +31,26 @@ export class UserService {
|
||||
|
||||
constructor(private http: Http) { }
|
||||
|
||||
//Handle the related exceptions
|
||||
// Handle the related exceptions
|
||||
handleError(error: any): Promise<any> {
|
||||
return Promise.reject(error.message || error);
|
||||
}
|
||||
|
||||
//Get the user list
|
||||
// Get the user list
|
||||
getUsers(): Promise<User[]> {
|
||||
return this.http.get(userMgmtEndpoint, HTTP_GET_OPTIONS).toPromise()
|
||||
.then(response => response.json() as User[])
|
||||
.catch(error => this.handleError(error));
|
||||
}
|
||||
|
||||
//Add new user
|
||||
// Add new user
|
||||
addUser(user: User): Promise<any> {
|
||||
return this.http.post(userMgmtEndpoint, JSON.stringify(user), HTTP_JSON_OPTIONS).toPromise()
|
||||
.then(() => null)
|
||||
.catch(error => this.handleError(error));
|
||||
}
|
||||
|
||||
//Delete the specified user
|
||||
// Delete the specified user
|
||||
deleteUser(userId: number): Promise<any> {
|
||||
return this.http.delete(userMgmtEndpoint + "/" + userId, HTTP_JSON_OPTIONS)
|
||||
.toPromise()
|
||||
@ -58,7 +58,7 @@ export class UserService {
|
||||
.catch(error => this.handleError(error));
|
||||
}
|
||||
|
||||
//Update user to enable/disable the admin role
|
||||
// Update user to enable/disable the admin role
|
||||
updateUser(user: User): Promise<any> {
|
||||
return this.http.put(userMgmtEndpoint + "/" + user.user_id, JSON.stringify(user), HTTP_JSON_OPTIONS)
|
||||
.toPromise()
|
||||
@ -66,7 +66,7 @@ export class UserService {
|
||||
.catch(error => this.handleError(error));
|
||||
}
|
||||
|
||||
//Set user admin role
|
||||
// Set user admin role
|
||||
updateUserRole(user: User): Promise<any> {
|
||||
return this.http.put(userMgmtEndpoint + "/" + user.user_id + "/sysadmin", JSON.stringify(user), HTTP_JSON_OPTIONS)
|
||||
.toPromise()
|
||||
|
@ -82,7 +82,10 @@
|
||||
"FULL_NAME": "First and last name",
|
||||
"COMMENT": "Comments",
|
||||
"PASSWORD": "Password",
|
||||
"SAVE_SUCCESS": "User profile saved successfully."
|
||||
"SAVE_SUCCESS": "User profile saved successfully.",
|
||||
"ADMIN_RENAME_TIP": "Double click to change your username to \"admin@harbor.local\", but this action can NOT redo.",
|
||||
"RENAME_SUCCESS": "Rename success!",
|
||||
"RENAME_CONFIRM_INFO": "This action can not undo, Confirm To Rename?"
|
||||
},
|
||||
"CHANGE_PWD": {
|
||||
"TITLE": "Change Password",
|
||||
@ -217,7 +220,6 @@
|
||||
"OF": "of",
|
||||
"SWITCH_TITLE": "Confirm project members switch",
|
||||
"SWITCH_SUMMARY": "Do you want to switch project members {{param}}?"
|
||||
|
||||
},
|
||||
"AUDIT_LOG": {
|
||||
"USERNAME": "Username",
|
||||
@ -426,6 +428,7 @@
|
||||
"REPLICATION": "Replication",
|
||||
"EMAIL": "Email",
|
||||
"SYSTEM": "System Settings",
|
||||
"VULNERABILITY": "Vulnerability",
|
||||
"CONFIRM_TITLE": "Confirm to cancel",
|
||||
"CONFIRM_SUMMARY": "Some changes have not been saved. Do you want to discard them?",
|
||||
"SAVE_SUCCESS": "Configuration has been successfully saved.",
|
||||
|
@ -82,7 +82,10 @@
|
||||
"FULL_NAME": "Nombre y apellidos",
|
||||
"COMMENT": "Comentarios",
|
||||
"PASSWORD": "Contraseña",
|
||||
"SAVE_SUCCESS": "Perfil de usuario guardado satisfactoriamente."
|
||||
"SAVE_SUCCESS": "Perfil de usuario guardado satisfactoriamente.",
|
||||
"ADMIN_RENAME_TIP": "Double click to change your username to \"admin@harbor.local\", but this action can NOT redo.",
|
||||
"RENAME_SUCCESS": "Rename success!",
|
||||
"RENAME_CONFIRM_INFO": "This action can not undo, Confirm To Rename?"
|
||||
},
|
||||
"CHANGE_PWD": {
|
||||
"TITLE": "Cambiar contraseña",
|
||||
@ -364,8 +367,7 @@
|
||||
"PLACEHOLDER": "We couldn't find any endpoints!"
|
||||
},
|
||||
"REPOSITORY": {
|
||||
"COPY_ID": "Copiar ID",
|
||||
"COPY_PARENT_ID": "Copiar ID padre",
|
||||
"COPY_DIGEST_ID": "Copy Digest",
|
||||
"DELETE": "Eliminar",
|
||||
"NAME": "Nombre",
|
||||
"TAGS_COUNT": "Etiquetas",
|
||||
@ -426,6 +428,7 @@
|
||||
"REPLICATION": "Replicación",
|
||||
"EMAIL": "Email",
|
||||
"SYSTEM": "Opciones del Sistema",
|
||||
"VULNERABILITY": "Vulnerability",
|
||||
"CONFIRM_TITLE": "Confirma cancelación",
|
||||
"CONFIRM_SUMMARY": "Algunos cambios no han sido guardados aún. ¿Quiere descartarlos?",
|
||||
"SAVE_SUCCESS": "La configuración ha sido guardada satisfactoriamente.",
|
||||
@ -528,6 +531,8 @@
|
||||
"PRO_ITEM": "PROYECTOS",
|
||||
"REPO_ITEM": "REPOSITORIOS",
|
||||
"INDEX_PRIVATE": "PRIVADO",
|
||||
"INDEX_MY_PROJECTS": "MY PROJECTS",
|
||||
"INDEX_MY_REPOSITORIES": "MY REPOSITORIES",
|
||||
"INDEX_PUB": "PÚBLICO",
|
||||
"INDEX_TOTAL": "TOTAL",
|
||||
"STORAGE": "ALMACENAMIENTO",
|
||||
|
@ -82,7 +82,10 @@
|
||||
"FULL_NAME": "全名",
|
||||
"COMMENT": "注释",
|
||||
"PASSWORD": "密码",
|
||||
"SAVE_SUCCESS": "成功保存用户设置。"
|
||||
"SAVE_SUCCESS": "成功保存用户设置。",
|
||||
"ADMIN_RENAME_TIP": "双击将用户名改为 \"admin@harbor.local\", 注意这个操作是不可逆的",
|
||||
"RENAME_SUCCESS": "用户名更改成功!",
|
||||
"RENAME_CONFIRM_INFO": "更改用户名无法撤销, 你确定更改吗·?"
|
||||
},
|
||||
"CHANGE_PWD": {
|
||||
"TITLE": "修改密码",
|
||||
@ -202,7 +205,6 @@
|
||||
"DEVELOPER": "开发人员",
|
||||
"GUEST": "访客",
|
||||
"DELETE": "删除",
|
||||
"OF": "共计",
|
||||
"ITEMS": "条记录",
|
||||
"ACTIONS": "操作",
|
||||
"USERNAME_IS_REQUIRED": "用户名为必填项。",
|
||||
@ -215,6 +217,7 @@
|
||||
"ADDED_SUCCESS": "成功新增成员。",
|
||||
"DELETED_SUCCESS": "成功删除成员。",
|
||||
"SWITCHED_SUCCESS": "切换角色成功。",
|
||||
"OF": "共计",
|
||||
"SWITCH_TITLE": "切换项目成员确认",
|
||||
"SWITCH_SUMMARY": "你确认切换项目成员 {{param}}??"
|
||||
},
|
||||
@ -233,10 +236,10 @@
|
||||
"OTHERS": "其他",
|
||||
"ADVANCED": "高级检索",
|
||||
"SIMPLE": "简单检索",
|
||||
"OF": "共计",
|
||||
"ITEMS": "条记录",
|
||||
"FILTER_PLACEHOLDER": "过滤日志",
|
||||
"INVALID_DATE": "无效日期。"
|
||||
"INVALID_DATE": "无效日期。",
|
||||
"OF": "共计"
|
||||
},
|
||||
"REPLICATION": {
|
||||
"REPLICATION_RULE": "复制规则",
|
||||
@ -425,6 +428,7 @@
|
||||
"REPLICATION": "复制",
|
||||
"EMAIL": "邮箱",
|
||||
"SYSTEM": "系统设置",
|
||||
"VULNERABILITY": "漏洞",
|
||||
"CONFIRM_TITLE": "确认取消",
|
||||
"CONFIRM_SUMMARY": "配置项有改动, 确定取消?",
|
||||
"SAVE_SUCCESS": "变更的配置项成功保存。",
|
||||
|
@ -6,7 +6,5 @@ cd /harbor_src
|
||||
|
||||
mv /harbor_resources/node_modules ./
|
||||
|
||||
npm install
|
||||
npm install -q --no-progress
|
||||
npm run test > ./npm-ut-test-results
|
||||
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user