mirror of
https://github.com/goharbor/harbor.git
synced 2024-11-22 18:25:56 +01:00
Improve login page for OIDC (#15214)
Signed-off-by: AllForNothing <sshijun@vmware.com>
This commit is contained in:
parent
29ccdff766
commit
bb57264f11
@ -2,55 +2,80 @@
|
||||
<global-message [isAppLevel]="true"></global-message>
|
||||
<navigator (showDialogModalAction)="openModal($event)"></navigator>
|
||||
<search-result></search-result>
|
||||
<div class="login-wrapper" [ngStyle]="{'background-image': customLoginBgImg? 'url(/images/' + customLoginBgImg + ')': ''}">
|
||||
<div class="login-wrapper"
|
||||
[ngStyle]="{'background-image': customLoginBgImg? 'url(/images/' + customLoginBgImg + ')': ''}">
|
||||
<form #signInForm="ngForm" class="login">
|
||||
<label class="title"> {{customAppTitle? customAppTitle:(appTitle | translate)}}
|
||||
<span *ngIf="isOidcLoginMode && steps===2" (click)="steps=1" class="back-icon">
|
||||
<clr-icon shape="angle left"></clr-icon>
|
||||
<div class="margin-left-3">{{'SEARCH.BACK' | translate }}</div>
|
||||
</span>
|
||||
<label class="title"> {{customAppTitle ? customAppTitle : (appTitle | translate)}}
|
||||
</label>
|
||||
<a href="/c/oidc/login" class="login-oidc" *ngIf="isOidcLoginMode">
|
||||
<button type="button" id="log_oidc" class="btn btn-primary btn-block">
|
||||
<span>{{'BUTTON.LOG_IN_OIDC' | translate }}</span>
|
||||
</button>
|
||||
</a>
|
||||
<div class="login-group">
|
||||
<clr-input-container>
|
||||
<input clrInput class="username" type="text" required [(ngModel)]="signInCredential.principal" name="login_username" id="login_username"
|
||||
placeholder='{{"PLACEHOLDER.SIGN_IN_NAME" | translate}}' #userNameInput='ngModel'>
|
||||
<clr-control-error>{{ 'TOOLTIP.SIGN_IN_USERNAME' | translate }}</clr-control-error>
|
||||
</clr-input-container>
|
||||
<div class="clr-form-control">
|
||||
<div class="clr-control-container" [class.clr-error] ="passwordInput.invalid && (passwordInput.dirty || passwordInput.touched)">
|
||||
<div class="clr-input-wrapper">
|
||||
<input class="clr-input pwd-input" [type]="showPwd?'text':'password'" required [(ngModel)]="signInCredential.password" name="login_password" id="login_password"
|
||||
placeholder='{{"PLACEHOLDER.SIGN_IN_PWD" | translate}}' #passwordInput="ngModel">
|
||||
<clr-icon *ngIf="!showPwd" shape="eye" class="pw-eye" (click)="showPwd =!showPwd"></clr-icon>
|
||||
<clr-icon *ngIf="showPwd" shape="eye-hide" class="pw-eye" (click)="showPwd =!showPwd"></clr-icon>
|
||||
<clr-icon class="clr-validate-icon" shape="exclamation-circle"></clr-icon>
|
||||
</div>
|
||||
<clr-control-error *ngIf="passwordInput.invalid && (passwordInput.dirty || passwordInput.touched)">
|
||||
{{ 'TOOLTIP.SIGN_IN_PWD' | translate }}
|
||||
</clr-control-error>
|
||||
<ng-container *ngIf="isOidcLoginMode && steps ===1">
|
||||
<a href="/c/oidc/login">
|
||||
<button type="button" id="log_oidc" class="btn btn-primary btn-block">
|
||||
<span>{{'BUTTON.LOG_IN_OIDC' | translate }}</span>
|
||||
</button>
|
||||
</a>
|
||||
<div class="divider-container mt-1 mb-1">
|
||||
<hr class="divider" size="1" noshade/>
|
||||
<h4 class="m-0"><span>{{'SIGN_IN.OR' | translate }}</span></h4>
|
||||
<hr class="divider" size="1" noshade/>
|
||||
</div>
|
||||
<a href="javascript:void(0)" (click)="steps=2">
|
||||
<button type="button" id="login-db" class="btn btn-primary btn-block mt-0">
|
||||
<span>{{'SIGN_IN.VIA_LOCAL_DB' | translate }}</span>
|
||||
</button>
|
||||
</a>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="(!isOidcLoginMode && steps === 1) || (isOidcLoginMode && steps ===2)">
|
||||
<clr-input-container class="mt-3">
|
||||
<input clrInput class="username" type="text" required [(ngModel)]="signInCredential.principal"
|
||||
name="login_username" id="login_username"
|
||||
placeholder='{{"PLACEHOLDER.SIGN_IN_NAME" | translate}}' #userNameInput='ngModel'>
|
||||
<clr-control-error>{{ 'TOOLTIP.SIGN_IN_USERNAME' | translate }}</clr-control-error>
|
||||
</clr-input-container>
|
||||
<div class="clr-form-control">
|
||||
<div class="clr-control-container"
|
||||
[class.clr-error]="passwordInput.invalid && (passwordInput.dirty || passwordInput.touched)">
|
||||
<div class="clr-input-wrapper">
|
||||
<input class="clr-input pwd-input" [type]="showPwd?'text':'password'" required
|
||||
[(ngModel)]="signInCredential.password" name="login_password" id="login_password"
|
||||
placeholder='{{"PLACEHOLDER.SIGN_IN_PWD" | translate}}' #passwordInput="ngModel">
|
||||
<clr-icon *ngIf="!showPwd" shape="eye" class="pw-eye"
|
||||
(click)="showPwd =!showPwd"></clr-icon>
|
||||
<clr-icon *ngIf="showPwd" shape="eye-hide" class="pw-eye"
|
||||
(click)="showPwd =!showPwd"></clr-icon>
|
||||
<clr-icon class="clr-validate-icon" shape="exclamation-circle"></clr-icon>
|
||||
</div>
|
||||
<clr-control-error
|
||||
*ngIf="passwordInput.invalid && (passwordInput.dirty || passwordInput.touched)">
|
||||
{{ 'TOOLTIP.SIGN_IN_PWD' | translate }}
|
||||
</clr-control-error>
|
||||
</div>
|
||||
</div>
|
||||
<div class="display-flex">
|
||||
<clr-checkbox-wrapper>
|
||||
<input clrCheckbox type="checkbox" id="rememberme" #rememberMeBox
|
||||
(click)="clickRememberMe($event)" [checked]="rememberMe">
|
||||
<label class="reset-label" for="rememberme">{{ 'SIGN_IN.REMEMBER' | translate }}</label>
|
||||
</clr-checkbox-wrapper>
|
||||
</div>
|
||||
<div *ngIf="isError" class="error active">
|
||||
<span *ngIf="isCoreServiceAvailable">{{ 'SIGN_IN.INVALID_MSG' | translate }}</span>
|
||||
<span *ngIf="!isCoreServiceAvailable">{{ 'SIGN_IN.CORE_SERVICE_NOT_AVAILABLE' | translate }}</span>
|
||||
</div>
|
||||
<button [disabled]="isOnGoing || !isValid" type="submit" class="btn btn-primary mt-2" (click)="signIn()"
|
||||
id="log_in">{{ 'BUTTON.LOG_IN' | translate }}</button>
|
||||
<a href="javascript:void(0)" class="signup" (click)="signUp()"
|
||||
*ngIf="selfSignUp">{{ 'BUTTON.SIGN_UP_LINK' | translate }}</a>
|
||||
</ng-container>
|
||||
<div class="more-info" [ngClass]="{'pt-1': (!isOidcLoginMode && steps === 1) || (isOidcLoginMode && steps ===2)}">
|
||||
<a href="https://github.com/goharbor/harbor" target="_blank">{{ 'BUTTON.MORE_INFO' | translate }}</a>
|
||||
</div>
|
||||
<div class="display-flex">
|
||||
<clr-checkbox-wrapper>
|
||||
<input clrCheckbox type="checkbox" id="rememberme" #rememberMeBox (click)="clickRememberMe($event)" [checked]="rememberMe">
|
||||
<label class="reset-label" for="rememberme">{{ 'SIGN_IN.REMEMBER' | translate }}</label>
|
||||
</clr-checkbox-wrapper>
|
||||
</div>
|
||||
<div [class.visibility-hidden]="!isError" class="error active">
|
||||
<span *ngIf="isCoreServiceAvailable">{{ 'SIGN_IN.INVALID_MSG' | translate }}</span>
|
||||
<span *ngIf="!isCoreServiceAvailable">{{ 'SIGN_IN.CORE_SERVICE_NOT_AVAILABLE' | translate }}</span>
|
||||
</div>
|
||||
<button [disabled]="isOnGoing || !isValid" type="submit" class="btn btn-primary" (click)="signIn()" id="log_in">{{ 'BUTTON.LOG_IN' | translate }}</button>
|
||||
<a href="javascript:void(0)" class="signup" (click)="signUp()" *ngIf="selfSignUp">{{ 'BUTTON.SIGN_UP_LINK' | translate }}</a>
|
||||
</div>
|
||||
<div>
|
||||
<a href="https://github.com/goharbor/harbor" target="_blank" class="more-info-link">{{ 'BUTTON.MORE_INFO' | translate }}</a>
|
||||
</div>
|
||||
</form>
|
||||
<div *ngIf="appConfig.show_popular_repo" id="pop_repo" class="popular-repo-wrapper">
|
||||
<top-repo class="repo-container"></top-repo>
|
||||
</div>
|
||||
</div>
|
||||
</clr-main-container>
|
||||
<sign-up #signupDialog (userCreation)="handleUserCreation($event)"></sign-up>
|
||||
|
@ -14,31 +14,6 @@
|
||||
top: -5px;
|
||||
}
|
||||
|
||||
.popular-repo-wrapper {
|
||||
background-color: white;
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
margin-top: 24px;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
height: auto;
|
||||
position: relative;
|
||||
border-left: 1px solid #eee;
|
||||
}
|
||||
|
||||
.repo-container {
|
||||
width: 100%;
|
||||
margin-top: -210px;
|
||||
}
|
||||
|
||||
.more-info-link {
|
||||
position: relative;
|
||||
top: 80px;
|
||||
left: 294px;
|
||||
padding-right: 36px;
|
||||
}
|
||||
|
||||
.tm-font {
|
||||
font-size: 14px !important;
|
||||
position: relative;
|
||||
@ -59,10 +34,6 @@
|
||||
background:transparent;
|
||||
}
|
||||
}
|
||||
.login-oidc{
|
||||
margin-top: 50px;
|
||||
}
|
||||
|
||||
.reset-label {
|
||||
font-size:inherit;
|
||||
color:inherit;
|
||||
@ -81,4 +52,42 @@
|
||||
}
|
||||
.pwd-input {
|
||||
padding-right: 26px;
|
||||
}
|
||||
}
|
||||
|
||||
.divider-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.divider {
|
||||
margin: 0;
|
||||
width: 8.5rem;
|
||||
}
|
||||
.login {
|
||||
padding-bottom: 180px;
|
||||
}
|
||||
.back-icon {
|
||||
position: absolute;
|
||||
top: 1rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color: #007cbb;
|
||||
font-size: 16px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.margin-left-3 {
|
||||
margin-left: 3px;
|
||||
}
|
||||
.title {
|
||||
position: absolute;
|
||||
top: 10rem
|
||||
}
|
||||
.login-group {
|
||||
width: 70%;
|
||||
position: absolute;
|
||||
top: 10rem
|
||||
}
|
||||
.more-info {
|
||||
text-align: right;
|
||||
padding-top: 2rem;
|
||||
}
|
||||
|
@ -28,6 +28,7 @@ import { AboutDialogComponent } from "../../shared/components/about-dialog/about
|
||||
import { CommonRoutes, CONFIG_AUTH_MODE } from "../../shared/entities/shared.const";
|
||||
import { SignInCredential } from "./sign-in-credential";
|
||||
import { UserPermissionService } from "../../shared/services";
|
||||
import {finalize} from "rxjs/operators";
|
||||
|
||||
// Define status flags for signing in states
|
||||
export const signInStatusNormal = 0;
|
||||
@ -45,7 +46,6 @@ const expireDays = 10;
|
||||
export class SignInComponent implements AfterViewChecked, OnInit {
|
||||
showPwd: boolean = false;
|
||||
redirectUrl: string = "";
|
||||
appConfig: AppConfig = new AppConfig();
|
||||
// Remeber me indicator
|
||||
rememberMe: boolean = false;
|
||||
rememberedName: string = "";
|
||||
@ -68,6 +68,8 @@ export class SignInComponent implements AfterViewChecked, OnInit {
|
||||
password: ""
|
||||
};
|
||||
isCoreServiceAvailable: boolean = true;
|
||||
steps: number = 1;
|
||||
hasLoadedAppConfig: boolean = false;
|
||||
constructor(
|
||||
private router: Router,
|
||||
private session: SessionService,
|
||||
@ -88,15 +90,6 @@ export class SignInComponent implements AfterViewChecked, OnInit {
|
||||
this.customAppTitle = customSkinObj.loginTitle;
|
||||
}
|
||||
}
|
||||
|
||||
// Before login: Make sure the updated configuration can be loaded
|
||||
this.appConfigService.load()
|
||||
.subscribe(updatedConfig => this.appConfig = updatedConfig
|
||||
, error => {
|
||||
// Catch the error
|
||||
console.error("Failed to load bootstrap options with error: ", error);
|
||||
});
|
||||
|
||||
this.route.queryParams
|
||||
.subscribe(params => {
|
||||
this.redirectUrl = params["redirect_url"] || "";
|
||||
@ -117,7 +110,7 @@ export class SignInComponent implements AfterViewChecked, OnInit {
|
||||
|
||||
// App title
|
||||
public get appTitle(): string {
|
||||
if (this.appConfig && this.appConfig.with_admiral) {
|
||||
if (this.appConfigService.getConfig() && this.appConfigService.getConfig().with_admiral) {
|
||||
return "APP_TITLE.VIC";
|
||||
}
|
||||
|
||||
@ -140,11 +133,11 @@ export class SignInComponent implements AfterViewChecked, OnInit {
|
||||
|
||||
// Whether show the 'sign up' link
|
||||
public get selfSignUp(): boolean {
|
||||
return this.appConfig.auth_mode === CONFIG_AUTH_MODE.DB_AUTH
|
||||
&& this.appConfig.self_registration;
|
||||
return this.appConfigService.getConfig() && this.appConfigService.getConfig().auth_mode === CONFIG_AUTH_MODE.DB_AUTH
|
||||
&& this.appConfigService.getConfig().self_registration;
|
||||
}
|
||||
public get isOidcLoginMode(): boolean {
|
||||
return this.appConfig.auth_mode === CONFIG_AUTH_MODE.OIDC_AUTH;
|
||||
return this.appConfigService.getConfig() && this.appConfigService.getConfig().auth_mode === CONFIG_AUTH_MODE.OIDC_AUTH;
|
||||
}
|
||||
clickRememberMe($event: any): void {
|
||||
if ($event && $event.target) {
|
||||
@ -261,11 +254,7 @@ export class SignInComponent implements AfterViewChecked, OnInit {
|
||||
|
||||
// after login successfully: Make sure the updated configuration can be loaded
|
||||
this.appConfigService.load()
|
||||
.subscribe(updatedConfig => this.appConfig = updatedConfig
|
||||
, error => {
|
||||
// Catch the error
|
||||
console.error("Failed to load bootstrap options with error: ", error);
|
||||
});
|
||||
.subscribe();
|
||||
}, error => {
|
||||
// 403 oidc login no body;
|
||||
if (this.isOidcLoginMode && error && error.status === 403) {
|
||||
|
@ -14,6 +14,8 @@
|
||||
"INVALID_MSG": "Falscher Nutzername oder Passwort.",
|
||||
"FORGOT_PWD": "Passwort vergessen",
|
||||
"HEADER_LINK": "Einloggen",
|
||||
"OR": "OR",
|
||||
"VIA_LOCAL_DB": "LOGIN VIA LOCAL DB",
|
||||
"CORE_SERVICE_NOT_AVAILABLE": "Core Service ist nicht verfügbar"
|
||||
},
|
||||
"SIGN_UP": {
|
||||
|
@ -14,7 +14,9 @@
|
||||
"INVALID_MSG": "Invalid user name or password.",
|
||||
"FORGOT_PWD": "Forgot password",
|
||||
"HEADER_LINK": "Sign In",
|
||||
"CORE_SERVICE_NOT_AVAILABLE": "Core service is not available."
|
||||
"CORE_SERVICE_NOT_AVAILABLE": "Core service is not available.",
|
||||
"OR": "OR",
|
||||
"VIA_LOCAL_DB": "LOGIN VIA LOCAL DB"
|
||||
},
|
||||
"SIGN_UP": {
|
||||
"TITLE": "Sign Up"
|
||||
|
@ -14,7 +14,9 @@
|
||||
"INVALID_MSG": "Nombre o contraseña no válidos.",
|
||||
"FORGOT_PWD": "Olvidé mi contraseña",
|
||||
"HEADER_LINK": "Identificarse",
|
||||
"CORE_SERVICE_NOT_AVAILABLE": "Core service is not available."
|
||||
"CORE_SERVICE_NOT_AVAILABLE": "Core service is not available.",
|
||||
"OR": "OR",
|
||||
"VIA_LOCAL_DB": "LOGIN VIA LOCAL DB"
|
||||
},
|
||||
"SIGN_UP": {
|
||||
"TITLE": "Registrarse"
|
||||
|
@ -14,7 +14,9 @@
|
||||
"INVALID_MSG": "Nom d'utilisateur ou mot de passe invalide.",
|
||||
"FORGOT_PWD": "Mot de passe oublié",
|
||||
"HEADER_LINK": "S'identifier",
|
||||
"CORE_SERVICE_NOT_AVAILABLE": "Core service is not available."
|
||||
"CORE_SERVICE_NOT_AVAILABLE": "Core service is not available.",
|
||||
"OR": "OR",
|
||||
"VIA_LOCAL_DB": "LOGIN VIA LOCAL DB"
|
||||
},
|
||||
"SIGN_UP": {
|
||||
"TITLE": "S'inscrire"
|
||||
|
@ -14,7 +14,9 @@
|
||||
"INVALID_MSG": "Usuário ou senha inválidos",
|
||||
"FORGOT_PWD": "Esqueci a senha",
|
||||
"HEADER_LINK": "Logar-se",
|
||||
"CORE_SERVICE_NOT_AVAILABLE": "Core service is not available."
|
||||
"CORE_SERVICE_NOT_AVAILABLE": "Core service is not available.",
|
||||
"OR": "OR",
|
||||
"VIA_LOCAL_DB": "LOGIN VIA LOCAL DB"
|
||||
},
|
||||
"SIGN_UP": {
|
||||
"TITLE": "Registrar-se"
|
||||
|
@ -14,7 +14,9 @@
|
||||
"INVALID_MSG": "Geçersiz kullanıcı adı veya şifre.",
|
||||
"FORGOT_PWD": "Parolamı Unuttum",
|
||||
"HEADER_LINK": "Oturum aç",
|
||||
"CORE_SERVICE_NOT_AVAILABLE": "Core service is not available."
|
||||
"CORE_SERVICE_NOT_AVAILABLE": "Core service is not available.",
|
||||
"OR": "OR",
|
||||
"VIA_LOCAL_DB": "LOGIN VIA LOCAL DB"
|
||||
},
|
||||
"SIGN_UP": {
|
||||
"TITLE": "Kayıt ol"
|
||||
|
@ -14,7 +14,9 @@
|
||||
"INVALID_MSG": "用户名或者密码不正确。",
|
||||
"FORGOT_PWD": "忘记密码",
|
||||
"HEADER_LINK": "登录",
|
||||
"CORE_SERVICE_NOT_AVAILABLE": "核心服务不可用。"
|
||||
"CORE_SERVICE_NOT_AVAILABLE": "核心服务不可用。",
|
||||
"OR": "或",
|
||||
"VIA_LOCAL_DB": "通过本地数据库登录"
|
||||
},
|
||||
"SIGN_UP": {
|
||||
"TITLE": "注册"
|
||||
|
@ -14,7 +14,9 @@
|
||||
"INVALID_MSG": "用戶名或者密碼不正確。",
|
||||
"FORGOT_PWD": "忘記密碼",
|
||||
"HEADER_LINK": "登錄",
|
||||
"CORE_SERVICE_NOT_AVAILABLE": "Core service is not available."
|
||||
"CORE_SERVICE_NOT_AVAILABLE": "Core service is not available.",
|
||||
"OR": "OR",
|
||||
"VIA_LOCAL_DB": "LOGIN VIA LOCAL DB"
|
||||
},
|
||||
"SIGN_UP": {
|
||||
"TITLE": "註冊"
|
||||
|
Loading…
Reference in New Issue
Block a user