mirror of
https://github.com/goharbor/harbor
synced 2025-04-22 18:57:38 +00:00
merge ui_ng
This commit is contained in:
parent
c1d9d68232
commit
da3ebdcdcd
@ -1,20 +0,0 @@
|
||||
# http://editorconfig.org
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
|
||||
[*.md]
|
||||
max_line_length = 0
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
# Indentation override
|
||||
#[lib/**.js]
|
||||
#[{package.json,.travis.yml}]
|
||||
#[**/**.js]
|
9
src/ui_ng/.gitignore
vendored
9
src/ui_ng/.gitignore
vendored
@ -1,9 +0,0 @@
|
||||
coverage/
|
||||
dist/
|
||||
html-report/
|
||||
node_modules/
|
||||
typings/
|
||||
**/*npm-debug.log.*
|
||||
**/*yarn-error.log.*
|
||||
.idea/
|
||||
.DS_Store
|
@ -1,10 +0,0 @@
|
||||
language: node_js
|
||||
node_js:
|
||||
- "6.9"
|
||||
|
||||
addons:
|
||||
apt:
|
||||
sources:
|
||||
- ubuntu-toolchain-r-test
|
||||
packages:
|
||||
- g++-4.8
|
@ -38,14 +38,7 @@
|
||||
</div>
|
||||
</section>
|
||||
</form>
|
||||
<div style="height: 30px;"></div>
|
||||
<clr-alert [clrAlertType]="'alert-danger'" [clrAlertClosable]="true" [(clrAlertClosed)]="alertClose">
|
||||
<div class="alert-item">
|
||||
<span class="alert-text">
|
||||
{{errorMessage}}
|
||||
</span>
|
||||
</div>
|
||||
</clr-alert>
|
||||
<inline-alert (confirmEvt)="confirmCancel($event)"></inline-alert>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<span class="spinner spinner-inline" style="top:8px;" [hidden]="showProgress === false"></span>
|
||||
|
@ -3,6 +3,10 @@ import { NgForm } from '@angular/forms';
|
||||
|
||||
import { SessionUser } from '../../shared/session-user';
|
||||
import { SessionService } from '../../shared/session.service';
|
||||
import { MessageService } from '../../global-message/message.service';
|
||||
import { AlertType, httpStatusCode } from '../../shared/shared.const';
|
||||
import { errorHandler, accessErrorHandler } from '../../shared/shared.utils';
|
||||
import { InlineAlertComponent } from '../../shared/inline-alert/inline-alert.component';
|
||||
|
||||
@Component({
|
||||
selector: "account-settings-modal",
|
||||
@ -13,43 +17,52 @@ export class AccountSettingsModalComponent implements OnInit, AfterViewChecked {
|
||||
opened: boolean = false;
|
||||
staticBackdrop: boolean = true;
|
||||
account: SessionUser;
|
||||
error: any;
|
||||
alertClose: boolean = true;
|
||||
error: any = null;
|
||||
originalStaticData: SessionUser;
|
||||
|
||||
private isOnCalling: boolean = false;
|
||||
private formValueChanged: boolean = false;
|
||||
|
||||
accountFormRef: NgForm;
|
||||
@ViewChild("accountSettingsFrom") accountForm: NgForm;
|
||||
@ViewChild(InlineAlertComponent)
|
||||
private inlineAlert: InlineAlertComponent;
|
||||
|
||||
constructor(private session: SessionService) { }
|
||||
constructor(
|
||||
private session: SessionService,
|
||||
private msgService: MessageService) { }
|
||||
|
||||
ngOnInit(): void {
|
||||
//Value copy
|
||||
this.account = Object.assign({}, this.session.getCurrentUser());
|
||||
}
|
||||
|
||||
private isUserDataChange(): boolean {
|
||||
if (!this.originalStaticData || !this.account) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (var prop in this.originalStaticData) {
|
||||
if (this.originalStaticData[prop]) {
|
||||
if (this.account[prop]) {
|
||||
if (this.originalStaticData[prop] != this.account[prop]) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public get isValid(): boolean {
|
||||
return this.accountForm && this.accountForm.valid;
|
||||
return this.accountForm && this.accountForm.valid && this.error === null;
|
||||
}
|
||||
|
||||
public get showProgress(): boolean {
|
||||
return this.isOnCalling;
|
||||
}
|
||||
|
||||
public get errorMessage(): string {
|
||||
if(this.error){
|
||||
if(this.error.message){
|
||||
return this.error.message;
|
||||
}else{
|
||||
if(this.error._body){
|
||||
return this.error._body;
|
||||
}
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
ngAfterViewChecked(): void {
|
||||
if (this.accountFormRef != this.accountForm) {
|
||||
this.accountFormRef = this.accountForm;
|
||||
@ -59,12 +72,15 @@ export class AccountSettingsModalComponent implements OnInit, AfterViewChecked {
|
||||
this.error = null;
|
||||
}
|
||||
this.formValueChanged = true;
|
||||
this.inlineAlert.close();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
open() {
|
||||
//Keep the initial data for future diff
|
||||
this.originalStaticData = Object.assign({}, this.session.getCurrentUser());
|
||||
this.account = Object.assign({}, this.session.getCurrentUser());
|
||||
this.formValueChanged = false;
|
||||
|
||||
@ -72,7 +88,18 @@ export class AccountSettingsModalComponent implements OnInit, AfterViewChecked {
|
||||
}
|
||||
|
||||
close() {
|
||||
this.opened = false;
|
||||
if (this.formValueChanged) {
|
||||
if (!this.isUserDataChange()) {
|
||||
this.opened = false;
|
||||
} else {
|
||||
//Need user confirmation
|
||||
this.inlineAlert.showInlineConfirmation({
|
||||
message: "ALERT.FORM_CHANGE_CONFIRMATION"
|
||||
});
|
||||
}
|
||||
} else {
|
||||
this.opened = false;
|
||||
}
|
||||
}
|
||||
|
||||
submit() {
|
||||
@ -92,12 +119,22 @@ export class AccountSettingsModalComponent implements OnInit, AfterViewChecked {
|
||||
.then(() => {
|
||||
this.isOnCalling = false;
|
||||
this.close();
|
||||
this.msgService.announceMessage(200, "PROFILE.SAVE_SUCCESS", AlertType.SUCCESS);
|
||||
})
|
||||
.catch(error => {
|
||||
this.isOnCalling = false;
|
||||
this.error = error;
|
||||
this.alertClose = false;
|
||||
if(accessErrorHandler(error, this.msgService)){
|
||||
this.opened = false;
|
||||
}else{
|
||||
this.inlineAlert.showInlineError(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
confirmCancel(): void {
|
||||
this.inlineAlert.close();
|
||||
this.opened = false;
|
||||
}
|
||||
|
||||
}
|
@ -6,6 +6,9 @@ import { SignInComponent } from './sign-in/sign-in.component';
|
||||
import { PasswordSettingComponent } from './password/password-setting.component';
|
||||
import { AccountSettingsModalComponent } from './account-settings/account-settings-modal.component';
|
||||
import { SharedModule } from '../shared/shared.module';
|
||||
import { SignUpComponent } from './sign-up/sign-up.component';
|
||||
import { ForgotPasswordComponent } from './password/forgot-password.component';
|
||||
import { ResetPasswordComponent } from './password/reset-password.component';
|
||||
|
||||
import { PasswordSettingService } from './password/password-setting.service';
|
||||
|
||||
@ -15,9 +18,19 @@ import { PasswordSettingService } from './password/password-setting.service';
|
||||
RouterModule,
|
||||
SharedModule
|
||||
],
|
||||
declarations: [SignInComponent, PasswordSettingComponent, AccountSettingsModalComponent],
|
||||
exports: [SignInComponent, PasswordSettingComponent, AccountSettingsModalComponent],
|
||||
|
||||
declarations: [
|
||||
SignInComponent,
|
||||
PasswordSettingComponent,
|
||||
AccountSettingsModalComponent,
|
||||
SignUpComponent,
|
||||
ForgotPasswordComponent,
|
||||
ResetPasswordComponent],
|
||||
exports: [
|
||||
SignInComponent,
|
||||
PasswordSettingComponent,
|
||||
AccountSettingsModalComponent,
|
||||
ResetPasswordComponent],
|
||||
|
||||
providers: [PasswordSettingService]
|
||||
})
|
||||
export class AccountModule { }
|
@ -0,0 +1,32 @@
|
||||
<clr-modal [(clrModalOpen)]="opened" [clrModalStaticBackdrop]="true">
|
||||
<h3 class="modal-title">{{'RESET_PWD.TITLE' | translate}}</h3>
|
||||
<label class="modal-title reset-modal-title-override">{{'RESET_PWD.CAPTION' | translate}}</label>
|
||||
<div class="modal-body" style="overflow-y: hidden;">
|
||||
<form #forgotPasswordFrom="ngForm" class="form">
|
||||
<section class="form-block">
|
||||
<div class="form-group">
|
||||
<label for="reset_pwd_email" class="col-md-4 required">{{'RESET_PWD.EMAIL' | translate}}</label>
|
||||
<label for="reset_pwd_email" aria-haspopup="true" role="tooltip" class="tooltip tooltip-validation tooltip-md tooltip-bottom-left" [class.invalid]="validationState === false">
|
||||
<input name="reset_pwd_email" type="text" #eamilInput="ngModel" [(ngModel)]="email"
|
||||
required
|
||||
pattern='^[a-zA-Z0-9.!#$%&’*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$'
|
||||
id="reset_pwd_email"
|
||||
size="36"
|
||||
(input)="handleValidation(true)"
|
||||
(focusout)="handleValidation(false)">
|
||||
<span class="tooltip-content">
|
||||
{{'TOOLTIP.EMAIL' | translate}}
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
</section>
|
||||
</form>
|
||||
<inline-alert></inline-alert>
|
||||
<div style="height: 30px;"></div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<span class="spinner spinner-inline" style="top:8px;" [hidden]="showProgress === false"></span>
|
||||
<button type="button" class="btn btn-outline" (click)="close()">{{'BUTTON.CANCEL' | translate}}</button>
|
||||
<button type="button" class="btn btn-primary" [disabled]="!isValid || showProgress" (click)="send()">{{'BUTTON.SEND' | translate}}</button>
|
||||
</div>
|
||||
</clr-modal>
|
@ -1,10 +1,78 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { Component, ViewChild } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
import { NgForm } from '@angular/forms';
|
||||
|
||||
import { PasswordSettingService } from './password-setting.service';
|
||||
import { InlineAlertComponent } from '../../shared/inline-alert/inline-alert.component';
|
||||
|
||||
@Component({
|
||||
selector: 'forgot-password',
|
||||
templateUrl: "forgot-password.component.html"
|
||||
templateUrl: "forgot-password.component.html",
|
||||
styleUrls: ['password.component.css']
|
||||
})
|
||||
export class ForgotPasswordComponent {
|
||||
// constructor(private router: Router){}
|
||||
opened: boolean = false;
|
||||
private onGoing: boolean = false;
|
||||
private email: string = "";
|
||||
private validationState: boolean = true;
|
||||
private forceValid: boolean = true;
|
||||
|
||||
@ViewChild("forgotPasswordFrom") forgotPwdForm: NgForm;
|
||||
@ViewChild(InlineAlertComponent)
|
||||
private inlineAlert: InlineAlertComponent;
|
||||
|
||||
constructor(private pwdService: PasswordSettingService) { }
|
||||
|
||||
public get showProgress(): boolean {
|
||||
return this.onGoing;
|
||||
}
|
||||
|
||||
public get isValid(): boolean {
|
||||
return this.forgotPwdForm && this.forgotPwdForm.valid && this.forceValid;
|
||||
}
|
||||
|
||||
public open(): void {
|
||||
this.opened = true;
|
||||
this.validationState = true;
|
||||
this.forceValid = true;
|
||||
this.forgotPwdForm.resetForm();
|
||||
}
|
||||
|
||||
public close(): void {
|
||||
this.opened = false;
|
||||
}
|
||||
|
||||
public send(): void {
|
||||
//Double confirm to avoid improper situations
|
||||
if (!this.email) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.isValid) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.onGoing = true;
|
||||
this.pwdService.sendResetPasswordMail(this.email)
|
||||
.then(response => {
|
||||
this.onGoing = false;
|
||||
this.forceValid = false;//diable the send button
|
||||
this.inlineAlert.showInlineSuccess({
|
||||
message: "RESET_PWD.SUCCESS"
|
||||
});
|
||||
})
|
||||
.catch(error => {
|
||||
this.onGoing = false;
|
||||
this.inlineAlert.showInlineError(error);
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
public handleValidation(flag: boolean): void {
|
||||
if (flag) {
|
||||
this.validationState = true;
|
||||
} else {
|
||||
this.validationState = this.isValid;
|
||||
}
|
||||
}
|
||||
}
|
@ -45,6 +45,7 @@
|
||||
</label>
|
||||
</div>
|
||||
</section>
|
||||
<inline-alert (confirmEvt)="confirmCancel($event)"></inline-alert>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
|
@ -4,6 +4,10 @@ import { NgForm } from '@angular/forms';
|
||||
|
||||
import { PasswordSettingService } from './password-setting.service';
|
||||
import { SessionService } from '../../shared/session.service';
|
||||
import { AlertType, httpStatusCode } from '../../shared/shared.const';
|
||||
import { MessageService } from '../../global-message/message.service';
|
||||
import { errorHandler, isEmptyForm, accessErrorHandler } from '../../shared/shared.utils';
|
||||
import { InlineAlertComponent } from '../../shared/inline-alert/inline-alert.component';
|
||||
|
||||
@Component({
|
||||
selector: 'password-setting',
|
||||
@ -14,19 +18,27 @@ export class PasswordSettingComponent implements AfterViewChecked {
|
||||
oldPwd: string = "";
|
||||
newPwd: string = "";
|
||||
reNewPwd: string = "";
|
||||
error: any = null;
|
||||
|
||||
private formValueChanged: boolean = false;
|
||||
private onCalling: boolean = false;
|
||||
|
||||
pwdFormRef: NgForm;
|
||||
@ViewChild("changepwdForm") pwdForm: NgForm;
|
||||
constructor(private passwordService: PasswordSettingService, private session: SessionService){}
|
||||
@ViewChild(InlineAlertComponent)
|
||||
private inlineAlert: InlineAlertComponent;
|
||||
|
||||
constructor(
|
||||
private passwordService: PasswordSettingService,
|
||||
private session: SessionService,
|
||||
private msgService: MessageService) { }
|
||||
|
||||
//If form is valid
|
||||
public get isValid(): boolean {
|
||||
if (this.pwdForm && this.pwdForm.form.get("newPassword")) {
|
||||
return this.pwdForm.valid &&
|
||||
this.pwdForm.form.get("newPassword").value === this.pwdForm.form.get("reNewPassword").value;
|
||||
(this.pwdForm.form.get("newPassword").value === this.pwdForm.form.get("reNewPassword").value) &&
|
||||
this.error === null;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@ -45,6 +57,8 @@ export class PasswordSettingComponent implements AfterViewChecked {
|
||||
if (this.pwdFormRef) {
|
||||
this.pwdFormRef.valueChanges.subscribe(data => {
|
||||
this.formValueChanged = true;
|
||||
this.error = null;
|
||||
this.inlineAlert.close();
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -54,10 +68,26 @@ export class PasswordSettingComponent implements AfterViewChecked {
|
||||
open(): void {
|
||||
this.opened = true;
|
||||
this.pwdForm.reset();
|
||||
this.formValueChanged = false;
|
||||
}
|
||||
|
||||
//Close the moal dialog
|
||||
close(): void {
|
||||
if (this.formValueChanged) {
|
||||
if (isEmptyForm(this.pwdForm)) {
|
||||
this.opened = false;
|
||||
} else {
|
||||
//Need user confirmation
|
||||
this.inlineAlert.showInlineConfirmation({
|
||||
message: "ALERT.FORM_CHANGE_CONFIRMATION"
|
||||
});
|
||||
}
|
||||
} else {
|
||||
this.opened = false;
|
||||
}
|
||||
}
|
||||
|
||||
confirmCancel(): void {
|
||||
this.opened = false;
|
||||
}
|
||||
|
||||
@ -73,26 +103,31 @@ export class PasswordSettingComponent implements AfterViewChecked {
|
||||
|
||||
//Double confirm session is valid
|
||||
let cUser = this.session.getCurrentUser();
|
||||
if(!cUser){
|
||||
if (!cUser) {
|
||||
return;
|
||||
}
|
||||
|
||||
//Call service
|
||||
this.onCalling = true;
|
||||
|
||||
this.passwordService.changePassword(cUser.user_id,
|
||||
{
|
||||
new_password: this.pwdForm.value.newPassword,
|
||||
old_password: this.pwdForm.value.oldPassword
|
||||
})
|
||||
.then(() => {
|
||||
this.onCalling = false;
|
||||
this.close();
|
||||
})
|
||||
.catch(error => {
|
||||
this.onCalling = false;
|
||||
console.error(error);//TODO:
|
||||
});
|
||||
//TODO:publish the successful message to general messae box
|
||||
this.passwordService.changePassword(cUser.user_id,
|
||||
{
|
||||
new_password: this.pwdForm.value.newPassword,
|
||||
old_password: this.pwdForm.value.oldPassword
|
||||
})
|
||||
.then(() => {
|
||||
this.onCalling = false;
|
||||
this.close();
|
||||
this.msgService.announceMessage(200, "CHANGE_PWD.SAVE_SUCCESS", AlertType.SUCCESS);
|
||||
})
|
||||
.catch(error => {
|
||||
this.onCalling = false;
|
||||
this.error = error;
|
||||
if(accessErrorHandler(error, this.msgService)){
|
||||
this.opened = false;
|
||||
}else{
|
||||
this.inlineAlert.showInlineError(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
@ -5,6 +5,8 @@ import 'rxjs/add/operator/toPromise';
|
||||
import { PasswordSetting } from './password-setting';
|
||||
|
||||
const passwordChangeEndpoint = "/api/users/:user_id/password";
|
||||
const sendEmailEndpoint = "/sendEmail";
|
||||
const resetPasswordEndpoint = "/reset";
|
||||
|
||||
@Injectable()
|
||||
export class PasswordSettingService {
|
||||
@ -19,17 +21,46 @@ export class PasswordSettingService {
|
||||
constructor(private http: Http) { }
|
||||
|
||||
changePassword(userId: number, setting: PasswordSetting): Promise<any> {
|
||||
if(!setting || setting.new_password.trim()==="" || setting.old_password.trim()===""){
|
||||
if (!setting || setting.new_password.trim() === "" || setting.old_password.trim() === "") {
|
||||
return Promise.reject("Invalid data");
|
||||
}
|
||||
|
||||
let putUrl = passwordChangeEndpoint.replace(":user_id", userId+"");
|
||||
let putUrl = passwordChangeEndpoint.replace(":user_id", userId + "");
|
||||
return this.http.put(putUrl, JSON.stringify(setting), this.options)
|
||||
.toPromise()
|
||||
.then(() => null)
|
||||
.catch(error=>{
|
||||
return Promise.reject(error);
|
||||
});
|
||||
.toPromise()
|
||||
.then(() => null)
|
||||
.catch(error => {
|
||||
return Promise.reject(error);
|
||||
});
|
||||
}
|
||||
|
||||
sendResetPasswordMail(email: string): Promise<any> {
|
||||
if (!email) {
|
||||
return Promise.reject("Invalid email");
|
||||
}
|
||||
|
||||
let getUrl = sendEmailEndpoint + "?email=" + email;
|
||||
return this.http.get(getUrl, this.options).toPromise()
|
||||
.then(response => response)
|
||||
.catch(error => {
|
||||
return Promise.reject(error);
|
||||
})
|
||||
}
|
||||
|
||||
resetPassword(uuid: string, newPassword: string): Promise<any> {
|
||||
if (!uuid || !newPassword) {
|
||||
return Promise.reject("Invalid reset uuid or password");
|
||||
}
|
||||
|
||||
return this.http.post(resetPasswordEndpoint, JSON.stringify({
|
||||
"reset_uuid": uuid,
|
||||
"password": newPassword
|
||||
}), this.options)
|
||||
.toPromise()
|
||||
.then(response => response)
|
||||
.catch(error => {
|
||||
return Promise.reject(error);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,3 @@
|
||||
.reset-modal-title-override {
|
||||
font-size: 14px !important;
|
||||
}
|
@ -0,0 +1,51 @@
|
||||
<clr-modal [(clrModalOpen)]="opened" [clrModalStaticBackdrop]="true">
|
||||
<h3 class="modal-title">{{'RESET_PWD.TITLE' | translate}}</h3>
|
||||
<label class="modal-title reset-modal-title-override">{{'RESET_PWD.CAPTION2' | translate}}</label>
|
||||
<div class="modal-body" style="overflow-y: hidden;">
|
||||
<form #resetPwdForm="ngForm" class="form">
|
||||
<section class="form-block">
|
||||
<div class="form-group">
|
||||
<label for="newPassword">{{'CHANGE_PWD.NEW_PWD' | translate}}</label>
|
||||
<label for="newPassword" aria-haspopup="true" role="tooltip" class="tooltip tooltip-validation tooltip-md tooltip-bottom-left" [class.invalid]='getValidationState("newPassword") === false'>
|
||||
<input type="password" id="newPassword" placeholder='{{"PLACEHOLDER.NEW_PWD" | translate}}'
|
||||
required
|
||||
pattern="^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)[a-zA-Z\d]{7,}$"
|
||||
name="newPassword"
|
||||
[(ngModel)]="password"
|
||||
#newPassInput="ngModel"
|
||||
size="25"
|
||||
(input)='handleValidation("newPassword", true)'
|
||||
(focusout)='handleValidation("newPassword", false)'>
|
||||
<span class="tooltip-content">
|
||||
{{'TOOLTIP.PASSWORD' | translate}}
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="reNewPassword">{{'CHANGE_PWD.CONFIRM_PWD' | translate}}</label>
|
||||
<label for="reNewPassword" aria-haspopup="true" role="tooltip" class="tooltip tooltip-validation tooltip-md tooltip-bottom-left" [class.invalid]='getValidationState("reNewPassword") === false'>
|
||||
<input type="password" id="reNewPassword" placeholder='{{"PLACEHOLDER.CONFIRM_PWD" | translate}}'
|
||||
required
|
||||
pattern="^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)[a-zA-Z\d]{7,}$"
|
||||
name="reNewPassword"
|
||||
[(ngModel)]="ngModel"
|
||||
#reNewPassInput
|
||||
size="25"
|
||||
(input)='handleValidation("reNewPassword", true)'
|
||||
(focusout)='handleValidation("reNewPassword", false)'>
|
||||
<span class="tooltip-content">
|
||||
{{'TOOLTIP.CONFIRM_PWD' | translate}}
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
</section>
|
||||
<inline-alert></inline-alert>
|
||||
<div style="height: 30px;"></div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<span class="spinner spinner-inline" style="top:8px;" [hidden]="showProgress === false"></span>
|
||||
<button type="button" class="btn btn-outline" (click)="close()">{{'BUTTON.CANCEL' | translate}}</button>
|
||||
<button type="button" class="btn btn-primary" [disabled]="!isValid || showProgress" (click)="send()">{{'BUTTON.OK' | translate}}</button>
|
||||
</div>
|
||||
</clr-modal>
|
123
src/ui_ng/src/app/account/password/reset-password.component.ts
Normal file
123
src/ui_ng/src/app/account/password/reset-password.component.ts
Normal file
@ -0,0 +1,123 @@
|
||||
import { Component, ViewChild, OnInit } from '@angular/core';
|
||||
import { Router, ActivatedRoute } from '@angular/router';
|
||||
import { NgForm } from '@angular/forms';
|
||||
|
||||
import { PasswordSettingService } from './password-setting.service';
|
||||
import { InlineAlertComponent } from '../../shared/inline-alert/inline-alert.component';
|
||||
import { errorHandler, accessErrorHandler } from '../../shared/shared.utils';
|
||||
import { AlertType } from '../../shared/shared.const';
|
||||
import { MessageService } from '../../global-message/message.service';
|
||||
|
||||
@Component({
|
||||
selector: 'reset-password',
|
||||
templateUrl: "reset-password.component.html",
|
||||
styleUrls: ['password.component.css']
|
||||
})
|
||||
export class ResetPasswordComponent implements OnInit{
|
||||
opened: boolean = true;
|
||||
private onGoing: boolean = false;
|
||||
private password: string = "";
|
||||
private validationState: any = {};
|
||||
private resetUuid: string = "";
|
||||
private resetOk: boolean = false;
|
||||
|
||||
@ViewChild("resetPwdForm") resetPwdForm: NgForm;
|
||||
@ViewChild(InlineAlertComponent)
|
||||
private inlineAlert: InlineAlertComponent;
|
||||
|
||||
constructor(
|
||||
private pwdService: PasswordSettingService,
|
||||
private route: ActivatedRoute,
|
||||
private msgService: MessageService,
|
||||
private router: Router) { }
|
||||
|
||||
ngOnInit(): void {
|
||||
this.route.queryParams.subscribe(params => this.resetUuid = params["reset_uuid"] || "");
|
||||
}
|
||||
|
||||
public get showProgress(): boolean {
|
||||
return this.onGoing;
|
||||
}
|
||||
|
||||
public get isValid(): boolean {
|
||||
return this.resetPwdForm && this.resetPwdForm.valid && this.samePassword();
|
||||
}
|
||||
|
||||
public getValidationState(key: string): boolean {
|
||||
return this.validationState && this.validationState[key];
|
||||
}
|
||||
|
||||
public open(): void {
|
||||
this.resetOk = false;
|
||||
this.opened = true;
|
||||
this.resetPwdForm.resetForm();
|
||||
}
|
||||
|
||||
public close(): void {
|
||||
this.opened = false;
|
||||
}
|
||||
|
||||
public send(): void {
|
||||
//If already reset password ok, navigator to sign-in
|
||||
if(this.resetOk){
|
||||
this.router.navigate(['sign-in']);
|
||||
return;
|
||||
}
|
||||
|
||||
//Double confirm to avoid improper situations
|
||||
if (!this.password) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.isValid) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.onGoing = true;
|
||||
this.pwdService.resetPassword(this.resetUuid, this.password)
|
||||
.then(() => {
|
||||
this.resetOk = true;
|
||||
this.inlineAlert.showInlineSuccess({message:'RESET_PWD.RESET_OK'});
|
||||
})
|
||||
.catch(error => {
|
||||
if(accessErrorHandler(error, this.msgService)){
|
||||
this.close();
|
||||
}else{
|
||||
this.inlineAlert.showInlineError(errorHandler(error));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public handleValidation(key: string, flag: boolean): void {
|
||||
if (flag) {
|
||||
if(!this.validationState[key]){
|
||||
this.validationState[key] = true;
|
||||
}
|
||||
} else {
|
||||
this.validationState[key] = this.getControlValidationState(key)
|
||||
}
|
||||
}
|
||||
|
||||
private getControlValidationState(key: string): boolean {
|
||||
if (this.resetPwdForm) {
|
||||
let control = this.resetPwdForm.controls[key];
|
||||
if (control) {
|
||||
return control.valid;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private samePassword(): boolean {
|
||||
if (this.resetPwdForm) {
|
||||
let control1 = this.resetPwdForm.controls["newPassword"];
|
||||
let control2 = this.resetPwdForm.controls["reNewPassword"];
|
||||
if (control1 && control2) {
|
||||
return control1.value == control2.value;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
@ -4,4 +4,12 @@
|
||||
|
||||
.visibility-hidden {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.forgot-password-link {
|
||||
position: relative;
|
||||
line-height: 36px;
|
||||
font-size: 14px;
|
||||
float: right;
|
||||
top: -5px;
|
||||
}
|
@ -7,30 +7,33 @@
|
||||
<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)">
|
||||
<input class="username" type="text" required
|
||||
[(ngModel)]="signInCredential.principal"
|
||||
name="login_username" id="login_username" placeholder="Username"
|
||||
name="login_username" id="login_username" placeholder='{{"PLACEHOLDER.SIGN_IN_NAME" | translate}}'
|
||||
#userNameInput='ngModel'>
|
||||
<span class="tooltip-content">
|
||||
Username is required!
|
||||
{{ 'TOOLTIP.SIGN_IN_USERNAME' | translate }}
|
||||
</span>
|
||||
</label>
|
||||
<label for="username" aria-haspopup="true" role="tooltip" class="tooltip tooltip-validation tooltip-md tooltip-top-left" [class.invalid]="passwordInput.invalid && (passwordInput.dirty || passwordInput.touched)">
|
||||
<input class="password" type="password" required
|
||||
[(ngModel)]="signInCredential.password"
|
||||
name="login_password" id="login_password" placeholder="Password"
|
||||
name="login_password" id="login_password" placeholder='{{"PLACEHOLDER.SIGN_IN_PWD" | translate}}'
|
||||
#passwordInput="ngModel">
|
||||
<span class="tooltip-content">
|
||||
Password is required!
|
||||
{{ 'TOOLTIP.SIGN_IN_PWD' | translate }}
|
||||
</span>
|
||||
</label>
|
||||
<div class="checkbox">
|
||||
<input type="checkbox" id="rememberme">
|
||||
<label for="rememberme">Remember me</label>
|
||||
<label for="rememberme">{{ 'SIGN_IN.REMEMBER' | translate }}</label>
|
||||
<a href="javascript:void(0)" class="forgot-password-link" (click)="forgotPassword()">Forgot password</a>
|
||||
</div>
|
||||
<div [class.visibility-hidden]="signInStatus != statusError" class="error active">
|
||||
Invalid user name or password
|
||||
<div [class.visibility-hidden]="!isError" class="error active">
|
||||
{{ 'SIGN_IN.INVALID_MSG' | translate }}
|
||||
</div>
|
||||
<button [disabled]="isOnGoing || !isValid" type="submit" class="btn btn-primary" (click)="signIn()">{{ 'LOG_IN' | translate }}</button>
|
||||
<a href="javascript:void(0)" class="signup" (click)="signUp()">{{ 'SIGN_UP' | translate }}</a>
|
||||
<button [disabled]="isOnGoing || !isValid" type="submit" class="btn btn-primary" (click)="signIn()">{{ 'BUTTON.LOG_IN' | translate }}</button>
|
||||
<a href="javascript:void(0)" class="signup" (click)="signUp()">{{ 'BUTTON.SIGN_UP_LINK' | translate }}</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<sign-up #signupDialog></sign-up>>
|
||||
<forgot-password #forgotPwdDialog></forgot-password>
|
@ -1,11 +1,15 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { Router, ActivatedRoute } from '@angular/router';
|
||||
import { Input, ViewChild, AfterViewChecked } from '@angular/core';
|
||||
import { NgForm } from '@angular/forms';
|
||||
|
||||
import { SessionService } from '../../shared/session.service';
|
||||
import { SignInCredential } from '../../shared/sign-in-credential';
|
||||
|
||||
import { SignUpComponent } from '../sign-up/sign-up.component';
|
||||
import { harborRootRoute } from '../../shared/shared.const';
|
||||
import { ForgotPasswordComponent } from '../password/forgot-password.component';
|
||||
|
||||
//Define status flags for signing in states
|
||||
export const signInStatusNormal = 0;
|
||||
export const signInStatusOnGoing = 1;
|
||||
@ -17,13 +21,16 @@ export const signInStatusError = -1;
|
||||
styleUrls: ['sign-in.component.css']
|
||||
})
|
||||
|
||||
export class SignInComponent implements AfterViewChecked {
|
||||
export class SignInComponent implements AfterViewChecked, OnInit {
|
||||
private redirectUrl: string = "";
|
||||
//Form reference
|
||||
signInForm: NgForm;
|
||||
@ViewChild('signInForm') currentForm: NgForm;
|
||||
@ViewChild('signupDialog') signUpDialog: SignUpComponent;
|
||||
@ViewChild('forgotPwdDialog') forgotPwdDialog: ForgotPasswordComponent;
|
||||
|
||||
//Status flag
|
||||
signInStatus: number = 0;
|
||||
signInStatus: number = signInStatusNormal;
|
||||
|
||||
//Initialize sign in credential
|
||||
@Input() signInCredential: SignInCredential = {
|
||||
@ -33,9 +40,17 @@ export class SignInComponent implements AfterViewChecked {
|
||||
|
||||
constructor(
|
||||
private router: Router,
|
||||
private session: SessionService
|
||||
private session: SessionService,
|
||||
private route: ActivatedRoute
|
||||
) { }
|
||||
|
||||
ngOnInit(): void {
|
||||
this.route.queryParams
|
||||
.subscribe(params => {
|
||||
this.redirectUrl = params["redirect_url"] || "";
|
||||
});
|
||||
}
|
||||
|
||||
//For template accessing
|
||||
public get isError(): boolean {
|
||||
return this.signInStatus === signInStatusError;
|
||||
@ -105,25 +120,26 @@ export class SignInComponent implements AfterViewChecked {
|
||||
//Set status
|
||||
this.signInStatus = signInStatusNormal;
|
||||
|
||||
//Validate the sign-in session
|
||||
this.session.retrieveUser()
|
||||
.then(user => {
|
||||
//Routing to the right location
|
||||
let nextRoute = ["/harbor", "projects"];
|
||||
this.router.navigate(nextRoute);
|
||||
})
|
||||
.catch(error => {
|
||||
this.handleError(error);
|
||||
});
|
||||
//Redirect to the right route
|
||||
if (this.redirectUrl === "") {
|
||||
//Routing to the default location
|
||||
this.router.navigateByUrl(harborRootRoute);
|
||||
}else{
|
||||
this.router.navigateByUrl(this.redirectUrl);
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
this.handleError(error);
|
||||
});
|
||||
}
|
||||
|
||||
//Help user navigate to the sign up
|
||||
//Open sign up dialog
|
||||
signUp(): void {
|
||||
let nextRoute = ["/harbor", "signup"];
|
||||
this.router.navigate(nextRoute);
|
||||
this.signUpDialog.open();
|
||||
}
|
||||
|
||||
//Open forgot password dialog
|
||||
forgotPassword(): void {
|
||||
this.forgotPwdDialog.open();
|
||||
}
|
||||
}
|
@ -2,10 +2,9 @@ import { Injectable } from '@angular/core';
|
||||
import { Headers, Http, URLSearchParams } from '@angular/http';
|
||||
import 'rxjs/add/operator/toPromise';
|
||||
|
||||
import { SignInCredential } from './sign-in-credential';
|
||||
import { SignInCredential } from '../../shared/sign-in-credential';
|
||||
|
||||
const url_prefix = '/ng';
|
||||
const signInUrl = url_prefix + '/login';
|
||||
const signInUrl = '/login';
|
||||
/**
|
||||
*
|
||||
* Define a service to provide sign in methods
|
||||
|
@ -1 +1,12 @@
|
||||
<p>Placeholder for signup</p>
|
||||
<clr-modal [(clrModalOpen)]="opened" [clrModalStaticBackdrop]="staticBackdrop" [clrModalClosable]="true">
|
||||
<h3 class="modal-title">{{'SIGN_UP.TITLE' | translate}}</h3>
|
||||
<div class="modal-body" style="overflow-y: hidden;">
|
||||
<new-user-form isSelfRegistration="true" (valueChange)="formValueChange($event)"></new-user-form>
|
||||
<inline-alert (confirmEvt)="confirmCancel($event)"></inline-alert>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<span class="spinner spinner-inline" style="top:8px;" [hidden]="inProgress === false"> </span>
|
||||
<button type="button" class="btn btn-outline" (click)="close()">{{'BUTTON.CANCEL' | translate}}</button>
|
||||
<button type="button" class="btn btn-primary" [disabled]="!isValid || inProgress" (click)="create()">{{ 'BUTTON.SIGN_UP' | translate }}</button>
|
||||
</div>
|
||||
</clr-modal>
|
@ -1,10 +1,108 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
import { Component, Output, ViewChild } from '@angular/core';
|
||||
import { NgForm } from '@angular/forms';
|
||||
|
||||
import { NewUserFormComponent } from '../../shared/new-user-form/new-user-form.component';
|
||||
import { User } from '../../user/user';
|
||||
|
||||
import { SessionService } from '../../shared/session.service';
|
||||
import { UserService } from '../../user/user.service';
|
||||
import { errorHandler } from '../../shared/shared.utils';
|
||||
import { InlineAlertComponent } from '../../shared/inline-alert/inline-alert.component';
|
||||
|
||||
@Component({
|
||||
selector: 'sign-up',
|
||||
templateUrl: "sign-up.component.html"
|
||||
})
|
||||
export class SignUpComponent {
|
||||
// constructor(private router: Router){}
|
||||
opened: boolean = false;
|
||||
staticBackdrop: boolean = true;
|
||||
private error: any;
|
||||
private onGoing: boolean = false;
|
||||
private formValueChanged: boolean = false;
|
||||
|
||||
constructor(
|
||||
private session: SessionService,
|
||||
private userService: UserService) { }
|
||||
|
||||
@ViewChild(NewUserFormComponent)
|
||||
private newUserForm: NewUserFormComponent;
|
||||
|
||||
@ViewChild(InlineAlertComponent)
|
||||
private inlienAlert: InlineAlertComponent;
|
||||
|
||||
private getNewUser(): User {
|
||||
return this.newUserForm.getData();
|
||||
}
|
||||
|
||||
public get inProgress(): boolean {
|
||||
return this.onGoing;
|
||||
}
|
||||
|
||||
public get isValid(): boolean {
|
||||
return this.newUserForm.isValid && this.error == null;
|
||||
}
|
||||
|
||||
formValueChange(flag: boolean): void {
|
||||
if (flag) {
|
||||
this.formValueChanged = true;
|
||||
}
|
||||
if (this.error != null) {
|
||||
this.error = null;//clear error
|
||||
}
|
||||
this.inlienAlert.close();//Close alert if being shown
|
||||
}
|
||||
|
||||
open(): void {
|
||||
this.newUserForm.reset();//Reset form
|
||||
this.formValueChanged = false;
|
||||
this.opened = true;
|
||||
}
|
||||
|
||||
close(): void {
|
||||
if (this.formValueChanged) {
|
||||
if (this.newUserForm.isEmpty()) {
|
||||
this.opened = false;
|
||||
} else {
|
||||
//Need user confirmation
|
||||
this.inlienAlert.showInlineConfirmation({
|
||||
message: "ALERT.FORM_CHANGE_CONFIRMATION"
|
||||
});
|
||||
}
|
||||
} else {
|
||||
this.opened = false;
|
||||
}
|
||||
}
|
||||
|
||||
confirmCancel(): void {
|
||||
this.opened = false;
|
||||
}
|
||||
|
||||
//Create new user
|
||||
create(): void {
|
||||
//Double confirm everything is ok
|
||||
//Form is valid
|
||||
if (!this.isValid) {
|
||||
return;
|
||||
}
|
||||
|
||||
//We have new user data
|
||||
let u = this.getNewUser();
|
||||
if (!u) {
|
||||
return;
|
||||
}
|
||||
|
||||
//Start process
|
||||
this.onGoing = true;
|
||||
|
||||
this.userService.addUser(u)
|
||||
.then(() => {
|
||||
this.onGoing = false;
|
||||
this.close();
|
||||
})
|
||||
.catch(error => {
|
||||
this.onGoing = false;
|
||||
this.error = error;
|
||||
this.inlienAlert.showInlineError(error);
|
||||
});
|
||||
}
|
||||
}
|
@ -24,7 +24,10 @@ export class AppComponent {
|
||||
//Use browser lang
|
||||
langSetting = translate.getBrowserLang();
|
||||
}
|
||||
translate.use(this.isLangMatch(langSetting, supportedLangs) ? langSetting : enLang);
|
||||
|
||||
let selectedLang = this.isLangMatch(langSetting, supportedLangs) ? langSetting : enLang;
|
||||
translate.use(selectedLang);
|
||||
//this.session.switchLanguage(selectedLang).catch(error => console.error(error));
|
||||
}
|
||||
|
||||
private isLangMatch(browserLang: string, supportedLangs: string[]) {
|
||||
|
@ -9,6 +9,7 @@ import { BaseModule } from './base/base.module';
|
||||
import { HarborRoutingModule } from './harbor-routing.module';
|
||||
import { SharedModule } from './shared/shared.module';
|
||||
import { AccountModule } from './account/account.module';
|
||||
import { ConfigurationModule } from './config/config.module';
|
||||
|
||||
import { TranslateModule, TranslateLoader, MissingTranslationHandler } from "@ngx-translate/core";
|
||||
import { MyMissingTranslationHandler } from './i18n/missing-trans.handler';
|
||||
@ -16,7 +17,7 @@ import { TranslateHttpLoader } from '@ngx-translate/http-loader';
|
||||
import { Http } from '@angular/http';
|
||||
|
||||
export function HttpLoaderFactory(http: Http) {
|
||||
return new TranslateHttpLoader(http, 'app/i18n/lang/', '-lang.json');
|
||||
return new TranslateHttpLoader(http, 'ng/i18n/lang/', '-lang.json');
|
||||
}
|
||||
|
||||
@NgModule({
|
||||
@ -28,6 +29,7 @@ export function HttpLoaderFactory(http: Http) {
|
||||
BaseModule,
|
||||
AccountModule,
|
||||
HarborRoutingModule,
|
||||
ConfigurationModule,
|
||||
TranslateModule.forRoot({
|
||||
loader: {
|
||||
provide: TranslateLoader,
|
||||
|
@ -1,22 +0,0 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import {
|
||||
CanActivate, Router,
|
||||
ActivatedRouteSnapshot,
|
||||
RouterStateSnapshot,
|
||||
CanActivateChild
|
||||
} from '@angular/router';
|
||||
import { SessionService } from '../shared/session.service';
|
||||
|
||||
@Injectable()
|
||||
export class AuthGuard implements CanActivate, CanActivateChild {
|
||||
constructor(private authService: SessionService, private router: Router) {}
|
||||
|
||||
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean {
|
||||
console.info("canActivate",route, state);
|
||||
return true;
|
||||
}
|
||||
|
||||
canActivateChild(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean {
|
||||
return this.canActivate(route, state);
|
||||
}
|
||||
}
|
@ -1,24 +0,0 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import {
|
||||
Router, Resolve, ActivatedRouteSnapshot, RouterStateSnapshot
|
||||
} from '@angular/router';
|
||||
|
||||
import { SessionService } from '../shared/session.service';
|
||||
import { SessionUser } from '../shared/session-user';
|
||||
|
||||
@Injectable()
|
||||
export class BaseRoutingResolver implements Resolve<SessionUser> {
|
||||
|
||||
constructor(private session: SessionService, private router: Router) { }
|
||||
|
||||
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Promise<SessionUser> {
|
||||
console.info("resolver....");
|
||||
return this.session.retrieveUser()
|
||||
.then(sessionUser => {
|
||||
return sessionUser;
|
||||
})
|
||||
.catch(error => {
|
||||
console.info("Anonymous user");
|
||||
});
|
||||
}
|
||||
}
|
@ -1,46 +0,0 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { RouterModule, Routes } from '@angular/router';
|
||||
import { HarborShellComponent } from './harbor-shell/harbor-shell.component';
|
||||
|
||||
import { DashboardComponent } from '../dashboard/dashboard.component';
|
||||
import { ProjectComponent } from '../project/project.component';
|
||||
import { UserComponent } from '../user/user.component';
|
||||
|
||||
import { BaseRoutingResolver } from './base-routing-resolver.service';
|
||||
import { AuthGuard } from './auth-guard.service';
|
||||
|
||||
const baseRoutes: Routes = [
|
||||
{
|
||||
path: 'harbor',
|
||||
component: HarborShellComponent,
|
||||
resolve: {
|
||||
rootResolver: BaseRoutingResolver
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: 'dashboard',
|
||||
component: DashboardComponent
|
||||
},
|
||||
{
|
||||
path: 'projects',
|
||||
component: ProjectComponent
|
||||
},
|
||||
{
|
||||
path: 'users',
|
||||
component: UserComponent,
|
||||
canActivate: [AuthGuard]
|
||||
}
|
||||
]
|
||||
}];
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
RouterModule.forChild(baseRoutes)
|
||||
],
|
||||
exports: [RouterModule],
|
||||
|
||||
providers: [BaseRoutingResolver, AuthGuard]
|
||||
})
|
||||
export class BaseRoutingModule {
|
||||
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { SharedModule } from '../shared/shared.module';
|
||||
import { RouterModule } from '@angular/router';
|
||||
|
||||
import { DashboardModule } from '../dashboard/dashboard.module';
|
||||
import { ProjectModule } from '../project/project.module';
|
||||
@ -12,16 +13,14 @@ import { FooterComponent } from './footer/footer.component';
|
||||
import { HarborShellComponent } from './harbor-shell/harbor-shell.component';
|
||||
import { SearchResultComponent } from './global-search/search-result.component';
|
||||
|
||||
import { BaseRoutingModule } from './base-routing.module';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
SharedModule,
|
||||
DashboardModule,
|
||||
ProjectModule,
|
||||
UserModule,
|
||||
BaseRoutingModule,
|
||||
AccountModule
|
||||
AccountModule,
|
||||
RouterModule
|
||||
],
|
||||
declarations: [
|
||||
NavigatorComponent,
|
||||
|
@ -1,12 +1,13 @@
|
||||
.search-overlay {
|
||||
display: block;
|
||||
position: absolute;
|
||||
height: 94%;
|
||||
height: 100%;
|
||||
width: 97%;
|
||||
/*shoud be lesser than 1000 to aoivd override the popup menu*/
|
||||
z-index: 999;
|
||||
box-sizing: border-box;
|
||||
background: #fafafa;
|
||||
top: 0px;
|
||||
}
|
||||
|
||||
.search-header {
|
||||
|
@ -1,23 +1,24 @@
|
||||
<clr-main-container>
|
||||
<global-message [isAppLevel]="true"></global-message>
|
||||
<navigator (showAccountSettingsModal)="openModal($event)" (searchEvt)="doSearch($event)" (showPwdChangeModal)="openModal($event)"></navigator>
|
||||
<global-message [isAppLevel]="false"></global-message>
|
||||
<div class="content-container">
|
||||
<div class="content-area" [class.container-override]="showSearch">
|
||||
<global-message [isAppLevel]="false"></global-message>
|
||||
<!-- Only appear when searching -->
|
||||
<search-result (closeEvt)="searchClose($event)"></search-result>
|
||||
<router-outlet></router-outlet>
|
||||
</div>
|
||||
<nav class="sidenav" [class.side-nav-override]="showSearch">
|
||||
<nav class="sidenav" *ngIf="isUserExisting" [class.side-nav-override]="showSearch" (click)='watchClickEvt()'>
|
||||
<section class="sidenav-content">
|
||||
<a routerLink="/harbor/projects" routerLinkActive="active" class="nav-link">{{'SIDE_NAV.PROJECTS' | translate}}</a>
|
||||
<a routerLink="/harbor/logs" routerLinkActive="active" class="nav-link">{{'SIDE_NAV.LOGS' | translate}}</a>
|
||||
<section class="nav-group collapsible" *ngIf="isSystemAdmin">
|
||||
<input id="tabsystem" type="checkbox">
|
||||
<label for="tabsystem">{{'SIDE_NAV.SYSTEM_MGMT.NAME' | translate}}</label>
|
||||
<ul class="nav-list">
|
||||
<li><a class="nav-link" routerLink="/harbor/users" routerLinkActive="active">{{'SIDE_NAV.SYSTEM_MGMT.USERS' | translate}}</a></li>
|
||||
<li><a class="nav-link">{{'SIDE_NAV.SYSTEM_MGMT.REPLICATIONS' | translate}}</a></li>
|
||||
<li><a class="nav-link">{{'SIDE_NAV.SYSTEM_MGMT.CONFIGS' | translate}}</a></li>
|
||||
<li><a class="nav-link" routerLink="/harbor/replications/endpoints" routerLinkActive="active">{{'SIDE_NAV.SYSTEM_MGMT.REPLICATIONS' | translate}}</a></li>
|
||||
<li><a class="nav-link" routerLink="/harbor/configs" routerLinkActive="active">{{'SIDE_NAV.SYSTEM_MGMT.CONFIGS' | translate}}</a></li>
|
||||
</ul>
|
||||
</section>
|
||||
</section>
|
||||
@ -26,4 +27,5 @@
|
||||
</clr-main-container>
|
||||
<account-settings-modal></account-settings-modal>
|
||||
<password-setting></password-setting>
|
||||
<deletion-dialog></deletion-dialog>
|
||||
<deletion-dialog></deletion-dialog>
|
||||
<about-dialog></about-dialog>
|
@ -3,7 +3,7 @@ import { Router, ActivatedRoute } from '@angular/router';
|
||||
|
||||
import { ModalEvent } from '../modal-event';
|
||||
import { SearchEvent } from '../search-event';
|
||||
import { modalAccountSettings, modalPasswordSetting } from '../modal-events.const';
|
||||
import { modalEvents } from '../modal-events.const';
|
||||
|
||||
import { AccountSettingsModalComponent } from '../../account/account-settings/account-settings-modal.component';
|
||||
import { SearchResultComponent } from '../global-search/search-result.component';
|
||||
@ -11,6 +11,8 @@ import { PasswordSettingComponent } from '../../account/password/password-settin
|
||||
import { NavigatorComponent } from '../navigator/navigator.component';
|
||||
import { SessionService } from '../../shared/session.service';
|
||||
|
||||
import { AboutDialogComponent } from '../../shared/about-dialog/about-dialog.component'
|
||||
|
||||
@Component({
|
||||
selector: 'harbor-shell',
|
||||
templateUrl: 'harbor-shell.component.html',
|
||||
@ -31,6 +33,9 @@ export class HarborShellComponent implements OnInit {
|
||||
@ViewChild(NavigatorComponent)
|
||||
private navigator: NavigatorComponent;
|
||||
|
||||
@ViewChild(AboutDialogComponent)
|
||||
private aboutDialog: AboutDialogComponent;
|
||||
|
||||
//To indicator whwther or not the search results page is displayed
|
||||
//We need to use this property to do some overriding work
|
||||
private isSearchResultsOpened: boolean = false;
|
||||
@ -51,18 +56,26 @@ export class HarborShellComponent implements OnInit {
|
||||
|
||||
public get isSystemAdmin(): boolean {
|
||||
let account = this.session.getCurrentUser();
|
||||
return account != null && account.has_admin_role>0;
|
||||
return account != null && account.has_admin_role > 0;
|
||||
}
|
||||
|
||||
public get isUserExisting(): boolean {
|
||||
let account = this.session.getCurrentUser();
|
||||
return account != null;
|
||||
}
|
||||
|
||||
//Open modal dialog
|
||||
openModal(event: ModalEvent): void {
|
||||
switch (event.modalName) {
|
||||
case modalAccountSettings:
|
||||
case modalEvents.USER_PROFILE:
|
||||
this.accountSettingsModal.open();
|
||||
break;
|
||||
case modalPasswordSetting:
|
||||
case modalEvents.CHANGE_PWD:
|
||||
this.pwdSetting.open();
|
||||
break;
|
||||
case modalEvents.ABOUT:
|
||||
this.aboutDialog.open();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@ -85,4 +98,10 @@ export class HarborShellComponent implements OnInit {
|
||||
this.isSearchResultsOpened = false;
|
||||
}
|
||||
}
|
||||
|
||||
//Close serch result panel if existing
|
||||
watchClickEvt(): void {
|
||||
this.searchResultComponet.close();
|
||||
this.isSearchResultsOpened = false;
|
||||
}
|
||||
}
|
@ -1,5 +1,7 @@
|
||||
import { modalEvents } from './modal-events.const'
|
||||
|
||||
//Define a object to store the modal event
|
||||
export class ModalEvent {
|
||||
modalName: string;
|
||||
modalName: modalEvents;
|
||||
modalFlag: boolean; //true for open, false for close
|
||||
}
|
@ -1,2 +1,3 @@
|
||||
export const modalAccountSettings= "account-settings";
|
||||
export const modalPasswordSetting = "password-setting";
|
||||
export const enum modalEvents {
|
||||
USER_PROFILE, CHANGE_PWD, ABOUT
|
||||
}
|
@ -7,11 +7,6 @@
|
||||
</div>
|
||||
<global-search (searchEvt)="transferSearchEvent($event)"></global-search>
|
||||
<div class="header-actions">
|
||||
<div *ngIf="!isSessionValid">
|
||||
<a href="javascript:void(0)" class="nav-link nav-text sign-in-override" routerLink="/sign-in" routerLinkActive="active">Sign In</a>
|
||||
<span class="custom-divider"></span>
|
||||
<a href="javascript:void(0)" class="nav-link nav-text sign-up-override" routerLink="/sign-up" routerLinkActive="active">Sign Up</a>
|
||||
</div>
|
||||
<clr-dropdown class="dropdown bottom-left">
|
||||
<button class="nav-icon" clrDropdownToggle style="width: 90px;">
|
||||
<clr-icon shape="world" style="left:-8px;"></clr-icon>
|
||||
@ -32,10 +27,11 @@
|
||||
<div class="dropdown-menu">
|
||||
<a href="javascript:void(0)" clrDropdownItem (click)="openAccountSettingsModal()">{{'ACCOUNT_SETTINGS.PROFILE' | translate}}</a>
|
||||
<a href="javascript:void(0)" clrDropdownItem (click)="openChangePwdModal()">{{'ACCOUNT_SETTINGS.CHANGE_PWD' | translate}}</a>
|
||||
<a href="javascript:void(0)" clrDropdownItem>{{'ACCOUNT_SETTINGS.ABOUT' | translate}}</a>
|
||||
<a href="javascript:void(0)" clrDropdownItem (click)="openAboutDialog()">{{'ACCOUNT_SETTINGS.ABOUT' | translate}}</a>
|
||||
<div class="dropdown-divider"></div>
|
||||
<a href="javascript:void(0)" clrDropdownItem (click)="logOut()">{{'ACCOUNT_SETTINGS.LOGOUT' | translate}}</a>
|
||||
</div>
|
||||
</clr-dropdown>
|
||||
<a href="javascript:void(0)" class="nav-link nav-text" (click)="openAboutDialog()" *ngIf="isSessionValid === false">{{'ACCOUNT_SETTINGS.ABOUT' | translate}}</a>
|
||||
</div>
|
||||
</clr-header>
|
@ -4,7 +4,7 @@ import { TranslateService } from '@ngx-translate/core';
|
||||
|
||||
import { ModalEvent } from '../modal-event';
|
||||
import { SearchEvent } from '../search-event';
|
||||
import { modalAccountSettings, modalPasswordSetting } from '../modal-events.const';
|
||||
import { modalEvents } from '../modal-events.const';
|
||||
|
||||
import { SessionUser } from '../../shared/session-user';
|
||||
import { SessionService } from '../../shared/session.service';
|
||||
@ -62,7 +62,7 @@ export class NavigatorComponent implements OnInit {
|
||||
//Open the account setting dialog
|
||||
openAccountSettingsModal(): void {
|
||||
this.showAccountSettingsModal.emit({
|
||||
modalName: modalAccountSettings,
|
||||
modalName: modalEvents.USER_PROFILE,
|
||||
modalFlag: true
|
||||
});
|
||||
}
|
||||
@ -70,7 +70,15 @@ export class NavigatorComponent implements OnInit {
|
||||
//Open change password dialog
|
||||
openChangePwdModal(): void {
|
||||
this.showPwdChangeModal.emit({
|
||||
modalName: modalPasswordSetting,
|
||||
modalName: modalEvents.CHANGE_PWD,
|
||||
modalFlag: true
|
||||
});
|
||||
}
|
||||
|
||||
//Open about dialog
|
||||
openAboutDialog(): void {
|
||||
this.showPwdChangeModal.emit({
|
||||
modalName: modalEvents.ABOUT,
|
||||
modalFlag: true
|
||||
});
|
||||
}
|
||||
@ -100,6 +108,8 @@ export class NavigatorComponent implements OnInit {
|
||||
//TODO:
|
||||
console.error('Language '+lang.trim()+' is not suppoted');
|
||||
}
|
||||
//Try to switch backend lang
|
||||
//this.session.switchLanguage(lang).catch(error => console.error(error));
|
||||
}
|
||||
|
||||
//Handle the home action
|
||||
|
117
src/ui_ng/src/app/config/auth/config-auth.component.html
Normal file
117
src/ui_ng/src/app/config/auth/config-auth.component.html
Normal file
@ -0,0 +1,117 @@
|
||||
<form #authConfigFrom="ngForm" class="form">
|
||||
<section class="form-block">
|
||||
<div class="form-group">
|
||||
<label for="authMode">{{'CONFIG.AUTH_MODE' | translate }}</label>
|
||||
<div class="select">
|
||||
<select id="authMode" name="authMode" [disabled]="disabled(currentConfig.auth_mode)" [(ngModel)]="currentConfig.auth_mode.value">
|
||||
<option>db_auth</option>
|
||||
<option>ldap</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<section class="form-block" *ngIf="showLdap">
|
||||
<div class="form-group">
|
||||
<label for="ldapUrl" class="required">LDAP URL</label>
|
||||
<label for="ldapUrl" aria-haspopup="true" role="tooltip" class="tooltip tooltip-validation tooltip-md tooltip-top-right" [class.invalid]="ldapUrlInput.invalid && (ldapUrlInput.dirty || ldapUrlInput.touched)">
|
||||
<input name="ldapUrl" type="text" #ldapUrlInput="ngModel" [(ngModel)]="currentConfig.ldap_url.value"
|
||||
required
|
||||
id="ldapUrl"
|
||||
size="40"
|
||||
[disabled]="disabled(currentConfig.ldap_url)">
|
||||
<span class="tooltip-content">
|
||||
{{'TOOLTIP.ITEM_REQUIRED' | translate}}
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="ldapSearchDN" class="required">LDAP Search DN</label>
|
||||
<label for="ldapSearchDN" aria-haspopup="true" role="tooltip" class="tooltip tooltip-validation tooltip-md tooltip-top-right" [class.invalid]="ldapSearchDNInput.invalid && (ldapSearchDNInput.dirty || ldapSearchDNInput.touched)">
|
||||
<input name="ldapSearchDN" type="text" #ldapSearchDNInput="ngModel" [(ngModel)]="currentConfig.ldap_search_dn.value"
|
||||
required
|
||||
id="ldapSearchDN"
|
||||
size="40" [disabled]="disabled(currentConfig.ldap_search_dn)">
|
||||
<span class="tooltip-content">
|
||||
{{'TOOLTIP.ITEM_REQUIRED' | translate}}
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="ldapSearchPwd" class="required">LDAP Search Password</label>
|
||||
<label for="ldapSearchPwd" aria-haspopup="true" role="tooltip" class="tooltip tooltip-validation tooltip-md tooltip-top-right" [class.invalid]="ldapSearchPwdInput.invalid && (ldapSearchPwdInput.dirty || ldapSearchPwdInput.touched)">
|
||||
<input name="ldapSearchPwd" type="password" #ldapSearchPwdInput="ngModel" [(ngModel)]="currentConfig.ldap_search_password.value"
|
||||
required
|
||||
id="ldapSearchPwd"
|
||||
size="40" [disabled]="disabled(currentConfig.ldap_search_password)">
|
||||
<span class="tooltip-content">
|
||||
{{'TOOLTIP.ITEM_REQUIRED' | translate}}
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="ldapBaseDN" class="required">LDAP Base DN</label>
|
||||
<label for="ldapBaseDN" aria-haspopup="true" role="tooltip" class="tooltip tooltip-validation tooltip-md tooltip-top-right" [class.invalid]="ldapBaseDNInput.invalid && (ldapBaseDNInput.dirty || ldapBaseDNInput.touched)">
|
||||
<input name="ldapBaseDN" type="text" #ldapBaseDNInput="ngModel" [(ngModel)]="currentConfig.ldap_base_dn.value"
|
||||
required
|
||||
id="ldapBaseDN"
|
||||
size="40" [disabled]="disabled(currentConfig.ldap_base_dn)">
|
||||
<span class="tooltip-content">
|
||||
{{'TOOLTIP.ITEM_REQUIRED' | translate}}
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="ldapFilter">LDAP Filter</label>
|
||||
<label for="ldapFilter" aria-haspopup="true" role="tooltip" class="tooltip tooltip-validation tooltip-md tooltip-top-right">
|
||||
<input name="ldapFilter" type="text" #ldapFilterInput="ngModel" [(ngModel)]="currentConfig.ldap_filter.value"
|
||||
id="ldapFilter"
|
||||
size="40" [disabled]="disabled(currentConfig.ldap_filter)">
|
||||
<span class="tooltip-content">
|
||||
{{'TOOLTIP.ITEM_REQUIRED' | translate}}
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="ldapUid" class="required">LDAP UID</label>
|
||||
<label for="ldapUid" aria-haspopup="true" role="tooltip" class="tooltip tooltip-validation tooltip-md tooltip-top-right" [class.invalid]="ldapUidInput.invalid && (ldapUidInput.dirty || ldapUidInput.touched)">
|
||||
<input name="ldapUid" type="text" #ldapUidInput="ngModel" [(ngModel)]="currentConfig.ldap_uid.value"
|
||||
required
|
||||
id="ldapUid"
|
||||
size="40" [disabled]="disabled(currentConfig.ldap_uid)">
|
||||
<span class="tooltip-content">
|
||||
{{'TOOLTIP.ITEM_REQUIRED' | translate}}
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="ldapScope">lDAP Scope</label>
|
||||
<div class="select">
|
||||
<select id="ldapScope" name="ldapScope" [(ngModel)]="currentConfig.ldap_scope.value" [disabled]="disabled(currentConfig.ldap_scope)">
|
||||
<option value="1">Base</option>
|
||||
<option value="2">OneLevel</option>
|
||||
<option value="3">Subtree</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<section class="form-block">
|
||||
<div class="form-group">
|
||||
<label for="proCreation">{{'CONFIG.PRO_CREATION_RESTRICTION' | translate}}</label>
|
||||
<div class="select">
|
||||
<select id="proCreation" name="proCreation" [(ngModel)]="currentConfig.project_creation_restriction.value" [disabled]="disabled(currentConfig.project_creation_restriction)">
|
||||
<option>everyone</option>
|
||||
<option>adminonly</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="selfReg">{{'CONFIG.SELF_REGISTRATION' | translate}}</label>
|
||||
<clr-checkbox name="selfReg" id="selfReg" [(ngModel)]="currentConfig.self_registration.value" [disabled]="disabled(currentConfig.self_registration)">
|
||||
<a href="javascript:void(0)" role="tooltip" aria-haspopup="true" class="tooltip tooltip-top-right" style="top:-8px;">
|
||||
<clr-icon shape="info-circle" class="is-info" size="24"></clr-icon>
|
||||
<span class="tooltip-content">{{'CONFIG.SELF_REGISTRATION_TOOLTIP' | translate}}</span>
|
||||
</a>
|
||||
</clr-checkbox>
|
||||
</div>
|
||||
</section>
|
||||
</form>
|
33
src/ui_ng/src/app/config/auth/config-auth.component.ts
Normal file
33
src/ui_ng/src/app/config/auth/config-auth.component.ts
Normal file
@ -0,0 +1,33 @@
|
||||
import { Component, Input, ViewChild } from '@angular/core';
|
||||
import { NgForm } from '@angular/forms';
|
||||
import { Subscription } from 'rxjs/Subscription';
|
||||
|
||||
import { Configuration } from '../config';
|
||||
|
||||
@Component({
|
||||
selector: 'config-auth',
|
||||
templateUrl: "config-auth.component.html",
|
||||
styleUrls: ['../config.component.css']
|
||||
})
|
||||
export class ConfigurationAuthComponent {
|
||||
private changeSub: Subscription;
|
||||
@Input("ldapConfig") currentConfig: Configuration = new Configuration();
|
||||
|
||||
@ViewChild("authConfigFrom") authForm: NgForm;
|
||||
|
||||
constructor() { }
|
||||
|
||||
public get showLdap(): boolean {
|
||||
return this.currentConfig &&
|
||||
this.currentConfig.auth_mode &&
|
||||
this.currentConfig.auth_mode.value === 'ldap';
|
||||
}
|
||||
|
||||
private disabled(prop: any): boolean {
|
||||
return !(prop && prop.editable);
|
||||
}
|
||||
|
||||
public isValid(): boolean {
|
||||
return this.authForm && this.authForm.valid;
|
||||
}
|
||||
}
|
0
src/ui_ng/src/app/config/config.component.css
Normal file
0
src/ui_ng/src/app/config/config.component.css
Normal file
54
src/ui_ng/src/app/config/config.component.html
Normal file
54
src/ui_ng/src/app/config/config.component.html
Normal file
@ -0,0 +1,54 @@
|
||||
<h1 style="display: inline-block;">{{'CONFIG.TITLE' | translate }}</h1>
|
||||
<span class="spinner spinner-inline" [hidden]="inProgress === false"></span>
|
||||
<clr-tabs (clrTabsCurrentTabLinkChanged)="tabLinkChanged($event)">
|
||||
<clr-tab-link [clrTabLinkId]="'config-auth'" [clrTabLinkActive]="true">{{'CONFIG.AUTH' | translate }}</clr-tab-link>
|
||||
<clr-tab-link [clrTabLinkId]="'config-replication'">{{'CONFIG.REPLICATION' | translate }}</clr-tab-link>
|
||||
<clr-tab-link [clrTabLinkId]="'config-email'">{{'CONFIG.EMAIL' | translate }}</clr-tab-link>
|
||||
<clr-tab-link [clrTabLinkId]="'config-system'">{{'CONFIG.SYSTEM' | translate }}</clr-tab-link>
|
||||
|
||||
<clr-tab-content [clrTabContentId]="'authentication'" [clrTabContentActive]="true">
|
||||
<config-auth [ldapConfig]="allConfig"></config-auth>
|
||||
</clr-tab-content>
|
||||
<clr-tab-content [clrTabContentId]="'replication'">
|
||||
<form #repoConfigFrom="ngForm" class="form">
|
||||
<section class="form-block">
|
||||
<div class="form-group">
|
||||
<label for="verifyRemoteCert">{{'CONFIG.VERIFY_REMOTE_CERT' | translate }}</label>
|
||||
<clr-checkbox name="verifyRemoteCert" id="verifyRemoteCert" [(ngModel)]="allConfig.verify_remote_cert.value" [disabled]="disabled(allConfig.verify_remote_cert)">
|
||||
<a href="javascript:void(0)" role="tooltip" aria-haspopup="true" class="tooltip tooltip-lg tooltip-top-right" style="top:-8px;">
|
||||
<clr-icon shape="info-circle" class="is-info" size="24"></clr-icon>
|
||||
<span class="tooltip-content">{{'CONFIG.VERIFY_REMOTE_CERT_TOOLTIP' | translate }}</span>
|
||||
</a>
|
||||
</clr-checkbox>
|
||||
</div>
|
||||
</section>
|
||||
</form>
|
||||
</clr-tab-content>
|
||||
<clr-tab-content [clrTabContentId]="'email'">
|
||||
<config-email [mailConfig]="allConfig"></config-email>
|
||||
</clr-tab-content>
|
||||
<clr-tab-content [clrTabContentId]="'system_settings'">
|
||||
<form #systemConfigFrom="ngForm" class="form">
|
||||
<section class="form-block">
|
||||
<div class="form-group">
|
||||
<label for="tokenExpiration" class="required">{{'CONFIG.TOKEN_EXPIRATION' | translate}}</label>
|
||||
<label for="tokenExpiration" aria-haspopup="true" role="tooltip" class="tooltip tooltip-validation tooltip-md tooltip-top-right" [class.invalid]="tokenExpirationInput.invalid && (tokenExpirationInput.dirty || tokenExpirationInput.touched)">
|
||||
<input name="tokenExpiration" type="text" #tokenExpirationInput="ngModel" [(ngModel)]="allConfig.token_expiration.value"
|
||||
required
|
||||
pattern="^[1-9]{1}[\d]*$"
|
||||
id="tokenExpiration"
|
||||
size="40" [disabled]="disabled(allConfig.token_expiration)">
|
||||
<span class="tooltip-content">
|
||||
{{'TOOLTIP.NUMBER_REQUIRED' | translate}}
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
</section>
|
||||
</form>
|
||||
</clr-tab-content>
|
||||
</clr-tabs>
|
||||
<div>
|
||||
<button type="button" class="btn btn-primary" (click)="save()" [disabled]="!isValid() || !hasChanges()">{{'BUTTON.SAVE' | translate}}</button>
|
||||
<button type="button" class="btn btn-outline" (click)="cancel()" [disabled]="!isValid() || !hasChanges()">{{'BUTTON.CANCEL' | translate}}</button>
|
||||
<button type="button" class="btn btn-outline" (click)="testMailServer()" *ngIf="showTestServerBtn" [disabled]="!isMailConfigValid()">{{'BUTTON.TEST_MAIL' | translate}}</button>
|
||||
</div>
|
267
src/ui_ng/src/app/config/config.component.ts
Normal file
267
src/ui_ng/src/app/config/config.component.ts
Normal file
@ -0,0 +1,267 @@
|
||||
import { Component, OnInit, OnDestroy, ViewChild } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
import { NgForm } from '@angular/forms';
|
||||
|
||||
import { ConfigurationService } from './config.service';
|
||||
import { Configuration } from './config';
|
||||
import { MessageService } from '../global-message/message.service';
|
||||
import { AlertType, DeletionTargets } from '../shared/shared.const';
|
||||
import { errorHandler, accessErrorHandler } from '../shared/shared.utils';
|
||||
import { StringValueItem } from './config';
|
||||
import { DeletionDialogService } from '../shared/deletion-dialog/deletion-dialog.service';
|
||||
import { Subscription } from 'rxjs/Subscription';
|
||||
import { DeletionMessage } from '../shared/deletion-dialog/deletion-message'
|
||||
|
||||
import { ConfigurationAuthComponent } from './auth/config-auth.component';
|
||||
import { ConfigurationEmailComponent } from './email/config-email.component';
|
||||
|
||||
const fakePass = "fakepassword";
|
||||
|
||||
@Component({
|
||||
selector: 'config',
|
||||
templateUrl: "config.component.html",
|
||||
styleUrls: ['config.component.css']
|
||||
})
|
||||
export class ConfigurationComponent implements OnInit, OnDestroy {
|
||||
private onGoing: boolean = false;
|
||||
allConfig: Configuration = new Configuration();
|
||||
private currentTabId: string = "";
|
||||
private originalCopy: Configuration;
|
||||
private confirmSub: Subscription;
|
||||
|
||||
@ViewChild("repoConfigFrom") repoConfigForm: NgForm;
|
||||
@ViewChild("systemConfigFrom") systemConfigForm: NgForm;
|
||||
@ViewChild(ConfigurationEmailComponent) mailConfig: ConfigurationEmailComponent;
|
||||
@ViewChild(ConfigurationAuthComponent) authConfig: ConfigurationAuthComponent;
|
||||
|
||||
constructor(
|
||||
private configService: ConfigurationService,
|
||||
private msgService: MessageService,
|
||||
private confirmService: DeletionDialogService) { }
|
||||
|
||||
ngOnInit(): void {
|
||||
//First load
|
||||
this.retrieveConfig();
|
||||
|
||||
this.confirmSub = this.confirmService.deletionConfirm$.subscribe(confirmation => {
|
||||
this.reset(confirmation.data);
|
||||
});
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
if (this.confirmSub) {
|
||||
this.confirmSub.unsubscribe();
|
||||
}
|
||||
}
|
||||
|
||||
public get inProgress(): boolean {
|
||||
return this.onGoing;
|
||||
}
|
||||
|
||||
public isValid(): boolean {
|
||||
return this.repoConfigForm &&
|
||||
this.repoConfigForm.valid &&
|
||||
this.systemConfigForm &&
|
||||
this.systemConfigForm.valid &&
|
||||
this.mailConfig &&
|
||||
this.mailConfig.isValid() &&
|
||||
this.authConfig &&
|
||||
this.authConfig.isValid();
|
||||
}
|
||||
|
||||
public hasChanges(): boolean {
|
||||
return !this.isEmpty(this.getChanges());
|
||||
}
|
||||
|
||||
public isMailConfigValid(): boolean {
|
||||
return this.mailConfig &&
|
||||
this.mailConfig.isValid();
|
||||
}
|
||||
|
||||
public get showTestServerBtn(): boolean {
|
||||
return this.currentTabId === 'config-email';
|
||||
}
|
||||
|
||||
public tabLinkChanged(tabLink: any) {
|
||||
this.currentTabId = tabLink.id;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Save the changed values
|
||||
*
|
||||
* @memberOf ConfigurationComponent
|
||||
*/
|
||||
public save(): void {
|
||||
let changes = this.getChanges();
|
||||
if (!this.isEmpty(changes)) {
|
||||
this.onGoing = true;
|
||||
this.configService.saveConfiguration(changes)
|
||||
.then(response => {
|
||||
this.onGoing = false;
|
||||
//API should return the updated configurations here
|
||||
//Unfortunately API does not do that
|
||||
//To refresh the view, we can clone the original data copy
|
||||
//or force refresh by calling service.
|
||||
//HERE we choose force way
|
||||
this.retrieveConfig();
|
||||
this.msgService.announceMessage(response.status, "CONFIG.SAVE_SUCCESS", AlertType.SUCCESS);
|
||||
})
|
||||
.catch(error => {
|
||||
this.onGoing = false;
|
||||
if (!accessErrorHandler(error, this.msgService)) {
|
||||
this.msgService.announceMessage(error.status, errorHandler(error), AlertType.DANGER);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
//Inprop situation, should not come here
|
||||
console.error("Save obort becasue nothing changed");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Discard current changes if have and reset
|
||||
*
|
||||
* @memberOf ConfigurationComponent
|
||||
*/
|
||||
public cancel(): void {
|
||||
let changes = this.getChanges();
|
||||
if (!this.isEmpty(changes)) {
|
||||
let msg = new DeletionMessage(
|
||||
"CONFIG.CONFIRM_TITLE",
|
||||
"CONFIG.CONFIRM_SUMMARY",
|
||||
"",
|
||||
changes,
|
||||
DeletionTargets.EMPTY
|
||||
);
|
||||
this.confirmService.openComfirmDialog(msg);
|
||||
} else {
|
||||
//Inprop situation, should not come here
|
||||
console.error("Nothing changed");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Test the connection of specified mail server
|
||||
*
|
||||
*
|
||||
* @memberOf ConfigurationComponent
|
||||
*/
|
||||
public testMailServer(): void {
|
||||
|
||||
}
|
||||
|
||||
private retrieveConfig(): void {
|
||||
this.onGoing = true;
|
||||
this.configService.getConfiguration()
|
||||
.then(configurations => {
|
||||
this.onGoing = false;
|
||||
|
||||
//Add two password fields
|
||||
configurations.email_password = new StringValueItem(fakePass, true);
|
||||
configurations.ldap_search_password = new StringValueItem(fakePass, true);
|
||||
this.allConfig = configurations;
|
||||
|
||||
//Keep the original copy of the data
|
||||
this.originalCopy = this.clone(configurations);
|
||||
})
|
||||
.catch(error => {
|
||||
this.onGoing = false;
|
||||
if (!accessErrorHandler(error, this.msgService)) {
|
||||
this.msgService.announceMessage(error.status, errorHandler(error), AlertType.DANGER);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Get the changed fields and return a map
|
||||
*
|
||||
* @private
|
||||
* @returns {*}
|
||||
*
|
||||
* @memberOf ConfigurationComponent
|
||||
*/
|
||||
private getChanges(): any {
|
||||
let changes = {};
|
||||
if (!this.allConfig || !this.originalCopy) {
|
||||
return changes;
|
||||
}
|
||||
|
||||
for (let prop in this.allConfig) {
|
||||
let field = this.originalCopy[prop];
|
||||
if (field && field.editable) {
|
||||
if (field.value != this.allConfig[prop].value) {
|
||||
changes[prop] = this.allConfig[prop].value;
|
||||
//Fix boolean issue
|
||||
if (typeof field.value === "boolean") {
|
||||
changes[prop] = changes[prop] ? "1" : "0";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return changes;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Deep clone the configuration object
|
||||
*
|
||||
* @private
|
||||
* @param {Configuration} src
|
||||
* @returns {Configuration}
|
||||
*
|
||||
* @memberOf ConfigurationComponent
|
||||
*/
|
||||
private clone(src: Configuration): Configuration {
|
||||
let dest = new Configuration();
|
||||
if (!src) {
|
||||
return dest;//Empty
|
||||
}
|
||||
|
||||
for (let prop in src) {
|
||||
if (src[prop]) {
|
||||
dest[prop] = Object.assign({}, src[prop]); //Deep copy inner object
|
||||
}
|
||||
}
|
||||
|
||||
return dest;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Reset the configuration form
|
||||
*
|
||||
* @private
|
||||
* @param {*} changes
|
||||
*
|
||||
* @memberOf ConfigurationComponent
|
||||
*/
|
||||
private reset(changes: any): void {
|
||||
if (!this.isEmpty(changes)) {
|
||||
for (let prop in changes) {
|
||||
if (this.originalCopy[prop]) {
|
||||
this.allConfig[prop] = Object.assign({}, this.originalCopy[prop]);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
//force reset
|
||||
this.retrieveConfig();
|
||||
}
|
||||
}
|
||||
|
||||
private isEmpty(obj) {
|
||||
for (let key in obj) {
|
||||
if (obj.hasOwnProperty(key))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private disabled(prop: any): boolean {
|
||||
return !(prop && prop.editable);
|
||||
}
|
||||
}
|
22
src/ui_ng/src/app/config/config.module.ts
Normal file
22
src/ui_ng/src/app/config/config.module.ts
Normal file
@ -0,0 +1,22 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { CoreModule } from '../core/core.module';
|
||||
import { SharedModule } from '../shared/shared.module';
|
||||
|
||||
import { ConfigurationComponent } from './config.component';
|
||||
import { ConfigurationService } from './config.service';
|
||||
import { ConfigurationAuthComponent } from './auth/config-auth.component';
|
||||
import { ConfigurationEmailComponent } from './email/config-email.component';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
CoreModule,
|
||||
SharedModule
|
||||
],
|
||||
declarations: [
|
||||
ConfigurationComponent,
|
||||
ConfigurationAuthComponent,
|
||||
ConfigurationEmailComponent],
|
||||
exports: [ConfigurationComponent],
|
||||
providers: [ConfigurationService]
|
||||
})
|
||||
export class ConfigurationModule { }
|
33
src/ui_ng/src/app/config/config.service.ts
Normal file
33
src/ui_ng/src/app/config/config.service.ts
Normal file
@ -0,0 +1,33 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Headers, Http, RequestOptions } from '@angular/http';
|
||||
import 'rxjs/add/operator/toPromise';
|
||||
|
||||
import { Configuration } from './config';
|
||||
|
||||
const configEndpoint = "/api/configurations";
|
||||
|
||||
@Injectable()
|
||||
export class ConfigurationService {
|
||||
private headers: Headers = new Headers({
|
||||
"Accept": 'application/json',
|
||||
"Content-Type": 'application/json'
|
||||
});
|
||||
private options: RequestOptions = new RequestOptions({
|
||||
'headers': this.headers
|
||||
});
|
||||
|
||||
constructor(private http: Http) { }
|
||||
|
||||
public getConfiguration(): Promise<Configuration> {
|
||||
return this.http.get(configEndpoint, this.options).toPromise()
|
||||
.then(response => response.json() as Configuration)
|
||||
.catch(error => Promise.reject(error));
|
||||
}
|
||||
|
||||
public saveConfiguration(values: any): Promise<any> {
|
||||
return this.http.put(configEndpoint, JSON.stringify(values), this.options)
|
||||
.toPromise()
|
||||
.then(response => response)
|
||||
.catch(error => Promise.reject(error));
|
||||
}
|
||||
}
|
77
src/ui_ng/src/app/config/config.ts
Normal file
77
src/ui_ng/src/app/config/config.ts
Normal file
@ -0,0 +1,77 @@
|
||||
export class StringValueItem {
|
||||
value: string;
|
||||
editable: boolean;
|
||||
|
||||
public constructor(v: string, e: boolean) {
|
||||
this.value = v;
|
||||
this.editable = e;
|
||||
}
|
||||
}
|
||||
|
||||
export class NumberValueItem {
|
||||
value: number;
|
||||
editable: boolean;
|
||||
|
||||
public constructor(v: number, e: boolean) {
|
||||
this.value = v;
|
||||
this.editable = e;
|
||||
}
|
||||
}
|
||||
|
||||
export class BoolValueItem {
|
||||
value: boolean;
|
||||
editable: boolean;
|
||||
|
||||
public constructor(v: boolean, e: boolean) {
|
||||
this.value = v;
|
||||
this.editable = e;
|
||||
}
|
||||
}
|
||||
|
||||
export class Configuration {
|
||||
auth_mode: StringValueItem;
|
||||
project_creation_restriction: StringValueItem;
|
||||
self_registration: BoolValueItem;
|
||||
ldap_base_dn: StringValueItem;
|
||||
ldap_filter?: StringValueItem;
|
||||
ldap_scope: NumberValueItem;
|
||||
ldap_search_dn?: StringValueItem;
|
||||
ldap_search_password?: StringValueItem;
|
||||
ldap_timeout: NumberValueItem;
|
||||
ldap_uid: StringValueItem;
|
||||
ldap_url: StringValueItem;
|
||||
email_host: StringValueItem;
|
||||
email_identity: StringValueItem;
|
||||
email_from: StringValueItem;
|
||||
email_port: NumberValueItem;
|
||||
email_ssl: BoolValueItem;
|
||||
email_username?: StringValueItem;
|
||||
email_password?: StringValueItem;
|
||||
verify_remote_cert: BoolValueItem;
|
||||
token_expiration: NumberValueItem;
|
||||
cfg_expiration: NumberValueItem;
|
||||
|
||||
public constructor() {
|
||||
this.auth_mode = new StringValueItem("db_auth", true);
|
||||
this.project_creation_restriction = new StringValueItem("everyone", true);
|
||||
this.self_registration = new BoolValueItem(false, true);
|
||||
this.ldap_base_dn = new StringValueItem("", true);
|
||||
this.ldap_filter = new StringValueItem("", true);
|
||||
this.ldap_scope = new NumberValueItem(0, true);
|
||||
this.ldap_search_dn = new StringValueItem("", true);
|
||||
this.ldap_search_password = new StringValueItem("", true);
|
||||
this.ldap_timeout = new NumberValueItem(5, true);
|
||||
this.ldap_uid = new StringValueItem("", true);
|
||||
this.ldap_url = new StringValueItem("", true);
|
||||
this.email_host = new StringValueItem("", true);
|
||||
this.email_identity = new StringValueItem("", true);
|
||||
this.email_from = new StringValueItem("", true);
|
||||
this.email_port = new NumberValueItem(25, true);
|
||||
this.email_ssl = new BoolValueItem(false, true);
|
||||
this.email_username = new StringValueItem("", true);
|
||||
this.email_password = new StringValueItem("", true);
|
||||
this.token_expiration = new NumberValueItem(5, true);
|
||||
this.cfg_expiration = new NumberValueItem(30, true);
|
||||
this.verify_remote_cert = new BoolValueItem(false, true);
|
||||
}
|
||||
}
|
74
src/ui_ng/src/app/config/email/config-email.component.html
Normal file
74
src/ui_ng/src/app/config/email/config-email.component.html
Normal file
@ -0,0 +1,74 @@
|
||||
<form #mailConfigFrom="ngForm" class="form">
|
||||
<section class="form-block">
|
||||
<div class="form-group">
|
||||
<label for="mailServer" class="required">{{'CONFIG.MAIL_SERVER' | translate}}</label>
|
||||
<label for="mailServer" aria-haspopup="true" role="tooltip" class="tooltip tooltip-validation tooltip-md tooltip-top-right" [class.invalid]="mailServerInput.invalid && (mailServerInput.dirty || mailServerInput.touched)">
|
||||
<input name="mailServer" type="text" #mailServerInput="ngModel" [(ngModel)]="currentConfig.email_host.value"
|
||||
required
|
||||
id="mailServer"
|
||||
size="40" [disabled]="disabled(currentConfig.email_host)">
|
||||
<span class="tooltip-content">
|
||||
{{'TOOLTIP.ITEM_REQUIRED' | translate}}
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="emailPort" class="required">{{'CONFIG.MAIL_SERVER_PORT' | translate}}</label>
|
||||
<label for="emailPort" aria-haspopup="true" role="tooltip" class="tooltip tooltip-validation tooltip-md tooltip-top-right" [class.invalid]="emailPortInput.invalid && (emailPortInput.dirty || emailPortInput.touched)">
|
||||
<input name="emailPort" type="text" #emailPortInput="ngModel" [(ngModel)]="currentConfig.email_port.value"
|
||||
required
|
||||
port
|
||||
id="emailPort"
|
||||
size="40" [disabled]="disabled(currentConfig.email_port)">
|
||||
<span class="tooltip-content">
|
||||
{{'TOOLTIP.PORT_REQUIRED' | translate}}
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="emailUsername">{{'CONFIG.MAIL_USERNAME' | translate}}</label>
|
||||
<label for="emailUsername" aria-haspopup="true" role="tooltip" class="tooltip tooltip-validation tooltip-md tooltip-top-right" [class.invalid]="false">
|
||||
<input name="emailUsername" type="text" #emailUsernameInput="ngModel" [(ngModel)]="currentConfig.email_username.value"
|
||||
required
|
||||
id="emailUsername"
|
||||
size="40" [disabled]="disabled(currentConfig.email_username)">
|
||||
<span class="tooltip-content">
|
||||
{{'TOOLTIP.ITEM_REQUIRED' | translate}}
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="emailPassword">{{'CONFIG.MAIL_PASSWORD' | translate}}</label>
|
||||
<label for="emailPassword" aria-haspopup="true" role="tooltip" class="tooltip tooltip-validation tooltip-md tooltip-top-right" [class.invalid]="false">
|
||||
<input name="emailPassword" type="password" #emailPasswordInput="ngModel" [(ngModel)]="currentConfig.email_password.value"
|
||||
required
|
||||
id="emailPassword"
|
||||
size="40" [disabled]="disabled(currentConfig.email_password)">
|
||||
<span class="tooltip-content">
|
||||
{{'TOOLTIP.ITEM_REQUIRED' | translate}}
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="emailFrom" class="required">{{'CONFIG.MAIL_FROM' | translate}}</label>
|
||||
<label for="emailFrom" aria-haspopup="true" role="tooltip" class="tooltip tooltip-validation tooltip-md tooltip-top-right" [class.invalid]="emailFromInput.invalid && (emailFromInput.dirty || emailFromInput.touched)">
|
||||
<input name="emailFrom" type="text" #emailFromInput="ngModel" [(ngModel)]="currentConfig.email_from.value"
|
||||
required
|
||||
id="emailFrom"
|
||||
size="40" [disabled]="disabled(currentConfig.email_from)">
|
||||
<span class="tooltip-content">
|
||||
{{'TOOLTIP.ITEM_REQUIRED' | translate}}
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="selfReg">{{'CONFIG.MAIL_SSL' | translate}}</label>
|
||||
<clr-checkbox name="emailSSL" id="emailSSL" [(ngModel)]="currentConfig.email_ssl.value" [disabled]="disabled(currentConfig.email_ssl)">
|
||||
<a href="javascript:void(0)" role="tooltip" aria-haspopup="true" class="tooltip tooltip-top-right" style="top:-8px;">
|
||||
<clr-icon shape="info-circle" class="is-info" size="24"></clr-icon>
|
||||
<span class="tooltip-content">{{'CONFIG.SSL_TOOLTIP' | translate}}</span>
|
||||
</a>
|
||||
</clr-checkbox>
|
||||
</div>
|
||||
</section>
|
||||
</form>
|
25
src/ui_ng/src/app/config/email/config-email.component.ts
Normal file
25
src/ui_ng/src/app/config/email/config-email.component.ts
Normal file
@ -0,0 +1,25 @@
|
||||
import { Component, Input, ViewChild } from '@angular/core';
|
||||
import { NgForm } from '@angular/forms';
|
||||
|
||||
import { Configuration } from '../config';
|
||||
|
||||
@Component({
|
||||
selector: 'config-email',
|
||||
templateUrl: "config-email.component.html",
|
||||
styleUrls: ['../config.component.css']
|
||||
})
|
||||
export class ConfigurationEmailComponent {
|
||||
@Input("mailConfig") currentConfig: Configuration = new Configuration();
|
||||
|
||||
@ViewChild("mailConfigFrom") mailForm: NgForm;
|
||||
|
||||
constructor() { }
|
||||
|
||||
private disabled(prop: any): boolean {
|
||||
return !(prop && prop.editable);
|
||||
}
|
||||
|
||||
public isValid(): boolean {
|
||||
return this.mailForm && this.mailForm.valid;
|
||||
}
|
||||
}
|
@ -1,8 +1,10 @@
|
||||
<clr-alert [clrAlertType]="globalMessage.type" [clrAlertAppLevel]="isAppLevel" [(clrAlertClosed)]="!globalMessageOpened" (clrAlertClosedChange)="onClose()">
|
||||
<div class="alert-item">
|
||||
<span class="alert-text">
|
||||
{{globalMessage.message}}
|
||||
</span>
|
||||
<a *ngIf="globalMessage.statusCode === 401" [routerLink]="['/sign-in']" style="color: #ffffff;">Sign In</a>
|
||||
</div>
|
||||
<div class="alert-item">
|
||||
<span class="alert-text">
|
||||
{{message}}
|
||||
</span>
|
||||
<div class="alert-actions" *ngIf="needAuth">
|
||||
<button class="btn alert-action" (click)="signIn()">{{ 'BUTTON.LOG_IN' | translate }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</clr-alert>
|
@ -1,43 +1,102 @@
|
||||
import { Component, Input } from '@angular/core';
|
||||
import { Component, Input, OnInit } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
|
||||
import { Message } from './message';
|
||||
import { MessageService } from './message.service';
|
||||
|
||||
import { AlertType, dismissInterval } from '../shared/shared.const';
|
||||
import { AlertType, dismissInterval, httpStatusCode } from '../shared/shared.const';
|
||||
|
||||
@Component({
|
||||
selector: 'global-message',
|
||||
templateUrl: 'message.component.html'
|
||||
})
|
||||
export class MessageComponent {
|
||||
|
||||
export class MessageComponent implements OnInit {
|
||||
|
||||
@Input() isAppLevel: boolean;
|
||||
|
||||
globalMessage: Message = new Message();
|
||||
globalMessageOpened: boolean;
|
||||
|
||||
constructor(messageService: MessageService) {
|
||||
|
||||
messageService.appLevelAnnounced$.subscribe(
|
||||
message=>{
|
||||
this.globalMessageOpened = this.isAppLevel && true;
|
||||
this.globalMessage = message;
|
||||
console.log('received app level message:' + message);
|
||||
}
|
||||
)
|
||||
|
||||
messageService.messageAnnounced$.subscribe(
|
||||
message=>{
|
||||
this.globalMessageOpened = !this.isAppLevel && true;
|
||||
this.globalMessage = message;
|
||||
console.log('received message:' + message);
|
||||
}
|
||||
);
|
||||
|
||||
// Make the message alert bar dismiss after several intervals.
|
||||
setInterval(()=>this.onClose(), dismissInterval);
|
||||
messageText: string = "";
|
||||
|
||||
constructor(
|
||||
private messageService: MessageService,
|
||||
private router: Router,
|
||||
private translate: TranslateService) { }
|
||||
|
||||
ngOnInit(): void {
|
||||
//Only subscribe application level message
|
||||
if (this.isAppLevel) {
|
||||
this.messageService.appLevelAnnounced$.subscribe(
|
||||
message => {
|
||||
this.globalMessageOpened = true;
|
||||
this.globalMessage = message;
|
||||
this.messageText = message.message;
|
||||
|
||||
this.translateMessage(message);
|
||||
}
|
||||
)
|
||||
} else {
|
||||
//Only subscribe general messages
|
||||
this.messageService.messageAnnounced$.subscribe(
|
||||
message => {
|
||||
this.globalMessageOpened = true;
|
||||
this.globalMessage = message;
|
||||
this.messageText = message.message;
|
||||
|
||||
this.translateMessage(message);
|
||||
|
||||
// Make the message alert bar dismiss after several intervals.
|
||||
//Only for this case
|
||||
setInterval(() => this.onClose(), dismissInterval);
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//Translate or refactor the message shown to user
|
||||
translateMessage(msg: Message): void {
|
||||
if (!msg) {
|
||||
return;
|
||||
}
|
||||
|
||||
let key = "";
|
||||
if (!msg.message) {
|
||||
key = "UNKNOWN_ERROR";
|
||||
} else {
|
||||
key = typeof msg.message === "string" ? msg.message.trim() : msg.message;
|
||||
if (key === "") {
|
||||
key = "UNKNOWN_ERROR";
|
||||
}
|
||||
}
|
||||
|
||||
//Override key for HTTP 401 and 403
|
||||
if (this.globalMessage.statusCode === httpStatusCode.Unauthorized) {
|
||||
key = "UNAUTHORIZED_ERROR";
|
||||
}
|
||||
|
||||
if (this.globalMessage.statusCode === httpStatusCode.Forbidden) {
|
||||
key = "FORBIDDEN_ERROR";
|
||||
}
|
||||
|
||||
this.translate.get(key).subscribe((res: string) => this.messageText = res);
|
||||
}
|
||||
|
||||
public get needAuth(): boolean {
|
||||
return this.globalMessage ?
|
||||
(this.globalMessage.statusCode === httpStatusCode.Unauthorized) ||
|
||||
(this.globalMessage.statusCode === httpStatusCode.Forbidden) : false;
|
||||
}
|
||||
|
||||
//Show message text
|
||||
public get message(): string {
|
||||
return this.messageText;
|
||||
}
|
||||
|
||||
signIn(): void {
|
||||
this.router.navigate(['sign-in']);
|
||||
}
|
||||
|
||||
onClose() {
|
||||
this.globalMessageOpened = false;
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ export class Message {
|
||||
statusCode: number;
|
||||
message: string;
|
||||
alertType: AlertType;
|
||||
isAppLevel: boolean = false;
|
||||
|
||||
get type(): string {
|
||||
switch (this.alertType) {
|
||||
|
@ -4,16 +4,101 @@ import { RouterModule, Routes } from '@angular/router';
|
||||
|
||||
import { SignInComponent } from './account/sign-in/sign-in.component';
|
||||
import { HarborShellComponent } from './base/harbor-shell/harbor-shell.component';
|
||||
import { ProjectComponent } from './project/project.component';
|
||||
import { UserComponent } from './user/user.component';
|
||||
import { ReplicationManagementComponent } from './replication/replication-management/replication-management.component';
|
||||
|
||||
import { BaseRoutingResolver } from './base/base-routing-resolver.service';
|
||||
import { TotalReplicationComponent } from './replication/total-replication/total-replication.component';
|
||||
import { DestinationComponent } from './replication/destination/destination.component';
|
||||
|
||||
import { ProjectDetailComponent } from './project/project-detail/project-detail.component';
|
||||
|
||||
import { RepositoryComponent } from './repository/repository.component';
|
||||
import { ReplicationComponent } from './replication/replication.component';
|
||||
import { MemberComponent } from './project/member/member.component';
|
||||
import { AuditLogComponent } from './log/audit-log.component';
|
||||
|
||||
import { BaseRoutingResolver } from './shared/route/base-routing-resolver.service';
|
||||
import { ProjectRoutingResolver } from './project/project-routing-resolver.service';
|
||||
import { SystemAdminGuard } from './shared/route/system-admin-activate.service';
|
||||
import { SignUpComponent } from './account/sign-up/sign-up.component';
|
||||
import { ResetPasswordComponent } from './account/password/reset-password.component';
|
||||
import { RecentLogComponent } from './log/recent-log.component';
|
||||
import { ConfigurationComponent } from './config/config.component';
|
||||
import { PageNotFoundComponent } from './shared/not-found/not-found.component'
|
||||
|
||||
const harborRoutes: Routes = [
|
||||
{ path: '', redirectTo: '/harbor', pathMatch: 'full' },
|
||||
{ path: 'sign-in', component: SignInComponent },
|
||||
{ path: 'sign-up', component: SignUpComponent},
|
||||
{ path: 'reset_password', component: ResetPasswordComponent},
|
||||
{
|
||||
path: 'harbor',
|
||||
component: HarborShellComponent
|
||||
component: HarborShellComponent,
|
||||
resolve: {
|
||||
authResolver: BaseRoutingResolver
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: 'projects',
|
||||
component: ProjectComponent
|
||||
},
|
||||
{
|
||||
path: 'logs',
|
||||
component: RecentLogComponent
|
||||
},
|
||||
{
|
||||
path: 'users',
|
||||
component: UserComponent,
|
||||
canActivate: [SystemAdminGuard]
|
||||
},
|
||||
{
|
||||
path: 'replications',
|
||||
component: ReplicationManagementComponent,
|
||||
canActivate: [SystemAdminGuard],
|
||||
children: [
|
||||
{
|
||||
path: 'rules',
|
||||
component: TotalReplicationComponent
|
||||
},
|
||||
{
|
||||
path: 'endpoints',
|
||||
component: DestinationComponent
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: 'projects/:id',
|
||||
component: ProjectDetailComponent,
|
||||
resolve: {
|
||||
projectResolver: ProjectRoutingResolver
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: 'repository',
|
||||
component: RepositoryComponent
|
||||
},
|
||||
{
|
||||
path: 'replication',
|
||||
component: ReplicationComponent
|
||||
},
|
||||
{
|
||||
path: 'member',
|
||||
component: MemberComponent
|
||||
},
|
||||
{
|
||||
path: 'log',
|
||||
component: AuditLogComponent
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: 'configs',
|
||||
component: ConfigurationComponent
|
||||
}
|
||||
]
|
||||
},
|
||||
{ path: '', redirectTo: '/harbor', pathMatch: 'full' },
|
||||
{ path: 'sign-in', component: SignInComponent }
|
||||
{ path: "**", component: PageNotFoundComponent}
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
|
@ -1,133 +0,0 @@
|
||||
{
|
||||
"LOG_IN": "LOG IN",
|
||||
"SIGN_UP": "Sign up for an account",
|
||||
"BUTTON": {
|
||||
"CANCEL": "Cancel",
|
||||
"OK": "Ok",
|
||||
"DELETE": "DELETE"
|
||||
},
|
||||
"TOOLTIP": {
|
||||
"EMAIL": "Email should be a valid email address like name@example.com",
|
||||
"USER_NAME": "Can not contain \"~#$% and max length should be less than 20",
|
||||
"FULL_NAME": "Max length should be less than 20",
|
||||
"COMMENT": "Length of comment should be less than 20",
|
||||
"CURRENT_PWD": "Current password is Required",
|
||||
"PASSWORD": "Password should be at least 7 characters with 1 uppercase, 1 lowercase letter and 1 number",
|
||||
"CONFIRM_PWD": "Password input here should be same with above password"
|
||||
},
|
||||
"PLACEHOLDER": {
|
||||
"CURRENT_PWD": "Enter current password",
|
||||
"NEW_PWD": "Enter new password",
|
||||
"CONFIRM_PWD": "Confirm new password",
|
||||
"USER_NAME": "Enter username",
|
||||
"MAIL": "Enter email address",
|
||||
"FULL_NAME": "Enter full name"
|
||||
},
|
||||
"PROFILE": {
|
||||
"TITLE": "User Profile",
|
||||
"USER_NAME": "Username",
|
||||
"EMAIL": "Email",
|
||||
"FULL_NAME": "Full name",
|
||||
"COMMENT": "Comments",
|
||||
"PASSWORD": "Password"
|
||||
},
|
||||
"CHANGE_PWD": {
|
||||
"TITLE": "Change Password",
|
||||
"CURRENT_PWD": "Current Password",
|
||||
"NEW_PWD": "New Password",
|
||||
"CONFIRM_PWD": "Confirm Password"
|
||||
},
|
||||
"ACCOUNT_SETTINGS": {
|
||||
"PROFILE": "User Profile",
|
||||
"CHANGE_PWD": "Change Password",
|
||||
"ABOUT": "About",
|
||||
"LOGOUT": "Log Out"
|
||||
},
|
||||
"GLOBAL_SEARCH": {
|
||||
"PLACEHOLDER": "Search Harbor..."
|
||||
},
|
||||
"SIDE_NAV": {
|
||||
"PROJECTS": "Projects",
|
||||
"SYSTEM_MGMT": {
|
||||
"NAME": "System Managements",
|
||||
"USERS": "Users",
|
||||
"REPLICATIONS": "Replications",
|
||||
"CONFIGS": "Configurations"
|
||||
}
|
||||
},
|
||||
"USER": {
|
||||
"ADD_ACTION": "USER",
|
||||
"ENABLE_ADMIN_ACTION": "Enable administrator",
|
||||
"DISABLE_ADMIN_ACTION": "Disable administrator",
|
||||
"DEL_ACTION": "Delete",
|
||||
"FILTER_PLACEHOLDER": "Filter users",
|
||||
"COLUMN_NAME": "Name",
|
||||
"COLUMN_ADMIN": "Administrator",
|
||||
"COLUMN_EMAIL": "Email",
|
||||
"COLUMN_REG_NAME": "Registration time",
|
||||
"IS_ADMIN": "Yes",
|
||||
"IS_NOT_ADMIN": "No",
|
||||
"ADD_USER_TITLE": "Add User"
|
||||
},
|
||||
"PROJECT": {
|
||||
"PROJECTS": "Projects",
|
||||
"NAME": "Project Name",
|
||||
"PUBLIC_OR_PRIVATE": "Public/Private",
|
||||
"REPO_COUNT": "Repositories Count",
|
||||
"CREATION_TIME": "Creation Time",
|
||||
"DESCRIPTION": "Description",
|
||||
"PUBLIC": "Public",
|
||||
"PRIVATE": "Private",
|
||||
"MAKE": "Make",
|
||||
"NEW_POLICY": "New Policy",
|
||||
"DELETE": "Delete",
|
||||
"MY_PROJECTS": "My Projects",
|
||||
"PUBLIC_PROJECTS": "Public Projects",
|
||||
"NEW_PROJECT": "New Project",
|
||||
"NAME_ALREADY_EXISTS": "Project name already exists.",
|
||||
"NAME_IS_ILLEGAL": "Project name is illegal.",
|
||||
"UNKNOWN_ERROR": "Unknown error occurred while creating project.",
|
||||
"ITEMS": "item(s)",
|
||||
"DELETE_TITLE": "Delete Project",
|
||||
"DELETE_MESSAGE": "Are you sure to delete the project?",
|
||||
"FILTER_PLACEHOLDER": "Filter Projects"
|
||||
},
|
||||
"PROJECT_DETAIL": {
|
||||
"REPOSITORIES": "Repositories",
|
||||
"REPLICATION": "Replication",
|
||||
"USERS": "Users",
|
||||
"LOGS": "Logs"
|
||||
},
|
||||
"MEMBER": {
|
||||
"NEW_MEMBER": "New Member",
|
||||
"NAME": "Name",
|
||||
"ROLE": "Role",
|
||||
"PROJECT_ADMIN": "Project Admin",
|
||||
"DEVELOPER": "Developer",
|
||||
"GUEST": "Guest",
|
||||
"DELETE": "Delete",
|
||||
"ITEMS": "item(s)",
|
||||
"ACTIONS": "Actions",
|
||||
"USERNAME_DOES_NOT_EXISTS": "Username does not exist.",
|
||||
"USERNAME_ALREADY_EXISTS": "Username already exists.",
|
||||
"UNKNOWN_ERROR": "Unknown error occurred while adding member.",
|
||||
"FILTER_PLACEHOLDER": "Filter Members"
|
||||
},
|
||||
"AUDIT_LOG": {
|
||||
"USERNAME": "Username",
|
||||
"REPOSITORY_NAME": "Repository Name",
|
||||
"TAGS": "Tags",
|
||||
"OPERATION": "Operation",
|
||||
"TIMESTAMP": "Timestamp",
|
||||
"ALL_OPERATIONS": "All Operations",
|
||||
"PULL": "Pull",
|
||||
"PUSH": "Push",
|
||||
"CREATE": "Create",
|
||||
"DELETE": "Delete",
|
||||
"OTHERS": "Others",
|
||||
"ADVANCED": "Advanced",
|
||||
"SIMPLE": "Simple",
|
||||
"ITEMS": "item(s)",
|
||||
"FILTER_PLACEHOLDER": "Filter Logs"
|
||||
}
|
||||
}
|
@ -1,134 +0,0 @@
|
||||
{
|
||||
"LOG_IN": "登录",
|
||||
"SIGN_UP": "注册账号",
|
||||
"BUTTON": {
|
||||
"CANCEL": "取消",
|
||||
"OK": "确定",
|
||||
"DELETE": "删除"
|
||||
},
|
||||
"TOOLTIP": {
|
||||
"EMAIL": "请使用正确的邮箱地址,比如name@example.com",
|
||||
"USER_NAME": "不能包含\"~#$%特殊字符且长度不能超过20",
|
||||
"FULL_NAME": "长度不能超过20",
|
||||
"COMMENT": "长度不能超过20",
|
||||
"CURRENT_PWD": "当前密码必需",
|
||||
"PASSWORD": "密码长度至少为7且需包含至少一个大写字符,一个小写字符和一个数字",
|
||||
"CONFIRM_PWD": "当前密码须与上述输入密码一致"
|
||||
},
|
||||
"PLACEHOLDER": {
|
||||
"CURRENT_PWD": "输入当前密码",
|
||||
"NEW_PWD": "输入新密码",
|
||||
"CONFIRM_PWD": "确认新密码",
|
||||
"USER_NAME": "输入用户名称",
|
||||
"MAIL": "输入邮箱地址",
|
||||
"FULL_NAME": "输入全名"
|
||||
},
|
||||
"PROFILE": {
|
||||
"TITLE": "用户设置",
|
||||
"USER_NAME": "用户名",
|
||||
"EMAIL": "邮箱",
|
||||
"FULL_NAME": "全名",
|
||||
"COMMENT": "注释",
|
||||
"PASSWORD": "密码"
|
||||
},
|
||||
"CHANGE_PWD": {
|
||||
"TITLE": "修改密码",
|
||||
"CURRENT_PWD": "当前密码",
|
||||
"NEW_PWD": "新密码",
|
||||
"CONFIRM_PWD": "确认密码"
|
||||
},
|
||||
"ACCOUNT_SETTINGS": {
|
||||
"PROFILE": "用户设置",
|
||||
"CHANGE_PWD": "修改密码",
|
||||
"ABOUT": "关于",
|
||||
"LOGOUT": "退出"
|
||||
},
|
||||
"GLOBAL_SEARCH": {
|
||||
"PLACEHOLDER": "搜索 Harbor..."
|
||||
},
|
||||
"SIDE_NAV": {
|
||||
"PROJECTS": "项目",
|
||||
"SYSTEM_MGMT": {
|
||||
"NAME": "系统管理",
|
||||
"USERS": "用户管理",
|
||||
"REPLICATIONS": "复制管理",
|
||||
"CONFIGS": "配置管理"
|
||||
}
|
||||
},
|
||||
"USER": {
|
||||
"ADD_ACTION": "用户",
|
||||
"ENABLE_ADMIN_ACTION": "设置为管理员",
|
||||
"DISABLE_ADMIN_ACTION": "取消管理员",
|
||||
"DEL_ACTION": "删除",
|
||||
"FILTER_PLACEHOLDER": "过滤用户",
|
||||
"COLUMN_NAME": "用户名",
|
||||
"COLUMN_ADMIN": "管理员",
|
||||
"COLUMN_EMAIL": "邮件",
|
||||
"COLUMN_REG_NAME": "注册时间",
|
||||
"IS_ADMIN": "是",
|
||||
"IS_NOT_ADMIN": "否",
|
||||
"ADD_USER_TITLE": "添加用户"
|
||||
},
|
||||
"PROJECT": {
|
||||
"PROJECTS": "项目",
|
||||
"NAME": "项目名称",
|
||||
"PUBLIC_OR_PRIVATE": "公开/私有",
|
||||
"REPO_COUNT": "镜像仓库数",
|
||||
"CREATION_TIME": "创建时间",
|
||||
"DESCRIPTION": "描述",
|
||||
"PUBLIC": "公开",
|
||||
"PRIVATE": "私有",
|
||||
"MAKE": "设为",
|
||||
"NEW_POLICY": "新建策略",
|
||||
"DELETE": "删除",
|
||||
"MY_PROJECTS": "我的项目",
|
||||
"PUBLIC_PROJECTS": "公开项目",
|
||||
"NEW_PROJECT": "新建项目",
|
||||
"NAME_ALREADY_EXISTS": "项目名称已存在。",
|
||||
"NAME_IS_ILLEGAL": "项目名称非法。",
|
||||
"UNKNOWN_ERROR": "创建项目时发生未知错误。",
|
||||
"ITEMS": "条记录",
|
||||
"DELETE_TITLE": "删除项目",
|
||||
"DELETE_MESSAGE": "确认删除项目吗?",
|
||||
"FILTER_PLACEHOLDER": "过滤项目"
|
||||
},
|
||||
"PROJECT_DETAIL": {
|
||||
"REPOSITORIES": "镜像仓库",
|
||||
"REPLICATION": "复制",
|
||||
"USERS": "用户",
|
||||
"LOGS": "日志"
|
||||
},
|
||||
"MEMBER": {
|
||||
"NEW_MEMBER": "新增成员",
|
||||
"NAME": "姓名",
|
||||
"ROLE": "角色",
|
||||
"SYS_ADMIN": "系统管理员",
|
||||
"PROJECT_ADMIN": "项目管理员",
|
||||
"DEVELOPER": "开发人员",
|
||||
"GUEST": "访客",
|
||||
"DELETE": "删除",
|
||||
"ITEMS": "条记录",
|
||||
"ACTIONS": "操作",
|
||||
"USERNAME_DOES_NOT_EXISTS": "用户名不存在",
|
||||
"USERNAME_ALREADY_EXISTS": "用户名已存在",
|
||||
"UNKNOWN_ERROR": "添加成员时发生未知错误。",
|
||||
"FILTER_PLACEHOLDER": "过滤成员"
|
||||
},
|
||||
"AUDIT_LOG": {
|
||||
"USERNAME": "用户名",
|
||||
"REPOSITORY_NAME": "镜像名称",
|
||||
"TAGS": "标签",
|
||||
"OPERATION": "操作",
|
||||
"TIMESTAMP": "时间戳",
|
||||
"ALL_OPERATIONS": "所有操作",
|
||||
"PULL": "Pull",
|
||||
"PUSH": "Push",
|
||||
"CREATE": "Create",
|
||||
"DELETE": "Delete",
|
||||
"OTHERS": "其他",
|
||||
"ADVANCED": "高级检索",
|
||||
"SIMPLE": "简单检索",
|
||||
"ITEMS": "条记录",
|
||||
"FILTER_PLACEHOLDER": "过滤日志"
|
||||
}
|
||||
}
|
@ -1,8 +1,8 @@
|
||||
import {MissingTranslationHandler, MissingTranslationHandlerParams} from '@ngx-translate/core';
|
||||
import { MissingTranslationHandler, MissingTranslationHandlerParams } from '@ngx-translate/core';
|
||||
|
||||
export class MyMissingTranslationHandler implements MissingTranslationHandler {
|
||||
handle(params: MissingTranslationHandlerParams) {
|
||||
const missingText = "{Miss Harbor Text}";
|
||||
return missingText;
|
||||
return params.key || missingText;
|
||||
}
|
||||
}
|
@ -5,7 +5,8 @@
|
||||
<button class="btn btn-link" (click)="toggleOptionalName(currentOption)">{{toggleName[currentOption] | translate}}</button>
|
||||
</div>
|
||||
<div class="col-xs-3 flex-xs-middle">
|
||||
<grid-filter class="filter-pos" filterPlaceholder='{{"AUDIT_LOG.FILTER_PLACEHOLDER" | translate}}' (filter)="doSearchAuditLogs($event)"></grid-filter>
|
||||
<grid-filter filterPlaceholder='{{"AUDIT_LOG.FILTER_PLACEHOLDER" | translate}}' (filter)="doSearchAuditLogs($event)"></grid-filter>
|
||||
<a href="javascript:void(0)" (click)="refresh()"><clr-icon shape="refresh"></clr-icon></a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row flex-items-xs-right advance-option" [hidden]="currentOption === 0">
|
||||
|
@ -136,4 +136,7 @@ export class AuditLogComponent implements OnInit {
|
||||
}
|
||||
this.doSearchByOptions();
|
||||
}
|
||||
refresh(): void {
|
||||
this.retrieve(this.queryParam);
|
||||
}
|
||||
}
|
@ -10,26 +10,39 @@ import 'rxjs/add/operator/catch';
|
||||
import 'rxjs/add/operator/map';
|
||||
import 'rxjs/add/observable/throw';
|
||||
|
||||
export const urlPrefix = '';
|
||||
export const logEndpoint = "/api/logs";
|
||||
|
||||
@Injectable()
|
||||
export class AuditLogService extends BaseService {
|
||||
|
||||
private httpOptions = new RequestOptions({
|
||||
headers: new Headers({
|
||||
"Content-Type": 'application/json',
|
||||
"Accept": 'application/json'
|
||||
})
|
||||
});
|
||||
|
||||
constructor(private http: Http) {
|
||||
super();
|
||||
}
|
||||
|
||||
listAuditLogs(queryParam: AuditLog): Observable<AuditLog[]> {
|
||||
return this.http
|
||||
.post(urlPrefix + `/api/projects/${queryParam.project_id}/logs/filter`, {
|
||||
begin_timestamp: queryParam.begin_timestamp,
|
||||
end_timestamp: queryParam.end_timestamp,
|
||||
keywords: queryParam.keywords,
|
||||
operation: queryParam.operation,
|
||||
project_id: queryParam.project_id,
|
||||
username: queryParam.username })
|
||||
.map(response=>response.json() as AuditLog[])
|
||||
.catch(error=>this.handleError(error));
|
||||
.post(`/api/projects/${queryParam.project_id}/logs/filter`, {
|
||||
begin_timestamp: queryParam.begin_timestamp,
|
||||
end_timestamp: queryParam.end_timestamp,
|
||||
keywords: queryParam.keywords,
|
||||
operation: queryParam.operation,
|
||||
project_id: queryParam.project_id,
|
||||
username: queryParam.username
|
||||
})
|
||||
.map(response => response.json() as AuditLog[])
|
||||
.catch(error => this.handleError(error));
|
||||
}
|
||||
|
||||
getRecentLogs(lines: number): Observable<AuditLog[]> {
|
||||
return this.http.get(logEndpoint + "?lines=" + lines, this.httpOptions)
|
||||
.map(response => response.json() as AuditLog[])
|
||||
.catch(error => this.handleError(error));
|
||||
}
|
||||
|
||||
}
|
@ -2,10 +2,16 @@ import { NgModule } from '@angular/core';
|
||||
import { AuditLogComponent } from './audit-log.component';
|
||||
import { SharedModule } from '../shared/shared.module';
|
||||
import { AuditLogService } from './audit-log.service';
|
||||
import { RecentLogComponent } from './recent-log.component';
|
||||
|
||||
@NgModule({
|
||||
imports: [ SharedModule ],
|
||||
declarations: [ AuditLogComponent ],
|
||||
providers: [ AuditLogService ],
|
||||
exports: [ AuditLogComponent ]
|
||||
imports: [SharedModule],
|
||||
declarations: [
|
||||
AuditLogComponent,
|
||||
RecentLogComponent],
|
||||
providers: [AuditLogService],
|
||||
exports: [
|
||||
AuditLogComponent,
|
||||
RecentLogComponent]
|
||||
})
|
||||
export class LogModule {}
|
||||
export class LogModule { }
|
32
src/ui_ng/src/app/log/recent-log.component.css
Normal file
32
src/ui_ng/src/app/log/recent-log.component.css
Normal file
@ -0,0 +1,32 @@
|
||||
.h2-log-override {
|
||||
margin-top: 0px !important;
|
||||
}
|
||||
|
||||
.filter-log {
|
||||
float: right;
|
||||
margin-right: 24px;
|
||||
position: relative;
|
||||
top: 8px;
|
||||
}
|
||||
|
||||
.action-head-pos {
|
||||
position: relative;
|
||||
top: 20px;
|
||||
}
|
||||
|
||||
.refresh-btn {
|
||||
position: absolute;
|
||||
right: -4px;
|
||||
top: 8px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.custom-lines-button {
|
||||
padding: 0px !important;
|
||||
min-width: 25px !important;
|
||||
}
|
||||
|
||||
.lines-button-toggole {
|
||||
font-size: 16px;
|
||||
text-decoration: underline;
|
||||
}
|
36
src/ui_ng/src/app/log/recent-log.component.html
Normal file
36
src/ui_ng/src/app/log/recent-log.component.html
Normal file
@ -0,0 +1,36 @@
|
||||
<div>
|
||||
<h2 class="h2-log-override">{{'SIDE_NAV.LOGS' | translate}}</h2>
|
||||
<div class="action-head-pos">
|
||||
<span>
|
||||
<label>{{'RECENT_LOG.SUB_TITLE' | translate}} </label>
|
||||
<button type="submit" class="btn btn-link custom-lines-button" [class.lines-button-toggole]="lines === 10" (click)="setLines(10)">10</button>
|
||||
<label> | </label>
|
||||
<button type="submit" class="btn btn-link custom-lines-button" [class.lines-button-toggole]="lines === 25" (click)="setLines(25)">25</button>
|
||||
<label> | </label>
|
||||
<button type="submit" class="btn btn-link custom-lines-button" [class.lines-button-toggole]="lines === 50" (click)="setLines(50)">50</button>
|
||||
<label>{{'RECENT_LOG.SUB_TITLE_SUFIX' | translate}}</label>
|
||||
</span>
|
||||
<grid-filter class="filter-log" filterPlaceholder='{{"AUDIT_LOG.FILTER_PLACEHOLDER" | translate}}' (filter)="doFilter($event)"></grid-filter>
|
||||
<span class="refresh-btn" (click)="refresh()">
|
||||
<clr-icon shape="refresh" [hidden]="inProgress" ng-disabled="inProgress"></clr-icon>
|
||||
<span class="spinner spinner-inline" [hidden]="inProgress === false"></span>
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<clr-datagrid>
|
||||
<clr-dg-column>{{'AUDIT_LOG.USERNAME' | translate}}</clr-dg-column>
|
||||
<clr-dg-column>{{'AUDIT_LOG.REPOSITORY_NAME' | translate}}</clr-dg-column>
|
||||
<clr-dg-column>{{'AUDIT_LOG.TAGS' | translate}}</clr-dg-column>
|
||||
<clr-dg-column>{{'AUDIT_LOG.OPERATION' | translate}}</clr-dg-column>
|
||||
<clr-dg-column>{{'AUDIT_LOG.TIMESTAMP' | translate}}</clr-dg-column>
|
||||
<clr-dg-row *ngFor="let l of recentLogs">
|
||||
<clr-dg-cell>{{l.username}}</clr-dg-cell>
|
||||
<clr-dg-cell>{{l.repo_name}}</clr-dg-cell>
|
||||
<clr-dg-cell>{{l.repo_tag}}</clr-dg-cell>
|
||||
<clr-dg-cell>{{l.operation}}</clr-dg-cell>
|
||||
<clr-dg-cell>{{formatDateTime(l.op_time)}}</clr-dg-cell>
|
||||
</clr-dg-row>
|
||||
<clr-dg-footer>{{ (recentLogs ? recentLogs.length : 0) }} {{'AUDIT_LOG.ITEMS' | translate}}</clr-dg-footer>
|
||||
</clr-datagrid>
|
||||
</div>
|
||||
</div>
|
96
src/ui_ng/src/app/log/recent-log.component.ts
Normal file
96
src/ui_ng/src/app/log/recent-log.component.ts
Normal file
@ -0,0 +1,96 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
|
||||
import { AuditLog } from './audit-log';
|
||||
import { SessionUser } from '../shared/session-user';
|
||||
|
||||
import { AuditLogService } from './audit-log.service';
|
||||
import { SessionService } from '../shared/session.service';
|
||||
import { MessageService } from '../global-message/message.service';
|
||||
import { AlertType } from '../shared/shared.const';
|
||||
import { errorHandler, accessErrorHandler } from '../shared/shared.utils';
|
||||
|
||||
@Component({
|
||||
selector: 'recent-log',
|
||||
templateUrl: './recent-log.component.html',
|
||||
styleUrls: ['recent-log.component.css']
|
||||
})
|
||||
|
||||
export class RecentLogComponent implements OnInit {
|
||||
private sessionUser: SessionUser = null;
|
||||
private recentLogs: AuditLog[];
|
||||
private logsCache: AuditLog[];
|
||||
private onGoing: boolean = false;
|
||||
private lines: number = 10; //Support 10, 25 and 50
|
||||
|
||||
constructor(
|
||||
private session: SessionService,
|
||||
private msgService: MessageService,
|
||||
private logService: AuditLogService) {
|
||||
this.sessionUser = this.session.getCurrentUser();//Initialize session
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.retrieveLogs();
|
||||
}
|
||||
|
||||
public get inProgress(): boolean {
|
||||
return this.onGoing;
|
||||
}
|
||||
|
||||
public setLines(lines: number): void {
|
||||
this.lines = lines;
|
||||
if (this.lines < 10) {
|
||||
this.lines = 10;
|
||||
}
|
||||
|
||||
this.retrieveLogs();
|
||||
}
|
||||
|
||||
public doFilter(terms: string): void {
|
||||
if (terms.trim() === "") {
|
||||
this.recentLogs = this.logsCache.filter(log => log.username != "");
|
||||
return;
|
||||
}
|
||||
|
||||
this.recentLogs = this.logsCache.filter(log => this.isMatched(terms, log));
|
||||
}
|
||||
|
||||
public refresh(): void {
|
||||
this.retrieveLogs();
|
||||
}
|
||||
|
||||
public formatDateTime(dateTime: string){
|
||||
let dt: Date = new Date(dateTime);
|
||||
return dt.toLocaleString();
|
||||
}
|
||||
|
||||
private retrieveLogs(): void {
|
||||
if (this.lines < 10) {
|
||||
this.lines = 10;
|
||||
}
|
||||
|
||||
this.onGoing = true;
|
||||
this.logService.getRecentLogs(this.lines)
|
||||
.subscribe(
|
||||
response => {
|
||||
this.onGoing = false;
|
||||
this.logsCache = response; //Keep the data
|
||||
this.recentLogs = this.logsCache.filter(log => log.username != "");//To display
|
||||
},
|
||||
error => {
|
||||
this.onGoing = false;
|
||||
if (!accessErrorHandler(error, this.msgService)) {
|
||||
this.msgService.announceMessage(error.status, errorHandler(error), AlertType.DANGER);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
private isMatched(terms: string, log: AuditLog): boolean {
|
||||
let reg = new RegExp('.*' + terms + '.*', 'i');
|
||||
return reg.test(log.username) ||
|
||||
reg.test(log.repo_name) ||
|
||||
reg.test(log.operation);
|
||||
}
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
<clr-dropdown [clrMenuPosition]="'bottom-right'" [clrCloseMenuOnItemClick]="true">
|
||||
<button clrDropdownToggle>
|
||||
<clr-icon shape="ellipses-vertical"></clr-icon>
|
||||
</button>
|
||||
<div class="dropdown-menu">
|
||||
<a href="javascript:void(0)" clrDropdownItem>{{'PROJECT.NEW_POLICY' | translate}}</a>
|
||||
<a href="javascript:void(0)" clrDropdownItem (click)="toggle()">{{'PROJECT.MAKE' | translate}}{{(project.public === 0 ? 'PROJECT.PUBLIC' : 'PROJECT.PRIVATE') | translate}} </a>
|
||||
<div class="dropdown-divider"></div>
|
||||
<a href="javascript:void(0)" clrDropdownItem (click)="delete()">{{'PROJECT.DELETE' | translate}}</a>
|
||||
</div>
|
||||
</clr-dropdown>
|
@ -1,40 +0,0 @@
|
||||
import { Component, EventEmitter, Input, Output } from '@angular/core';
|
||||
import { Project } from '../project';
|
||||
import { ProjectService } from '../project.service';
|
||||
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { DeletionDialogService } from '../../shared/deletion-dialog/deletion-dialog.service';
|
||||
import { DeletionMessage } from '../../shared/deletion-dialog/deletion-message';
|
||||
|
||||
@Component({
|
||||
selector: 'action-project',
|
||||
templateUrl: 'action-project.component.html'
|
||||
})
|
||||
export class ActionProjectComponent {
|
||||
|
||||
@Output() togglePublic = new EventEmitter<Project>();
|
||||
@Output() deleteProject = new EventEmitter<Project>();
|
||||
|
||||
@Input() project: Project;
|
||||
|
||||
constructor(private projectService: ProjectService,
|
||||
private deletionDialogService: DeletionDialogService,
|
||||
private translateService: TranslateService) {
|
||||
deletionDialogService.deletionConfirm$.subscribe(project=>this.deleteProject.emit(project));
|
||||
}
|
||||
|
||||
toggle() {
|
||||
if(this.project) {
|
||||
this.project.public === 0 ? this.project.public = 1 : this.project.public = 0;
|
||||
this.togglePublic.emit(this.project);
|
||||
}
|
||||
}
|
||||
|
||||
delete() {
|
||||
// if(this.project) {
|
||||
// this.deleteProject.emit(this.project);
|
||||
// }
|
||||
let deletionMessage = new DeletionMessage('Delete Project', 'Do you confirm to delete project?', this.project);
|
||||
this.deletionDialogService.openComfirmDialog(deletionMessage);
|
||||
}
|
||||
}
|
@ -1,14 +1,24 @@
|
||||
<clr-modal [(clrModalOpen)]="createProjectOpened">
|
||||
<h3 class="modal-title">{{'PROJECT.NEW_PROJECT' | translate}}</h3>
|
||||
<div class="modal-body">
|
||||
<form>
|
||||
<form #projectForm="ngForm">
|
||||
<section class="form-block">
|
||||
<clr-alert [clrAlertType]="'alert-danger'" [(clrAlertClosed)]="!errorMessageOpened" (clrAlertClosedChange)="onErrorMessageClose()">
|
||||
<div class="alert-item">
|
||||
<span class="alert-text">
|
||||
{{errorMessage}}
|
||||
</span>
|
||||
</div>
|
||||
</clr-alert>
|
||||
<div class="form-group">
|
||||
<label for="create_project_name" class="col-md-4">{{'PROJECT.NAME' | translate}}</label>
|
||||
<label for="create_project_name" aria-haspopup="true" role="tooltip" [class.invalid]="hasError" [class.valid]="!hasError" class="tooltip tooltip-validation tooltip-sm tooltip-bottom-right">
|
||||
<input type="text" id="create_project_name" [(ngModel)]="project.name" name="name" size="20" (keyup)="hasError=false;">
|
||||
<span class="tooltip-content">
|
||||
{{errorMessage}}
|
||||
<label for="create_project_name" aria-haspopup="true" role="tooltip" [class.invalid]="projectName.invalid && (projectName.dirty || projectName.touched)" [class.valid]="projectName.valid" class="tooltip tooltip-validation tooltip-sm tooltip-bottom-right">
|
||||
<input type="text" id="create_project_name" [(ngModel)]="project.name" name="name" size="20" required minlength="2" #projectName="ngModel">
|
||||
<span class="tooltip-content" *ngIf="projectName.errors && projectName.errors.required && (projectName.dirty || projectName.touched)">
|
||||
Project name is required.
|
||||
</span>
|
||||
<span class="tooltip-content" *ngIf="projectName.errors && projectName.errors.minlength && (projectName.dirty || projectName.touched)">
|
||||
Minimum length of project name is 2 characters.
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
@ -24,6 +34,6 @@
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-outline" (click)="createProjectOpened = false">{{'BUTTON.CANCEL' | translate}}</button>
|
||||
<button type="button" class="btn btn-primary" (click)="onSubmit()">{{'BUTTON.OK' | translate}}</button>
|
||||
<button type="button" class="btn btn-primary" [disabled]="!projectForm.form.valid" (click)="onSubmit()">{{'BUTTON.OK' | translate}}</button>
|
||||
</div>
|
||||
</clr-modal>
|
||||
|
@ -20,8 +20,8 @@ export class CreateProjectComponent {
|
||||
project: Project = new Project();
|
||||
createProjectOpened: boolean;
|
||||
|
||||
errorMessageOpened: boolean;
|
||||
errorMessage: string;
|
||||
hasError: boolean;
|
||||
|
||||
@Output() create = new EventEmitter<boolean>();
|
||||
|
||||
@ -30,7 +30,6 @@ export class CreateProjectComponent {
|
||||
private translateService: TranslateService) {}
|
||||
|
||||
onSubmit() {
|
||||
this.hasError = false;
|
||||
this.projectService
|
||||
.createProject(this.project.name, this.project.public ? 1 : 0)
|
||||
.subscribe(
|
||||
@ -39,7 +38,7 @@ export class CreateProjectComponent {
|
||||
this.createProjectOpened = false;
|
||||
},
|
||||
error=>{
|
||||
this.hasError = true;
|
||||
this.errorMessageOpened = true;
|
||||
if (error instanceof Response) {
|
||||
switch(error.status) {
|
||||
case 409:
|
||||
@ -59,9 +58,15 @@ export class CreateProjectComponent {
|
||||
}
|
||||
|
||||
newProject() {
|
||||
this.hasError = false;
|
||||
this.project = new Project();
|
||||
this.createProjectOpened = true;
|
||||
this.errorMessageOpened = false;
|
||||
this.errorMessage = '';
|
||||
}
|
||||
|
||||
onErrorMessageClose(): void {
|
||||
this.errorMessageOpened = false;
|
||||
this.errorMessage = '';
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -15,9 +15,12 @@
|
||||
<clr-dg-cell>{{p.creation_time}}</clr-dg-cell>
|
||||
<clr-dg-cell>
|
||||
{{p.description}}
|
||||
<span style="float: right;">
|
||||
<action-project (togglePublic)="toggleProject($event)" (deleteProject)="deleteProject($event)" [project]="p"></action-project>
|
||||
</span>
|
||||
<harbor-action-overflow>
|
||||
<a href="javascript:void(0)" class="dropdown-item">{{'PROJECT.NEW_POLICY' | translate}}</a>
|
||||
<a href="javascript:void(0)" class="dropdown-item" (click)="toggleProject(p)">{{'PROJECT.MAKE' | translate}} {{(p.public === 0 ? 'PROJECT.PUBLIC' : 'PROJECT.PRIVATE') | translate}} </a>
|
||||
<div class="dropdown-divider"></div>
|
||||
<a href="javascript:void(0)" class="dropdown-item" (click)="deleteProject(p)">{{'PROJECT.DELETE' | translate}}</a>
|
||||
</harbor-action-overflow>
|
||||
</clr-dg-cell>
|
||||
</clr-dg-row>
|
||||
<clr-dg-footer>{{ (projects ? projects.length : 0) }} {{'PROJECT.ITEMS' | translate}}</clr-dg-footer>
|
||||
|
@ -1,14 +1,21 @@
|
||||
<clr-modal [(clrModalOpen)]="addMemberOpened">
|
||||
<h3 class="modal-title">{{'MEMBER.NEW_MEMBER' | translate}}</h3>
|
||||
<div class="modal-body">
|
||||
<form>
|
||||
<form #memberForm="ngForm">
|
||||
<section class="form-block">
|
||||
<clr-alert [clrAlertType]="'alert-danger'" [(clrAlertClosed)]="!errorMessageOpened" (clrAlertClosedChange)="onErrorMessageClose()">
|
||||
<div class="alert-item">
|
||||
<span class="alert-text">
|
||||
{{errorMessage}}
|
||||
</span>
|
||||
</div>
|
||||
</clr-alert>
|
||||
<div class="form-group">
|
||||
<label for="member_name" class="col-md-4">{{'MEMBER.NAME' | translate}}</label>
|
||||
<label for="member_name" aria-haspopup="true" role="tooltip" [class.invalid]="hasError" [class.valid]="!hasError" class="tooltip tooltip-validation tooltip-sm tooltip-bottom-right">
|
||||
<input type="text" id="member_name" [(ngModel)]="member.username" name="name" size="20" (keyup)="hasError=false;">
|
||||
<span class="tooltip-content">
|
||||
{{errorMessage}}
|
||||
<label for="member_name" aria-haspopup="true" role="tooltip" [class.invalid]="memberName.invalid && (memberName.dirty || memberName.touched)" [class.valid]="memberName.valid" class="tooltip tooltip-validation tooltip-sm tooltip-bottom-right">
|
||||
<input type="text" id="member_name" [(ngModel)]="member.username" name="name" size="20" #memberName="ngModel" required>
|
||||
<span class="tooltip-content" *ngIf="memberName.errors && memberName.errors.required && (memberName.dirty || memberName.touched)">
|
||||
Username is required.
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
|
@ -18,7 +18,9 @@ export class AddMemberComponent {
|
||||
member: Member = new Member();
|
||||
addMemberOpened: boolean;
|
||||
errorMessage: string;
|
||||
hasError: boolean;
|
||||
|
||||
errorMessageOpened: boolean;
|
||||
|
||||
|
||||
@Input() projectId: number;
|
||||
@Output() added = new EventEmitter<boolean>();
|
||||
@ -28,7 +30,6 @@ export class AddMemberComponent {
|
||||
private translateService: TranslateService) {}
|
||||
|
||||
onSubmit(): void {
|
||||
this.hasError = false;
|
||||
console.log('Adding member:' + JSON.stringify(this.member));
|
||||
this.memberService
|
||||
.addMember(this.projectId, this.member.username, this.member.role_id)
|
||||
@ -39,7 +40,7 @@ export class AddMemberComponent {
|
||||
this.addMemberOpened = false;
|
||||
},
|
||||
error=>{
|
||||
this.hasError = true;
|
||||
this.errorMessageOpened = true;
|
||||
if (error instanceof Response) {
|
||||
switch(error.status){
|
||||
case 404:
|
||||
@ -59,13 +60,17 @@ export class AddMemberComponent {
|
||||
console.log('Failed to add member of project:' + this.projectId, ' with error:' + error);
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
}
|
||||
|
||||
openAddMemberModal(): void {
|
||||
this.hasError = false;
|
||||
this.errorMessageOpened = false;
|
||||
this.errorMessage = '';
|
||||
this.member = new Member();
|
||||
this.addMemberOpened = true;
|
||||
}
|
||||
|
||||
onErrorMessageClose(): void {
|
||||
this.errorMessageOpened = false;
|
||||
this.errorMessage = '';
|
||||
}
|
||||
}
|
@ -1,38 +1,34 @@
|
||||
<div class="row">
|
||||
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
|
||||
<div class="row flex-items-xs-between">
|
||||
<div class="col-xs-4 flex-xs-middle">
|
||||
<button class="btn btn-link" (click)="openAddMemberModal()"><clr-icon shape="add"></clr-icon>{{'MEMBER.NEW_MEMBER' | translate }}</button>
|
||||
<add-member [projectId]="projectId" (added)="addedMember($event)"></add-member>
|
||||
</div>
|
||||
<div class="col-xs-4 flex-xs-middle">
|
||||
<grid-filter class="filter-pos" filterPlaceholder='{{"MEMBER.FILTER_PLACEHOLDER" | translate}}' (filter)="doSearch($event)"></grid-filter>
|
||||
</div>
|
||||
</div>
|
||||
<clr-datagrid>
|
||||
<clr-dg-column>{{'MEMBER.NAME' | translate}}</clr-dg-column>
|
||||
<clr-dg-column>{{'MEMBER.ROLE' | translate}}</clr-dg-column>
|
||||
<clr-dg-column>{{'MEMBER.ACTIONS' | translate}}</clr-dg-column>
|
||||
<clr-dg-row *ngFor="let u of members">
|
||||
<clr-dg-cell>{{u.username}}</clr-dg-cell>
|
||||
<clr-dg-cell>{{roleInfo[u.role_id] | translate}}</clr-dg-cell>
|
||||
<clr-dg-cell>
|
||||
<clr-dropdown [clrMenuPosition]="'bottom-left'" [hidden]="u.user_id === currentUser.user_id">
|
||||
<button class="btn btn-sm btn-link" clrDropdownToggle>
|
||||
{{'MEMBER.ACTIONS' | translate}}
|
||||
<clr-icon shape="caret down"></clr-icon>
|
||||
</button>
|
||||
<div class="dropdown-menu">
|
||||
<a href="javascript:void(0)" clrDropdownItem (click)="changeRole(u.user_id, 1)">{{'MEMBER.PROJECT_ADMIN' | translate}}</a>
|
||||
<a href="javascript:void(0)" clrDropdownItem (click)="changeRole(u.user_id, 2)">{{'MEMBER.DEVELOPER' | translate}}</a>
|
||||
<a href="javascript:void(0)" clrDropdownItem (click)="changeRole(u.user_id, 3)">{{'MEMBER.GUEST' | translate}}</a>
|
||||
<div class="dropdown-divider"></div>
|
||||
<a href="javascript:void(0)" clrDropdownItem (click)="deleteMember(u.user_id)">{{'MEMBER.DELETE' | translate}}</a>
|
||||
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
|
||||
<div class="row flex-items-xs-between">
|
||||
<div class="col-xs-4 flex-xs-middle">
|
||||
<button class="btn btn-link" (click)="openAddMemberModal()"><clr-icon shape="add"></clr-icon>{{'MEMBER.NEW_MEMBER' | translate }}</button>
|
||||
<add-member [projectId]="projectId" (added)="addedMember($event)"></add-member>
|
||||
</div>
|
||||
</clr-dropdown>
|
||||
</clr-dg-cell>
|
||||
</clr-dg-row>
|
||||
<clr-dg-footer>{{ (members ? members.length : 0) }} {{'MEMBER.ITEMS' | translate}}</clr-dg-footer>
|
||||
</clr-datagrid>
|
||||
</div>
|
||||
<div class="col-xs-4 flex-xs-middle push-xs-1">
|
||||
<grid-filter filterPlaceholder='{{"MEMBER.FILTER_PLACEHOLDER" | translate}}' (filter)="doSearch($event)"></grid-filter>
|
||||
<a href="javascript:void(0)" (click)="refresh()">
|
||||
<clr-icon shape="refresh"></clr-icon>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<clr-datagrid>
|
||||
<clr-dg-column>{{'MEMBER.NAME' | translate}}</clr-dg-column>
|
||||
<clr-dg-column>{{'MEMBER.ROLE' | translate}}</clr-dg-column>
|
||||
<clr-dg-row *ngFor="let u of members">
|
||||
<clr-dg-cell>{{u.username}}</clr-dg-cell>
|
||||
<clr-dg-cell>
|
||||
{{roleInfo[u.role_id] | translate}}
|
||||
<harbor-action-overflow [hidden]="u.user_id === currentUser.user_id">
|
||||
<a href="javascript:void(0)" class="dropdown-item" (click)="changeRole(u.user_id, 1)">{{'MEMBER.PROJECT_ADMIN' | translate}}</a>
|
||||
<a href="javascript:void(0)" class="dropdown-item" (click)="changeRole(u.user_id, 2)">{{'MEMBER.DEVELOPER' | translate}}</a>
|
||||
<a href="javascript:void(0)" class="dropdown-item" (click)="changeRole(u.user_id, 3)">{{'MEMBER.GUEST' | translate}}</a>
|
||||
<div class="dropdown-divider"></div>
|
||||
<a href="javascript:void(0)" class="dropdown-item" (click)="deleteMember(u.user_id)">{{'MEMBER.DELETE' | translate}}</a>
|
||||
</harbor-action-overflow>
|
||||
</clr-dg-cell>
|
||||
</clr-dg-row>
|
||||
<clr-dg-footer>{{ (members ? members.length : 0) }} {{'MEMBER.ITEMS' | translate}}</clr-dg-footer>
|
||||
</clr-datagrid>
|
||||
</div>
|
||||
</div>
|
@ -9,11 +9,11 @@ import { MemberService } from './member.service';
|
||||
import { AddMemberComponent } from './add-member/add-member.component';
|
||||
|
||||
import { MessageService } from '../../global-message/message.service';
|
||||
import { AlertType } from '../../shared/shared.const';
|
||||
import { AlertType, DeletionTargets } from '../../shared/shared.const';
|
||||
|
||||
import { DeletionDialogService } from '../../shared/deletion-dialog/deletion-dialog.service';
|
||||
import { DeletionMessage } from '../../shared/deletion-dialog/deletion-message';
|
||||
|
||||
import { SessionService } from '../../shared/session.service';
|
||||
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import 'rxjs/add/operator/switchMap';
|
||||
@ -21,13 +21,13 @@ import 'rxjs/add/operator/catch';
|
||||
import 'rxjs/add/operator/map';
|
||||
import 'rxjs/add/observable/throw';
|
||||
|
||||
export const roleInfo: {} = { 1: 'MEMBER.PROJECT_ADMIN', 2: 'MEMBER.DEVELOPER', 3: 'MEMBER.GUEST'};
|
||||
export const roleInfo: {} = { 1: 'MEMBER.PROJECT_ADMIN', 2: 'MEMBER.DEVELOPER', 3: 'MEMBER.GUEST' };
|
||||
|
||||
@Component({
|
||||
templateUrl: 'member.component.html'
|
||||
})
|
||||
export class MemberComponent implements OnInit {
|
||||
|
||||
|
||||
currentUser: SessionUser;
|
||||
members: Member[];
|
||||
projectId: number;
|
||||
@ -36,34 +36,37 @@ export class MemberComponent implements OnInit {
|
||||
@ViewChild(AddMemberComponent)
|
||||
addMemberComponent: AddMemberComponent;
|
||||
|
||||
constructor(private route: ActivatedRoute, private router: Router,
|
||||
private memberService: MemberService, private messageService: MessageService,
|
||||
private deletionDialogService: DeletionDialogService) {
|
||||
constructor(private route: ActivatedRoute, private router: Router,
|
||||
private memberService: MemberService, private messageService: MessageService,
|
||||
private deletionDialogService: DeletionDialogService,
|
||||
session:SessionService) {
|
||||
//Get current user from registered resolver.
|
||||
this.route.data.subscribe(data=>this.currentUser = <SessionUser>data['memberResolver']);
|
||||
deletionDialogService.deletionConfirm$.subscribe(userId=>{
|
||||
this.memberService
|
||||
.deleteMember(this.projectId, userId)
|
||||
.subscribe(
|
||||
response=>{
|
||||
console.log('Successful change role with user ' + userId);
|
||||
this.currentUser = session.getCurrentUser();
|
||||
deletionDialogService.deletionConfirm$.subscribe(message => {
|
||||
if (message && message.targetId === DeletionTargets.PROJECT_MEMBER) {
|
||||
this.memberService
|
||||
.deleteMember(this.projectId, message.data)
|
||||
.subscribe(
|
||||
response => {
|
||||
console.log('Successful change role with user ' + message.data);
|
||||
this.retrieve(this.projectId, '');
|
||||
},
|
||||
error => this.messageService.announceMessage(error.status, 'Failed to change role with user ' + userId, AlertType.DANGER)
|
||||
);
|
||||
})
|
||||
error => this.messageService.announceMessage(error.status, 'Failed to change role with user ' + message.data, AlertType.DANGER)
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
retrieve(projectId:number, username: string) {
|
||||
retrieve(projectId: number, username: string) {
|
||||
this.memberService
|
||||
.listMembers(projectId, username)
|
||||
.subscribe(
|
||||
response=>this.members = response,
|
||||
error=>{
|
||||
this.router.navigate(['/harbor', 'projects']);
|
||||
this.messageService.announceMessage(error.status, 'Failed to get project member with project ID:' + projectId, AlertType.DANGER);
|
||||
}
|
||||
);
|
||||
.listMembers(projectId, username)
|
||||
.subscribe(
|
||||
response => this.members = response,
|
||||
error => {
|
||||
this.router.navigate(['/harbor', 'projects']);
|
||||
this.messageService.announceMessage(error.status, 'Failed to get project member with project ID:' + projectId, AlertType.DANGER);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
@ -77,29 +80,39 @@ export class MemberComponent implements OnInit {
|
||||
openAddMemberModal() {
|
||||
this.addMemberComponent.openAddMemberModal();
|
||||
}
|
||||
|
||||
|
||||
addedMember() {
|
||||
this.retrieve(this.projectId, '');
|
||||
}
|
||||
|
||||
changeRole(userId: number, roleId: number) {
|
||||
this.memberService
|
||||
.changeMemberRole(this.projectId, userId, roleId)
|
||||
.subscribe(
|
||||
response=>{
|
||||
console.log('Successful change role with user ' + userId + ' to roleId ' + roleId);
|
||||
this.retrieve(this.projectId, '');
|
||||
},
|
||||
error => this.messageService.announceMessage(error.status, 'Failed to change role with user ' + userId + ' to roleId ' + roleId, AlertType.DANGER)
|
||||
);
|
||||
.changeMemberRole(this.projectId, userId, roleId)
|
||||
.subscribe(
|
||||
response => {
|
||||
console.log('Successful change role with user ' + userId + ' to roleId ' + roleId);
|
||||
this.retrieve(this.projectId, '');
|
||||
},
|
||||
error => this.messageService.announceMessage(error.status, 'Failed to change role with user ' + userId + ' to roleId ' + roleId, AlertType.DANGER)
|
||||
);
|
||||
}
|
||||
|
||||
deleteMember(userId: number) {
|
||||
let deletionMessage: DeletionMessage = new DeletionMessage('Delete Member', 'Confirm to delete this member?', userId);
|
||||
let deletionMessage: DeletionMessage = new DeletionMessage(
|
||||
'MEMBER.DELETION_TITLE',
|
||||
'MEMBER.DELETION_SUMMARY',
|
||||
userId+"",
|
||||
userId,
|
||||
DeletionTargets.PROJECT_MEMBER
|
||||
);
|
||||
this.deletionDialogService.openComfirmDialog(deletionMessage);
|
||||
}
|
||||
|
||||
|
||||
doSearch(searchMember) {
|
||||
this.retrieve(this.projectId, searchMember);
|
||||
}
|
||||
|
||||
refresh() {
|
||||
this.retrieve(this.projectId, '');
|
||||
}
|
||||
}
|
@ -9,8 +9,6 @@ import 'rxjs/add/observable/throw';
|
||||
import { BaseService } from '../../service/base.service';
|
||||
import { Member } from './member';
|
||||
|
||||
export const urlPrefix = '';
|
||||
|
||||
@Injectable()
|
||||
export class MemberService extends BaseService {
|
||||
|
||||
@ -21,7 +19,7 @@ export class MemberService extends BaseService {
|
||||
listMembers(projectId: number, username: string): Observable<Member[]> {
|
||||
console.log('Get member from project_id:' + projectId + ', username:' + username);
|
||||
return this.http
|
||||
.get(urlPrefix + `/api/projects/${projectId}/members?username=${username}`)
|
||||
.get(`/api/projects/${projectId}/members?username=${username}`)
|
||||
.map(response=>response.json())
|
||||
.catch(error=>this.handleError(error));
|
||||
}
|
||||
@ -29,7 +27,7 @@ export class MemberService extends BaseService {
|
||||
addMember(projectId: number, username: string, roleId: number): Observable<any> {
|
||||
console.log('Adding member with username:' + username + ', roleId:' + roleId + ' under projectId:' + projectId);
|
||||
return this.http
|
||||
.post(urlPrefix + `/api/projects/${projectId}/members`, { username: username, roles: [ roleId ] })
|
||||
.post(`/api/projects/${projectId}/members`, { username: username, roles: [ roleId ] })
|
||||
.map(response=>response.status)
|
||||
.catch(error=>Observable.throw(error));
|
||||
}
|
||||
@ -37,7 +35,7 @@ export class MemberService extends BaseService {
|
||||
changeMemberRole(projectId: number, userId: number, roleId: number): Observable<any> {
|
||||
console.log('Changing member role with userId:' + ' to roleId:' + roleId + ' under projectId:' + projectId);
|
||||
return this.http
|
||||
.put(urlPrefix + `/api/projects/${projectId}/members/${userId}`, { roles: [ roleId ]})
|
||||
.put(`/api/projects/${projectId}/members/${userId}`, { roles: [ roleId ]})
|
||||
.map(response=>response.status)
|
||||
.catch(error=>Observable.throw(error));
|
||||
}
|
||||
@ -45,7 +43,7 @@ export class MemberService extends BaseService {
|
||||
deleteMember(projectId: number, userId: number): Observable<any> {
|
||||
console.log('Deleting member role with userId:' + userId + ' under projectId:' + projectId);
|
||||
return this.http
|
||||
.delete(urlPrefix + `/api/projects/${projectId}/members/${userId}`)
|
||||
.delete(`/api/projects/${projectId}/members/${userId}`)
|
||||
.map(response=>response.status)
|
||||
.catch(error=>Observable.throw(error));
|
||||
}
|
||||
|
@ -1,69 +0,0 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { RouterModule, Routes } from '@angular/router';
|
||||
|
||||
import { HarborShellComponent } from '../base/harbor-shell/harbor-shell.component';
|
||||
import { ProjectComponent } from './project.component';
|
||||
import { ProjectDetailComponent } from './project-detail/project-detail.component';
|
||||
|
||||
import { RepositoryComponent } from '../repository/repository.component';
|
||||
import { ReplicationComponent } from '../replication/replication.component';
|
||||
import { MemberComponent } from './member/member.component';
|
||||
import { AuditLogComponent } from '../log/audit-log.component';
|
||||
|
||||
import { BaseRoutingResolver } from '../base/base-routing-resolver.service';
|
||||
import { ProjectRoutingResolver } from './project-routing-resolver.service';
|
||||
|
||||
const projectRoutes: Routes = [
|
||||
{
|
||||
path: 'harbor',
|
||||
component: HarborShellComponent,
|
||||
resolve: {
|
||||
harborResolver: BaseRoutingResolver
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: 'projects',
|
||||
component: ProjectComponent,
|
||||
resolve: {
|
||||
projectsResolver: BaseRoutingResolver
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'projects/:id',
|
||||
component: ProjectDetailComponent,
|
||||
resolve: {
|
||||
projectResolver: ProjectRoutingResolver
|
||||
},
|
||||
children: [
|
||||
{ path: 'repository', component: RepositoryComponent },
|
||||
{
|
||||
path: 'replication', component: ReplicationComponent,
|
||||
resolve: {
|
||||
replicationResolver: BaseRoutingResolver
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'member', component: MemberComponent,
|
||||
resolve: {
|
||||
memberResolver: BaseRoutingResolver
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'log', component: AuditLogComponent,
|
||||
resolve: {
|
||||
auditLogResolver: BaseRoutingResolver
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
RouterModule.forChild(projectRoutes)
|
||||
],
|
||||
exports: [RouterModule]
|
||||
})
|
||||
export class ProjectRoutingModule { }
|
@ -4,7 +4,7 @@
|
||||
<button class="btn btn-link" (click)="openModal()"><clr-icon shape="add"></clr-icon>{{'PROJECT.NEW_PROJECT' | translate}}</button>
|
||||
<create-project (create)="createProject($event)"></create-project>
|
||||
</div>
|
||||
<div class="col-xs-5">
|
||||
<div class="col-xs-5 push-xs-1">
|
||||
<clr-dropdown [clrMenuPosition]="'bottom-left'">
|
||||
<button class="btn btn-sm btn-link" clrDropdownToggle>
|
||||
{{projectTypes[currentFilteredType] | translate}}
|
||||
@ -15,7 +15,8 @@
|
||||
<a href="javascript:void(0)" clrDropdownItem (click)="doFilterProjects(1)">{{projectTypes[1] | translate}}</a>
|
||||
</div>
|
||||
</clr-dropdown>
|
||||
<grid-filter class="filter-pos" filterPlaceholder='{{"PROJECT.FILTER_PLACEHOLDER" | translate}}' (filter)="doSearchProjects($event)"></grid-filter>
|
||||
<grid-filter filterPlaceholder='{{"PROJECT.FILTER_PLACEHOLDER" | translate}}' (filter)="doSearchProjects($event)"></grid-filter>
|
||||
<a href="javascript:void(0)" (click)="refresh()"><clr-icon shape="refresh"></clr-icon></a>
|
||||
</div>
|
||||
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
|
||||
<list-project [projects]="changedProjects" (toggle)="toggleProject($event)" (delete)="deleteProject($event)"></list-project>
|
||||
|
@ -12,11 +12,18 @@ import { ListProjectComponent } from './list-project/list-project.component';
|
||||
import { MessageService } from '../global-message/message.service';
|
||||
import { Message } from '../global-message/message';
|
||||
|
||||
export const types: {} = { 0: 'PROJECT.MY_PROJECTS', 1: 'PROJECT.PUBLIC_PROJECTS'};
|
||||
|
||||
import { AlertType } from '../shared/shared.const';
|
||||
import { Response } from '@angular/http';
|
||||
|
||||
import { DeletionDialogService } from '../shared/deletion-dialog/deletion-dialog.service';
|
||||
import { DeletionMessage } from '../shared/deletion-dialog/deletion-message';
|
||||
import { DeletionTargets } from '../shared/shared.const';
|
||||
|
||||
import { Subscription } from 'rxjs/Subscription';
|
||||
|
||||
|
||||
const types: {} = { 0: 'PROJECT.MY_PROJECTS', 1: 'PROJECT.PUBLIC_PROJECTS'};
|
||||
|
||||
@Component({
|
||||
selector: 'project',
|
||||
templateUrl: 'project.component.html',
|
||||
@ -37,7 +44,27 @@ export class ProjectComponent implements OnInit {
|
||||
currentFilteredType: number = 0;
|
||||
lastFilteredType: number = 0;
|
||||
|
||||
constructor(private projectService: ProjectService, private messageService: MessageService){}
|
||||
subscription: Subscription;
|
||||
|
||||
constructor(
|
||||
private projectService: ProjectService,
|
||||
private messageService: MessageService,
|
||||
private deletionDialogService: DeletionDialogService){
|
||||
this.subscription = deletionDialogService.deletionConfirm$.subscribe(message => {
|
||||
if (message && message.targetId === DeletionTargets.PROJECT) {
|
||||
let projectId = message.data;
|
||||
this.projectService
|
||||
.deleteProject(projectId)
|
||||
.subscribe(
|
||||
response=>{
|
||||
console.log('Successful delete project with ID:' + projectId);
|
||||
this.retrieve('', this.lastFilteredType);
|
||||
},
|
||||
error=>this.messageService.announceMessage(error.status, error, AlertType.WARNING)
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.retrieve('', this.lastFilteredType);
|
||||
@ -75,24 +102,30 @@ export class ProjectComponent implements OnInit {
|
||||
}
|
||||
|
||||
toggleProject(p: Project) {
|
||||
this.projectService
|
||||
if (p) {
|
||||
p.public === 0 ? p.public = 1 : p.public = 0;
|
||||
this.projectService
|
||||
.toggleProjectPublic(p.project_id, p.public)
|
||||
.subscribe(
|
||||
response=>console.log('Successful toggled project_id:' + p.project_id),
|
||||
error=>this.messageService.announceMessage(error.status, error, AlertType.WARNING)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
deleteProject(p: Project) {
|
||||
this.projectService
|
||||
.deleteProject(p.project_id)
|
||||
.subscribe(
|
||||
response=>{
|
||||
console.log('Successful delete project_id:' + p.project_id);
|
||||
this.retrieve('', this.lastFilteredType);
|
||||
},
|
||||
error=>this.messageService.announceMessage(error.status, error, AlertType.WARNING)
|
||||
);
|
||||
let deletionMessage = new DeletionMessage(
|
||||
'PROJECT.DELETION_TITLE',
|
||||
'PROJECT.DELETION_SUMMARY',
|
||||
p.name,
|
||||
p.project_id,
|
||||
DeletionTargets.PROJECT
|
||||
);
|
||||
this.deletionDialogService.openComfirmDialog(deletionMessage);
|
||||
}
|
||||
|
||||
refresh(): void {
|
||||
this.retrieve('', this.lastFilteredType);
|
||||
}
|
||||
|
||||
}
|
@ -1,3 +0,0 @@
|
||||
.my-project-pull-right {
|
||||
float: right;
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
|
||||
import { RouterModule } from '@angular/router';
|
||||
import { SharedModule } from '../shared/shared.module';
|
||||
import { RepositoryModule } from '../repository/repository.module';
|
||||
import { ReplicationModule } from '../replication/replication.module';
|
||||
@ -7,40 +8,35 @@ import { LogModule } from '../log/log.module';
|
||||
|
||||
import { ProjectComponent } from './project.component';
|
||||
import { CreateProjectComponent } from './create-project/create-project.component';
|
||||
import { ActionProjectComponent } from './action-project/action-project.component';
|
||||
import { ListProjectComponent } from './list-project/list-project.component';
|
||||
|
||||
import { ProjectDetailComponent } from './project-detail/project-detail.component';
|
||||
|
||||
import { MemberComponent } from './member/member.component';
|
||||
import { AddMemberComponent } from './member/add-member/add-member.component';
|
||||
|
||||
import { ProjectRoutingModule } from './project-routing.module';
|
||||
|
||||
import { ProjectService } from './project.service';
|
||||
import { MemberService } from './member/member.service';
|
||||
import { ProjectRoutingResolver } from './project-routing-resolver.service';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
imports: [
|
||||
SharedModule,
|
||||
RepositoryModule,
|
||||
ReplicationModule,
|
||||
LogModule,
|
||||
ProjectRoutingModule
|
||||
RouterModule
|
||||
],
|
||||
declarations: [
|
||||
declarations: [
|
||||
ProjectComponent,
|
||||
CreateProjectComponent,
|
||||
ActionProjectComponent,
|
||||
ListProjectComponent,
|
||||
ProjectDetailComponent,
|
||||
MemberComponent,
|
||||
AddMemberComponent
|
||||
],
|
||||
exports: [ ProjectComponent ],
|
||||
providers: [ ProjectRoutingResolver, ProjectService, MemberService ]
|
||||
exports: [ProjectComponent],
|
||||
providers: [ProjectRoutingResolver, ProjectService, MemberService]
|
||||
})
|
||||
export class ProjectModule {
|
||||
|
||||
|
||||
}
|
@ -12,8 +12,6 @@ import 'rxjs/add/operator/catch';
|
||||
import 'rxjs/add/operator/map';
|
||||
import 'rxjs/add/observable/throw';
|
||||
|
||||
const url_prefix = '';
|
||||
|
||||
@Injectable()
|
||||
export class ProjectService {
|
||||
|
||||
@ -24,7 +22,7 @@ export class ProjectService {
|
||||
|
||||
getProject(projectId: number): Promise<Project> {
|
||||
return this.http
|
||||
.get(url_prefix + `/api/projects/${projectId}`)
|
||||
.get(`/api/projects/${projectId}`)
|
||||
.toPromise()
|
||||
.then(response=>response.json() as Project)
|
||||
.catch(error=>Observable.throw(error));
|
||||
@ -32,14 +30,14 @@ export class ProjectService {
|
||||
|
||||
listProjects(name: string, isPublic: number): Observable<any>{
|
||||
return this.http
|
||||
.get(url_prefix + `/api/projects?project_name=${name}&is_public=${isPublic}`, this.options)
|
||||
.get(`/api/projects?project_name=${name}&is_public=${isPublic}`, this.options)
|
||||
.map(response=>response.json())
|
||||
.catch(error=>Observable.throw(error));
|
||||
}
|
||||
|
||||
createProject(name: string, isPublic: number): Observable<any> {
|
||||
return this.http
|
||||
.post(url_prefix + `/api/projects`,
|
||||
.post(`/api/projects`,
|
||||
JSON.stringify({'project_name': name, 'public': isPublic})
|
||||
, this.options)
|
||||
.map(response=>response.status)
|
||||
@ -48,14 +46,14 @@ export class ProjectService {
|
||||
|
||||
toggleProjectPublic(projectId: number, isPublic: number): Observable<any> {
|
||||
return this.http
|
||||
.put(url_prefix + `/api/projects/${projectId}/publicity`, { 'public': isPublic }, this.options)
|
||||
.put(`/api/projects/${projectId}/publicity`, { 'public': isPublic }, this.options)
|
||||
.map(response=>response.status)
|
||||
.catch(error=>Observable.throw(error));
|
||||
}
|
||||
|
||||
deleteProject(projectId: number): Observable<any> {
|
||||
return this.http
|
||||
.delete(url_prefix + `/api/projects/${projectId}`)
|
||||
.delete(`/api/projects/${projectId}`)
|
||||
.map(response=>response.status)
|
||||
.catch(error=>Observable.throw(error));
|
||||
}
|
||||
|
@ -0,0 +1,52 @@
|
||||
<clr-modal [(clrModalOpen)]="createEditDestinationOpened">
|
||||
<h3 class="modal-title">New Endpoint</h3>
|
||||
<div class="modal-body">
|
||||
<form #targetForm="ngForm">
|
||||
<section class="form-block">
|
||||
<clr-alert [clrAlertType]="'alert-danger'" [(clrAlertClosed)]="!errorMessageOpened" (clrAlertClosedChange)="onErrorMessageClose()">
|
||||
<div class="alert-item">
|
||||
<span class="alert-text">
|
||||
{{errorMessage}}
|
||||
</span>
|
||||
</div>
|
||||
</clr-alert>
|
||||
<div class="form-group">
|
||||
<label for="destination_name" class="col-md-4">Destination name<span style="color: red">*</span></label>
|
||||
<label class="col-md-8" for="destination_name" aria-haspopup="true" role="tooltip" [class.invalid]="targetName.errors && (targetName.dirty || targetName.touched)" [class.valid]="targetName.valid" class="tooltip tooltip-validation tooltip-sm tooltip-bottom-right">
|
||||
<input type="text" id="destination_name" [(ngModel)]="target.name" name="targetName" size="20" #targetName="ngModel" value="" required>
|
||||
<span class="tooltip-content" *ngIf="targetName.errors && targetName.errors.required && (targetName.dirty || targetName.touched)">
|
||||
Destination name is required.
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="destination_url" class="col-md-4">Destination URL<span style="color: red">*</span></label>
|
||||
<label class="col-md-8" for="destination_url" aria-haspopup="true" role="tooltip" [class.invalid]="targetEndpoint.errors && (targetEndpoint.dirty || targetEndpoint.touched)" [class.valid]="targetEndpoint.valid" class="tooltip tooltip-validation tooltip-sm tooltip-bottom-right">
|
||||
<input type="text" id="destination_url" [disabled]="testOngoing" [(ngModel)]="target.endpoint" size="20" name="endpointUrl" #targetEndpoint="ngModel" required>
|
||||
<span class="tooltip-content" *ngIf="targetEndpoint.errors && targetEndpoint.errors.required && (targetEndpoint.dirty || targetEndpoint.touched)">
|
||||
Destination URL is required.
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="destination_username" class="col-md-4">Username</label>
|
||||
<input type="text" class="col-md-8" id="destination_username" [disabled]="testOngoing" [(ngModel)]="target.username" size="20" name="username" #username="ngModel">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="destination_password" class="col-md-4">Password</label>
|
||||
<input type="password" class="col-md-8" id="destination_password" [disabled]="testOngoing" [(ngModel)]="target.password" size="20" name="password" #password="ngModel">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="spin" class="col-md-4"></label>
|
||||
<span class="col-md-8 spinner spinner-inline" [hidden]="!testOngoing"></span>
|
||||
<span [style.color]="!pingStatus ? 'red': ''">{{ pingTestMessage }}</span>
|
||||
</div>
|
||||
</section>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-outline" (click)="testConnection()" [disabled]="testOngoing || targetEndpoint.errors">Test Connection</button>
|
||||
<button type="button" class="btn btn-outline" (click)="createEditDestinationOpened = false">Cancel</button>
|
||||
<button type="submit" class="btn btn-primary" [disabled]="!targetForm.form.valid" (click)="onSubmit()">Ok</button>
|
||||
</div>
|
||||
</clr-modal>
|
@ -0,0 +1,128 @@
|
||||
import { Component, Output, EventEmitter } from '@angular/core';
|
||||
|
||||
import { ReplicationService } from '../replication.service';
|
||||
import { MessageService } from '../../global-message/message.service';
|
||||
import { AlertType, ActionType } from '../../shared/shared.const';
|
||||
|
||||
import { Target } from '../target';
|
||||
|
||||
|
||||
@Component({
|
||||
selector: 'create-edit-destination',
|
||||
templateUrl: './create-edit-destination.component.html'
|
||||
})
|
||||
export class CreateEditDestinationComponent {
|
||||
|
||||
createEditDestinationOpened: boolean;
|
||||
|
||||
errorMessageOpened: boolean;
|
||||
errorMessage: string;
|
||||
|
||||
testOngoing: boolean;
|
||||
pingTestMessage: string;
|
||||
pingStatus: boolean;
|
||||
|
||||
actionType: ActionType;
|
||||
|
||||
target: Target = new Target();
|
||||
|
||||
@Output() reload = new EventEmitter<boolean>();
|
||||
|
||||
constructor(
|
||||
private replicationService: ReplicationService,
|
||||
private messageService: MessageService) {}
|
||||
|
||||
openCreateEditTarget(targetId?: number) {
|
||||
this.target = new Target();
|
||||
|
||||
this.createEditDestinationOpened = true;
|
||||
|
||||
this.errorMessageOpened = false;
|
||||
this.errorMessage = '';
|
||||
|
||||
this.pingTestMessage = '';
|
||||
this.pingStatus = true;
|
||||
this.testOngoing = false;
|
||||
|
||||
if(targetId) {
|
||||
this.actionType = ActionType.EDIT;
|
||||
this.replicationService
|
||||
.getTarget(targetId)
|
||||
.subscribe(
|
||||
target=>this.target=target,
|
||||
error=>this.messageService
|
||||
.announceMessage(error.status, 'Failed to get target with ID:' + targetId, AlertType.DANGER)
|
||||
);
|
||||
} else {
|
||||
this.actionType = ActionType.ADD_NEW;
|
||||
}
|
||||
}
|
||||
|
||||
testConnection() {
|
||||
this.pingTestMessage = 'Testing connection...';
|
||||
this.pingStatus = true;
|
||||
this.testOngoing = !this.testOngoing;
|
||||
this.replicationService
|
||||
.pingTarget(this.target)
|
||||
.subscribe(
|
||||
response=>{
|
||||
this.pingStatus = true;
|
||||
this.pingTestMessage = 'Connection tested successfully.';
|
||||
this.testOngoing = !this.testOngoing;
|
||||
},
|
||||
error=>{
|
||||
this.pingStatus = false;
|
||||
this.pingTestMessage = 'Failed to ping target.';
|
||||
this.testOngoing = !this.testOngoing;
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
onSubmit() {
|
||||
this.errorMessage = '';
|
||||
this.errorMessageOpened = false;
|
||||
|
||||
switch(this.actionType) {
|
||||
case ActionType.ADD_NEW:
|
||||
this.replicationService
|
||||
.createTarget(this.target)
|
||||
.subscribe(
|
||||
response=>{
|
||||
console.log('Successful added target.');
|
||||
this.createEditDestinationOpened = false;
|
||||
this.reload.emit(true);
|
||||
},
|
||||
error=>{
|
||||
this.errorMessageOpened = true;
|
||||
this.errorMessage = 'Failed to add target:' + error;
|
||||
this.messageService
|
||||
.announceMessage(error.status, this.errorMessage, AlertType.DANGER);
|
||||
}
|
||||
);
|
||||
break;
|
||||
case ActionType.EDIT:
|
||||
this.replicationService
|
||||
.updateTarget(this.target)
|
||||
.subscribe(
|
||||
response=>{
|
||||
console.log('Successful updated target.');
|
||||
this.createEditDestinationOpened = false;
|
||||
this.reload.emit(true);
|
||||
},
|
||||
error=>{
|
||||
this.errorMessageOpened = true;
|
||||
this.errorMessage = 'Failed to update target:' + error;
|
||||
this.messageService
|
||||
.announceMessage(error.status, this.errorMessage, AlertType.DANGER);
|
||||
}
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
onErrorMessageClose(): void {
|
||||
this.errorMessageOpened = false;
|
||||
this.errorMessage = '';
|
||||
}
|
||||
|
||||
}
|
@ -1,54 +0,0 @@
|
||||
<clr-modal [(clrModalOpen)]="createEditPolicyOpened">
|
||||
<h3 class="modal-title">Add Policy</h3>
|
||||
<div class="modal-body">
|
||||
<form>
|
||||
<section class="form-block">
|
||||
<div class="form-group">
|
||||
<label for="policy_name" class="col-md-4">Name</label>
|
||||
<input type="text" class="col-md-8" id="policy_name" size="20">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="policy_description" class="col-md-4">Description</label>
|
||||
<input type="text" class="col-md-8" id="policy_description" size="20">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="col-md-4">Enable</label>
|
||||
<div class="checkbox-inline">
|
||||
<input type="checkbox" id="policy_enable">
|
||||
<label for="policy_enable"></label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="destination_name" class="col-md-4">Destination name</label>
|
||||
<div class="select">
|
||||
<select id="destination_name">
|
||||
<option>10.117.5.114</option>
|
||||
<option>10.117.5.61</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="checkbox-inline">
|
||||
<input type="checkbox" id="check_new">
|
||||
<label for="check_new">New destination</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="destination_url" class="col-md-4">Destination URL</label>
|
||||
<input type="text" class="col-md-8" id="destination_url" size="20">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="destination_username" class="col-md-4">Username</label>
|
||||
<input type="text" class="col-md-8" id="destination_username" size="20">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="destination_password" class="col-md-4">Password</label>
|
||||
<input type="text" class="col-md-8" id="destination_password" size="20">
|
||||
</div>
|
||||
</section>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-outline">Test Connection</button>
|
||||
<button type="button" class="btn btn-outline" (click)="createEditPolicyOpened = false">Cancel</button>
|
||||
<button type="button" class="btn btn-primary" (click)="createEditPolicyOpened = false">Ok</button>
|
||||
</div>
|
||||
</clr-modal>
|
@ -1,19 +0,0 @@
|
||||
import { Component, Input } from '@angular/core';
|
||||
|
||||
import { ReplicationService } from '../replication.service';
|
||||
|
||||
@Component({
|
||||
selector: 'create-edit-policy',
|
||||
templateUrl: 'create-edit-policy.component.html'
|
||||
})
|
||||
export class CreateEditPolicyComponent {
|
||||
|
||||
createEditPolicyOpened: boolean;
|
||||
|
||||
constructor(private replicationService: ReplicationService) {}
|
||||
|
||||
openCreateEditPolicy(): void {
|
||||
console.log('createEditPolicyOpened:' + this.createEditPolicyOpened);
|
||||
this.createEditPolicyOpened = true;
|
||||
}
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
<div class="row">
|
||||
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
|
||||
<div class="row flex-items-xs-between ">
|
||||
<div class="col-xs-4">
|
||||
<button class="btn btn-link" (click)="openModal()"><clr-icon shape="add"></clr-icon> Endpoint</button>
|
||||
<create-edit-destination (reload)="reload($event)"></create-edit-destination>
|
||||
</div>
|
||||
<div class="col-xs-4 push-xs-1">
|
||||
<grid-filter filterPlaceholder='{{"REPLICATION.FILTER_TARGETS_PLACEHOLDER" | translate}}' (filter)="doSearchTargets($event)"></grid-filter>
|
||||
<a href="javascript:void(0)" (click)="refreshTargets()"><clr-icon shape="refresh"></clr-icon></a>
|
||||
</div>
|
||||
</div>
|
||||
<clr-datagrid>
|
||||
<clr-dg-column>Name</clr-dg-column>
|
||||
<clr-dg-column>Destination</clr-dg-column>
|
||||
<clr-dg-column>Creation Time</clr-dg-column>
|
||||
<clr-dg-row *ngFor="let t of targets">
|
||||
<clr-dg-cell>{{t.name}}</clr-dg-cell>
|
||||
<clr-dg-cell>{{t.endpoint}}</clr-dg-cell>
|
||||
<clr-dg-cell>{{t.creation_time}}
|
||||
<harbor-action-overflow>
|
||||
<a href="javascript:void(0)" class="dropdown-item" (click)="editTarget(t)">Edit Target</a>
|
||||
<a href="javascript:void(0)" class="dropdown-item" (click)="deleteTarget(t)">Delete</a>
|
||||
</harbor-action-overflow>
|
||||
</clr-dg-cell>
|
||||
</clr-dg-row>
|
||||
<clr-dg-footer>{{ (targets ? targets.length : 0) }} item(s)</clr-dg-footer>
|
||||
</clr-datagrid>
|
||||
</div>
|
||||
</div>
|
@ -0,0 +1,103 @@
|
||||
import { Component, OnInit, ViewChild, OnDestroy } from '@angular/core';
|
||||
import { Target } from '../target';
|
||||
import { ReplicationService } from '../replication.service';
|
||||
import { MessageService } from '../../global-message/message.service';
|
||||
import { AlertType } from '../../shared/shared.const';
|
||||
|
||||
import { DeletionDialogService } from '../../shared/deletion-dialog/deletion-dialog.service';
|
||||
import { DeletionMessage } from '../../shared/deletion-dialog/deletion-message';
|
||||
|
||||
import { DeletionTargets } from '../../shared/shared.const';
|
||||
|
||||
import { Subscription } from 'rxjs/Subscription';
|
||||
|
||||
import { CreateEditDestinationComponent } from '../create-edit-destination/create-edit-destination.component';
|
||||
|
||||
@Component({
|
||||
selector: 'destination',
|
||||
templateUrl: 'destination.component.html'
|
||||
})
|
||||
export class DestinationComponent implements OnInit {
|
||||
|
||||
@ViewChild(CreateEditDestinationComponent)
|
||||
createEditDestinationComponent: CreateEditDestinationComponent;
|
||||
|
||||
targets: Target[];
|
||||
target: Target;
|
||||
|
||||
targetName: string;
|
||||
subscription : Subscription;
|
||||
|
||||
constructor(
|
||||
private replicationService: ReplicationService,
|
||||
private messageService: MessageService,
|
||||
private deletionDialogService: DeletionDialogService) {
|
||||
this.subscription = this.deletionDialogService.deletionConfirm$.subscribe(message=>{
|
||||
let targetId = message.data;
|
||||
this.replicationService
|
||||
.deleteTarget(targetId)
|
||||
.subscribe(
|
||||
response=>{
|
||||
console.log('Successful deleted target with ID:' + targetId);
|
||||
this.reload();
|
||||
},
|
||||
error=>this.messageService
|
||||
.announceMessage(error.status,
|
||||
'Failed to delete target with ID:' + targetId + ', error:' + error,
|
||||
AlertType.DANGER)
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.targetName = '';
|
||||
this.retrieve('');
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
if(this.subscription) {
|
||||
this.subscription.unsubscribe();
|
||||
}
|
||||
}
|
||||
|
||||
retrieve(targetName: string): void {
|
||||
this.replicationService
|
||||
.listTargets(targetName)
|
||||
.subscribe(
|
||||
targets=>this.targets = targets,
|
||||
error=>this.messageService.announceMessage(error.status,'Failed to get targets:' + error, AlertType.DANGER)
|
||||
);
|
||||
}
|
||||
|
||||
doSearchTargets(targetName: string) {
|
||||
this.targetName = targetName;
|
||||
this.retrieve(targetName);
|
||||
}
|
||||
|
||||
refreshTargets() {
|
||||
this.retrieve('');
|
||||
}
|
||||
|
||||
reload() {
|
||||
this.retrieve(this.targetName);
|
||||
}
|
||||
|
||||
openModal() {
|
||||
this.createEditDestinationComponent.openCreateEditTarget();
|
||||
this.target = new Target();
|
||||
}
|
||||
|
||||
editTarget(target: Target) {
|
||||
if(target) {
|
||||
this.createEditDestinationComponent.openCreateEditTarget(target.id);
|
||||
}
|
||||
}
|
||||
|
||||
deleteTarget(target: Target) {
|
||||
if(target) {
|
||||
let targetId = target.id;
|
||||
let deletionMessage = new DeletionMessage('REPLICATION.DELETION_TITLE_TARGET', 'REPLICATION.DELETION_SUMMARY_TARGET', target.name, target.id, DeletionTargets.TARGET);
|
||||
this.deletionDialogService.openComfirmDialog(deletionMessage);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,23 +0,0 @@
|
||||
import { Directive, ElementRef, HostListener } from '@angular/core';
|
||||
|
||||
export const customColor = 'blue';
|
||||
export const customFontColor = 'white';
|
||||
|
||||
@Directive({
|
||||
selector: '[custom-highlight]'
|
||||
})
|
||||
export class CustomHighlightDirective {
|
||||
constructor(private el: ElementRef) {}
|
||||
|
||||
@HostListener('mouseenter')
|
||||
onMouseEnter(): void {
|
||||
this.el.nativeElement.style.backgroundColor = customColor;
|
||||
this.el.nativeElement.style.color = customFontColor;
|
||||
}
|
||||
|
||||
@HostListener('mouseout')
|
||||
onMouseOut(): void {
|
||||
this.el.nativeElement.style.backgroundColor = null;
|
||||
this.el.nativeElement.style.color = null;
|
||||
}
|
||||
}
|
@ -1,29 +0,0 @@
|
||||
<clr-datagrid>
|
||||
<clr-dg-column>Name</clr-dg-column>
|
||||
<clr-dg-column>Description</clr-dg-column>
|
||||
<clr-dg-column>Destination</clr-dg-column>
|
||||
<clr-dg-column>Last start time</clr-dg-column>
|
||||
<clr-dg-column>Activation</clr-dg-column>
|
||||
<clr-dg-column>Action</clr-dg-column>
|
||||
<clr-dg-row *ngFor="let p of policies" (click)="selectPolicy(p)">
|
||||
<clr-dg-cell>{{p.name}}</clr-dg-cell>
|
||||
<clr-dg-cell>{{p.description}}</clr-dg-cell>
|
||||
<clr-dg-cell>{{p.target_name}}</clr-dg-cell>
|
||||
<clr-dg-cell>{{p.start_time}}</clr-dg-cell>
|
||||
<clr-dg-cell>{{p.enabled}}</clr-dg-cell>
|
||||
<clr-dg-cell>
|
||||
<clr-dropdown [clrMenuPosition]="'bottom-left'">
|
||||
<button class="btn btn-sm btn-link" clrDropdownToggle>
|
||||
Actions
|
||||
<clr-icon shape="caret down"></clr-icon>
|
||||
</button>
|
||||
<div class="dropdown-menu">
|
||||
<a href="javascript:void(0)" clrDropdownItem>Enable</a>
|
||||
<a href="javascript:void(0)" clrDropdownItem>Disable</a>
|
||||
</div>
|
||||
</clr-dropdown>
|
||||
</clr-dg-cell>
|
||||
</clr-dg-row>
|
||||
|
||||
<clr-dg-footer>{{ (policies ? policies.length : 0) }} item(s)</clr-dg-footer>
|
||||
</clr-datagrid>
|
@ -1,21 +0,0 @@
|
||||
import { Component, Input, Output, EventEmitter } from '@angular/core';
|
||||
|
||||
import { ReplicationService } from '../replication.service';
|
||||
import { Policy } from '../policy';
|
||||
|
||||
@Component({
|
||||
selector: 'list-policy',
|
||||
templateUrl: 'list-policy.component.html'
|
||||
})
|
||||
export class ListPolicyComponent {
|
||||
|
||||
@Input() policies: Policy[];
|
||||
@Output() selectOne = new EventEmitter<number>();
|
||||
|
||||
constructor(private replicationService: ReplicationService){}
|
||||
|
||||
selectPolicy(policy: Policy): void {
|
||||
console.log('Select policy ID:' + policy.id);
|
||||
this.selectOne.emit(policy.id);
|
||||
}
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
<h2>Replications</h2>
|
||||
<nav class="subnav">
|
||||
<ul class="nav">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" routerLink="endpoints" routerLinkActive="active">Endpoints</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" routerLink="rules" routerLinkActive="active">Rules</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
<router-outlet></router-outlet>
|
@ -0,0 +1,8 @@
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'replication-management',
|
||||
templateUrl: 'replication-management.component.html',
|
||||
styleUrls: [ 'replication-management.css' ]
|
||||
})
|
||||
export class ReplicationManagementComponent {}
|
@ -0,0 +1,32 @@
|
||||
.custom-h2 {
|
||||
margin-top: 0px !important;
|
||||
}
|
||||
|
||||
.custom-add-button {
|
||||
font-size: medium;
|
||||
margin-left: -12px;
|
||||
}
|
||||
|
||||
.filter-icon {
|
||||
position: relative;
|
||||
right: -12px;
|
||||
}
|
||||
|
||||
.filter-pos {
|
||||
float: right;
|
||||
margin-right: 24px;
|
||||
position: relative;
|
||||
top: 8px;
|
||||
}
|
||||
|
||||
.action-panel-pos {
|
||||
position: relative;
|
||||
top: 20px;
|
||||
}
|
||||
|
||||
.refresh-btn {
|
||||
position: absolute;
|
||||
right: -4px;
|
||||
top: 8px;
|
||||
cursor: pointer;
|
||||
}
|
@ -2,33 +2,47 @@
|
||||
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
|
||||
<div class="row flex-items-xs-between">
|
||||
<div class="col-xs-4">
|
||||
<button class="btn btn-link" (click)="openModal()"><clr-icon shape="add"></clr-icon> Policy</button>
|
||||
<create-edit-policy></create-edit-policy>
|
||||
<button class="btn btn-link" (click)="openModal()"><clr-icon shape="add"></clr-icon> Replication Rule</button>
|
||||
<create-edit-policy [projectId]="projectId" (reload)="reloadPolicies($event)"></create-edit-policy>
|
||||
</div>
|
||||
<div class="col-xs-4">
|
||||
<input type="text" placeholder="Search for policies">
|
||||
</div>
|
||||
</div>
|
||||
<list-policy [policies]="changedPolicies" (selectOne)="fetchPolicyJobs($event)"></list-policy>
|
||||
<div class="row flex-items-xs-between flex-items-xs-bottom">
|
||||
<div class="col-xs-4">
|
||||
<span>Replication Jobs for 'project01/sync_01'</span>
|
||||
</div>
|
||||
<div class="col-xs-4">
|
||||
<div class="col-xs-5 push-xs-1">
|
||||
<clr-dropdown [clrMenuPosition]="'bottom-left'">
|
||||
<button class="btn btn-sm btn-outline-primary" clrDropdownToggle>
|
||||
All
|
||||
<button class="btn btn-link" clrDropdownToggle>
|
||||
{{currentRuleStatus.description}}
|
||||
<clr-icon shape="caret down"></clr-icon>
|
||||
</button>
|
||||
<div class="dropdown-menu">
|
||||
<a href="javascript:void(0)" clrDropdownItem>Finished</a>
|
||||
<a href="javascript:void(0)" clrDropdownItem>Running</a>
|
||||
<a href="javascript:void(0)" clrDropdownItem>Error</a>
|
||||
<a href="javascript:void(0)" clrDropdownItem>Stopped</a>
|
||||
<a href="javascript:void(0)" clrDropdownItem>Retrying</a>
|
||||
<a href="javascript:void(0)" clrDropdownItem *ngFor="let r of ruleStatus" (click)="doFilterPolicyStatus(r.key)"> {{r.description}}</a>
|
||||
</div>
|
||||
</clr-dropdown>
|
||||
<input type="text" placeholder="Search for jobs">
|
||||
<grid-filter filterPlaceholder='{{"REPLICATION.FILTER_POLICIES_PLACEHOLDER" | translate}}' (filter)="doSearchPolicies($event)"></grid-filter>
|
||||
<a href="javascript:void(0)" (click)="refreshPolicies()"><clr-icon shape="refresh"></clr-icon></a>
|
||||
</div>
|
||||
</div>
|
||||
<list-policy [policies]="changedPolicies" [projectless]="false" (selectOne)="selectOne($event)" (editOne)="openEditPolicy($event)" (reload)="reloadPolicies($event)"></list-policy>
|
||||
<div class="row flex-items-xs-between flex-items-xs-bottom">
|
||||
<div class="col-xs-4">
|
||||
<span>Replication Jobs</span>
|
||||
</div>
|
||||
<div class="col-xs-4">
|
||||
<button class="btn btn-link" (click)="toggleSearchJobOptionalName(currentJobSearchOption)">{{toggleJobSearchOption[currentJobSearchOption]}}</button>
|
||||
<grid-filter filterPlaceholder='{{"REPLICATION.FILTER_POLICIES_PLACEHOLDER" | translate}}' (filter)="doSearchJobs($event)"></grid-filter>
|
||||
<a href="javascript:void(0)" (click)="refreshJobs()"><clr-icon shape="refresh"></clr-icon></a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row flex-items-xs-right" [hidden]="currentJobSearchOption === 0">
|
||||
<clr-dropdown [clrMenuPosition]="'bottom-left'">
|
||||
<button class="btn btn-link" clrDropdownToggle>
|
||||
{{currentJobStatus.description}}
|
||||
<clr-icon shape="caret down"></clr-icon>
|
||||
</button>
|
||||
<div class="dropdown-menu">
|
||||
<a href="javascript:void(0)" clrDropdownItem *ngFor="let j of jobStatus" (click)="doFilterJobStatus(j.key)"> {{j.description}}</a>
|
||||
</div>
|
||||
</clr-dropdown>
|
||||
<div class="flex-items-xs-bottom">
|
||||
<clr-icon shape="date"></clr-icon><input type="date" #fromTime (change)="doJobSearchByTimeRange(fromTime.value, 'begin')">
|
||||
<clr-icon shape="date"></clr-icon><input type="date" #toTime (change)="doJobSearchByTimeRange(toTime.value, 'end')">
|
||||
</div>
|
||||
</div>
|
||||
<list-job [jobs]="changedJobs"></list-job>
|
||||
|
@ -1,11 +1,13 @@
|
||||
import { Component, OnInit, ViewChild } from '@angular/core';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
|
||||
import { CreateEditPolicyComponent } from './create-edit-policy/create-edit-policy.component';
|
||||
import { CreateEditPolicyComponent } from '../shared/create-edit-policy/create-edit-policy.component';
|
||||
|
||||
import { MessageService } from '../global-message/message.service';
|
||||
import { AlertType } from '../shared/shared.const';
|
||||
|
||||
import { SessionService } from '../shared/session.service';
|
||||
|
||||
import { ReplicationService } from './replication.service';
|
||||
|
||||
import { SessionUser } from '../shared/session-user';
|
||||
@ -13,6 +15,34 @@ import { Policy } from './policy';
|
||||
import { Job } from './job';
|
||||
import { Target } from './target';
|
||||
|
||||
const ruleStatus = [
|
||||
{ 'key': '', 'description': 'All Status'},
|
||||
{ 'key': '1', 'description': 'Enabled'},
|
||||
{ 'key': '0', 'description': 'Disabled'}
|
||||
];
|
||||
|
||||
const jobStatus = [
|
||||
{ 'key': '', 'description': 'All' },
|
||||
{ 'key': 'pending', 'description': 'Pending' },
|
||||
{ 'key': 'running', 'description': 'Running' },
|
||||
{ 'key': 'error', 'description': 'Error' },
|
||||
{ 'key': 'retrying', 'description': 'Retrying' },
|
||||
{ 'key': 'stopped' , 'description': 'Stopped' },
|
||||
{ 'key': 'finished', 'description': 'Finished' },
|
||||
{ 'key': 'canceled', 'description': 'Canceled' }
|
||||
];
|
||||
|
||||
const optionalSearch: {} = {0: 'Advanced', 1: 'Simple'};
|
||||
|
||||
class SearchOption {
|
||||
policyId: number;
|
||||
policyName: string = '';
|
||||
repoName: string = '';
|
||||
status: string = '';
|
||||
startTime: string = '';
|
||||
endTime: string = '';
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'replicaton',
|
||||
templateUrl: 'replication.component.html'
|
||||
@ -22,34 +52,55 @@ export class ReplicationComponent implements OnInit {
|
||||
currentUser: SessionUser;
|
||||
projectId: number;
|
||||
|
||||
policyName: string;
|
||||
|
||||
policy: Policy;
|
||||
|
||||
search: SearchOption;
|
||||
|
||||
ruleStatus = ruleStatus;
|
||||
currentRuleStatus: {key: string, description: string};
|
||||
|
||||
jobStatus = jobStatus;
|
||||
currentJobStatus: {key: string, description: string};
|
||||
|
||||
changedPolicies: Policy[];
|
||||
changedJobs: Job[];
|
||||
|
||||
@ViewChild(CreateEditPolicyComponent)
|
||||
createEditPolicyComponent: CreateEditPolicyComponent
|
||||
policies: Policy[];
|
||||
jobs: Job[];
|
||||
|
||||
constructor(private route: ActivatedRoute, private messageService: MessageService, private replicationService: ReplicationService) {
|
||||
this.route.data.subscribe(data=>this.currentUser = <SessionUser>data);
|
||||
toggleJobSearchOption = optionalSearch;
|
||||
currentJobSearchOption: number;
|
||||
|
||||
@ViewChild(CreateEditPolicyComponent)
|
||||
createEditPolicyComponent: CreateEditPolicyComponent;
|
||||
|
||||
constructor(
|
||||
private sessionService: SessionService,
|
||||
private messageService: MessageService,
|
||||
private replicationService: ReplicationService,
|
||||
private route: ActivatedRoute) {
|
||||
this.currentUser = this.sessionService.getCurrentUser();
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.projectId = +this.route.snapshot.parent.params['id'];
|
||||
console.log('Get projectId from route params snapshot:' + this.projectId);
|
||||
this.search = new SearchOption();
|
||||
this.currentRuleStatus = this.ruleStatus[0];
|
||||
this.currentJobStatus = this.jobStatus[0];
|
||||
this.currentJobSearchOption = 0;
|
||||
this.retrievePolicies();
|
||||
}
|
||||
|
||||
retrievePolicies(): void {
|
||||
this.replicationService
|
||||
.listPolicies(this.projectId, this.policyName)
|
||||
.listPolicies(this.search.policyName, this.projectId)
|
||||
.subscribe(
|
||||
response=>{
|
||||
this.changedPolicies = response;
|
||||
this.policies = this.changedPolicies;
|
||||
if(this.changedPolicies && this.changedPolicies.length > 0) {
|
||||
this.fetchPolicyJobs(this.changedPolicies[0].id);
|
||||
} else {
|
||||
this.changedJobs = [];
|
||||
}
|
||||
},
|
||||
error=>this.messageService.announceMessage(error.status,'Failed to get policies with project ID:' + this.projectId, AlertType.DANGER)
|
||||
@ -57,17 +108,98 @@ export class ReplicationComponent implements OnInit {
|
||||
}
|
||||
|
||||
openModal(): void {
|
||||
console.log('Open modal to create policy.');
|
||||
this.createEditPolicyComponent.openCreateEditPolicy();
|
||||
console.log('Clicked open create-edit policy.');
|
||||
}
|
||||
|
||||
fetchPolicyJobs(policyId: number) {
|
||||
console.log('Received policy ID ' + policyId + ' by clicked row.');
|
||||
openEditPolicy(policyId: number) {
|
||||
console.log('Open modal to edit policy ID:' + policyId);
|
||||
this.createEditPolicyComponent.openCreateEditPolicy(policyId);
|
||||
}
|
||||
|
||||
fetchPolicyJobs(policyId: number) {
|
||||
this.search.policyId = policyId;
|
||||
console.log('Received policy ID ' + this.search.policyId + ' by clicked row.');
|
||||
this.replicationService
|
||||
.listJobs(policyId)
|
||||
.listJobs(this.search.policyId, this.search.status, this.search.repoName, this.search.startTime, this.search.endTime)
|
||||
.subscribe(
|
||||
response=>this.changedJobs = response,
|
||||
error=>this.messageService.announceMessage(error.status, 'Failed to fetch jobs with policy ID:' + policyId, AlertType.DANGER)
|
||||
response=>{
|
||||
this.changedJobs = response;
|
||||
this.jobs = this.changedJobs;
|
||||
},
|
||||
error=>this.messageService.announceMessage(error.status, 'Failed to fetch jobs with policy ID:' + this.search.policyId, AlertType.DANGER)
|
||||
);
|
||||
}
|
||||
|
||||
selectOne(policy: Policy) {
|
||||
if(policy) {
|
||||
this.fetchPolicyJobs(policy.id);
|
||||
}
|
||||
}
|
||||
|
||||
doSearchPolicies(policyName: string) {
|
||||
this.search.policyName = policyName;
|
||||
this.retrievePolicies();
|
||||
}
|
||||
|
||||
doFilterPolicyStatus(status: string) {
|
||||
console.log('Do filter policies with status:' + status);
|
||||
this.currentRuleStatus = this.ruleStatus.find(r=>r.key === status);
|
||||
if(status.trim() === '') {
|
||||
this.changedPolicies = this.policies;
|
||||
} else {
|
||||
this.changedPolicies = this.policies.filter(policy=>policy.enabled === +this.currentRuleStatus.key);
|
||||
}
|
||||
}
|
||||
|
||||
doFilterJobStatus(status: string) {
|
||||
console.log('Do filter jobs with status:' + status);
|
||||
this.currentJobStatus = this.jobStatus.find(r=>r.key === status);
|
||||
if(status.trim() === '') {
|
||||
this.changedJobs = this.jobs;
|
||||
} else {
|
||||
this.changedJobs = this.jobs.filter(job=>job.status === status);
|
||||
}
|
||||
}
|
||||
|
||||
doSearchJobs(repoName: string) {
|
||||
this.search.repoName = repoName;
|
||||
this.fetchPolicyJobs(this.search.policyId);
|
||||
}
|
||||
|
||||
reloadPolicies(isReady: boolean) {
|
||||
if(isReady) {
|
||||
this.retrievePolicies();
|
||||
}
|
||||
}
|
||||
|
||||
refreshPolicies() {
|
||||
this.retrievePolicies();
|
||||
}
|
||||
|
||||
refreshJobs() {
|
||||
this.fetchPolicyJobs(this.search.policyId);
|
||||
}
|
||||
|
||||
toggleSearchJobOptionalName(option: number) {
|
||||
(option === 1) ? this.currentJobSearchOption = 0 : this.currentJobSearchOption = 1;
|
||||
}
|
||||
|
||||
doJobSearchByTimeRange(strDate: string, target: string) {
|
||||
if(!strDate || strDate.trim() === '') {
|
||||
strDate = 0 + '';
|
||||
}
|
||||
let oneDayOffset = 3600 * 24;
|
||||
switch(target) {
|
||||
case 'begin':
|
||||
this.search.startTime = (new Date(strDate).getTime() / 1000) + '';
|
||||
break;
|
||||
case 'end':
|
||||
this.search.endTime = (new Date(strDate).getTime() / 1000 + oneDayOffset) + '';
|
||||
break;
|
||||
}
|
||||
console.log('Search jobs filtered by time range, begin: ' + this.search.startTime + ', end:' + this.search.endTime);
|
||||
this.fetchPolicyJobs(this.search.policyId);
|
||||
}
|
||||
|
||||
}
|
@ -1,23 +1,28 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { RouterModule } from '@angular/router';
|
||||
import { ReplicationManagementComponent } from './replication-management/replication-management.component';
|
||||
|
||||
import { ReplicationComponent } from './replication.component';
|
||||
import { CreateEditPolicyComponent } from './create-edit-policy/create-edit-policy.component';
|
||||
import { ListPolicyComponent } from './list-policy/list-policy.component';
|
||||
import { ListJobComponent } from './list-job/list-job.component';
|
||||
|
||||
import { CustomHighlightDirective } from './list-policy/custom-highlight.directive';
|
||||
import { TotalReplicationComponent } from './total-replication/total-replication.component';
|
||||
import { DestinationComponent } from './destination/destination.component';
|
||||
import { CreateEditDestinationComponent } from './create-edit-destination/create-edit-destination.component';
|
||||
|
||||
import { SharedModule } from '../shared/shared.module';
|
||||
import { ReplicationService } from './replication.service';
|
||||
|
||||
@NgModule({
|
||||
imports: [ SharedModule ],
|
||||
imports: [
|
||||
SharedModule,
|
||||
RouterModule
|
||||
],
|
||||
declarations: [
|
||||
ReplicationComponent,
|
||||
CreateEditPolicyComponent,
|
||||
ListPolicyComponent,
|
||||
ReplicationManagementComponent,
|
||||
ListJobComponent,
|
||||
CustomHighlightDirective
|
||||
TotalReplicationComponent,
|
||||
DestinationComponent,
|
||||
CreateEditDestinationComponent
|
||||
],
|
||||
exports: [ ReplicationComponent ],
|
||||
providers: [ ReplicationService ]
|
||||
|
@ -1,17 +1,17 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Http, URLSearchParams } from '@angular/http';
|
||||
import { Http, URLSearchParams, Response } from '@angular/http';
|
||||
|
||||
import { BaseService } from '../service/base.service';
|
||||
|
||||
import { Policy } from './policy';
|
||||
import { Job } from './job';
|
||||
import { Target } from './target';
|
||||
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import 'rxjs/add/operator/catch';
|
||||
import 'rxjs/add/operator/map';
|
||||
import 'rxjs/add/observable/throw';
|
||||
|
||||
export const urlPrefix = '';
|
||||
import 'rxjs/add/operator/mergeMap';
|
||||
|
||||
@Injectable()
|
||||
export class ReplicationService extends BaseService {
|
||||
@ -19,20 +19,154 @@ export class ReplicationService extends BaseService {
|
||||
super();
|
||||
}
|
||||
|
||||
listPolicies(projectId: number, policyName: string): Observable<Policy[]> {
|
||||
listPolicies(policyName: string, projectId?: any): Observable<Policy[]> {
|
||||
if(!projectId) {
|
||||
projectId = '';
|
||||
}
|
||||
console.log('Get policies with project ID:' + projectId + ', policy name:' + policyName);
|
||||
return this.http
|
||||
.get(urlPrefix + `/api/policies/replication?project_id=${projectId}`)
|
||||
.get(`/api/policies/replication?project_id=${projectId}&name=${policyName}`)
|
||||
.map(response=>response.json() as Policy[])
|
||||
.catch(error=>Observable.throw(error));
|
||||
}
|
||||
|
||||
// /api/jobs/replication/?page=1&page_size=20&end_time=&policy_id=1&start_time=&status=
|
||||
listJobs(policyId: number, status: string = ''): Observable<Job[]> {
|
||||
getPolicy(policyId: number): Observable<Policy> {
|
||||
console.log('Get policy with ID:' + policyId);
|
||||
return this.http
|
||||
.get(`/api/policies/replication/${policyId}`)
|
||||
.map(response=>response.json() as Policy)
|
||||
.catch(error=>Observable.throw(error));
|
||||
}
|
||||
|
||||
createPolicy(policy: Policy): Observable<any> {
|
||||
console.log('Create policy with project ID:' + policy.project_id + ', policy:' + JSON.stringify(policy));
|
||||
return this.http
|
||||
.post(`/api/policies/replication`, JSON.stringify(policy))
|
||||
.map(response=>response.status)
|
||||
.catch(error=>Observable.throw(error));
|
||||
}
|
||||
|
||||
updatePolicy(policy: Policy): Observable<any> {
|
||||
if (policy && policy.id) {
|
||||
return this.http
|
||||
.put(`/api/policies/replication/${policy.id}`, JSON.stringify(policy))
|
||||
.map(response=>response.status)
|
||||
.catch(error=>Observable.throw(error));
|
||||
}
|
||||
return Observable.throw(new Error("Policy is nil or has no ID set."));
|
||||
}
|
||||
|
||||
createOrUpdatePolicyWithNewTarget(policy: Policy, target: Target): Observable<any> {
|
||||
return this.http
|
||||
.post(`/api/targets`, JSON.stringify(target))
|
||||
.map(response=>{
|
||||
return response.status;
|
||||
})
|
||||
.flatMap((status)=>{
|
||||
if(status === 201) {
|
||||
return this.http
|
||||
.get(`/api/targets?name=${target.name}`)
|
||||
.map(res=>res)
|
||||
.catch(error=>Observable.throw(error));
|
||||
}
|
||||
})
|
||||
.flatMap((res: Response) => {
|
||||
if(res.status === 200) {
|
||||
let lastAddedTarget= <Target>res.json()[0];
|
||||
if(lastAddedTarget && lastAddedTarget.id) {
|
||||
policy.target_id = lastAddedTarget.id;
|
||||
if(policy.id) {
|
||||
return this.http
|
||||
.put(`/api/policies/replication/${policy.id}`, JSON.stringify(policy))
|
||||
.map(response=>response.status)
|
||||
.catch(error=>Observable.throw(error));
|
||||
} else {
|
||||
return this.http
|
||||
.post(`/api/policies/replication`, JSON.stringify(policy))
|
||||
.map(response=>response.status)
|
||||
.catch(error=>Observable.throw(error));
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch(error=>Observable.throw(error));
|
||||
}
|
||||
|
||||
enablePolicy(policyId: number, enabled: number): Observable<any> {
|
||||
console.log('Enable or disable policy ID:' + policyId + ' with activation status:' + enabled);
|
||||
return this.http
|
||||
.put(`/api/policies/replication/${policyId}/enablement`, {enabled: enabled})
|
||||
.map(response=>response.status)
|
||||
.catch(error=>Observable.throw(error));
|
||||
}
|
||||
|
||||
deletePolicy(policyId: number): Observable<any> {
|
||||
console.log('Delete policy ID:' + policyId);
|
||||
return this.http
|
||||
.delete(`/api/policies/replication/${policyId}`)
|
||||
.map(response=>response.status)
|
||||
.catch(error=>Observable.throw(error));
|
||||
}
|
||||
|
||||
// /api/jobs/replication/?page=1&page_size=20&end_time=&policy_id=1&start_time=&status=&repository=
|
||||
listJobs(policyId: number, status: string = '', repoName: string = '', startTime: string = '', endTime: string = ''): Observable<Job[]> {
|
||||
console.log('Get jobs under policy ID:' + policyId);
|
||||
return this.http
|
||||
.get(urlPrefix + `/api/jobs/replication?policy_id=${policyId}&status=${status}`)
|
||||
.get(`/api/jobs/replication?policy_id=${policyId}&status=${status}&repository=${repoName}&start_time=${startTime}&end_time=${endTime}`)
|
||||
.map(response=>response.json() as Job[])
|
||||
.catch(error=>Observable.throw(error));
|
||||
}
|
||||
|
||||
listTargets(targetName: string): Observable<Target[]> {
|
||||
console.log('Get targets.');
|
||||
return this.http
|
||||
.get(`/api/targets?name=${targetName}`)
|
||||
.map(response=>response.json() as Target[])
|
||||
.catch(error=>Observable.throw(error));
|
||||
}
|
||||
|
||||
getTarget(targetId: number): Observable<Target> {
|
||||
console.log('Get target by ID:' + targetId);
|
||||
return this.http
|
||||
.get(`/api/targets/${targetId}`)
|
||||
.map(response=>response.json() as Target)
|
||||
.catch(error=>Observable.throw(error));
|
||||
}
|
||||
|
||||
createTarget(target: Target): Observable<any> {
|
||||
console.log('Create target:' + JSON.stringify(target));
|
||||
return this.http
|
||||
.post(`/api/targets`, JSON.stringify(target))
|
||||
.map(response=>response.status)
|
||||
.catch(error=>Observable.throw(error));
|
||||
}
|
||||
|
||||
pingTarget(target: Target): Observable<any> {
|
||||
console.log('Ping target.');
|
||||
let body = new URLSearchParams();
|
||||
body.set('endpoint', target.endpoint);
|
||||
body.set('username', target.username);
|
||||
body.set('password', target.password);
|
||||
return this.http
|
||||
.post(`/api/targets/ping`, body)
|
||||
.map(response=>response.status)
|
||||
.catch(error=>Observable.throw(error));
|
||||
}
|
||||
|
||||
updateTarget(target: Target): Observable<any> {
|
||||
console.log('Update target with target ID' + target.id);
|
||||
return this.http
|
||||
.put(`/api/targets/${target.id}`, JSON.stringify(target))
|
||||
.map(response=>response.status)
|
||||
.catch(error=>Observable.throw(error));
|
||||
}
|
||||
|
||||
deleteTarget(targetId: number): Observable<any> {
|
||||
console.log('Deleting target with ID:' + targetId);
|
||||
return this.http
|
||||
.delete(`/api/targets/${targetId}`)
|
||||
.map(response=>response.status)
|
||||
.catch(error=>Observable.throw(error));
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
<div class="row">
|
||||
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
|
||||
<div class="row flex-items-xs-right">
|
||||
<div class="col-xs-4 push-xs-1">
|
||||
<grid-filter filterPlaceholder='{{"REPLICATION.FILTER_POLICIES_PLACEHOLDER" | translate}}' (filter)="doSearchPolicies($event)"></grid-filter>
|
||||
<a href="javascript:void(0)" (click)="refreshPolicies()"><clr-icon shape="refresh"></clr-icon></a>
|
||||
</div>
|
||||
</div>
|
||||
<create-edit-policy [projectId]="projectId" (reload)="reloadPolicies($event)"></create-edit-policy>
|
||||
<list-policy [policies]="changedPolicies" [projectless]="true" (editOne)="openEditPolicy($event)" (selectOne)="selectPolicy($event)" (reload)="reloadPolicies($event)"></list-policy>
|
||||
</div>
|
||||
</div>
|
@ -0,0 +1,71 @@
|
||||
import { Component, OnInit, ViewChild } from '@angular/core';
|
||||
import { ReplicationService } from '../../replication/replication.service';
|
||||
|
||||
import { CreateEditPolicyComponent } from '../../shared/create-edit-policy/create-edit-policy.component';
|
||||
|
||||
import { MessageService } from '../../global-message/message.service';
|
||||
import { AlertType } from '../../shared/shared.const';
|
||||
|
||||
import { Policy } from '../../replication/policy';
|
||||
|
||||
@Component({
|
||||
selector: 'total-replication',
|
||||
templateUrl: 'total-replication.component.html',
|
||||
providers: [ ReplicationService ]
|
||||
})
|
||||
export class TotalReplicationComponent implements OnInit {
|
||||
|
||||
changedPolicies: Policy[];
|
||||
policies: Policy[];
|
||||
policyName: string = '';
|
||||
projectId: number;
|
||||
|
||||
@ViewChild(CreateEditPolicyComponent)
|
||||
createEditPolicyComponent: CreateEditPolicyComponent;
|
||||
|
||||
constructor(
|
||||
private replicationService: ReplicationService,
|
||||
private messageService: MessageService) {}
|
||||
|
||||
ngOnInit() {
|
||||
this.retrievePolicies();
|
||||
}
|
||||
|
||||
retrievePolicies(): void {
|
||||
this.replicationService
|
||||
.listPolicies(this.policyName)
|
||||
.subscribe(
|
||||
response=>{
|
||||
this.changedPolicies = response;
|
||||
this.policies = this.changedPolicies;
|
||||
},
|
||||
error=>this.messageService.announceMessage(error.status,'Failed to get policies.', AlertType.DANGER)
|
||||
);
|
||||
}
|
||||
|
||||
doSearchPolicies(policyName: string) {
|
||||
this.policyName = policyName;
|
||||
this.retrievePolicies();
|
||||
}
|
||||
|
||||
openEditPolicy(policyId: number) {
|
||||
console.log('Open modal to edit policy ID:' + policyId);
|
||||
this.createEditPolicyComponent.openCreateEditPolicy(policyId);
|
||||
}
|
||||
|
||||
selectPolicy(policy: Policy) {
|
||||
if(policy) {
|
||||
this.projectId = policy.project_id;
|
||||
}
|
||||
}
|
||||
|
||||
refreshPolicies() {
|
||||
this.retrievePolicies();
|
||||
}
|
||||
|
||||
reloadPolicies(isReady: boolean) {
|
||||
if(isReady) {
|
||||
this.retrievePolicies();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
<div class="row">
|
||||
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
|
||||
<div class="row flex-items-lg-right">
|
||||
<div class="col-lg-3 col-md-3 col-sm-12 col-xs-12">
|
||||
<div class="row flex-items-xs-between">
|
||||
<div class="col-xs-4 flex-xs-middle">
|
||||
<clr-dropdown [clrMenuPosition]="'bottom-left'">
|
||||
<button class="btn btn-sm btn-outline-primary" clrDropdownToggle>
|
||||
My Projects
|
||||
@ -12,6 +12,9 @@
|
||||
<a href="#/project" clrDropdownItem>Public Projects</a>
|
||||
</div>
|
||||
</clr-dropdown>
|
||||
|
||||
</div>
|
||||
<div class="col-xs-4 flex-xs-middle">
|
||||
<input type="text" placeholder="Search for projects">
|
||||
</div>
|
||||
</div>
|
||||
|
@ -0,0 +1,37 @@
|
||||
.margin-left-override {
|
||||
margin-left: 24px !important;
|
||||
}
|
||||
|
||||
.about-text-link {
|
||||
font-family: "Proxima Nova Light";
|
||||
font-size: 14px;
|
||||
color: #007CBB;
|
||||
line-height: 24px;
|
||||
}
|
||||
|
||||
.about-copyright-text {
|
||||
font-family: "Proxima Nova Light";
|
||||
font-size: 13px;
|
||||
color: #565656;
|
||||
line-height: 16px;
|
||||
}
|
||||
|
||||
.about-product-title {
|
||||
font-family: "Metropolis Light";
|
||||
font-size: 28px;
|
||||
color: #000000;
|
||||
line-height: 36px;
|
||||
}
|
||||
|
||||
.about-version {
|
||||
font-family: "Metropolis";
|
||||
font-size: 14px;
|
||||
color: #565656;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.about-build {
|
||||
font-family: "Metropolis";
|
||||
font-size: 14px;
|
||||
color: #565656;
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
<clr-modal [(clrModalOpen)]="opened" [clrModalClosable]="true" [clrModalStaticBackdrop]="false">
|
||||
<h3 class="modal-title margin-left-override">vmware</h3>
|
||||
<div class="modal-body margin-left-override">
|
||||
<div class="about-product-title">Harbor</div>
|
||||
<div style="height: 12px;"></div>
|
||||
<div>
|
||||
<span class="about-version">{{'ABOUT.VERSION' | translate}} {{version}}</span>
|
||||
<span>|</span>
|
||||
<span class="about-build">{{'ABOUT.BUILD' | translate}} {{build}}</span>
|
||||
</div>
|
||||
<div style="height: 12px;"></div>
|
||||
<div>
|
||||
<p class="about-copyright-text">{{'ABOUT.COPYRIGHT' | translate}} <a href="http://www.vmware.com/go/patents" target="_blank" class="about-text-link">http://www.vmware.com/go/patents</a></p>
|
||||
<p class="about-copyright-text">{{'ABOUT.TRADEMARK' | translate}}</p>
|
||||
<p>
|
||||
<a href="#" target="_blank" class="about-text-link">{{'ABOUT.END_USER_LICENSE' | translate}}</a><br>
|
||||
<a href="#" target="_blank" class="about-text-link">{{'ABOUT.OPEN_SOURCE_LICENSE' | translate}}</a>
|
||||
</p>
|
||||
<div style="height: 24px;"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer margin-left-override">
|
||||
<button type="button" class="btn btn-primary" (click)="close()">{{'BUTTON.CLOSE' | translate}}</button>
|
||||
</div>
|
||||
</clr-modal>
|
@ -0,0 +1,20 @@
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'about-dialog',
|
||||
templateUrl: "about-dialog.component.html",
|
||||
styleUrls: ["about-dialog.component.css"]
|
||||
})
|
||||
export class AboutDialogComponent {
|
||||
private opened: boolean = false;
|
||||
private version: string ="0.4.1";
|
||||
private build: string ="4276418";
|
||||
|
||||
public open(): void {
|
||||
this.opened = true;
|
||||
}
|
||||
|
||||
public close(): void {
|
||||
this.opened = false;
|
||||
}
|
||||
}
|
@ -0,0 +1,81 @@
|
||||
<clr-modal [(clrModalOpen)]="createEditPolicyOpened">
|
||||
<h3 class="modal-title">New Replication Rule</h3>
|
||||
<div class="modal-body">
|
||||
<form #policyForm="ngForm">
|
||||
<section class="form-block">
|
||||
<clr-alert [clrAlertType]="'alert-danger'" [(clrAlertClosed)]="!errorMessageOpened" (clrAlertClosedChange)="onErrorMessageClose()">
|
||||
<div class="alert-item">
|
||||
<span class="alert-text">
|
||||
{{errorMessage}}
|
||||
</span>
|
||||
</div>
|
||||
</clr-alert>
|
||||
<div class="form-group">
|
||||
<label for="policy_name" class="col-md-4">Name<span style="color: red">*</span></label>
|
||||
<label for="policy_name" class="col-md-8" aria-haspopup="true" role="tooltip" [class.invalid]="name.errors && (name.dirty || name.touched)" [class.valid]="name.valid" class="tooltip tooltip-validation tooltip-sm tooltip-bottom-right">
|
||||
<input type="text" id="policy_name" [(ngModel)]="createEditPolicy.name" name="name" #name="ngModel" required>
|
||||
<span class="tooltip-content" *ngIf="name.errors && name.errors.required && (name.dirty || name.touched)">
|
||||
Name is required
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="policy_description" class="col-md-4">Description</label>
|
||||
<input type="text" class="col-md-8" id="policy_description" [(ngModel)]="createEditPolicy.description" name="description" size="20" #description="ngModel">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="col-md-4">Enable</label>
|
||||
<div class="checkbox-inline">
|
||||
<input type="checkbox" id="policy_enable" [(ngModel)]="createEditPolicy.enable" name="enable" #enable="ngModel">
|
||||
<label for="policy_enable"></label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="destination_name" class="col-md-4">Destination name<span style="color: red">*</span></label>
|
||||
<div class="select" *ngIf="!isCreateDestination">
|
||||
<select id="destination_name" [(ngModel)]="createEditPolicy.targetId" name="targetId" (change)="selectTarget()" [disabled]="testOngoing">
|
||||
<option *ngFor="let t of targets" [value]="t.id" [selected]="t.id == createEditPolicy.targetId">{{t.name}}</option>
|
||||
</select>
|
||||
</div>
|
||||
<label class="col-md-8" *ngIf="isCreateDestination" for="destination_name" aria-haspopup="true" role="tooltip" [class.invalid]="targetName.errors && (targetName.dirty || targetName.touched)" [class.valid]="targetName.valid" class="tooltip tooltip-validation tooltip-sm tooltip-bottom-right">
|
||||
<input type="text" id="destination_name" [(ngModel)]="createEditPolicy.targetName" name="targetName" size="20" #targetName="ngModel" value="" required>
|
||||
<span class="tooltip-content" *ngIf="targetName.errors && targetName.errors.required && (targetName.dirty || targetName.touched)">
|
||||
Destination name is required.
|
||||
</span>
|
||||
</label>
|
||||
<div class="checkbox-inline">
|
||||
<input type="checkbox" id="check_new" (click)="newDestination(checkedAddNew.checked)" #checkedAddNew [checked]="isCreateDestination" [disabled]="testOngoing">
|
||||
<label for="check_new">New destination</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="destination_url" class="col-md-4">Destination URL<span style="color: red">*</span></label>
|
||||
<label for="destination_url" class="col-md-8" aria-haspopup="true" role="tooltip" [class.invalid]="endpointUrl.errors && (endpointUrl.dirty || endpointUrl.touched)" [class.valid]="endpointUrl.valid" class="tooltip tooltip-validation tooltip-sm tooltip-bottom-right">
|
||||
<input type="text" id="destination_url" [disabled]="testOngoing" [(ngModel)]="createEditPolicy.endpointUrl" size="20" name="endpointUrl" required #endpointUrl="ngModel">
|
||||
<span class="tooltip-content" *ngIf="endpointUrl.errors && endpointUrl.errors.required && (endpointUrl.dirty || endpointUrl.touched)">
|
||||
Destination URL is required.
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="destination_username" class="col-md-4">Username</label>
|
||||
<input type="text" class="col-md-8" id="destination_username" [disabled]="testOngoing" [(ngModel)]="createEditPolicy.username" size="20" name="username" #username="ngModel">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="destination_password" class="col-md-4">Password</label>
|
||||
<input type="password" class="col-md-8" id="destination_password" [disabled]="testOngoing" [(ngModel)]="createEditPolicy.password" size="20" name="password" #password="ngModel">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="spin" class="col-md-4"></label>
|
||||
<span class="col-md-8 spinner spinner-inline" [hidden]="!testOngoing"></span>
|
||||
<span [style.color]="!pingStatus ? 'red': ''">{{ pingTestMessage }}</span>
|
||||
</div>
|
||||
</section>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-outline" (click)="testConnection()" [disabled]="testOngoing">Test Connection</button>
|
||||
<button type="button" class="btn btn-outline" (click)="createEditPolicyOpened = false">Cancel</button>
|
||||
<button type="submit" class="btn btn-primary" [disabled]="!policyForm.form.valid" (click)="onSubmit()">Ok</button>
|
||||
</div>
|
||||
</clr-modal>
|
@ -0,0 +1,228 @@
|
||||
import { Component, Input, Output, EventEmitter, OnInit, HostBinding } from '@angular/core';
|
||||
|
||||
import { CreateEditPolicy } from './create-edit-policy';
|
||||
|
||||
import { ReplicationService } from '../../replication/replication.service';
|
||||
import { MessageService } from '../../global-message/message.service';
|
||||
import { AlertType, ActionType } from '../../shared/shared.const';
|
||||
|
||||
import { Policy } from '../../replication/policy';
|
||||
import { Target } from '../../replication/target';
|
||||
|
||||
@Component({
|
||||
selector: 'create-edit-policy',
|
||||
templateUrl: 'create-edit-policy.component.html'
|
||||
})
|
||||
export class CreateEditPolicyComponent implements OnInit {
|
||||
|
||||
createEditPolicyOpened: boolean;
|
||||
createEditPolicy: CreateEditPolicy = new CreateEditPolicy();
|
||||
|
||||
actionType: ActionType;
|
||||
|
||||
errorMessageOpened: boolean;
|
||||
errorMessage: string;
|
||||
|
||||
isCreateDestination: boolean;
|
||||
@Input() projectId: number;
|
||||
|
||||
@Output() reload = new EventEmitter();
|
||||
|
||||
targets: Target[];
|
||||
|
||||
pingTestMessage: string;
|
||||
testOngoing: boolean;
|
||||
pingStatus: boolean;
|
||||
|
||||
constructor(private replicationService: ReplicationService,
|
||||
private messageService: MessageService) {}
|
||||
|
||||
prepareTargets(targetId?: number) {
|
||||
this.replicationService
|
||||
.listTargets('')
|
||||
.subscribe(
|
||||
targets=>{
|
||||
this.targets = targets;
|
||||
if(this.targets && this.targets.length > 0) {
|
||||
let initialTarget: Target;
|
||||
(targetId) ? initialTarget = this.targets.find(t=>t.id==targetId) : initialTarget = this.targets[0];
|
||||
this.createEditPolicy.targetId = initialTarget.id;
|
||||
this.createEditPolicy.targetName = initialTarget.name;
|
||||
this.createEditPolicy.endpointUrl = initialTarget.endpoint;
|
||||
this.createEditPolicy.username = initialTarget.username;
|
||||
this.createEditPolicy.password = initialTarget.password;
|
||||
}
|
||||
},
|
||||
error=>this.messageService.announceMessage(error.status, 'Error occurred while get targets.', AlertType.DANGER)
|
||||
);
|
||||
}
|
||||
|
||||
ngOnInit(): void {}
|
||||
|
||||
openCreateEditPolicy(policyId?: number): void {
|
||||
this.createEditPolicyOpened = true;
|
||||
this.createEditPolicy = new CreateEditPolicy();
|
||||
this.isCreateDestination = false;
|
||||
this.errorMessageOpened = false;
|
||||
this.errorMessage = '';
|
||||
|
||||
this.pingTestMessage = '';
|
||||
this.pingStatus = true;
|
||||
this.testOngoing = false;
|
||||
|
||||
if(policyId) {
|
||||
this.actionType = ActionType.EDIT;
|
||||
this.replicationService
|
||||
.getPolicy(policyId)
|
||||
.subscribe(
|
||||
policy=>{
|
||||
this.createEditPolicy.policyId = policyId;
|
||||
this.createEditPolicy.name = policy.name;
|
||||
this.createEditPolicy.description = policy.description;
|
||||
this.createEditPolicy.enable = policy.enabled === 1? true : false;
|
||||
this.prepareTargets(policy.target_id);
|
||||
}
|
||||
)
|
||||
} else {
|
||||
this.actionType = ActionType.ADD_NEW;
|
||||
this.prepareTargets();
|
||||
}
|
||||
}
|
||||
|
||||
newDestination(checkedAddNew: boolean): void {
|
||||
console.log('CheckedAddNew:' + checkedAddNew);
|
||||
this.isCreateDestination = checkedAddNew;
|
||||
this.createEditPolicy.targetName = '';
|
||||
this.createEditPolicy.endpointUrl = '';
|
||||
this.createEditPolicy.username = '';
|
||||
this.createEditPolicy.password = '';
|
||||
}
|
||||
|
||||
selectTarget(): void {
|
||||
let result = this.targets.find(target=>target.id == this.createEditPolicy.targetId);
|
||||
if(result) {
|
||||
this.createEditPolicy.targetId = result.id;
|
||||
this.createEditPolicy.endpointUrl = result.endpoint;
|
||||
this.createEditPolicy.username = result.username;
|
||||
this.createEditPolicy.password = result.password;
|
||||
}
|
||||
}
|
||||
|
||||
onErrorMessageClose(): void {
|
||||
this.errorMessageOpened = false;
|
||||
this.errorMessage = '';
|
||||
}
|
||||
|
||||
getPolicyByForm(): Policy {
|
||||
let policy = new Policy();
|
||||
policy.project_id = this.projectId;
|
||||
policy.id = this.createEditPolicy.policyId;
|
||||
policy.name = this.createEditPolicy.name;
|
||||
policy.description = this.createEditPolicy.description;
|
||||
policy.enabled = this.createEditPolicy.enable ? 1 : 0;
|
||||
policy.target_id = this.createEditPolicy.targetId;
|
||||
return policy;
|
||||
}
|
||||
|
||||
getTargetByForm(): Target {
|
||||
let target = new Target();
|
||||
target.id = this.createEditPolicy.targetId;
|
||||
target.name = this.createEditPolicy.targetName;
|
||||
target.endpoint = this.createEditPolicy.endpointUrl;
|
||||
target.username = this.createEditPolicy.username;
|
||||
target.password = this.createEditPolicy.password;
|
||||
return target;
|
||||
}
|
||||
|
||||
createPolicy(): void {
|
||||
console.log('Create policy with existing target in component.');
|
||||
this.replicationService
|
||||
.createPolicy(this.getPolicyByForm())
|
||||
.subscribe(
|
||||
response=>{
|
||||
console.log('Successful created policy: ' + response);
|
||||
this.createEditPolicyOpened = false;
|
||||
this.reload.emit(true);
|
||||
},
|
||||
error=>{
|
||||
this.errorMessageOpened = true;
|
||||
this.errorMessage = error['_body'];
|
||||
console.log('Failed to create policy:' + error.status + ', error message:' + JSON.stringify(error['_body']));
|
||||
});
|
||||
}
|
||||
|
||||
createOrUpdatePolicyAndCreateTarget(): void {
|
||||
console.log('Creating policy with new created target.');
|
||||
this.replicationService
|
||||
.createOrUpdatePolicyWithNewTarget(this.getPolicyByForm(), this.getTargetByForm())
|
||||
.subscribe(
|
||||
response=>{
|
||||
console.log('Successful created policy and target:' + response);
|
||||
this.createEditPolicyOpened = false;
|
||||
this.reload.emit(true);
|
||||
},
|
||||
error=>{
|
||||
this.errorMessageOpened = true;
|
||||
this.errorMessage = error['_body'];
|
||||
console.log('Failed to create policy and target:' + error.status + ', error message:' + JSON.stringify(error['_body']));
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
updatePolicy(): void {
|
||||
console.log('Creating policy with existing target.');
|
||||
this.replicationService
|
||||
.updatePolicy(this.getPolicyByForm())
|
||||
.subscribe(
|
||||
response=>{
|
||||
console.log('Successful created policy and target:' + response);
|
||||
this.createEditPolicyOpened = false;
|
||||
this.reload.emit(true);
|
||||
},
|
||||
error=>{
|
||||
this.errorMessageOpened = true;
|
||||
this.errorMessage = error['_body'];
|
||||
console.log('Failed to create policy and target:' + error.status + ', error message:' + JSON.stringify(error['_body']));
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
onSubmit() {
|
||||
if(this.isCreateDestination) {
|
||||
this.createOrUpdatePolicyAndCreateTarget();
|
||||
} else {
|
||||
if(this.actionType === ActionType.ADD_NEW) {
|
||||
this.createPolicy();
|
||||
} else if(this.actionType === ActionType.EDIT){
|
||||
this.updatePolicy();
|
||||
}
|
||||
}
|
||||
|
||||
this.errorMessageOpened = false;
|
||||
this.errorMessage = '';
|
||||
}
|
||||
|
||||
testConnection() {
|
||||
this.pingStatus = true;
|
||||
this.pingTestMessage = 'Testing connection...';
|
||||
this.testOngoing = !this.testOngoing;
|
||||
let pingTarget = new Target();
|
||||
pingTarget.endpoint = this.createEditPolicy.endpointUrl;
|
||||
pingTarget.username = this.createEditPolicy.username;
|
||||
pingTarget.password = this.createEditPolicy.password;
|
||||
this.replicationService
|
||||
.pingTarget(pingTarget)
|
||||
.subscribe(
|
||||
response=>{
|
||||
this.testOngoing = !this.testOngoing;
|
||||
this.pingTestMessage = 'Connection tested successfully.';
|
||||
this.pingStatus = true;
|
||||
},
|
||||
error=>{
|
||||
this.testOngoing = !this.testOngoing;
|
||||
this.pingTestMessage = 'Failed to ping target.';
|
||||
this.pingStatus = false;
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
export class CreateEditPolicy {
|
||||
policyId: number;
|
||||
name: string;
|
||||
description: string;
|
||||
enable: boolean;
|
||||
targetId: number;
|
||||
targetName: string;
|
||||
endpointUrl: string;
|
||||
username: string;
|
||||
password: string;
|
||||
}
|
@ -1,6 +1,9 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { Component, OnDestroy } from '@angular/core';
|
||||
import { Subscription } from 'rxjs/Subscription';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
|
||||
import { DeletionDialogService } from './deletion-dialog.service';
|
||||
import { DeletionMessage } from './deletion-message';
|
||||
|
||||
@Component({
|
||||
selector: 'deletion-dialog',
|
||||
@ -8,20 +11,32 @@ import { DeletionDialogService } from './deletion-dialog.service';
|
||||
styleUrls: ['deletion-dialog.component.css']
|
||||
})
|
||||
|
||||
export class DeletionDialogComponent{
|
||||
export class DeletionDialogComponent implements OnDestroy{
|
||||
opened: boolean = false;
|
||||
dialogTitle: string = "";
|
||||
dialogContent: string = "";
|
||||
data: any;
|
||||
message: DeletionMessage;
|
||||
private annouceSubscription: Subscription;
|
||||
|
||||
constructor(private delService: DeletionDialogService){
|
||||
delService.deletionAnnouced$.subscribe(msg => {
|
||||
this.dialogTitle = msg.title;
|
||||
this.dialogContent = msg.message;
|
||||
this.data = msg.data;
|
||||
//Open dialog
|
||||
this.open();
|
||||
});
|
||||
constructor(
|
||||
private delService: DeletionDialogService,
|
||||
private translate: TranslateService) {
|
||||
this.annouceSubscription = delService.deletionAnnouced$.subscribe(msg => {
|
||||
this.dialogTitle = msg.title;
|
||||
this.dialogContent = msg.message;
|
||||
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
|
||||
this.open();
|
||||
});
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
if(this.annouceSubscription){
|
||||
this.annouceSubscription.unsubscribe();
|
||||
}
|
||||
}
|
||||
|
||||
open(): void {
|
||||
@ -33,7 +48,7 @@ export class DeletionDialogComponent{
|
||||
}
|
||||
|
||||
confirm(): void {
|
||||
this.delService.confirmDeletion(this.data);
|
||||
this.delService.confirmDeletion(this.message);
|
||||
this.close();
|
||||
}
|
||||
}
|
@ -6,13 +6,13 @@ import { DeletionMessage } from './deletion-message';
|
||||
@Injectable()
|
||||
export class DeletionDialogService {
|
||||
private deletionAnnoucedSource = new Subject<DeletionMessage>();
|
||||
private deletionConfirmSource = new Subject<any>();
|
||||
private deletionConfirmSource = new Subject<DeletionMessage>();
|
||||
|
||||
deletionAnnouced$ = this.deletionAnnoucedSource.asObservable();
|
||||
deletionConfirm$ = this.deletionConfirmSource.asObservable();
|
||||
|
||||
confirmDeletion(obj: any): void {
|
||||
this.deletionConfirmSource.next(obj);
|
||||
confirmDeletion(message: any): void {
|
||||
this.deletionConfirmSource.next(message);
|
||||
}
|
||||
|
||||
openComfirmDialog(message: DeletionMessage): void {
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user