mirror of
https://github.com/goharbor/harbor.git
synced 2024-10-03 07:47:54 +02:00
commit
e83aba5cc2
@ -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>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</form>
|
</form>
|
||||||
<div style="height: 30px;"></div>
|
<inline-alert (confirmEvt)="confirmCancel($event)"></inline-alert>
|
||||||
<clr-alert [clrAlertType]="'alert-danger'" [clrAlertClosable]="true" [(clrAlertClosed)]="alertClose">
|
|
||||||
<div class="alert-item">
|
|
||||||
<span class="alert-text">
|
|
||||||
{{errorMessage}}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</clr-alert>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<span class="spinner spinner-inline" style="top:8px;" [hidden]="showProgress === false"></span>
|
<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 { SessionUser } from '../../shared/session-user';
|
||||||
import { SessionService } from '../../shared/session.service';
|
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({
|
@Component({
|
||||||
selector: "account-settings-modal",
|
selector: "account-settings-modal",
|
||||||
@ -13,43 +17,52 @@ export class AccountSettingsModalComponent implements OnInit, AfterViewChecked {
|
|||||||
opened: boolean = false;
|
opened: boolean = false;
|
||||||
staticBackdrop: boolean = true;
|
staticBackdrop: boolean = true;
|
||||||
account: SessionUser;
|
account: SessionUser;
|
||||||
error: any;
|
error: any = null;
|
||||||
alertClose: boolean = true;
|
originalStaticData: SessionUser;
|
||||||
|
|
||||||
private isOnCalling: boolean = false;
|
private isOnCalling: boolean = false;
|
||||||
private formValueChanged: boolean = false;
|
private formValueChanged: boolean = false;
|
||||||
|
|
||||||
accountFormRef: NgForm;
|
accountFormRef: NgForm;
|
||||||
@ViewChild("accountSettingsFrom") accountForm: NgForm;
|
@ViewChild("accountSettingsFrom") accountForm: NgForm;
|
||||||
|
@ViewChild(InlineAlertComponent)
|
||||||
|
private inlineAlert: InlineAlertComponent;
|
||||||
|
|
||||||
constructor(private session: SessionService) { }
|
constructor(
|
||||||
|
private session: SessionService,
|
||||||
|
private msgService: MessageService) { }
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
//Value copy
|
//Value copy
|
||||||
this.account = Object.assign({}, this.session.getCurrentUser());
|
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 {
|
public get isValid(): boolean {
|
||||||
return this.accountForm && this.accountForm.valid;
|
return this.accountForm && this.accountForm.valid && this.error === null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public get showProgress(): boolean {
|
public get showProgress(): boolean {
|
||||||
return this.isOnCalling;
|
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 {
|
ngAfterViewChecked(): void {
|
||||||
if (this.accountFormRef != this.accountForm) {
|
if (this.accountFormRef != this.accountForm) {
|
||||||
this.accountFormRef = this.accountForm;
|
this.accountFormRef = this.accountForm;
|
||||||
@ -59,12 +72,15 @@ export class AccountSettingsModalComponent implements OnInit, AfterViewChecked {
|
|||||||
this.error = null;
|
this.error = null;
|
||||||
}
|
}
|
||||||
this.formValueChanged = true;
|
this.formValueChanged = true;
|
||||||
|
this.inlineAlert.close();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
open() {
|
open() {
|
||||||
|
//Keep the initial data for future diff
|
||||||
|
this.originalStaticData = Object.assign({}, this.session.getCurrentUser());
|
||||||
this.account = Object.assign({}, this.session.getCurrentUser());
|
this.account = Object.assign({}, this.session.getCurrentUser());
|
||||||
this.formValueChanged = false;
|
this.formValueChanged = false;
|
||||||
|
|
||||||
@ -72,7 +88,18 @@ export class AccountSettingsModalComponent implements OnInit, AfterViewChecked {
|
|||||||
}
|
}
|
||||||
|
|
||||||
close() {
|
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() {
|
submit() {
|
||||||
@ -92,12 +119,22 @@ export class AccountSettingsModalComponent implements OnInit, AfterViewChecked {
|
|||||||
.then(() => {
|
.then(() => {
|
||||||
this.isOnCalling = false;
|
this.isOnCalling = false;
|
||||||
this.close();
|
this.close();
|
||||||
|
this.msgService.announceMessage(200, "PROFILE.SAVE_SUCCESS", AlertType.SUCCESS);
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
this.isOnCalling = false;
|
this.isOnCalling = false;
|
||||||
this.error = error;
|
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 { PasswordSettingComponent } from './password/password-setting.component';
|
||||||
import { AccountSettingsModalComponent } from './account-settings/account-settings-modal.component';
|
import { AccountSettingsModalComponent } from './account-settings/account-settings-modal.component';
|
||||||
import { SharedModule } from '../shared/shared.module';
|
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';
|
import { PasswordSettingService } from './password/password-setting.service';
|
||||||
|
|
||||||
@ -15,8 +18,18 @@ import { PasswordSettingService } from './password/password-setting.service';
|
|||||||
RouterModule,
|
RouterModule,
|
||||||
SharedModule
|
SharedModule
|
||||||
],
|
],
|
||||||
declarations: [SignInComponent, PasswordSettingComponent, AccountSettingsModalComponent],
|
declarations: [
|
||||||
exports: [SignInComponent, PasswordSettingComponent, AccountSettingsModalComponent],
|
SignInComponent,
|
||||||
|
PasswordSettingComponent,
|
||||||
|
AccountSettingsModalComponent,
|
||||||
|
SignUpComponent,
|
||||||
|
ForgotPasswordComponent,
|
||||||
|
ResetPasswordComponent],
|
||||||
|
exports: [
|
||||||
|
SignInComponent,
|
||||||
|
PasswordSettingComponent,
|
||||||
|
AccountSettingsModalComponent,
|
||||||
|
ResetPasswordComponent],
|
||||||
|
|
||||||
providers: [PasswordSettingService]
|
providers: [PasswordSettingService]
|
||||||
})
|
})
|
||||||
|
@ -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 { 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({
|
@Component({
|
||||||
selector: 'forgot-password',
|
selector: 'forgot-password',
|
||||||
templateUrl: "forgot-password.component.html"
|
templateUrl: "forgot-password.component.html",
|
||||||
|
styleUrls: ['password.component.css']
|
||||||
})
|
})
|
||||||
export class ForgotPasswordComponent {
|
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>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
<inline-alert (confirmEvt)="confirmCancel($event)"></inline-alert>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
|
@ -4,6 +4,10 @@ import { NgForm } from '@angular/forms';
|
|||||||
|
|
||||||
import { PasswordSettingService } from './password-setting.service';
|
import { PasswordSettingService } from './password-setting.service';
|
||||||
import { SessionService } from '../../shared/session.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({
|
@Component({
|
||||||
selector: 'password-setting',
|
selector: 'password-setting',
|
||||||
@ -14,19 +18,27 @@ export class PasswordSettingComponent implements AfterViewChecked {
|
|||||||
oldPwd: string = "";
|
oldPwd: string = "";
|
||||||
newPwd: string = "";
|
newPwd: string = "";
|
||||||
reNewPwd: string = "";
|
reNewPwd: string = "";
|
||||||
|
error: any = null;
|
||||||
|
|
||||||
private formValueChanged: boolean = false;
|
private formValueChanged: boolean = false;
|
||||||
private onCalling: boolean = false;
|
private onCalling: boolean = false;
|
||||||
|
|
||||||
pwdFormRef: NgForm;
|
pwdFormRef: NgForm;
|
||||||
@ViewChild("changepwdForm") pwdForm: 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
|
//If form is valid
|
||||||
public get isValid(): boolean {
|
public get isValid(): boolean {
|
||||||
if (this.pwdForm && this.pwdForm.form.get("newPassword")) {
|
if (this.pwdForm && this.pwdForm.form.get("newPassword")) {
|
||||||
return this.pwdForm.valid &&
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
@ -45,6 +57,8 @@ export class PasswordSettingComponent implements AfterViewChecked {
|
|||||||
if (this.pwdFormRef) {
|
if (this.pwdFormRef) {
|
||||||
this.pwdFormRef.valueChanges.subscribe(data => {
|
this.pwdFormRef.valueChanges.subscribe(data => {
|
||||||
this.formValueChanged = true;
|
this.formValueChanged = true;
|
||||||
|
this.error = null;
|
||||||
|
this.inlineAlert.close();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -54,10 +68,26 @@ export class PasswordSettingComponent implements AfterViewChecked {
|
|||||||
open(): void {
|
open(): void {
|
||||||
this.opened = true;
|
this.opened = true;
|
||||||
this.pwdForm.reset();
|
this.pwdForm.reset();
|
||||||
|
this.formValueChanged = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
//Close the moal dialog
|
//Close the moal dialog
|
||||||
close(): void {
|
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;
|
this.opened = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -73,7 +103,7 @@ export class PasswordSettingComponent implements AfterViewChecked {
|
|||||||
|
|
||||||
//Double confirm session is valid
|
//Double confirm session is valid
|
||||||
let cUser = this.session.getCurrentUser();
|
let cUser = this.session.getCurrentUser();
|
||||||
if(!cUser){
|
if (!cUser) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -81,18 +111,23 @@ export class PasswordSettingComponent implements AfterViewChecked {
|
|||||||
this.onCalling = true;
|
this.onCalling = true;
|
||||||
|
|
||||||
this.passwordService.changePassword(cUser.user_id,
|
this.passwordService.changePassword(cUser.user_id,
|
||||||
{
|
{
|
||||||
new_password: this.pwdForm.value.newPassword,
|
new_password: this.pwdForm.value.newPassword,
|
||||||
old_password: this.pwdForm.value.oldPassword
|
old_password: this.pwdForm.value.oldPassword
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.onCalling = false;
|
this.onCalling = false;
|
||||||
this.close();
|
this.close();
|
||||||
})
|
this.msgService.announceMessage(200, "CHANGE_PWD.SAVE_SUCCESS", AlertType.SUCCESS);
|
||||||
.catch(error => {
|
})
|
||||||
this.onCalling = false;
|
.catch(error => {
|
||||||
console.error(error);//TODO:
|
this.onCalling = false;
|
||||||
});
|
this.error = error;
|
||||||
//TODO:publish the successful message to general messae box
|
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';
|
import { PasswordSetting } from './password-setting';
|
||||||
|
|
||||||
const passwordChangeEndpoint = "/api/users/:user_id/password";
|
const passwordChangeEndpoint = "/api/users/:user_id/password";
|
||||||
|
const sendEmailEndpoint = "/sendEmail";
|
||||||
|
const resetPasswordEndpoint = "/reset";
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class PasswordSettingService {
|
export class PasswordSettingService {
|
||||||
@ -19,17 +21,46 @@ export class PasswordSettingService {
|
|||||||
constructor(private http: Http) { }
|
constructor(private http: Http) { }
|
||||||
|
|
||||||
changePassword(userId: number, setting: PasswordSetting): Promise<any> {
|
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");
|
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)
|
return this.http.put(putUrl, JSON.stringify(setting), this.options)
|
||||||
.toPromise()
|
.toPromise()
|
||||||
.then(() => null)
|
.then(() => null)
|
||||||
.catch(error=>{
|
.catch(error => {
|
||||||
return Promise.reject(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;
|
||||||
|
}
|
||||||
|
}
|
@ -5,3 +5,11 @@
|
|||||||
.visibility-hidden {
|
.visibility-hidden {
|
||||||
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)">
|
<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
|
<input class="username" type="text" required
|
||||||
[(ngModel)]="signInCredential.principal"
|
[(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'>
|
#userNameInput='ngModel'>
|
||||||
<span class="tooltip-content">
|
<span class="tooltip-content">
|
||||||
Username is required!
|
{{ 'TOOLTIP.SIGN_IN_USERNAME' | translate }}
|
||||||
</span>
|
</span>
|
||||||
</label>
|
</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)">
|
<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
|
<input class="password" type="password" required
|
||||||
[(ngModel)]="signInCredential.password"
|
[(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">
|
#passwordInput="ngModel">
|
||||||
<span class="tooltip-content">
|
<span class="tooltip-content">
|
||||||
Password is required!
|
{{ 'TOOLTIP.SIGN_IN_PWD' | translate }}
|
||||||
</span>
|
</span>
|
||||||
</label>
|
</label>
|
||||||
<div class="checkbox">
|
<div class="checkbox">
|
||||||
<input type="checkbox" id="rememberme">
|
<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>
|
||||||
<div [class.visibility-hidden]="signInStatus != statusError" class="error active">
|
<div [class.visibility-hidden]="!isError" class="error active">
|
||||||
Invalid user name or password
|
{{ 'SIGN_IN.INVALID_MSG' | translate }}
|
||||||
</div>
|
</div>
|
||||||
<button [disabled]="isOnGoing || !isValid" type="submit" class="btn btn-primary" (click)="signIn()">{{ 'LOG_IN' | translate }}</button>
|
<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()">{{ 'SIGN_UP' | translate }}</a>
|
<a href="javascript:void(0)" class="signup" (click)="signUp()">{{ 'BUTTON.SIGN_UP_LINK' | translate }}</a>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
<sign-up #signupDialog></sign-up>>
|
||||||
|
<forgot-password #forgotPwdDialog></forgot-password>
|
@ -1,11 +1,15 @@
|
|||||||
import { Component } from '@angular/core';
|
import { Component, OnInit } from '@angular/core';
|
||||||
import { Router } from '@angular/router';
|
import { Router, ActivatedRoute } from '@angular/router';
|
||||||
import { Input, ViewChild, AfterViewChecked } from '@angular/core';
|
import { Input, ViewChild, AfterViewChecked } from '@angular/core';
|
||||||
import { NgForm } from '@angular/forms';
|
import { NgForm } from '@angular/forms';
|
||||||
|
|
||||||
import { SessionService } from '../../shared/session.service';
|
import { SessionService } from '../../shared/session.service';
|
||||||
import { SignInCredential } from '../../shared/sign-in-credential';
|
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
|
//Define status flags for signing in states
|
||||||
export const signInStatusNormal = 0;
|
export const signInStatusNormal = 0;
|
||||||
export const signInStatusOnGoing = 1;
|
export const signInStatusOnGoing = 1;
|
||||||
@ -17,13 +21,16 @@ export const signInStatusError = -1;
|
|||||||
styleUrls: ['sign-in.component.css']
|
styleUrls: ['sign-in.component.css']
|
||||||
})
|
})
|
||||||
|
|
||||||
export class SignInComponent implements AfterViewChecked {
|
export class SignInComponent implements AfterViewChecked, OnInit {
|
||||||
|
private redirectUrl: string = "";
|
||||||
//Form reference
|
//Form reference
|
||||||
signInForm: NgForm;
|
signInForm: NgForm;
|
||||||
@ViewChild('signInForm') currentForm: NgForm;
|
@ViewChild('signInForm') currentForm: NgForm;
|
||||||
|
@ViewChild('signupDialog') signUpDialog: SignUpComponent;
|
||||||
|
@ViewChild('forgotPwdDialog') forgotPwdDialog: ForgotPasswordComponent;
|
||||||
|
|
||||||
//Status flag
|
//Status flag
|
||||||
signInStatus: number = 0;
|
signInStatus: number = signInStatusNormal;
|
||||||
|
|
||||||
//Initialize sign in credential
|
//Initialize sign in credential
|
||||||
@Input() signInCredential: SignInCredential = {
|
@Input() signInCredential: SignInCredential = {
|
||||||
@ -33,9 +40,17 @@ export class SignInComponent implements AfterViewChecked {
|
|||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private router: Router,
|
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
|
//For template accessing
|
||||||
public get isError(): boolean {
|
public get isError(): boolean {
|
||||||
return this.signInStatus === signInStatusError;
|
return this.signInStatus === signInStatusError;
|
||||||
@ -105,25 +120,26 @@ export class SignInComponent implements AfterViewChecked {
|
|||||||
//Set status
|
//Set status
|
||||||
this.signInStatus = signInStatusNormal;
|
this.signInStatus = signInStatusNormal;
|
||||||
|
|
||||||
//Validate the sign-in session
|
//Redirect to the right route
|
||||||
this.session.retrieveUser()
|
if (this.redirectUrl === "") {
|
||||||
.then(user => {
|
//Routing to the default location
|
||||||
//Routing to the right location
|
this.router.navigateByUrl(harborRootRoute);
|
||||||
let nextRoute = ["/harbor", "projects"];
|
}else{
|
||||||
this.router.navigate(nextRoute);
|
this.router.navigateByUrl(this.redirectUrl);
|
||||||
})
|
}
|
||||||
.catch(error => {
|
|
||||||
this.handleError(error);
|
|
||||||
});
|
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
this.handleError(error);
|
this.handleError(error);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
//Help user navigate to the sign up
|
//Open sign up dialog
|
||||||
signUp(): void {
|
signUp(): void {
|
||||||
let nextRoute = ["/harbor", "signup"];
|
this.signUpDialog.open();
|
||||||
this.router.navigate(nextRoute);
|
}
|
||||||
|
|
||||||
|
//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 { Headers, Http, URLSearchParams } from '@angular/http';
|
||||||
import 'rxjs/add/operator/toPromise';
|
import 'rxjs/add/operator/toPromise';
|
||||||
|
|
||||||
import { SignInCredential } from './sign-in-credential';
|
import { SignInCredential } from '../../shared/sign-in-credential';
|
||||||
|
|
||||||
const url_prefix = '/ng';
|
const signInUrl = '/login';
|
||||||
const signInUrl = url_prefix + '/login';
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* Define a service to provide sign in methods
|
* 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 { Component, Output, ViewChild } from '@angular/core';
|
||||||
import { Router } from '@angular/router';
|
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({
|
@Component({
|
||||||
selector: 'sign-up',
|
selector: 'sign-up',
|
||||||
templateUrl: "sign-up.component.html"
|
templateUrl: "sign-up.component.html"
|
||||||
})
|
})
|
||||||
export class SignUpComponent {
|
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
|
//Use browser lang
|
||||||
langSetting = translate.getBrowserLang();
|
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[]) {
|
private isLangMatch(browserLang: string, supportedLangs: string[]) {
|
||||||
|
@ -9,6 +9,7 @@ import { BaseModule } from './base/base.module';
|
|||||||
import { HarborRoutingModule } from './harbor-routing.module';
|
import { HarborRoutingModule } from './harbor-routing.module';
|
||||||
import { SharedModule } from './shared/shared.module';
|
import { SharedModule } from './shared/shared.module';
|
||||||
import { AccountModule } from './account/account.module';
|
import { AccountModule } from './account/account.module';
|
||||||
|
import { ConfigurationModule } from './config/config.module';
|
||||||
|
|
||||||
import { TranslateModule, TranslateLoader, MissingTranslationHandler } from "@ngx-translate/core";
|
import { TranslateModule, TranslateLoader, MissingTranslationHandler } from "@ngx-translate/core";
|
||||||
import { MyMissingTranslationHandler } from './i18n/missing-trans.handler';
|
import { MyMissingTranslationHandler } from './i18n/missing-trans.handler';
|
||||||
@ -16,7 +17,7 @@ import { TranslateHttpLoader } from '@ngx-translate/http-loader';
|
|||||||
import { Http } from '@angular/http';
|
import { Http } from '@angular/http';
|
||||||
|
|
||||||
export function HttpLoaderFactory(http: 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({
|
@NgModule({
|
||||||
@ -28,6 +29,7 @@ export function HttpLoaderFactory(http: Http) {
|
|||||||
BaseModule,
|
BaseModule,
|
||||||
AccountModule,
|
AccountModule,
|
||||||
HarborRoutingModule,
|
HarborRoutingModule,
|
||||||
|
ConfigurationModule,
|
||||||
TranslateModule.forRoot({
|
TranslateModule.forRoot({
|
||||||
loader: {
|
loader: {
|
||||||
provide: TranslateLoader,
|
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 { NgModule } from '@angular/core';
|
||||||
import { SharedModule } from '../shared/shared.module';
|
import { SharedModule } from '../shared/shared.module';
|
||||||
|
import { RouterModule } from '@angular/router';
|
||||||
|
|
||||||
import { DashboardModule } from '../dashboard/dashboard.module';
|
import { DashboardModule } from '../dashboard/dashboard.module';
|
||||||
import { ProjectModule } from '../project/project.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 { HarborShellComponent } from './harbor-shell/harbor-shell.component';
|
||||||
import { SearchResultComponent } from './global-search/search-result.component';
|
import { SearchResultComponent } from './global-search/search-result.component';
|
||||||
|
|
||||||
import { BaseRoutingModule } from './base-routing.module';
|
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
SharedModule,
|
SharedModule,
|
||||||
DashboardModule,
|
DashboardModule,
|
||||||
ProjectModule,
|
ProjectModule,
|
||||||
UserModule,
|
UserModule,
|
||||||
BaseRoutingModule,
|
AccountModule,
|
||||||
AccountModule
|
RouterModule
|
||||||
],
|
],
|
||||||
declarations: [
|
declarations: [
|
||||||
NavigatorComponent,
|
NavigatorComponent,
|
||||||
|
@ -1,12 +1,13 @@
|
|||||||
.search-overlay {
|
.search-overlay {
|
||||||
display: block;
|
display: block;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
height: 94%;
|
height: 100%;
|
||||||
width: 97%;
|
width: 97%;
|
||||||
/*shoud be lesser than 1000 to aoivd override the popup menu*/
|
/*shoud be lesser than 1000 to aoivd override the popup menu*/
|
||||||
z-index: 999;
|
z-index: 999;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
background: #fafafa;
|
background: #fafafa;
|
||||||
|
top: 0px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.search-header {
|
.search-header {
|
||||||
|
@ -1,23 +1,24 @@
|
|||||||
<clr-main-container>
|
<clr-main-container>
|
||||||
<global-message [isAppLevel]="true"></global-message>
|
<global-message [isAppLevel]="true"></global-message>
|
||||||
<navigator (showAccountSettingsModal)="openModal($event)" (searchEvt)="doSearch($event)" (showPwdChangeModal)="openModal($event)"></navigator>
|
<navigator (showAccountSettingsModal)="openModal($event)" (searchEvt)="doSearch($event)" (showPwdChangeModal)="openModal($event)"></navigator>
|
||||||
<global-message [isAppLevel]="false"></global-message>
|
|
||||||
<div class="content-container">
|
<div class="content-container">
|
||||||
<div class="content-area" [class.container-override]="showSearch">
|
<div class="content-area" [class.container-override]="showSearch">
|
||||||
|
<global-message [isAppLevel]="false"></global-message>
|
||||||
<!-- Only appear when searching -->
|
<!-- Only appear when searching -->
|
||||||
<search-result (closeEvt)="searchClose($event)"></search-result>
|
<search-result (closeEvt)="searchClose($event)"></search-result>
|
||||||
<router-outlet></router-outlet>
|
<router-outlet></router-outlet>
|
||||||
</div>
|
</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">
|
<section class="sidenav-content">
|
||||||
<a routerLink="/harbor/projects" routerLinkActive="active" class="nav-link">{{'SIDE_NAV.PROJECTS' | translate}}</a>
|
<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">
|
<section class="nav-group collapsible" *ngIf="isSystemAdmin">
|
||||||
<input id="tabsystem" type="checkbox">
|
<input id="tabsystem" type="checkbox">
|
||||||
<label for="tabsystem">{{'SIDE_NAV.SYSTEM_MGMT.NAME' | translate}}</label>
|
<label for="tabsystem">{{'SIDE_NAV.SYSTEM_MGMT.NAME' | translate}}</label>
|
||||||
<ul class="nav-list">
|
<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" 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" routerLink="/harbor/replications/endpoints" routerLinkActive="active">{{'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/configs" routerLinkActive="active">{{'SIDE_NAV.SYSTEM_MGMT.CONFIGS' | translate}}</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</section>
|
</section>
|
||||||
</section>
|
</section>
|
||||||
@ -27,3 +28,4 @@
|
|||||||
<account-settings-modal></account-settings-modal>
|
<account-settings-modal></account-settings-modal>
|
||||||
<password-setting></password-setting>
|
<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 { ModalEvent } from '../modal-event';
|
||||||
import { SearchEvent } from '../search-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 { AccountSettingsModalComponent } from '../../account/account-settings/account-settings-modal.component';
|
||||||
import { SearchResultComponent } from '../global-search/search-result.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 { NavigatorComponent } from '../navigator/navigator.component';
|
||||||
import { SessionService } from '../../shared/session.service';
|
import { SessionService } from '../../shared/session.service';
|
||||||
|
|
||||||
|
import { AboutDialogComponent } from '../../shared/about-dialog/about-dialog.component'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'harbor-shell',
|
selector: 'harbor-shell',
|
||||||
templateUrl: 'harbor-shell.component.html',
|
templateUrl: 'harbor-shell.component.html',
|
||||||
@ -31,6 +33,9 @@ export class HarborShellComponent implements OnInit {
|
|||||||
@ViewChild(NavigatorComponent)
|
@ViewChild(NavigatorComponent)
|
||||||
private navigator: NavigatorComponent;
|
private navigator: NavigatorComponent;
|
||||||
|
|
||||||
|
@ViewChild(AboutDialogComponent)
|
||||||
|
private aboutDialog: AboutDialogComponent;
|
||||||
|
|
||||||
//To indicator whwther or not the search results page is displayed
|
//To indicator whwther or not the search results page is displayed
|
||||||
//We need to use this property to do some overriding work
|
//We need to use this property to do some overriding work
|
||||||
private isSearchResultsOpened: boolean = false;
|
private isSearchResultsOpened: boolean = false;
|
||||||
@ -51,18 +56,26 @@ export class HarborShellComponent implements OnInit {
|
|||||||
|
|
||||||
public get isSystemAdmin(): boolean {
|
public get isSystemAdmin(): boolean {
|
||||||
let account = this.session.getCurrentUser();
|
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
|
//Open modal dialog
|
||||||
openModal(event: ModalEvent): void {
|
openModal(event: ModalEvent): void {
|
||||||
switch (event.modalName) {
|
switch (event.modalName) {
|
||||||
case modalAccountSettings:
|
case modalEvents.USER_PROFILE:
|
||||||
this.accountSettingsModal.open();
|
this.accountSettingsModal.open();
|
||||||
break;
|
break;
|
||||||
case modalPasswordSetting:
|
case modalEvents.CHANGE_PWD:
|
||||||
this.pwdSetting.open();
|
this.pwdSetting.open();
|
||||||
break;
|
break;
|
||||||
|
case modalEvents.ABOUT:
|
||||||
|
this.aboutDialog.open();
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -85,4 +98,10 @@ export class HarborShellComponent implements OnInit {
|
|||||||
this.isSearchResultsOpened = false;
|
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
|
//Define a object to store the modal event
|
||||||
export class ModalEvent {
|
export class ModalEvent {
|
||||||
modalName: string;
|
modalName: modalEvents;
|
||||||
modalFlag: boolean; //true for open, false for close
|
modalFlag: boolean; //true for open, false for close
|
||||||
}
|
}
|
@ -1,2 +1,3 @@
|
|||||||
export const modalAccountSettings= "account-settings";
|
export const enum modalEvents {
|
||||||
export const modalPasswordSetting = "password-setting";
|
USER_PROFILE, CHANGE_PWD, ABOUT
|
||||||
|
}
|
@ -7,11 +7,6 @@
|
|||||||
</div>
|
</div>
|
||||||
<global-search (searchEvt)="transferSearchEvent($event)"></global-search>
|
<global-search (searchEvt)="transferSearchEvent($event)"></global-search>
|
||||||
<div class="header-actions">
|
<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">
|
<clr-dropdown class="dropdown bottom-left">
|
||||||
<button class="nav-icon" clrDropdownToggle style="width: 90px;">
|
<button class="nav-icon" clrDropdownToggle style="width: 90px;">
|
||||||
<clr-icon shape="world" style="left:-8px;"></clr-icon>
|
<clr-icon shape="world" style="left:-8px;"></clr-icon>
|
||||||
@ -32,10 +27,11 @@
|
|||||||
<div class="dropdown-menu">
|
<div class="dropdown-menu">
|
||||||
<a href="javascript:void(0)" clrDropdownItem (click)="openAccountSettingsModal()">{{'ACCOUNT_SETTINGS.PROFILE' | translate}}</a>
|
<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 (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>
|
<div class="dropdown-divider"></div>
|
||||||
<a href="javascript:void(0)" clrDropdownItem (click)="logOut()">{{'ACCOUNT_SETTINGS.LOGOUT' | translate}}</a>
|
<a href="javascript:void(0)" clrDropdownItem (click)="logOut()">{{'ACCOUNT_SETTINGS.LOGOUT' | translate}}</a>
|
||||||
</div>
|
</div>
|
||||||
</clr-dropdown>
|
</clr-dropdown>
|
||||||
|
<a href="javascript:void(0)" class="nav-link nav-text" (click)="openAboutDialog()" *ngIf="isSessionValid === false">{{'ACCOUNT_SETTINGS.ABOUT' | translate}}</a>
|
||||||
</div>
|
</div>
|
||||||
</clr-header>
|
</clr-header>
|
@ -4,7 +4,7 @@ import { TranslateService } from '@ngx-translate/core';
|
|||||||
|
|
||||||
import { ModalEvent } from '../modal-event';
|
import { ModalEvent } from '../modal-event';
|
||||||
import { SearchEvent } from '../search-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 { SessionUser } from '../../shared/session-user';
|
||||||
import { SessionService } from '../../shared/session.service';
|
import { SessionService } from '../../shared/session.service';
|
||||||
@ -62,7 +62,7 @@ export class NavigatorComponent implements OnInit {
|
|||||||
//Open the account setting dialog
|
//Open the account setting dialog
|
||||||
openAccountSettingsModal(): void {
|
openAccountSettingsModal(): void {
|
||||||
this.showAccountSettingsModal.emit({
|
this.showAccountSettingsModal.emit({
|
||||||
modalName: modalAccountSettings,
|
modalName: modalEvents.USER_PROFILE,
|
||||||
modalFlag: true
|
modalFlag: true
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -70,7 +70,15 @@ export class NavigatorComponent implements OnInit {
|
|||||||
//Open change password dialog
|
//Open change password dialog
|
||||||
openChangePwdModal(): void {
|
openChangePwdModal(): void {
|
||||||
this.showPwdChangeModal.emit({
|
this.showPwdChangeModal.emit({
|
||||||
modalName: modalPasswordSetting,
|
modalName: modalEvents.CHANGE_PWD,
|
||||||
|
modalFlag: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
//Open about dialog
|
||||||
|
openAboutDialog(): void {
|
||||||
|
this.showPwdChangeModal.emit({
|
||||||
|
modalName: modalEvents.ABOUT,
|
||||||
modalFlag: true
|
modalFlag: true
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -100,6 +108,8 @@ export class NavigatorComponent implements OnInit {
|
|||||||
//TODO:
|
//TODO:
|
||||||
console.error('Language '+lang.trim()+' is not suppoted');
|
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
|
//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()">
|
<clr-alert [clrAlertType]="globalMessage.type" [clrAlertAppLevel]="isAppLevel" [(clrAlertClosed)]="!globalMessageOpened" (clrAlertClosedChange)="onClose()">
|
||||||
<div class="alert-item">
|
<div class="alert-item">
|
||||||
<span class="alert-text">
|
<span class="alert-text">
|
||||||
{{globalMessage.message}}
|
{{message}}
|
||||||
</span>
|
</span>
|
||||||
<a *ngIf="globalMessage.statusCode === 401" [routerLink]="['/sign-in']" style="color: #ffffff;">Sign In</a>
|
<div class="alert-actions" *ngIf="needAuth">
|
||||||
</div>
|
<button class="btn alert-action" (click)="signIn()">{{ 'BUTTON.LOG_IN' | translate }}</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</clr-alert>
|
</clr-alert>
|
@ -1,41 +1,100 @@
|
|||||||
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 { Message } from './message';
|
||||||
import { MessageService } from './message.service';
|
import { MessageService } from './message.service';
|
||||||
|
|
||||||
import { AlertType, dismissInterval } from '../shared/shared.const';
|
import { AlertType, dismissInterval, httpStatusCode } from '../shared/shared.const';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'global-message',
|
selector: 'global-message',
|
||||||
templateUrl: 'message.component.html'
|
templateUrl: 'message.component.html'
|
||||||
})
|
})
|
||||||
export class MessageComponent {
|
export class MessageComponent implements OnInit {
|
||||||
|
|
||||||
@Input() isAppLevel: boolean;
|
@Input() isAppLevel: boolean;
|
||||||
|
|
||||||
globalMessage: Message = new Message();
|
globalMessage: Message = new Message();
|
||||||
globalMessageOpened: boolean;
|
globalMessageOpened: boolean;
|
||||||
|
messageText: string = "";
|
||||||
|
|
||||||
constructor(messageService: MessageService) {
|
constructor(
|
||||||
|
private messageService: MessageService,
|
||||||
|
private router: Router,
|
||||||
|
private translate: TranslateService) { }
|
||||||
|
|
||||||
messageService.appLevelAnnounced$.subscribe(
|
ngOnInit(): void {
|
||||||
message=>{
|
//Only subscribe application level message
|
||||||
this.globalMessageOpened = this.isAppLevel && true;
|
if (this.isAppLevel) {
|
||||||
this.globalMessage = message;
|
this.messageService.appLevelAnnounced$.subscribe(
|
||||||
console.log('received app level message:' + message);
|
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";
|
||||||
}
|
}
|
||||||
)
|
}
|
||||||
|
|
||||||
messageService.messageAnnounced$.subscribe(
|
//Override key for HTTP 401 and 403
|
||||||
message=>{
|
if (this.globalMessage.statusCode === httpStatusCode.Unauthorized) {
|
||||||
this.globalMessageOpened = !this.isAppLevel && true;
|
key = "UNAUTHORIZED_ERROR";
|
||||||
this.globalMessage = message;
|
}
|
||||||
console.log('received message:' + message);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
// Make the message alert bar dismiss after several intervals.
|
if (this.globalMessage.statusCode === httpStatusCode.Forbidden) {
|
||||||
setInterval(()=>this.onClose(), dismissInterval);
|
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() {
|
onClose() {
|
||||||
|
@ -4,6 +4,7 @@ export class Message {
|
|||||||
statusCode: number;
|
statusCode: number;
|
||||||
message: string;
|
message: string;
|
||||||
alertType: AlertType;
|
alertType: AlertType;
|
||||||
|
isAppLevel: boolean = false;
|
||||||
|
|
||||||
get type(): string {
|
get type(): string {
|
||||||
switch (this.alertType) {
|
switch (this.alertType) {
|
||||||
|
@ -4,16 +4,101 @@ import { RouterModule, Routes } from '@angular/router';
|
|||||||
|
|
||||||
import { SignInComponent } from './account/sign-in/sign-in.component';
|
import { SignInComponent } from './account/sign-in/sign-in.component';
|
||||||
import { HarborShellComponent } from './base/harbor-shell/harbor-shell.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 = [
|
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',
|
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: "**", component: PageNotFoundComponent}
|
||||||
{ path: 'sign-in', component: SignInComponent }
|
|
||||||
];
|
];
|
||||||
|
|
||||||
@NgModule({
|
@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 {
|
export class MyMissingTranslationHandler implements MissingTranslationHandler {
|
||||||
handle(params: MissingTranslationHandlerParams) {
|
handle(params: MissingTranslationHandlerParams) {
|
||||||
const missingText = "{Miss Harbor Text}";
|
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>
|
<button class="btn btn-link" (click)="toggleOptionalName(currentOption)">{{toggleName[currentOption] | translate}}</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-xs-3 flex-xs-middle">
|
<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>
|
</div>
|
||||||
<div class="row flex-items-xs-right advance-option" [hidden]="currentOption === 0">
|
<div class="row flex-items-xs-right advance-option" [hidden]="currentOption === 0">
|
||||||
|
@ -136,4 +136,7 @@ export class AuditLogComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
this.doSearchByOptions();
|
this.doSearchByOptions();
|
||||||
}
|
}
|
||||||
|
refresh(): void {
|
||||||
|
this.retrieve(this.queryParam);
|
||||||
|
}
|
||||||
}
|
}
|
@ -10,10 +10,16 @@ import 'rxjs/add/operator/catch';
|
|||||||
import 'rxjs/add/operator/map';
|
import 'rxjs/add/operator/map';
|
||||||
import 'rxjs/add/observable/throw';
|
import 'rxjs/add/observable/throw';
|
||||||
|
|
||||||
export const urlPrefix = '';
|
export const logEndpoint = "/api/logs";
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class AuditLogService extends BaseService {
|
export class AuditLogService extends BaseService {
|
||||||
|
private httpOptions = new RequestOptions({
|
||||||
|
headers: new Headers({
|
||||||
|
"Content-Type": 'application/json',
|
||||||
|
"Accept": 'application/json'
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
constructor(private http: Http) {
|
constructor(private http: Http) {
|
||||||
super();
|
super();
|
||||||
@ -21,15 +27,22 @@ export class AuditLogService extends BaseService {
|
|||||||
|
|
||||||
listAuditLogs(queryParam: AuditLog): Observable<AuditLog[]> {
|
listAuditLogs(queryParam: AuditLog): Observable<AuditLog[]> {
|
||||||
return this.http
|
return this.http
|
||||||
.post(urlPrefix + `/api/projects/${queryParam.project_id}/logs/filter`, {
|
.post(`/api/projects/${queryParam.project_id}/logs/filter`, {
|
||||||
begin_timestamp: queryParam.begin_timestamp,
|
begin_timestamp: queryParam.begin_timestamp,
|
||||||
end_timestamp: queryParam.end_timestamp,
|
end_timestamp: queryParam.end_timestamp,
|
||||||
keywords: queryParam.keywords,
|
keywords: queryParam.keywords,
|
||||||
operation: queryParam.operation,
|
operation: queryParam.operation,
|
||||||
project_id: queryParam.project_id,
|
project_id: queryParam.project_id,
|
||||||
username: queryParam.username })
|
username: queryParam.username
|
||||||
.map(response=>response.json() as AuditLog[])
|
})
|
||||||
.catch(error=>this.handleError(error));
|
.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 { AuditLogComponent } from './audit-log.component';
|
||||||
import { SharedModule } from '../shared/shared.module';
|
import { SharedModule } from '../shared/shared.module';
|
||||||
import { AuditLogService } from './audit-log.service';
|
import { AuditLogService } from './audit-log.service';
|
||||||
|
import { RecentLogComponent } from './recent-log.component';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [ SharedModule ],
|
imports: [SharedModule],
|
||||||
declarations: [ AuditLogComponent ],
|
declarations: [
|
||||||
providers: [ AuditLogService ],
|
AuditLogComponent,
|
||||||
exports: [ 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">
|
<clr-modal [(clrModalOpen)]="createProjectOpened">
|
||||||
<h3 class="modal-title">{{'PROJECT.NEW_PROJECT' | translate}}</h3>
|
<h3 class="modal-title">{{'PROJECT.NEW_PROJECT' | translate}}</h3>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<form>
|
<form #projectForm="ngForm">
|
||||||
<section class="form-block">
|
<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">
|
<div class="form-group">
|
||||||
<label for="create_project_name" class="col-md-4">{{'PROJECT.NAME' | translate}}</label>
|
<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">
|
<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" (keyup)="hasError=false;">
|
<input type="text" id="create_project_name" [(ngModel)]="project.name" name="name" size="20" required minlength="2" #projectName="ngModel">
|
||||||
<span class="tooltip-content">
|
<span class="tooltip-content" *ngIf="projectName.errors && projectName.errors.required && (projectName.dirty || projectName.touched)">
|
||||||
{{errorMessage}}
|
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>
|
</span>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
@ -24,6 +34,6 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<button type="button" class="btn btn-outline" (click)="createProjectOpened = false">{{'BUTTON.CANCEL' | translate}}</button>
|
<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>
|
</div>
|
||||||
</clr-modal>
|
</clr-modal>
|
||||||
|
@ -20,8 +20,8 @@ export class CreateProjectComponent {
|
|||||||
project: Project = new Project();
|
project: Project = new Project();
|
||||||
createProjectOpened: boolean;
|
createProjectOpened: boolean;
|
||||||
|
|
||||||
|
errorMessageOpened: boolean;
|
||||||
errorMessage: string;
|
errorMessage: string;
|
||||||
hasError: boolean;
|
|
||||||
|
|
||||||
@Output() create = new EventEmitter<boolean>();
|
@Output() create = new EventEmitter<boolean>();
|
||||||
|
|
||||||
@ -30,7 +30,6 @@ export class CreateProjectComponent {
|
|||||||
private translateService: TranslateService) {}
|
private translateService: TranslateService) {}
|
||||||
|
|
||||||
onSubmit() {
|
onSubmit() {
|
||||||
this.hasError = false;
|
|
||||||
this.projectService
|
this.projectService
|
||||||
.createProject(this.project.name, this.project.public ? 1 : 0)
|
.createProject(this.project.name, this.project.public ? 1 : 0)
|
||||||
.subscribe(
|
.subscribe(
|
||||||
@ -39,7 +38,7 @@ export class CreateProjectComponent {
|
|||||||
this.createProjectOpened = false;
|
this.createProjectOpened = false;
|
||||||
},
|
},
|
||||||
error=>{
|
error=>{
|
||||||
this.hasError = true;
|
this.errorMessageOpened = true;
|
||||||
if (error instanceof Response) {
|
if (error instanceof Response) {
|
||||||
switch(error.status) {
|
switch(error.status) {
|
||||||
case 409:
|
case 409:
|
||||||
@ -59,9 +58,15 @@ export class CreateProjectComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
newProject() {
|
newProject() {
|
||||||
this.hasError = false;
|
|
||||||
this.project = new Project();
|
this.project = new Project();
|
||||||
this.createProjectOpened = true;
|
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.creation_time}}</clr-dg-cell>
|
||||||
<clr-dg-cell>
|
<clr-dg-cell>
|
||||||
{{p.description}}
|
{{p.description}}
|
||||||
<span style="float: right;">
|
<harbor-action-overflow>
|
||||||
<action-project (togglePublic)="toggleProject($event)" (deleteProject)="deleteProject($event)" [project]="p"></action-project>
|
<a href="javascript:void(0)" class="dropdown-item">{{'PROJECT.NEW_POLICY' | translate}}</a>
|
||||||
</span>
|
<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-cell>
|
||||||
</clr-dg-row>
|
</clr-dg-row>
|
||||||
<clr-dg-footer>{{ (projects ? projects.length : 0) }} {{'PROJECT.ITEMS' | translate}}</clr-dg-footer>
|
<clr-dg-footer>{{ (projects ? projects.length : 0) }} {{'PROJECT.ITEMS' | translate}}</clr-dg-footer>
|
||||||
|
@ -1,14 +1,21 @@
|
|||||||
<clr-modal [(clrModalOpen)]="addMemberOpened">
|
<clr-modal [(clrModalOpen)]="addMemberOpened">
|
||||||
<h3 class="modal-title">{{'MEMBER.NEW_MEMBER' | translate}}</h3>
|
<h3 class="modal-title">{{'MEMBER.NEW_MEMBER' | translate}}</h3>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<form>
|
<form #memberForm="ngForm">
|
||||||
<section class="form-block">
|
<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">
|
<div class="form-group">
|
||||||
<label for="member_name" class="col-md-4">{{'MEMBER.NAME' | translate}}</label>
|
<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">
|
<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" (keyup)="hasError=false;">
|
<input type="text" id="member_name" [(ngModel)]="member.username" name="name" size="20" #memberName="ngModel" required>
|
||||||
<span class="tooltip-content">
|
<span class="tooltip-content" *ngIf="memberName.errors && memberName.errors.required && (memberName.dirty || memberName.touched)">
|
||||||
{{errorMessage}}
|
Username is required.
|
||||||
</span>
|
</span>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
@ -18,7 +18,9 @@ export class AddMemberComponent {
|
|||||||
member: Member = new Member();
|
member: Member = new Member();
|
||||||
addMemberOpened: boolean;
|
addMemberOpened: boolean;
|
||||||
errorMessage: string;
|
errorMessage: string;
|
||||||
hasError: boolean;
|
|
||||||
|
errorMessageOpened: boolean;
|
||||||
|
|
||||||
|
|
||||||
@Input() projectId: number;
|
@Input() projectId: number;
|
||||||
@Output() added = new EventEmitter<boolean>();
|
@Output() added = new EventEmitter<boolean>();
|
||||||
@ -28,7 +30,6 @@ export class AddMemberComponent {
|
|||||||
private translateService: TranslateService) {}
|
private translateService: TranslateService) {}
|
||||||
|
|
||||||
onSubmit(): void {
|
onSubmit(): void {
|
||||||
this.hasError = false;
|
|
||||||
console.log('Adding member:' + JSON.stringify(this.member));
|
console.log('Adding member:' + JSON.stringify(this.member));
|
||||||
this.memberService
|
this.memberService
|
||||||
.addMember(this.projectId, this.member.username, this.member.role_id)
|
.addMember(this.projectId, this.member.username, this.member.role_id)
|
||||||
@ -39,7 +40,7 @@ export class AddMemberComponent {
|
|||||||
this.addMemberOpened = false;
|
this.addMemberOpened = false;
|
||||||
},
|
},
|
||||||
error=>{
|
error=>{
|
||||||
this.hasError = true;
|
this.errorMessageOpened = true;
|
||||||
if (error instanceof Response) {
|
if (error instanceof Response) {
|
||||||
switch(error.status){
|
switch(error.status){
|
||||||
case 404:
|
case 404:
|
||||||
@ -59,13 +60,17 @@ export class AddMemberComponent {
|
|||||||
console.log('Failed to add member of project:' + this.projectId, ' with error:' + error);
|
console.log('Failed to add member of project:' + this.projectId, ' with error:' + error);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
openAddMemberModal(): void {
|
openAddMemberModal(): void {
|
||||||
this.hasError = false;
|
this.errorMessageOpened = false;
|
||||||
|
this.errorMessage = '';
|
||||||
this.member = new Member();
|
this.member = new Member();
|
||||||
this.addMemberOpened = true;
|
this.addMemberOpened = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onErrorMessageClose(): void {
|
||||||
|
this.errorMessageOpened = false;
|
||||||
|
this.errorMessage = '';
|
||||||
|
}
|
||||||
}
|
}
|
@ -1,38 +1,34 @@
|
|||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
|
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
|
||||||
<div class="row flex-items-xs-between">
|
<div class="row flex-items-xs-between">
|
||||||
<div class="col-xs-4 flex-xs-middle">
|
<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>
|
<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>
|
<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>
|
</div>
|
||||||
</clr-dropdown>
|
<div class="col-xs-4 flex-xs-middle push-xs-1">
|
||||||
</clr-dg-cell>
|
<grid-filter filterPlaceholder='{{"MEMBER.FILTER_PLACEHOLDER" | translate}}' (filter)="doSearch($event)"></grid-filter>
|
||||||
</clr-dg-row>
|
<a href="javascript:void(0)" (click)="refresh()">
|
||||||
<clr-dg-footer>{{ (members ? members.length : 0) }} {{'MEMBER.ITEMS' | translate}}</clr-dg-footer>
|
<clr-icon shape="refresh"></clr-icon>
|
||||||
</clr-datagrid>
|
</a>
|
||||||
</div>
|
</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>
|
</div>
|
@ -9,11 +9,11 @@ import { MemberService } from './member.service';
|
|||||||
import { AddMemberComponent } from './add-member/add-member.component';
|
import { AddMemberComponent } from './add-member/add-member.component';
|
||||||
|
|
||||||
import { MessageService } from '../../global-message/message.service';
|
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 { DeletionDialogService } from '../../shared/deletion-dialog/deletion-dialog.service';
|
||||||
import { DeletionMessage } from '../../shared/deletion-dialog/deletion-message';
|
import { DeletionMessage } from '../../shared/deletion-dialog/deletion-message';
|
||||||
|
import { SessionService } from '../../shared/session.service';
|
||||||
|
|
||||||
import { Observable } from 'rxjs/Observable';
|
import { Observable } from 'rxjs/Observable';
|
||||||
import 'rxjs/add/operator/switchMap';
|
import 'rxjs/add/operator/switchMap';
|
||||||
@ -21,7 +21,7 @@ import 'rxjs/add/operator/catch';
|
|||||||
import 'rxjs/add/operator/map';
|
import 'rxjs/add/operator/map';
|
||||||
import 'rxjs/add/observable/throw';
|
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({
|
@Component({
|
||||||
templateUrl: 'member.component.html'
|
templateUrl: 'member.component.html'
|
||||||
@ -37,33 +37,36 @@ export class MemberComponent implements OnInit {
|
|||||||
addMemberComponent: AddMemberComponent;
|
addMemberComponent: AddMemberComponent;
|
||||||
|
|
||||||
constructor(private route: ActivatedRoute, private router: Router,
|
constructor(private route: ActivatedRoute, private router: Router,
|
||||||
private memberService: MemberService, private messageService: MessageService,
|
private memberService: MemberService, private messageService: MessageService,
|
||||||
private deletionDialogService: DeletionDialogService) {
|
private deletionDialogService: DeletionDialogService,
|
||||||
|
session:SessionService) {
|
||||||
//Get current user from registered resolver.
|
//Get current user from registered resolver.
|
||||||
this.route.data.subscribe(data=>this.currentUser = <SessionUser>data['memberResolver']);
|
this.currentUser = session.getCurrentUser();
|
||||||
deletionDialogService.deletionConfirm$.subscribe(userId=>{
|
deletionDialogService.deletionConfirm$.subscribe(message => {
|
||||||
this.memberService
|
if (message && message.targetId === DeletionTargets.PROJECT_MEMBER) {
|
||||||
.deleteMember(this.projectId, userId)
|
this.memberService
|
||||||
.subscribe(
|
.deleteMember(this.projectId, message.data)
|
||||||
response=>{
|
.subscribe(
|
||||||
console.log('Successful change role with user ' + userId);
|
response => {
|
||||||
|
console.log('Successful change role with user ' + message.data);
|
||||||
this.retrieve(this.projectId, '');
|
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
|
this.memberService
|
||||||
.listMembers(projectId, username)
|
.listMembers(projectId, username)
|
||||||
.subscribe(
|
.subscribe(
|
||||||
response=>this.members = response,
|
response => this.members = response,
|
||||||
error=>{
|
error => {
|
||||||
this.router.navigate(['/harbor', 'projects']);
|
this.router.navigate(['/harbor', 'projects']);
|
||||||
this.messageService.announceMessage(error.status, 'Failed to get project member with project ID:' + projectId, AlertType.DANGER);
|
this.messageService.announceMessage(error.status, 'Failed to get project member with project ID:' + projectId, AlertType.DANGER);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
@ -84,22 +87,32 @@ export class MemberComponent implements OnInit {
|
|||||||
|
|
||||||
changeRole(userId: number, roleId: number) {
|
changeRole(userId: number, roleId: number) {
|
||||||
this.memberService
|
this.memberService
|
||||||
.changeMemberRole(this.projectId, userId, roleId)
|
.changeMemberRole(this.projectId, userId, roleId)
|
||||||
.subscribe(
|
.subscribe(
|
||||||
response=>{
|
response => {
|
||||||
console.log('Successful change role with user ' + userId + ' to roleId ' + roleId);
|
console.log('Successful change role with user ' + userId + ' to roleId ' + roleId);
|
||||||
this.retrieve(this.projectId, '');
|
this.retrieve(this.projectId, '');
|
||||||
},
|
},
|
||||||
error => this.messageService.announceMessage(error.status, 'Failed to change role with user ' + userId + ' to roleId ' + roleId, AlertType.DANGER)
|
error => this.messageService.announceMessage(error.status, 'Failed to change role with user ' + userId + ' to roleId ' + roleId, AlertType.DANGER)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
deleteMember(userId: number) {
|
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);
|
this.deletionDialogService.openComfirmDialog(deletionMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
doSearch(searchMember) {
|
doSearch(searchMember) {
|
||||||
this.retrieve(this.projectId, 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 { BaseService } from '../../service/base.service';
|
||||||
import { Member } from './member';
|
import { Member } from './member';
|
||||||
|
|
||||||
export const urlPrefix = '';
|
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class MemberService extends BaseService {
|
export class MemberService extends BaseService {
|
||||||
|
|
||||||
@ -21,7 +19,7 @@ export class MemberService extends BaseService {
|
|||||||
listMembers(projectId: number, username: string): Observable<Member[]> {
|
listMembers(projectId: number, username: string): Observable<Member[]> {
|
||||||
console.log('Get member from project_id:' + projectId + ', username:' + username);
|
console.log('Get member from project_id:' + projectId + ', username:' + username);
|
||||||
return this.http
|
return this.http
|
||||||
.get(urlPrefix + `/api/projects/${projectId}/members?username=${username}`)
|
.get(`/api/projects/${projectId}/members?username=${username}`)
|
||||||
.map(response=>response.json())
|
.map(response=>response.json())
|
||||||
.catch(error=>this.handleError(error));
|
.catch(error=>this.handleError(error));
|
||||||
}
|
}
|
||||||
@ -29,7 +27,7 @@ export class MemberService extends BaseService {
|
|||||||
addMember(projectId: number, username: string, roleId: number): Observable<any> {
|
addMember(projectId: number, username: string, roleId: number): Observable<any> {
|
||||||
console.log('Adding member with username:' + username + ', roleId:' + roleId + ' under projectId:' + projectId);
|
console.log('Adding member with username:' + username + ', roleId:' + roleId + ' under projectId:' + projectId);
|
||||||
return this.http
|
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)
|
.map(response=>response.status)
|
||||||
.catch(error=>Observable.throw(error));
|
.catch(error=>Observable.throw(error));
|
||||||
}
|
}
|
||||||
@ -37,7 +35,7 @@ export class MemberService extends BaseService {
|
|||||||
changeMemberRole(projectId: number, userId: number, roleId: number): Observable<any> {
|
changeMemberRole(projectId: number, userId: number, roleId: number): Observable<any> {
|
||||||
console.log('Changing member role with userId:' + ' to roleId:' + roleId + ' under projectId:' + projectId);
|
console.log('Changing member role with userId:' + ' to roleId:' + roleId + ' under projectId:' + projectId);
|
||||||
return this.http
|
return this.http
|
||||||
.put(urlPrefix + `/api/projects/${projectId}/members/${userId}`, { roles: [ roleId ]})
|
.put(`/api/projects/${projectId}/members/${userId}`, { roles: [ roleId ]})
|
||||||
.map(response=>response.status)
|
.map(response=>response.status)
|
||||||
.catch(error=>Observable.throw(error));
|
.catch(error=>Observable.throw(error));
|
||||||
}
|
}
|
||||||
@ -45,7 +43,7 @@ export class MemberService extends BaseService {
|
|||||||
deleteMember(projectId: number, userId: number): Observable<any> {
|
deleteMember(projectId: number, userId: number): Observable<any> {
|
||||||
console.log('Deleting member role with userId:' + userId + ' under projectId:' + projectId);
|
console.log('Deleting member role with userId:' + userId + ' under projectId:' + projectId);
|
||||||
return this.http
|
return this.http
|
||||||
.delete(urlPrefix + `/api/projects/${projectId}/members/${userId}`)
|
.delete(`/api/projects/${projectId}/members/${userId}`)
|
||||||
.map(response=>response.status)
|
.map(response=>response.status)
|
||||||
.catch(error=>Observable.throw(error));
|
.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>
|
<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>
|
<create-project (create)="createProject($event)"></create-project>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-xs-5">
|
<div class="col-xs-5 push-xs-1">
|
||||||
<clr-dropdown [clrMenuPosition]="'bottom-left'">
|
<clr-dropdown [clrMenuPosition]="'bottom-left'">
|
||||||
<button class="btn btn-sm btn-link" clrDropdownToggle>
|
<button class="btn btn-sm btn-link" clrDropdownToggle>
|
||||||
{{projectTypes[currentFilteredType] | translate}}
|
{{projectTypes[currentFilteredType] | translate}}
|
||||||
@ -15,7 +15,8 @@
|
|||||||
<a href="javascript:void(0)" clrDropdownItem (click)="doFilterProjects(1)">{{projectTypes[1] | translate}}</a>
|
<a href="javascript:void(0)" clrDropdownItem (click)="doFilterProjects(1)">{{projectTypes[1] | translate}}</a>
|
||||||
</div>
|
</div>
|
||||||
</clr-dropdown>
|
</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>
|
||||||
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
|
<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>
|
<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 { MessageService } from '../global-message/message.service';
|
||||||
import { Message } from '../global-message/message';
|
import { Message } from '../global-message/message';
|
||||||
|
|
||||||
export const types: {} = { 0: 'PROJECT.MY_PROJECTS', 1: 'PROJECT.PUBLIC_PROJECTS'};
|
|
||||||
|
|
||||||
import { AlertType } from '../shared/shared.const';
|
import { AlertType } from '../shared/shared.const';
|
||||||
import { Response } from '@angular/http';
|
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({
|
@Component({
|
||||||
selector: 'project',
|
selector: 'project',
|
||||||
templateUrl: 'project.component.html',
|
templateUrl: 'project.component.html',
|
||||||
@ -37,7 +44,27 @@ export class ProjectComponent implements OnInit {
|
|||||||
currentFilteredType: number = 0;
|
currentFilteredType: number = 0;
|
||||||
lastFilteredType: 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 {
|
ngOnInit(): void {
|
||||||
this.retrieve('', this.lastFilteredType);
|
this.retrieve('', this.lastFilteredType);
|
||||||
@ -75,24 +102,30 @@ export class ProjectComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
toggleProject(p: Project) {
|
toggleProject(p: Project) {
|
||||||
this.projectService
|
if (p) {
|
||||||
|
p.public === 0 ? p.public = 1 : p.public = 0;
|
||||||
|
this.projectService
|
||||||
.toggleProjectPublic(p.project_id, p.public)
|
.toggleProjectPublic(p.project_id, p.public)
|
||||||
.subscribe(
|
.subscribe(
|
||||||
response=>console.log('Successful toggled project_id:' + p.project_id),
|
response=>console.log('Successful toggled project_id:' + p.project_id),
|
||||||
error=>this.messageService.announceMessage(error.status, error, AlertType.WARNING)
|
error=>this.messageService.announceMessage(error.status, error, AlertType.WARNING)
|
||||||
);
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
deleteProject(p: Project) {
|
deleteProject(p: Project) {
|
||||||
this.projectService
|
let deletionMessage = new DeletionMessage(
|
||||||
.deleteProject(p.project_id)
|
'PROJECT.DELETION_TITLE',
|
||||||
.subscribe(
|
'PROJECT.DELETION_SUMMARY',
|
||||||
response=>{
|
p.name,
|
||||||
console.log('Successful delete project_id:' + p.project_id);
|
p.project_id,
|
||||||
this.retrieve('', this.lastFilteredType);
|
DeletionTargets.PROJECT
|
||||||
},
|
);
|
||||||
error=>this.messageService.announceMessage(error.status, error, AlertType.WARNING)
|
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 { NgModule } from '@angular/core';
|
||||||
|
|
||||||
|
import { RouterModule } from '@angular/router';
|
||||||
import { SharedModule } from '../shared/shared.module';
|
import { SharedModule } from '../shared/shared.module';
|
||||||
import { RepositoryModule } from '../repository/repository.module';
|
import { RepositoryModule } from '../repository/repository.module';
|
||||||
import { ReplicationModule } from '../replication/replication.module';
|
import { ReplicationModule } from '../replication/replication.module';
|
||||||
@ -7,16 +8,12 @@ import { LogModule } from '../log/log.module';
|
|||||||
|
|
||||||
import { ProjectComponent } from './project.component';
|
import { ProjectComponent } from './project.component';
|
||||||
import { CreateProjectComponent } from './create-project/create-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 { ListProjectComponent } from './list-project/list-project.component';
|
||||||
|
|
||||||
import { ProjectDetailComponent } from './project-detail/project-detail.component';
|
import { ProjectDetailComponent } from './project-detail/project-detail.component';
|
||||||
|
|
||||||
import { MemberComponent } from './member/member.component';
|
import { MemberComponent } from './member/member.component';
|
||||||
import { AddMemberComponent } from './member/add-member/add-member.component';
|
import { AddMemberComponent } from './member/add-member/add-member.component';
|
||||||
|
|
||||||
import { ProjectRoutingModule } from './project-routing.module';
|
|
||||||
|
|
||||||
import { ProjectService } from './project.service';
|
import { ProjectService } from './project.service';
|
||||||
import { MemberService } from './member/member.service';
|
import { MemberService } from './member/member.service';
|
||||||
import { ProjectRoutingResolver } from './project-routing-resolver.service';
|
import { ProjectRoutingResolver } from './project-routing-resolver.service';
|
||||||
@ -27,19 +24,18 @@ import { ProjectRoutingResolver } from './project-routing-resolver.service';
|
|||||||
RepositoryModule,
|
RepositoryModule,
|
||||||
ReplicationModule,
|
ReplicationModule,
|
||||||
LogModule,
|
LogModule,
|
||||||
ProjectRoutingModule
|
RouterModule
|
||||||
],
|
],
|
||||||
declarations: [
|
declarations: [
|
||||||
ProjectComponent,
|
ProjectComponent,
|
||||||
CreateProjectComponent,
|
CreateProjectComponent,
|
||||||
ActionProjectComponent,
|
|
||||||
ListProjectComponent,
|
ListProjectComponent,
|
||||||
ProjectDetailComponent,
|
ProjectDetailComponent,
|
||||||
MemberComponent,
|
MemberComponent,
|
||||||
AddMemberComponent
|
AddMemberComponent
|
||||||
],
|
],
|
||||||
exports: [ ProjectComponent ],
|
exports: [ProjectComponent],
|
||||||
providers: [ ProjectRoutingResolver, ProjectService, MemberService ]
|
providers: [ProjectRoutingResolver, ProjectService, MemberService]
|
||||||
})
|
})
|
||||||
export class ProjectModule {
|
export class ProjectModule {
|
||||||
|
|
||||||
|
@ -12,8 +12,6 @@ import 'rxjs/add/operator/catch';
|
|||||||
import 'rxjs/add/operator/map';
|
import 'rxjs/add/operator/map';
|
||||||
import 'rxjs/add/observable/throw';
|
import 'rxjs/add/observable/throw';
|
||||||
|
|
||||||
const url_prefix = '';
|
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ProjectService {
|
export class ProjectService {
|
||||||
|
|
||||||
@ -24,7 +22,7 @@ export class ProjectService {
|
|||||||
|
|
||||||
getProject(projectId: number): Promise<Project> {
|
getProject(projectId: number): Promise<Project> {
|
||||||
return this.http
|
return this.http
|
||||||
.get(url_prefix + `/api/projects/${projectId}`)
|
.get(`/api/projects/${projectId}`)
|
||||||
.toPromise()
|
.toPromise()
|
||||||
.then(response=>response.json() as Project)
|
.then(response=>response.json() as Project)
|
||||||
.catch(error=>Observable.throw(error));
|
.catch(error=>Observable.throw(error));
|
||||||
@ -32,14 +30,14 @@ export class ProjectService {
|
|||||||
|
|
||||||
listProjects(name: string, isPublic: number): Observable<any>{
|
listProjects(name: string, isPublic: number): Observable<any>{
|
||||||
return this.http
|
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())
|
.map(response=>response.json())
|
||||||
.catch(error=>Observable.throw(error));
|
.catch(error=>Observable.throw(error));
|
||||||
}
|
}
|
||||||
|
|
||||||
createProject(name: string, isPublic: number): Observable<any> {
|
createProject(name: string, isPublic: number): Observable<any> {
|
||||||
return this.http
|
return this.http
|
||||||
.post(url_prefix + `/api/projects`,
|
.post(`/api/projects`,
|
||||||
JSON.stringify({'project_name': name, 'public': isPublic})
|
JSON.stringify({'project_name': name, 'public': isPublic})
|
||||||
, this.options)
|
, this.options)
|
||||||
.map(response=>response.status)
|
.map(response=>response.status)
|
||||||
@ -48,14 +46,14 @@ export class ProjectService {
|
|||||||
|
|
||||||
toggleProjectPublic(projectId: number, isPublic: number): Observable<any> {
|
toggleProjectPublic(projectId: number, isPublic: number): Observable<any> {
|
||||||
return this.http
|
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)
|
.map(response=>response.status)
|
||||||
.catch(error=>Observable.throw(error));
|
.catch(error=>Observable.throw(error));
|
||||||
}
|
}
|
||||||
|
|
||||||
deleteProject(projectId: number): Observable<any> {
|
deleteProject(projectId: number): Observable<any> {
|
||||||
return this.http
|
return this.http
|
||||||
.delete(url_prefix + `/api/projects/${projectId}`)
|
.delete(`/api/projects/${projectId}`)
|
||||||
.map(response=>response.status)
|
.map(response=>response.status)
|
||||||
.catch(error=>Observable.throw(error));
|
.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="col-lg-12 col-md-12 col-sm-12 col-xs-12">
|
||||||
<div class="row flex-items-xs-between">
|
<div class="row flex-items-xs-between">
|
||||||
<div class="col-xs-4">
|
<div class="col-xs-4">
|
||||||
<button class="btn btn-link" (click)="openModal()"><clr-icon shape="add"></clr-icon> Policy</button>
|
<button class="btn btn-link" (click)="openModal()"><clr-icon shape="add"></clr-icon> Replication Rule</button>
|
||||||
<create-edit-policy></create-edit-policy>
|
<create-edit-policy [projectId]="projectId" (reload)="reloadPolicies($event)"></create-edit-policy>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-xs-4">
|
<div class="col-xs-5 push-xs-1">
|
||||||
<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">
|
|
||||||
<clr-dropdown [clrMenuPosition]="'bottom-left'">
|
<clr-dropdown [clrMenuPosition]="'bottom-left'">
|
||||||
<button class="btn btn-sm btn-outline-primary" clrDropdownToggle>
|
<button class="btn btn-link" clrDropdownToggle>
|
||||||
All
|
{{currentRuleStatus.description}}
|
||||||
<clr-icon shape="caret down"></clr-icon>
|
<clr-icon shape="caret down"></clr-icon>
|
||||||
</button>
|
</button>
|
||||||
<div class="dropdown-menu">
|
<div class="dropdown-menu">
|
||||||
<a href="javascript:void(0)" clrDropdownItem>Finished</a>
|
<a href="javascript:void(0)" clrDropdownItem *ngFor="let r of ruleStatus" (click)="doFilterPolicyStatus(r.key)"> {{r.description}}</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>
|
|
||||||
</div>
|
</div>
|
||||||
</clr-dropdown>
|
</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>
|
||||||
</div>
|
</div>
|
||||||
<list-job [jobs]="changedJobs"></list-job>
|
<list-job [jobs]="changedJobs"></list-job>
|
||||||
|
@ -1,11 +1,13 @@
|
|||||||
import { Component, OnInit, ViewChild } from '@angular/core';
|
import { Component, OnInit, ViewChild } from '@angular/core';
|
||||||
import { ActivatedRoute } from '@angular/router';
|
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 { MessageService } from '../global-message/message.service';
|
||||||
import { AlertType } from '../shared/shared.const';
|
import { AlertType } from '../shared/shared.const';
|
||||||
|
|
||||||
|
import { SessionService } from '../shared/session.service';
|
||||||
|
|
||||||
import { ReplicationService } from './replication.service';
|
import { ReplicationService } from './replication.service';
|
||||||
|
|
||||||
import { SessionUser } from '../shared/session-user';
|
import { SessionUser } from '../shared/session-user';
|
||||||
@ -13,6 +15,34 @@ import { Policy } from './policy';
|
|||||||
import { Job } from './job';
|
import { Job } from './job';
|
||||||
import { Target } from './target';
|
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({
|
@Component({
|
||||||
selector: 'replicaton',
|
selector: 'replicaton',
|
||||||
templateUrl: 'replication.component.html'
|
templateUrl: 'replication.component.html'
|
||||||
@ -22,34 +52,55 @@ export class ReplicationComponent implements OnInit {
|
|||||||
currentUser: SessionUser;
|
currentUser: SessionUser;
|
||||||
projectId: number;
|
projectId: number;
|
||||||
|
|
||||||
policyName: string;
|
search: SearchOption;
|
||||||
|
|
||||||
policy: Policy;
|
ruleStatus = ruleStatus;
|
||||||
|
currentRuleStatus: {key: string, description: string};
|
||||||
|
|
||||||
|
jobStatus = jobStatus;
|
||||||
|
currentJobStatus: {key: string, description: string};
|
||||||
|
|
||||||
changedPolicies: Policy[];
|
changedPolicies: Policy[];
|
||||||
changedJobs: Job[];
|
changedJobs: Job[];
|
||||||
|
|
||||||
@ViewChild(CreateEditPolicyComponent)
|
policies: Policy[];
|
||||||
createEditPolicyComponent: CreateEditPolicyComponent
|
jobs: Job[];
|
||||||
|
|
||||||
constructor(private route: ActivatedRoute, private messageService: MessageService, private replicationService: ReplicationService) {
|
toggleJobSearchOption = optionalSearch;
|
||||||
this.route.data.subscribe(data=>this.currentUser = <SessionUser>data);
|
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 {
|
ngOnInit(): void {
|
||||||
this.projectId = +this.route.snapshot.parent.params['id'];
|
this.projectId = +this.route.snapshot.parent.params['id'];
|
||||||
console.log('Get projectId from route params snapshot:' + this.projectId);
|
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();
|
this.retrievePolicies();
|
||||||
}
|
}
|
||||||
|
|
||||||
retrievePolicies(): void {
|
retrievePolicies(): void {
|
||||||
this.replicationService
|
this.replicationService
|
||||||
.listPolicies(this.projectId, this.policyName)
|
.listPolicies(this.search.policyName, this.projectId)
|
||||||
.subscribe(
|
.subscribe(
|
||||||
response=>{
|
response=>{
|
||||||
this.changedPolicies = response;
|
this.changedPolicies = response;
|
||||||
|
this.policies = this.changedPolicies;
|
||||||
if(this.changedPolicies && this.changedPolicies.length > 0) {
|
if(this.changedPolicies && this.changedPolicies.length > 0) {
|
||||||
this.fetchPolicyJobs(this.changedPolicies[0].id);
|
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)
|
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 {
|
openModal(): void {
|
||||||
|
console.log('Open modal to create policy.');
|
||||||
this.createEditPolicyComponent.openCreateEditPolicy();
|
this.createEditPolicyComponent.openCreateEditPolicy();
|
||||||
console.log('Clicked open create-edit policy.');
|
}
|
||||||
|
|
||||||
|
openEditPolicy(policyId: number) {
|
||||||
|
console.log('Open modal to edit policy ID:' + policyId);
|
||||||
|
this.createEditPolicyComponent.openCreateEditPolicy(policyId);
|
||||||
}
|
}
|
||||||
|
|
||||||
fetchPolicyJobs(policyId: number) {
|
fetchPolicyJobs(policyId: number) {
|
||||||
console.log('Received policy ID ' + policyId + ' by clicked row.');
|
this.search.policyId = policyId;
|
||||||
|
console.log('Received policy ID ' + this.search.policyId + ' by clicked row.');
|
||||||
this.replicationService
|
this.replicationService
|
||||||
.listJobs(policyId)
|
.listJobs(this.search.policyId, this.search.status, this.search.repoName, this.search.startTime, this.search.endTime)
|
||||||
.subscribe(
|
.subscribe(
|
||||||
response=>this.changedJobs = response,
|
response=>{
|
||||||
error=>this.messageService.announceMessage(error.status, 'Failed to fetch jobs with policy ID:' + policyId, AlertType.DANGER)
|
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 { NgModule } from '@angular/core';
|
||||||
|
import { RouterModule } from '@angular/router';
|
||||||
|
import { ReplicationManagementComponent } from './replication-management/replication-management.component';
|
||||||
|
|
||||||
import { ReplicationComponent } from './replication.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 { ListJobComponent } from './list-job/list-job.component';
|
||||||
|
import { TotalReplicationComponent } from './total-replication/total-replication.component';
|
||||||
import { CustomHighlightDirective } from './list-policy/custom-highlight.directive';
|
import { DestinationComponent } from './destination/destination.component';
|
||||||
|
import { CreateEditDestinationComponent } from './create-edit-destination/create-edit-destination.component';
|
||||||
|
|
||||||
import { SharedModule } from '../shared/shared.module';
|
import { SharedModule } from '../shared/shared.module';
|
||||||
import { ReplicationService } from './replication.service';
|
import { ReplicationService } from './replication.service';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [ SharedModule ],
|
imports: [
|
||||||
|
SharedModule,
|
||||||
|
RouterModule
|
||||||
|
],
|
||||||
declarations: [
|
declarations: [
|
||||||
ReplicationComponent,
|
ReplicationComponent,
|
||||||
CreateEditPolicyComponent,
|
ReplicationManagementComponent,
|
||||||
ListPolicyComponent,
|
|
||||||
ListJobComponent,
|
ListJobComponent,
|
||||||
CustomHighlightDirective
|
TotalReplicationComponent,
|
||||||
|
DestinationComponent,
|
||||||
|
CreateEditDestinationComponent
|
||||||
],
|
],
|
||||||
exports: [ ReplicationComponent ],
|
exports: [ ReplicationComponent ],
|
||||||
providers: [ ReplicationService ]
|
providers: [ ReplicationService ]
|
||||||
|
@ -1,17 +1,17 @@
|
|||||||
import { Injectable } from '@angular/core';
|
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 { BaseService } from '../service/base.service';
|
||||||
|
|
||||||
import { Policy } from './policy';
|
import { Policy } from './policy';
|
||||||
import { Job } from './job';
|
import { Job } from './job';
|
||||||
|
import { Target } from './target';
|
||||||
|
|
||||||
import { Observable } from 'rxjs/Observable';
|
import { Observable } from 'rxjs/Observable';
|
||||||
import 'rxjs/add/operator/catch';
|
import 'rxjs/add/operator/catch';
|
||||||
import 'rxjs/add/operator/map';
|
import 'rxjs/add/operator/map';
|
||||||
import 'rxjs/add/observable/throw';
|
import 'rxjs/add/observable/throw';
|
||||||
|
import 'rxjs/add/operator/mergeMap';
|
||||||
export const urlPrefix = '';
|
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ReplicationService extends BaseService {
|
export class ReplicationService extends BaseService {
|
||||||
@ -19,20 +19,154 @@ export class ReplicationService extends BaseService {
|
|||||||
super();
|
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);
|
console.log('Get policies with project ID:' + projectId + ', policy name:' + policyName);
|
||||||
return this.http
|
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[])
|
.map(response=>response.json() as Policy[])
|
||||||
.catch(error=>Observable.throw(error));
|
.catch(error=>Observable.throw(error));
|
||||||
}
|
}
|
||||||
|
|
||||||
// /api/jobs/replication/?page=1&page_size=20&end_time=&policy_id=1&start_time=&status=
|
getPolicy(policyId: number): Observable<Policy> {
|
||||||
listJobs(policyId: number, status: string = ''): Observable<Job[]> {
|
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);
|
console.log('Get jobs under policy ID:' + policyId);
|
||||||
return this.http
|
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[])
|
.map(response=>response.json() as Job[])
|
||||||
.catch(error=>Observable.throw(error));
|
.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="row">
|
||||||
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
|
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
|
||||||
<div class="row flex-items-lg-right">
|
<div class="row flex-items-xs-between">
|
||||||
<div class="col-lg-3 col-md-3 col-sm-12 col-xs-12">
|
<div class="col-xs-4 flex-xs-middle">
|
||||||
<clr-dropdown [clrMenuPosition]="'bottom-left'">
|
<clr-dropdown [clrMenuPosition]="'bottom-left'">
|
||||||
<button class="btn btn-sm btn-outline-primary" clrDropdownToggle>
|
<button class="btn btn-sm btn-outline-primary" clrDropdownToggle>
|
||||||
My Projects
|
My Projects
|
||||||
@ -12,6 +12,9 @@
|
|||||||
<a href="#/project" clrDropdownItem>Public Projects</a>
|
<a href="#/project" clrDropdownItem>Public Projects</a>
|
||||||
</div>
|
</div>
|
||||||
</clr-dropdown>
|
</clr-dropdown>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="col-xs-4 flex-xs-middle">
|
||||||
<input type="text" placeholder="Search for projects">
|
<input type="text" placeholder="Search for projects">
|
||||||
</div>
|
</div>
|
||||||
</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 { DeletionDialogService } from './deletion-dialog.service';
|
||||||
|
import { DeletionMessage } from './deletion-message';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'deletion-dialog',
|
selector: 'deletion-dialog',
|
||||||
@ -8,20 +11,32 @@ import { DeletionDialogService } from './deletion-dialog.service';
|
|||||||
styleUrls: ['deletion-dialog.component.css']
|
styleUrls: ['deletion-dialog.component.css']
|
||||||
})
|
})
|
||||||
|
|
||||||
export class DeletionDialogComponent{
|
export class DeletionDialogComponent implements OnDestroy{
|
||||||
opened: boolean = false;
|
opened: boolean = false;
|
||||||
dialogTitle: string = "";
|
dialogTitle: string = "";
|
||||||
dialogContent: string = "";
|
dialogContent: string = "";
|
||||||
data: any;
|
message: DeletionMessage;
|
||||||
|
private annouceSubscription: Subscription;
|
||||||
|
|
||||||
constructor(private delService: DeletionDialogService){
|
constructor(
|
||||||
delService.deletionAnnouced$.subscribe(msg => {
|
private delService: DeletionDialogService,
|
||||||
this.dialogTitle = msg.title;
|
private translate: TranslateService) {
|
||||||
this.dialogContent = msg.message;
|
this.annouceSubscription = delService.deletionAnnouced$.subscribe(msg => {
|
||||||
this.data = msg.data;
|
this.dialogTitle = msg.title;
|
||||||
//Open dialog
|
this.dialogContent = msg.message;
|
||||||
this.open();
|
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 {
|
open(): void {
|
||||||
@ -33,7 +48,7 @@ export class DeletionDialogComponent{
|
|||||||
}
|
}
|
||||||
|
|
||||||
confirm(): void {
|
confirm(): void {
|
||||||
this.delService.confirmDeletion(this.data);
|
this.delService.confirmDeletion(this.message);
|
||||||
this.close();
|
this.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -6,13 +6,13 @@ import { DeletionMessage } from './deletion-message';
|
|||||||
@Injectable()
|
@Injectable()
|
||||||
export class DeletionDialogService {
|
export class DeletionDialogService {
|
||||||
private deletionAnnoucedSource = new Subject<DeletionMessage>();
|
private deletionAnnoucedSource = new Subject<DeletionMessage>();
|
||||||
private deletionConfirmSource = new Subject<any>();
|
private deletionConfirmSource = new Subject<DeletionMessage>();
|
||||||
|
|
||||||
deletionAnnouced$ = this.deletionAnnoucedSource.asObservable();
|
deletionAnnouced$ = this.deletionAnnoucedSource.asObservable();
|
||||||
deletionConfirm$ = this.deletionConfirmSource.asObservable();
|
deletionConfirm$ = this.deletionConfirmSource.asObservable();
|
||||||
|
|
||||||
confirmDeletion(obj: any): void {
|
confirmDeletion(message: any): void {
|
||||||
this.deletionConfirmSource.next(obj);
|
this.deletionConfirmSource.next(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
openComfirmDialog(message: DeletionMessage): void {
|
openComfirmDialog(message: DeletionMessage): void {
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user