mirror of
https://github.com/goharbor/harbor.git
synced 2024-12-23 00:57:44 +01:00
update ui code for fixing bugs and upgrade Clarity version to 0.8.7
This commit is contained in:
parent
29f3e609e7
commit
a311ade53d
BIN
src/ui_ng/favicon.ico
Normal file
BIN
src/ui_ng/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 7.3 KiB |
@ -4,7 +4,7 @@
|
||||
"description": "Angular-CLI starter for a Clarity project",
|
||||
"angular-cli": {},
|
||||
"scripts": {
|
||||
"start": "ng serve --host 0.0.0.0",
|
||||
"start": "ng serve --host 0.0.0.0 --proxy-config proxy.config.json",
|
||||
"lint": "tslint \"src/**/*.ts\"",
|
||||
"test": "ng test --single-run",
|
||||
"pree2e": "webdriver-manager update",
|
||||
@ -24,9 +24,9 @@
|
||||
"@ngx-translate/http-loader": "0.0.3",
|
||||
"@webcomponents/custom-elements": "1.0.0-alpha.3",
|
||||
"angular2-cookie": "^1.2.6",
|
||||
"clarity-angular": "^0.8.0",
|
||||
"clarity-icons": "^0.8.0",
|
||||
"clarity-ui": "^0.8.0",
|
||||
"clarity-angular": "0.8.7",
|
||||
"clarity-icons": "0.8.7",
|
||||
"clarity-ui": "0.8.7",
|
||||
"core-js": "^2.4.1",
|
||||
"fs": "0.0.1-security",
|
||||
"mutationobserver-shim": "^0.3.2",
|
||||
|
@ -1,42 +0,0 @@
|
||||
{
|
||||
"/api/*": {
|
||||
"target": "http://10.117.4.165",
|
||||
"secure": false,
|
||||
"logLevel": "debug"
|
||||
},
|
||||
"/service/*": {
|
||||
"target": "http://10.117.4.165",
|
||||
"secure": false,
|
||||
"logLevel": "debug"
|
||||
},
|
||||
"/login": {
|
||||
"target": "http://10.117.4.165",
|
||||
"secure": false,
|
||||
"logLevel": "debug"
|
||||
},
|
||||
"/sign_in": {
|
||||
"target": "http://10.117.4.165",
|
||||
"secure": false,
|
||||
"logLevel": "debug"
|
||||
},
|
||||
"/log_out": {
|
||||
"target": "http://10.117.4.165",
|
||||
"secure": false,
|
||||
"logLevel": "debug"
|
||||
},
|
||||
"/sendEmail": {
|
||||
"target": "http://10.117.4.165",
|
||||
"secure": false,
|
||||
"logLevel": "debug"
|
||||
},
|
||||
"/language": {
|
||||
"target": "http://10.117.4.165",
|
||||
"secure": false,
|
||||
"logLevel": "debug"
|
||||
},
|
||||
"/reset": {
|
||||
"target": "http://10.117.4.165",
|
||||
"secure": false,
|
||||
"logLevel": "debug"
|
||||
}
|
||||
}
|
@ -1,36 +1,39 @@
|
||||
<clr-modal [(clrModalOpen)]="opened" [clrModalStaticBackdrop]="staticBackdrop">
|
||||
<h3 class="modal-title">{{'PROFILE.TITLE' | translate}}</h3>
|
||||
<inline-alert class="modal-title" (confirmEvt)="confirmCancel($event)"></inline-alert>
|
||||
<div class="modal-body" style="overflow-y: hidden;">
|
||||
<form #accountSettingsFrom="ngForm" class="form">
|
||||
<section class="form-block">
|
||||
<div class="form-group">
|
||||
<label for="account_settings_username" class="col-md-4">{{'PROFILE.USER_NAME' | translate}}</label>
|
||||
<input type="text" name="account_settings_username" [(ngModel)]="account.username" disabled id="account_settings_username" size="31">
|
||||
<div class="form-group form-group-override">
|
||||
<label for="account_settings_username" class="form-group-label-override">{{'PROFILE.USER_NAME' | translate}}</label>
|
||||
<input type="text" name="account_settings_username" [(ngModel)]="account.username" disabled id="account_settings_username" size="33">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="account_settings_email" class="col-md-4 required">{{'PROFILE.EMAIL' | translate}}</label>
|
||||
<label for="account_settings_email" aria-haspopup="true" role="tooltip" class="tooltip tooltip-validation tooltip-md tooltip-top-left" [class.invalid]="eamilInput.invalid && (eamilInput.dirty || eamilInput.touched)">
|
||||
<div class="form-group form-group-override">
|
||||
<label for="account_settings_email" class="required form-group-label-override">{{'PROFILE.EMAIL' | translate}}</label>
|
||||
<label for="account_settings_email" aria-haspopup="true" role="tooltip" class="tooltip tooltip-validation tooltip-md tooltip-top-left" [class.invalid]='!getValidationState("account_settings_email")'>
|
||||
<input name="account_settings_email" type="text" #eamilInput="ngModel" [(ngModel)]="account.email"
|
||||
required
|
||||
pattern='^[a-zA-Z0-9.!#$%&’*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$' id="account_settings_email" size="28">
|
||||
pattern='^[a-zA-Z0-9.!#$%&’*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$' id="account_settings_email" size="30"
|
||||
(input)='handleValidation("account_settings_email", false)'
|
||||
(focusout)='handleValidation("account_settings_email", true)'>
|
||||
<span class="tooltip-content">
|
||||
{{'TOOLTIP.EMAIL' | translate}}
|
||||
{{emailTooltip | translate}}
|
||||
</span>
|
||||
</label>
|
||||
</label><span class="spinner spinner-inline" [hidden]="!checkProgress"></span>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="account_settings_full_name" class="col-md-4 required">{{'PROFILE.FULL_NAME' | translate}}</label>
|
||||
<label for="account_settings_full_name" aria-haspopup="true" role="tooltip" class="tooltip tooltip-validation tooltip-md tooltip-top-left" [class.invalid]="fullNameInput.invalid && (fullNameInput.dirty || fullNameInput.touched)">
|
||||
<input type="text" name="account_settings_full_name" #fullNameInput="ngModel" [(ngModel)]="account.realname" required maxLengthExt="20" id="account_settings_full_name" size="28">
|
||||
<div class="form-group form-group-override">
|
||||
<label for="account_settings_full_name" class="required form-group-label-override">{{'PROFILE.FULL_NAME' | translate}}</label>
|
||||
<label for="account_settings_full_name" aria-haspopup="true" role="tooltip" class="tooltip tooltip-validation tooltip-md tooltip-top-left" [class.invalid]='!getValidationState("account_settings_full_name")'>
|
||||
<input type="text" name="account_settings_full_name" #fullNameInput="ngModel" [(ngModel)]="account.realname" required maxLengthExt="20" id="account_settings_full_name" size="30" (input)='handleValidation("account_settings_full_name", false)' (focusout)='handleValidation("account_settings_full_name", true)'>
|
||||
<span class="tooltip-content">
|
||||
{{'TOOLTIP.FULL_NAME' | translate}}
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="account_settings_comments" class="col-md-4">{{'PROFILE.COMMENT' | translate}}</label>
|
||||
<div class="form-group form-group-override">
|
||||
<label for="account_settings_comments" class="form-group-label-override">{{'PROFILE.COMMENT' | translate}}</label>
|
||||
<label for="account_settings_comments" aria-haspopup="true" role="tooltip" class="tooltip tooltip-validation tooltip-md tooltip-top-left" [class.invalid]="commentInput.invalid && (commentInput.dirty || commentInput.touched)">
|
||||
<input type="text" #commentInput="ngModel" maxLengthExt="20" name="account_settings_comments" [(ngModel)]="account.comment" id="account_settings_comments" size="28">
|
||||
<input type="text" #commentInput="ngModel" maxLengthExt="20" name="account_settings_comments" [(ngModel)]="account.comment" id="account_settings_comments" size="30">
|
||||
<span class="tooltip-content">
|
||||
{{'TOOLTIP.COMMENT' | translate}}
|
||||
</span>
|
||||
@ -38,7 +41,6 @@
|
||||
</div>
|
||||
</section>
|
||||
</form>
|
||||
<inline-alert (confirmEvt)="confirmCancel($event)"></inline-alert>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<span class="spinner spinner-inline" style="top:8px;" [hidden]="showProgress === false"></span>
|
||||
|
@ -10,7 +10,8 @@ import { InlineAlertComponent } from '../../shared/inline-alert/inline-alert.com
|
||||
|
||||
@Component({
|
||||
selector: "account-settings-modal",
|
||||
templateUrl: "account-settings-modal.component.html"
|
||||
templateUrl: "account-settings-modal.component.html",
|
||||
styleUrls: ['../../common.css']
|
||||
})
|
||||
|
||||
export class AccountSettingsModalComponent implements OnInit, AfterViewChecked {
|
||||
@ -19,9 +20,16 @@ export class AccountSettingsModalComponent implements OnInit, AfterViewChecked {
|
||||
account: SessionUser;
|
||||
error: any = null;
|
||||
originalStaticData: SessionUser;
|
||||
private emailTooltip: string = 'TOOLTIP.EMAIL';
|
||||
private validationStateMap: any = {
|
||||
"account_settings_email": true,
|
||||
"account_settings_full_name": true
|
||||
};
|
||||
private mailAlreadyChecked = {};
|
||||
|
||||
private isOnCalling: boolean = false;
|
||||
private formValueChanged: boolean = false;
|
||||
private checkOnGoing: boolean = false;
|
||||
|
||||
accountFormRef: NgForm;
|
||||
@ViewChild("accountSettingsFrom") accountForm: NgForm;
|
||||
@ -37,6 +45,54 @@ export class AccountSettingsModalComponent implements OnInit, AfterViewChecked {
|
||||
this.account = Object.assign({}, this.session.getCurrentUser());
|
||||
}
|
||||
|
||||
private getValidationState(key: string): boolean {
|
||||
return this.validationStateMap[key];
|
||||
}
|
||||
|
||||
private handleValidation(key: string, flag: boolean): void {
|
||||
if (flag) {
|
||||
//Checking
|
||||
let cont = this.accountForm.controls[key];
|
||||
if (cont) {
|
||||
this.validationStateMap[key] = cont.valid;
|
||||
//Check email existing from backend
|
||||
if (cont.valid && key === "account_settings_email") {
|
||||
if (this.formValueChanged && this.account.email != this.originalStaticData.email) {
|
||||
if (this.mailAlreadyChecked[this.account.email]) {
|
||||
this.validationStateMap[key] = !this.mailAlreadyChecked[this.account.email].result;
|
||||
if (!this.validationStateMap[key]) {
|
||||
this.emailTooltip = "TOOLTIP.EMAIL_EXISTING";
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
//Mail changed
|
||||
this.checkOnGoing = true;
|
||||
this.session.checkUserExisting("email", this.account.email)
|
||||
.then((res: boolean) => {
|
||||
this.checkOnGoing = false;
|
||||
this.validationStateMap[key] = !res;
|
||||
if (res) {
|
||||
this.emailTooltip = "TOOLTIP.EMAIL_EXISTING";
|
||||
}
|
||||
this.mailAlreadyChecked[this.account.email] = {
|
||||
result: res
|
||||
}; //Tag it checked
|
||||
})
|
||||
.catch(error => {
|
||||
this.checkOnGoing = false;
|
||||
this.validationStateMap[key] = false;//Not valid @ backend
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
//Reset
|
||||
this.validationStateMap[key] = true;
|
||||
this.emailTooltip = "TOOLTIP.EMAIL";
|
||||
}
|
||||
}
|
||||
|
||||
private isUserDataChange(): boolean {
|
||||
if (!this.originalStaticData || !this.account) {
|
||||
return false;
|
||||
@ -56,13 +112,20 @@ export class AccountSettingsModalComponent implements OnInit, AfterViewChecked {
|
||||
}
|
||||
|
||||
public get isValid(): boolean {
|
||||
return this.accountForm && this.accountForm.valid && this.error === null;
|
||||
return this.accountForm &&
|
||||
this.accountForm.valid &&
|
||||
this.error === null &&
|
||||
this.validationStateMap["account_settings_email"]; //backend check is valid as well
|
||||
}
|
||||
|
||||
public get showProgress(): boolean {
|
||||
return this.isOnCalling;
|
||||
}
|
||||
|
||||
public get checkProgress(): boolean {
|
||||
return this.checkOnGoing;
|
||||
}
|
||||
|
||||
ngAfterViewChecked(): void {
|
||||
if (this.accountFormRef != this.accountForm) {
|
||||
this.accountFormRef = this.accountForm;
|
||||
@ -124,9 +187,9 @@ export class AccountSettingsModalComponent implements OnInit, AfterViewChecked {
|
||||
.catch(error => {
|
||||
this.isOnCalling = false;
|
||||
this.error = error;
|
||||
if(accessErrorHandler(error, this.msgService)){
|
||||
if (accessErrorHandler(error, this.msgService)) {
|
||||
this.opened = false;
|
||||
}else{
|
||||
} else {
|
||||
this.inlineAlert.showInlineError(error);
|
||||
}
|
||||
});
|
||||
|
@ -9,14 +9,17 @@ 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 { SignUpPageComponent } from './sign-up/sign-up-page.component';
|
||||
|
||||
import { PasswordSettingService } from './password/password-setting.service';
|
||||
import { RepositoryModule } from '../repository/repository.module';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
CoreModule,
|
||||
RouterModule,
|
||||
SharedModule
|
||||
SharedModule,
|
||||
RepositoryModule
|
||||
],
|
||||
declarations: [
|
||||
SignInComponent,
|
||||
@ -24,12 +27,14 @@ import { PasswordSettingService } from './password/password-setting.service';
|
||||
AccountSettingsModalComponent,
|
||||
SignUpComponent,
|
||||
ForgotPasswordComponent,
|
||||
ResetPasswordComponent],
|
||||
ResetPasswordComponent,
|
||||
SignUpPageComponent],
|
||||
exports: [
|
||||
SignInComponent,
|
||||
PasswordSettingComponent,
|
||||
AccountSettingsModalComponent,
|
||||
ResetPasswordComponent],
|
||||
ResetPasswordComponent,
|
||||
SignUpPageComponent],
|
||||
|
||||
providers: [PasswordSettingService]
|
||||
})
|
||||
|
@ -1,17 +1,18 @@
|
||||
<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;">
|
||||
<inline-alert class="modal-title"></inline-alert>
|
||||
<div class="modal-body" style="overflow-y: hidden; min-height: 130px;">
|
||||
<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>
|
||||
<div class="form-group form-group-override">
|
||||
<label for="reset_pwd_email" class="required form-group-label-override">{{'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"
|
||||
size="40"
|
||||
(input)="handleValidation(true)"
|
||||
(focusout)="handleValidation(false)">
|
||||
<span class="tooltip-content">
|
||||
@ -21,8 +22,6 @@
|
||||
</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>
|
||||
|
@ -8,7 +8,7 @@ import { InlineAlertComponent } from '../../shared/inline-alert/inline-alert.com
|
||||
@Component({
|
||||
selector: 'forgot-password',
|
||||
templateUrl: "forgot-password.component.html",
|
||||
styleUrls: ['password.component.css']
|
||||
styleUrls: ['password.component.css', '../../common.css']
|
||||
})
|
||||
export class ForgotPasswordComponent {
|
||||
opened: boolean = false;
|
||||
|
@ -1,51 +1,51 @@
|
||||
<clr-modal [(clrModalOpen)]="opened" [clrModalStaticBackdrop]="true">
|
||||
<h3 class="modal-title">{{'CHANGE_PWD.TITLE' | translate}}</h3>
|
||||
<div class="modal-body" style="min-height: 250px; overflow-y: hidden;">
|
||||
<inline-alert class="modal-title" (confirmEvt)="confirmCancel($event)"></inline-alert>
|
||||
<div class="modal-body" style="overflow-y: hidden;">
|
||||
<form #changepwdForm="ngForm" class="form">
|
||||
<section class="form-block">
|
||||
<div class="form-group">
|
||||
<label for="oldPassword">{{'CHANGE_PWD.CURRENT_PWD' | translate}}</label>
|
||||
<div class="form-group form-group-override">
|
||||
<label for="oldPassword" class="required form-group-label-override">{{'CHANGE_PWD.CURRENT_PWD' | translate}}</label>
|
||||
<label for="oldPassword" aria-haspopup="true" role="tooltip" class="tooltip tooltip-validation tooltip-md tooltip-bottom-left" [class.invalid]="oldPassInput.invalid && (oldPassInput.dirty || oldPassInput.touched)">
|
||||
<input type="password" id="oldPassword" placeholder='{{"PLACEHOLDER.CURRENT_PWD" | translate}}'
|
||||
required
|
||||
name="oldPassword"
|
||||
[(ngModel)]="oldPwd"
|
||||
#oldPassInput="ngModel" size="25">
|
||||
#oldPassInput="ngModel" size="30">
|
||||
<span class="tooltip-content">
|
||||
{{'TOOLTIP.CURRENT_PWD' | translate}}
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="newPassword">{{'CHANGE_PWD.NEW_PWD' | translate}}</label>
|
||||
<div class="form-group form-group-override">
|
||||
<label for="newPassword" class="required form-group-label-override">{{'CHANGE_PWD.NEW_PWD' | translate}}</label>
|
||||
<label for="newPassword" aria-haspopup="true" role="tooltip" class="tooltip tooltip-validation tooltip-md tooltip-top-left" [class.invalid]="newPassInput.invalid && (newPassInput.dirty || newPassInput.touched)">
|
||||
<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)]="newPwd"
|
||||
#newPassInput="ngModel" size="25">
|
||||
#newPassInput="ngModel" size="30">
|
||||
<span class="tooltip-content">
|
||||
{{'TOOLTIP.PASSWORD' | translate}}
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="reNewPassword">{{'CHANGE_PWD.CONFIRM_PWD' | translate}}</label>
|
||||
<div class="form-group form-group-override">
|
||||
<label for="reNewPassword" class="required form-group-label-override">{{'CHANGE_PWD.CONFIRM_PWD' | translate}}</label>
|
||||
<label for="reNewPassword" aria-haspopup="true" role="tooltip" class="tooltip tooltip-validation tooltip-md tooltip-top-left" [class.invalid]="(reNewPassInput.invalid && (reNewPassInput.dirty || reNewPassInput.touched)) || (!newPassInput.invalid && reNewPassInput.value != newPassInput.value)">
|
||||
<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)]="reNewPwd"
|
||||
#reNewPassInput="ngModel" size="25">
|
||||
#reNewPassInput="ngModel" size="30">
|
||||
<span class="tooltip-content">
|
||||
{{'TOOLTIP.CONFIRM_PWD' | translate}}
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
</section>
|
||||
<inline-alert (confirmEvt)="confirmCancel($event)"></inline-alert>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
|
@ -11,7 +11,8 @@ import { InlineAlertComponent } from '../../shared/inline-alert/inline-alert.com
|
||||
|
||||
@Component({
|
||||
selector: 'password-setting',
|
||||
templateUrl: "password-setting.component.html"
|
||||
templateUrl: "password-setting.component.html",
|
||||
styleUrls: ['../../common.css']
|
||||
})
|
||||
export class PasswordSettingComponent implements AfterViewChecked {
|
||||
opened: boolean = false;
|
||||
|
@ -1,3 +1,7 @@
|
||||
.reset-modal-title-override {
|
||||
font-size: 14px !important;
|
||||
}
|
||||
|
||||
.form-group-override {
|
||||
padding-left: 130px !important;
|
||||
}
|
@ -5,7 +5,7 @@
|
||||
<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" class="form-group-label-override">{{'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
|
||||
@ -22,7 +22,7 @@
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="reNewPassword">{{'CHANGE_PWD.CONFIRM_PWD' | translate}}</label>
|
||||
<label for="reNewPassword" class="form-group-label-override">{{'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
|
||||
|
@ -11,7 +11,7 @@ import { MessageService } from '../../global-message/message.service';
|
||||
@Component({
|
||||
selector: 'reset-password',
|
||||
templateUrl: "reset-password.component.html",
|
||||
styleUrls: ['password.component.css']
|
||||
styleUrls: ['password.component.css', '../../common.css']
|
||||
})
|
||||
export class ResetPasswordComponent implements OnInit{
|
||||
opened: boolean = true;
|
||||
|
@ -12,4 +12,32 @@
|
||||
font-size: 14px;
|
||||
float: right;
|
||||
top: -5px;
|
||||
}
|
||||
|
||||
.popular-repo-wrapper {
|
||||
background-color: white;
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
margin-top: 24px;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
height: auto;
|
||||
position: relative;
|
||||
margin-left: 1px;
|
||||
}
|
||||
|
||||
.login-wrapper-override {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.repo-container {
|
||||
width: 100%;
|
||||
margin-top: -250px;
|
||||
}
|
||||
|
||||
.more-info-link {
|
||||
position: relative;
|
||||
top: 100px;
|
||||
left: 330px;
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
<div class="login-wrapper">
|
||||
<div class="login-wrapper login-wrapper-override">
|
||||
<form #signInForm="ngForm" class="login">
|
||||
<label class="title">
|
||||
VMware Harbor<span class="trademark">™</span>
|
||||
@ -33,7 +33,13 @@
|
||||
<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()" *ngIf="selfSignUp">{{ 'BUTTON.SIGN_UP_LINK' | translate }}</a>
|
||||
</div>
|
||||
<div>
|
||||
<a href="https://github.com/vmware/harbor" target="_blank" class="more-info-link">{{ 'BUTTON.MORE_INFO' | translate }}</a>
|
||||
</div>
|
||||
</form>
|
||||
<div id="pop_repo" class="popular-repo-wrapper">
|
||||
<top-repo class="repo-container"></top-repo>
|
||||
</div>
|
||||
</div>
|
||||
<sign-up #signupDialog></sign-up>
|
||||
<sign-up #signupDialog (userCreation)="handleUserCreation($event)"></sign-up>
|
||||
<forgot-password #forgotPwdDialog></forgot-password>
|
@ -7,11 +7,12 @@ import { SessionService } from '../../shared/session.service';
|
||||
import { SignInCredential } from '../../shared/sign-in-credential';
|
||||
|
||||
import { SignUpComponent } from '../sign-up/sign-up.component';
|
||||
import { harborRootRoute } from '../../shared/shared.const';
|
||||
import { CommonRoutes } from '../../shared/shared.const';
|
||||
import { ForgotPasswordComponent } from '../password/forgot-password.component';
|
||||
|
||||
import { AppConfigService } from '../../app-config.service';
|
||||
import { AppConfig } from '../../app-config';
|
||||
import { User } from '../../user/user';
|
||||
|
||||
//Define status flags for signing in states
|
||||
export const signInStatusNormal = 0;
|
||||
@ -105,6 +106,17 @@ export class SignInComponent implements AfterViewChecked, OnInit {
|
||||
|
||||
}
|
||||
|
||||
//Fill the new user info into the sign in form
|
||||
private handleUserCreation(user: User): void {
|
||||
if(user){
|
||||
this.currentForm.setValue({
|
||||
"login_username": user.username,
|
||||
"login_password": user.password
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
//Implement interface
|
||||
//Watch the view change only when view is in error state
|
||||
ngAfterViewChecked() {
|
||||
@ -139,7 +151,7 @@ export class SignInComponent implements AfterViewChecked, OnInit {
|
||||
//Redirect to the right route
|
||||
if (this.redirectUrl === "") {
|
||||
//Routing to the default location
|
||||
this.router.navigateByUrl(harborRootRoute);
|
||||
this.router.navigateByUrl(CommonRoutes.HARBOR_DEFAULT);
|
||||
} else {
|
||||
this.router.navigateByUrl(this.redirectUrl);
|
||||
}
|
||||
|
@ -0,0 +1,7 @@
|
||||
<h3 class="modal-title">{{'SIGN_UP.TITLE' | translate}}</h3>
|
||||
<new-user-form isSelfRegistration="true" (valueChange)="formValueChange($event)"></new-user-form>
|
||||
<div>
|
||||
<span class="spinner spinner-inline" style="top:8px;" [hidden]="!inProgress"> </span>
|
||||
<button type="button" class="btn btn-outline" [disabled]="!canBeCancelled" (click)="cancel()">{{'BUTTON.CANCEL' | translate}}</button>
|
||||
<button type="button" class="btn btn-primary" [disabled]="!isValid || inProgress" (click)="create()">{{ 'BUTTON.SIGN_UP' | translate }}</button>
|
||||
</div>
|
97
src/ui_ng/src/app/account/sign-up/sign-up-page.component.ts
Normal file
97
src/ui_ng/src/app/account/sign-up/sign-up-page.component.ts
Normal file
@ -0,0 +1,97 @@
|
||||
import { Component, Output, ViewChild, OnInit } from '@angular/core';
|
||||
import { NgForm } from '@angular/forms';
|
||||
import { Router } from '@angular/router';
|
||||
|
||||
import { NewUserFormComponent } from '../../shared/new-user-form/new-user-form.component';
|
||||
import { User } from '../../user/user';
|
||||
|
||||
import { UserService } from '../../user/user.service';
|
||||
import { errorHandler } from '../../shared/shared.utils';
|
||||
import { AlertType } from '../../shared/shared.const';
|
||||
|
||||
import { MessageService } from '../../global-message/message.service';
|
||||
|
||||
@Component({
|
||||
selector: 'sign-up-page',
|
||||
templateUrl: "sign-up-page.component.html"
|
||||
})
|
||||
export class SignUpPageComponent implements OnInit {
|
||||
private error: any;
|
||||
private onGoing: boolean = false;
|
||||
private formValueChanged: boolean = false;
|
||||
|
||||
constructor(
|
||||
private userService: UserService,
|
||||
private msgService: MessageService,
|
||||
private router: Router) { }
|
||||
|
||||
@ViewChild(NewUserFormComponent)
|
||||
private newUserForm: NewUserFormComponent;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
public get canBeCancelled(): boolean {
|
||||
return this.formValueChanged && this.newUserForm && !this.newUserForm.isEmpty();
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.newUserForm.reset();//Reset form
|
||||
this.formValueChanged = false;
|
||||
}
|
||||
|
||||
formValueChange(flag: boolean): void {
|
||||
if (flag) {
|
||||
this.formValueChanged = true;
|
||||
}
|
||||
if (this.error != null) {
|
||||
this.error = null;//clear error
|
||||
}
|
||||
}
|
||||
|
||||
cancel(): void {
|
||||
if (this.newUserForm) {
|
||||
this.newUserForm.reset();
|
||||
}
|
||||
}
|
||||
|
||||
//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.msgService.announceMessage(200, "", AlertType.SUCCESS);
|
||||
//Navigate to embeded sign-in
|
||||
this.router.navigate(['harbor', 'sign-in']);
|
||||
})
|
||||
.catch(error => {
|
||||
this.onGoing = false;
|
||||
this.error = error
|
||||
this.msgService.announceMessage(error.status | 500, "", AlertType.WARNING);
|
||||
});
|
||||
}
|
||||
}
|
@ -1,8 +1,8 @@
|
||||
<clr-modal [(clrModalOpen)]="opened" [clrModalStaticBackdrop]="staticBackdrop" [clrModalClosable]="true">
|
||||
<h3 class="modal-title">{{'SIGN_UP.TITLE' | translate}}</h3>
|
||||
<inline-alert class="modal-title" (confirmEvt)="confirmCancel($event)"></inline-alert>
|
||||
<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>
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { Component, Output, ViewChild } from '@angular/core';
|
||||
import { Component, Output, ViewChild, EventEmitter } from '@angular/core';
|
||||
import { NgForm } from '@angular/forms';
|
||||
|
||||
import { NewUserFormComponent } from '../../shared/new-user-form/new-user-form.component';
|
||||
@ -22,6 +22,8 @@ export class SignUpComponent {
|
||||
private onGoing: boolean = false;
|
||||
private formValueChanged: boolean = false;
|
||||
|
||||
@Output() userCreation = new EventEmitter<User>();
|
||||
|
||||
constructor(
|
||||
private session: SessionService,
|
||||
private userService: UserService) { }
|
||||
@ -103,6 +105,7 @@ export class SignUpComponent {
|
||||
.then(() => {
|
||||
this.onGoing = false;
|
||||
this.modal.close();
|
||||
this.userCreation.emit(u);
|
||||
})
|
||||
.catch(error => {
|
||||
this.onGoing = false;
|
||||
|
@ -38,4 +38,10 @@ export class AppConfigService {
|
||||
public getConfig(): AppConfig {
|
||||
return this.configurations;
|
||||
}
|
||||
|
||||
public isIntegrationMode(): boolean {
|
||||
return this.configurations &&
|
||||
this.configurations.with_admiral &&
|
||||
this.configurations.admiral_endpoint.trim() != "";
|
||||
}
|
||||
}
|
@ -19,7 +19,7 @@ import { Http } from '@angular/http';
|
||||
import { AppConfigService } from './app-config.service';
|
||||
|
||||
export function HttpLoaderFactory(http: Http) {
|
||||
return new TranslateHttpLoader(http, 'ng/i18n/lang/', '-lang.json');
|
||||
return new TranslateHttpLoader(http, 'i18n/lang/', '-lang.json');
|
||||
}
|
||||
|
||||
export function initConfig(configService: AppConfigService) {
|
||||
|
@ -5,7 +5,6 @@ import { Observable } from 'rxjs/Observable';
|
||||
import { Subscription } from 'rxjs/Subscription';
|
||||
|
||||
import { SearchTriggerService } from './search-trigger.service';
|
||||
import { harborRootRoute } from '../../shared/shared.const';
|
||||
|
||||
import 'rxjs/add/operator/debounceTime';
|
||||
import 'rxjs/add/operator/distinctUntilChanged';
|
||||
@ -35,7 +34,7 @@ export class GlobalSearchComponent implements OnInit, OnDestroy {
|
||||
ngOnInit(): void {
|
||||
this.searchSub = this.searchTerms
|
||||
.debounceTime(deBounceTime)
|
||||
.distinctUntilChanged()
|
||||
//.distinctUntilChanged()
|
||||
.subscribe(term => {
|
||||
this.searchTrigger.triggerSearch(term);
|
||||
});
|
||||
|
@ -1,14 +1,16 @@
|
||||
.search-overlay {
|
||||
display: block;
|
||||
position: absolute;
|
||||
position: fixed;
|
||||
height: 100%;
|
||||
width: 98%;
|
||||
width: 100%;
|
||||
/*shoud be lesser than 1000 to aoivd override the popup menu*/
|
||||
z-index: 999;
|
||||
box-sizing: border-box;
|
||||
background: #fafafa;
|
||||
top: 0px;
|
||||
padding-left: 24px;
|
||||
top: 60px;
|
||||
left: 0px;
|
||||
padding-left: 36px;
|
||||
padding-right: 24px;
|
||||
}
|
||||
|
||||
.search-header {
|
||||
@ -47,4 +49,8 @@
|
||||
position: relative;
|
||||
top: 8px;
|
||||
margin: 0px auto 0px auto;
|
||||
}
|
||||
|
||||
.search-header a:hover {
|
||||
text-decoration: none;
|
||||
}
|
@ -1,20 +1,14 @@
|
||||
<div class="search-overlay" *ngIf="state">
|
||||
<div id="placeholder1" style="height: 24px;"></div>
|
||||
<div class="search-header">
|
||||
<span class="search-title">Search results for '{{currentTerm}}'</span>
|
||||
<span class="search-close" (mouseover)="mouseAction(true)" (mouseout)="mouseAction(false)">
|
||||
<clr-icon shape="close" [class.is-highlight]="hover" size="36" (click)="close()"></clr-icon>
|
||||
</span>
|
||||
<a href="javascript:void(0)" (click)="close()">< {{'SEARCH.BACK' | translate}}</a>
|
||||
</div>
|
||||
<!-- spinner -->
|
||||
<div class="spinner spinner-lg search-spinner" [hidden]="done">Search...</div>
|
||||
<div class="spinner spinner-lg search-spinner" [hidden]="done">{{'SEARCH.IN_PROGRESS' | translate}}</div>
|
||||
<div id="results">
|
||||
<h2>Projects</h2>
|
||||
<div class="grid-header-wrapper">
|
||||
<grid-filter class="grid-filter" filterPlaceholder='{{"PROJECT.FILTER_PLACEHOLDER" | translate}}' (filter)="doFilterProjects($event)"></grid-filter>
|
||||
</div>
|
||||
<h2>{{'PROJECT.PROJECTS' | translate}}</h2>
|
||||
<list-project [projects]="searchResults.project" [mode]="listMode"></list-project>
|
||||
<h2>Repositories</h2>
|
||||
<h2>{{'PROJECT_DETAIL.REPOSITORIES' | translate}}</h2>
|
||||
<list-repository [repositories]="searchResults.repository" [mode]="listMode"></list-repository>
|
||||
</div>
|
||||
</div>
|
@ -35,10 +35,6 @@ export class SearchResultComponent {
|
||||
private msgService: MessageService,
|
||||
private searchTrigger: SearchTriggerService) { }
|
||||
|
||||
private doFilterProjects(event: string) {
|
||||
this.searchResults.project = this.originalCopy.project.filter(pro => pro.name.indexOf(event) != -1);
|
||||
}
|
||||
|
||||
private clone(src: SearchResults): SearchResults {
|
||||
let res: SearchResults = new SearchResults();
|
||||
|
||||
|
@ -7,7 +7,6 @@
|
||||
}
|
||||
|
||||
.start-content-padding {
|
||||
padding-top: 0px !important;
|
||||
padding-bottom: 0px !important;
|
||||
padding-left: 0px !important;
|
||||
padding: 0px !important;
|
||||
background-color: white;
|
||||
}
|
@ -2,7 +2,7 @@
|
||||
<global-message [isAppLevel]="true"></global-message>
|
||||
<navigator (showAccountSettingsModal)="openModal($event)" (showPwdChangeModal)="openModal($event)"></navigator>
|
||||
<div class="content-container">
|
||||
<div class="content-area" [class.container-override]="showSearch" [class.start-content-padding]="isStartPage">
|
||||
<div class="content-area" [class.container-override]="showSearch" [class.start-content-padding]="shouldOverrideContent">
|
||||
<global-message [isAppLevel]="false"></global-message>
|
||||
<!-- Only appear when searching -->
|
||||
<search-result></search-result>
|
||||
@ -10,10 +10,9 @@
|
||||
</div>
|
||||
<nav class="sidenav" *ngIf="isUserExisting" [class.side-nav-override]="showSearch" (click)='watchClickEvt()'>
|
||||
<section class="sidenav-content">
|
||||
<a routerLink="/harbor/dashboard" routerLinkActive="active" class="nav-link">{{'SIDE_NAV.DASHBOARD' | 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">
|
||||
<a routerLink="/harbor/logs" routerLinkActive="active" class="nav-link" style="margin-top: 4px;">{{'SIDE_NAV.LOGS' | translate}}</a>
|
||||
<section class="nav-group collapsible" *ngIf="isSystemAdmin" style="margin-top: 4px;">
|
||||
<input id="tabsystem" type="checkbox">
|
||||
<label for="tabsystem">{{'SIDE_NAV.SYSTEM_MGMT.NAME' | translate}}</label>
|
||||
<ul class="nav-list">
|
||||
@ -28,5 +27,5 @@
|
||||
</clr-main-container>
|
||||
<account-settings-modal></account-settings-modal>
|
||||
<password-setting></password-setting>
|
||||
<deletion-dialog></deletion-dialog>
|
||||
<confiramtion-dialog></confiramtion-dialog>
|
||||
<about-dialog></about-dialog>
|
@ -17,7 +17,7 @@ import { SearchTriggerService } from '../global-search/search-trigger.service';
|
||||
|
||||
import { Subscription } from 'rxjs/Subscription';
|
||||
|
||||
import { harborRootRoute } from '../../shared/shared.const';
|
||||
import { CommonRoutes } from '../../shared/shared.const';
|
||||
|
||||
@Component({
|
||||
selector: 'harbor-shell',
|
||||
@ -82,8 +82,8 @@ export class HarborShellComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
}
|
||||
|
||||
public get isStartPage(): boolean {
|
||||
return this.router.routerState.snapshot.url.toString() === harborRootRoute;
|
||||
public get shouldOverrideContent(): boolean {
|
||||
return this.router.routerState.snapshot.url.toString().startsWith(CommonRoutes.EMBEDDED_SIGN_IN);
|
||||
}
|
||||
|
||||
public get showSearch(): boolean {
|
||||
|
@ -26,4 +26,6 @@
|
||||
background-color: #fafafa;
|
||||
position: relative;
|
||||
top: 10px;
|
||||
opacity: 0.15;
|
||||
content: '';
|
||||
}
|
@ -7,13 +7,10 @@
|
||||
</div>
|
||||
<div class="header-nav">
|
||||
<a href="{{admiralLink}}" class="nav-link" *ngIf="isIntegrationMode"><span class="nav-text">Management</span></a>
|
||||
<a href="javascript:void(0)" routerLink="/harbor/dashboard" class="active nav-link" *ngIf="isIntegrationMode"><span class="nav-text">Registry</span></a>
|
||||
<a href="javascript:void(0)" routerLink="/harbor" class="active nav-link" *ngIf="isIntegrationMode"><span class="nav-text">Registry</span></a>
|
||||
</div>
|
||||
<global-search></global-search>
|
||||
<div class="header-actions">
|
||||
<a href="javascript:void(0)" class="nav-link nav-text" routerLink="/sign-in" routerLinkActive="active" *ngIf="isSessionValid === false">{{'SIGN_IN.HEADER_LINK' | translate}}</a>
|
||||
<div class="nav-divider" *ngIf="!isSessionValid"></div>
|
||||
<a href="javascript:void(0)" class="nav-link nav-text" (click)="openSignUp()" *ngIf="isSessionValid === false">{{'SIGN_UP.TITLE' | translate}}</a>
|
||||
<clr-dropdown class="dropdown bottom-left">
|
||||
<button class="nav-icon" clrDropdownToggle style="width: 98px;">
|
||||
<clr-icon shape="world" style="left:-8px;"></clr-icon>
|
||||
|
@ -9,10 +9,9 @@ import { SessionUser } from '../../shared/session-user';
|
||||
import { SessionService } from '../../shared/session.service';
|
||||
import { CookieService } from 'angular2-cookie/core';
|
||||
|
||||
import { supportedLangs, enLang, languageNames, signInRoute } from '../../shared/shared.const';
|
||||
import { supportedLangs, enLang, languageNames, CommonRoutes } from '../../shared/shared.const';
|
||||
|
||||
import { AppConfigService } from '../../app-config.service';
|
||||
import { AppConfig } from '../../app-config';
|
||||
|
||||
@Component({
|
||||
selector: 'navigator',
|
||||
@ -25,9 +24,7 @@ export class NavigatorComponent implements OnInit {
|
||||
@Output() showAccountSettingsModal = new EventEmitter<ModalEvent>();
|
||||
@Output() showPwdChangeModal = new EventEmitter<ModalEvent>();
|
||||
|
||||
private sessionUser: SessionUser = null;
|
||||
private selectedLang: string = enLang;
|
||||
private appConfig: AppConfig = new AppConfig();
|
||||
|
||||
constructor(
|
||||
private session: SessionService,
|
||||
@ -37,42 +34,40 @@ export class NavigatorComponent implements OnInit {
|
||||
private appConfigService: AppConfigService) { }
|
||||
|
||||
ngOnInit(): void {
|
||||
this.sessionUser = this.session.getCurrentUser();
|
||||
this.selectedLang = this.translate.currentLang;
|
||||
this.translate.onLangChange.subscribe(langChange => {
|
||||
this.selectedLang = langChange.lang;
|
||||
//Keep in cookie for next use
|
||||
this.cookie.put("harbor-lang", langChange.lang);
|
||||
});
|
||||
|
||||
this.appConfig = this.appConfigService.getConfig();
|
||||
}
|
||||
|
||||
public get isSessionValid(): boolean {
|
||||
return this.sessionUser != null;
|
||||
return this.session.getCurrentUser() != null;
|
||||
}
|
||||
|
||||
public get accountName(): string {
|
||||
return this.sessionUser ? this.sessionUser.username : "";
|
||||
return this.session.getCurrentUser() ? this.session.getCurrentUser().username : "N/A";
|
||||
}
|
||||
|
||||
public get currentLang(): string {
|
||||
return languageNames[this.selectedLang];
|
||||
}
|
||||
|
||||
public get isIntegrationMode(): boolean {
|
||||
return this.appConfig.with_admiral && this.appConfig.admiral_endpoint.trim() != "";
|
||||
}
|
||||
|
||||
public get admiralLink(): string {
|
||||
let routeSegments = [this.appConfig.admiral_endpoint,
|
||||
"?registry_url=",
|
||||
let appConfig = this.appConfigService.getConfig();
|
||||
let routeSegments = [appConfig.admiral_endpoint,
|
||||
"?registry_url=",
|
||||
encodeURIComponent(window.location.href)
|
||||
];
|
||||
|
||||
return routeSegments.join("");
|
||||
}
|
||||
|
||||
public get isIntegrationMode(): boolean {
|
||||
return this.appConfigService.isIntegrationMode();
|
||||
}
|
||||
|
||||
matchLang(lang: string): boolean {
|
||||
return lang.trim() === this.selectedLang;
|
||||
}
|
||||
@ -105,11 +100,12 @@ export class NavigatorComponent implements OnInit {
|
||||
logOut(): void {
|
||||
this.session.signOff()
|
||||
.then(() => {
|
||||
this.sessionUser = null;
|
||||
//Naviagte to the sign in route
|
||||
this.router.navigate(["/sign-in"]);
|
||||
this.router.navigate([CommonRoutes.EMBEDDED_SIGN_IN]);
|
||||
})
|
||||
.catch()//TODO:
|
||||
.catch(error => {
|
||||
console.error("Log out with error: ", error);
|
||||
});
|
||||
}
|
||||
|
||||
//Switch languages
|
||||
@ -127,20 +123,12 @@ export class NavigatorComponent implements OnInit {
|
||||
|
||||
//Handle the home action
|
||||
homeAction(): void {
|
||||
if (this.sessionUser != null) {
|
||||
if (this.session.getCurrentUser() != null) {
|
||||
//Navigate to default page
|
||||
this.router.navigate(['harbor']);
|
||||
this.router.navigate([CommonRoutes.HARBOR_DEFAULT]);
|
||||
} else {
|
||||
//Naviagte to signin page
|
||||
this.router.navigate(['sign-in']);
|
||||
this.router.navigate([CommonRoutes.HARBOR_ROOT]);
|
||||
}
|
||||
}
|
||||
|
||||
openSignUp(): void {
|
||||
let navigatorExtra: NavigationExtras = {
|
||||
queryParams: { "sign_up": true }
|
||||
};
|
||||
|
||||
this.router.navigate([signInRoute], navigatorExtra);
|
||||
}
|
||||
}
|
8
src/ui_ng/src/app/common.css
Normal file
8
src/ui_ng/src/app/common.css
Normal file
@ -0,0 +1,8 @@
|
||||
.form-group-override {
|
||||
padding-left: 170px !important;
|
||||
}
|
||||
|
||||
.form-group-label-override {
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
.custom-h2 {
|
||||
margin-top: 0px !important;
|
||||
}
|
||||
|
||||
.config-container {
|
||||
margin-left: 24px;
|
||||
}
|
@ -1,38 +1,39 @@
|
||||
<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>
|
||||
<div class="config-container">
|
||||
<h2 style="display: inline-block;" class="custom-h2">{{'CONFIG.TITLE' | translate }}</h2>
|
||||
<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.TOOLTIP.VERIFY_REMOTE_CERT' | 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)">
|
||||
<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.TOOLTIP.VERIFY_REMOTE_CERT' | 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]*$"
|
||||
@ -42,19 +43,20 @@
|
||||
{{'TOOLTIP.NUMBER_REQUIRED' | translate}}
|
||||
</span>
|
||||
</label>
|
||||
<a href="javascript:void(0)" role="tooltip" aria-haspopup="true" class="tooltip tooltip-top-right">
|
||||
<clr-icon shape="info-circle" class="is-info" size="24"></clr-icon>
|
||||
<span class="tooltip-content">{{'CONFIG.TOOLTIP.TOKEN_EXPIRATION' | translate}}</span>
|
||||
</a>
|
||||
</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>
|
||||
<button type="button" class="btn btn-outline" (click)="testLDAPServer()" *ngIf="showLdapServerBtn" [disabled]="!isLDAPConfigValid()">{{'BUTTON.TEST_LDAP' | translate}}</button>
|
||||
<span class="spinner spinner-inline" [hidden]="!testingInProgress"></span>
|
||||
<a href="javascript:void(0)" role="tooltip" aria-haspopup="true" class="tooltip tooltip-top-right">
|
||||
<clr-icon shape="info-circle" class="is-info" size="24"></clr-icon>
|
||||
<span class="tooltip-content">{{'CONFIG.TOOLTIP.TOKEN_EXPIRATION' | translate}}</span>
|
||||
</a>
|
||||
</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>
|
||||
<button type="button" class="btn btn-outline" (click)="testLDAPServer()" *ngIf="showLdapServerBtn" [disabled]="!isLDAPConfigValid()">{{'BUTTON.TEST_LDAP' | translate}}</button>
|
||||
<span class="spinner spinner-inline" [hidden]="!testingInProgress"></span>
|
||||
</div>
|
||||
</div>
|
@ -5,12 +5,12 @@ 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 { AlertType, ConfirmationTargets, ConfirmationState } 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 { ConfirmationDialogService } from '../shared/confirmation-dialog/confirmation-dialog.service';
|
||||
import { Subscription } from 'rxjs/Subscription';
|
||||
import { DeletionMessage } from '../shared/deletion-dialog/deletion-message'
|
||||
import { ConfirmationMessage } from '../shared/confirmation-dialog/confirmation-message'
|
||||
|
||||
import { ConfigurationAuthComponent } from './auth/config-auth.component';
|
||||
import { ConfigurationEmailComponent } from './email/config-email.component';
|
||||
@ -40,15 +40,19 @@ export class ConfigurationComponent implements OnInit, OnDestroy {
|
||||
constructor(
|
||||
private msgService: MessageService,
|
||||
private configService: ConfigurationService,
|
||||
private confirmService: DeletionDialogService,
|
||||
private confirmService: ConfirmationDialogService,
|
||||
private appConfigService: AppConfigService) { }
|
||||
|
||||
ngOnInit(): void {
|
||||
//First load
|
||||
this.retrieveConfig();
|
||||
|
||||
this.confirmSub = this.confirmService.deletionConfirm$.subscribe(confirmation => {
|
||||
this.reset(confirmation.data);
|
||||
this.confirmSub = this.confirmService.confirmationConfirm$.subscribe(confirmation => {
|
||||
if (confirmation &&
|
||||
confirmation.state === ConfirmationState.CONFIRMED &&
|
||||
confirmation.source === ConfirmationTargets.CONFIG) {
|
||||
this.reset(confirmation.data);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ -125,7 +129,7 @@ export class ConfigurationComponent implements OnInit, OnDestroy {
|
||||
this.retrieveConfig();
|
||||
|
||||
//Reload bootstrap option
|
||||
this.appConfigService.load().catch(error=> console.error("Failed to reload bootstrap option with error: ", error));
|
||||
this.appConfigService.load().catch(error => console.error("Failed to reload bootstrap option with error: ", error));
|
||||
|
||||
this.msgService.announceMessage(response.status, "CONFIG.SAVE_SUCCESS", AlertType.SUCCESS);
|
||||
})
|
||||
@ -150,12 +154,12 @@ export class ConfigurationComponent implements OnInit, OnDestroy {
|
||||
public cancel(): void {
|
||||
let changes = this.getChanges();
|
||||
if (!this.isEmpty(changes)) {
|
||||
let msg = new DeletionMessage(
|
||||
let msg = new ConfirmationMessage(
|
||||
"CONFIG.CONFIRM_TITLE",
|
||||
"CONFIG.CONFIRM_SUMMARY",
|
||||
"",
|
||||
changes,
|
||||
DeletionTargets.EMPTY
|
||||
ConfirmationTargets.CONFIG
|
||||
);
|
||||
this.confirmService.openComfirmDialog(msg);
|
||||
} else {
|
||||
|
@ -6,7 +6,7 @@ import { TranslateService } from '@ngx-translate/core';
|
||||
import { Message } from './message';
|
||||
import { MessageService } from './message.service';
|
||||
|
||||
import { AlertType, dismissInterval, httpStatusCode } from '../shared/shared.const';
|
||||
import { AlertType, dismissInterval, httpStatusCode, CommonRoutes } from '../shared/shared.const';
|
||||
|
||||
@Component({
|
||||
selector: 'global-message',
|
||||
@ -94,7 +94,7 @@ export class MessageComponent implements OnInit {
|
||||
}
|
||||
|
||||
signIn(): void {
|
||||
this.router.navigate(['sign-in']);
|
||||
this.router.navigate([CommonRoutes.EMBEDDED_SIGN_IN]);
|
||||
}
|
||||
|
||||
onClose() {
|
||||
|
@ -19,7 +19,6 @@ 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';
|
||||
@ -28,43 +27,40 @@ import { RecentLogComponent } from './log/recent-log.component';
|
||||
import { ConfigurationComponent } from './config/config.component';
|
||||
import { PageNotFoundComponent } from './shared/not-found/not-found.component'
|
||||
import { StartPageComponent } from './base/start-page/start.component';
|
||||
import { SignUpPageComponent } from './account/sign-up/sign-up-page.component';
|
||||
|
||||
import { AuthCheckGuard } from './shared/route/auth-user-activate.service';
|
||||
import { SignInGuard } from './shared/route/sign-in-guard-activate.service';
|
||||
import { LeavingConfigRouteDeactivate } from './shared/route/leaving-config-deactivate.service';
|
||||
|
||||
const harborRoutes: Routes = [
|
||||
{ path: '', redirectTo: '/harbor/dashboard', pathMatch: 'full' },
|
||||
{ path: 'harbor', redirectTo: '/harbor/dashboard', pathMatch: 'full' },
|
||||
{ path: 'sign-in', component: SignInComponent, canActivate: [SignInGuard] },
|
||||
{ path: 'sign-up', component: SignUpComponent },
|
||||
{ path: '', redirectTo: 'harbor', pathMatch: 'full' },
|
||||
{ path: 'password-reset', component: ResetPasswordComponent },
|
||||
{
|
||||
path: 'harbor',
|
||||
component: HarborShellComponent,
|
||||
canActivateChild: [AuthCheckGuard],
|
||||
children: [
|
||||
{ path: '', redirectTo: 'sign-in', pathMatch: 'full' },
|
||||
{ path: 'sign-in', component: SignInComponent, canActivate: [SignInGuard] },
|
||||
{ path: 'sign-up', component: SignUpComponent },
|
||||
{ path: 'dashboard', component: StartPageComponent, canActivate: [AuthCheckGuard]},
|
||||
{
|
||||
path: 'projects',
|
||||
component: ProjectComponent,
|
||||
canActivate: [AuthCheckGuard]
|
||||
component: ProjectComponent
|
||||
},
|
||||
{
|
||||
path: 'logs',
|
||||
component: RecentLogComponent,
|
||||
canActivate: [AuthCheckGuard]
|
||||
component: RecentLogComponent
|
||||
},
|
||||
{
|
||||
path: 'users',
|
||||
component: UserComponent,
|
||||
canActivate: [AuthCheckGuard, SystemAdminGuard]
|
||||
canActivate: [SystemAdminGuard]
|
||||
},
|
||||
{
|
||||
path: 'replications',
|
||||
component: ReplicationManagementComponent,
|
||||
canActivate: [AuthCheckGuard, SystemAdminGuard],
|
||||
canActivateChild: [AuthCheckGuard, SystemAdminGuard],
|
||||
canActivate: [SystemAdminGuard],
|
||||
canActivateChild: [SystemAdminGuard],
|
||||
children: [
|
||||
{
|
||||
path: 'rules',
|
||||
@ -78,14 +74,11 @@ const harborRoutes: Routes = [
|
||||
},
|
||||
{
|
||||
path: 'tags/:id/:repo',
|
||||
component: TagRepositoryComponent,
|
||||
canActivate: [AuthCheckGuard]
|
||||
component: TagRepositoryComponent
|
||||
},
|
||||
{
|
||||
path: 'projects/:id',
|
||||
component: ProjectDetailComponent,
|
||||
canActivate: [AuthCheckGuard],
|
||||
canActivateChild: [AuthCheckGuard],
|
||||
resolve: {
|
||||
projectResolver: ProjectRoutingResolver
|
||||
},
|
||||
@ -111,7 +104,8 @@ const harborRoutes: Routes = [
|
||||
{
|
||||
path: 'configs',
|
||||
component: ConfigurationComponent,
|
||||
canActivate: [AuthCheckGuard, SystemAdminGuard],
|
||||
canActivate: [SystemAdminGuard],
|
||||
canDeactivate: [LeavingConfigRouteDeactivate]
|
||||
}
|
||||
]
|
||||
},
|
||||
|
5
src/ui_ng/src/app/log/audit-log.component.css
Normal file
5
src/ui_ng/src/app/log/audit-log.component.css
Normal file
@ -0,0 +1,5 @@
|
||||
.option-right {
|
||||
padding-right: 16px;
|
||||
margin-top: 22px;
|
||||
margin-bottom: 2px;
|
||||
}
|
@ -1,18 +1,16 @@
|
||||
<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="row flex-items-xs-right option-right">
|
||||
<div class="flex-xs-middle">
|
||||
<button class="btn btn-link" (click)="toggleOptionalName(currentOption)">{{toggleName[currentOption] | translate}}</button>
|
||||
</div>
|
||||
<div class="flex-xs-middle">
|
||||
<grid-filter filterPlaceholder='{{"AUDIT_LOG.FILTER_PLACEHOLDER" | translate}}' (filter)="doSearchAuditLogs($event)"></grid-filter>
|
||||
<a href="javascript:void(0)" (click)="refresh()"><clr-icon shape="refresh"></clr-icon></a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row flex-items-xs-right" [hidden]="currentOption === 0">
|
||||
<div class="row flex-items-xs-right option-right" [hidden]="currentOption === 0">
|
||||
<clr-dropdown [clrMenuPosition]="'bottom-left'" >
|
||||
<button class="btn btn-link" clrDropdownToggle>
|
||||
{{'AUDIT_LOG.ALL_OPERATIONS' | translate}}
|
||||
{{'AUDIT_LOG.OPERATIONS' | translate}}
|
||||
<clr-icon shape="caret down"></clr-icon>
|
||||
</button>
|
||||
<div class="dropdown-menu">
|
||||
@ -24,6 +22,8 @@
|
||||
<clr-icon shape="date"></clr-icon><input type="date" #toTime (change)="doSearchByTimeRange(toTime.value, 'end')">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12 datagrid-margin-top ">
|
||||
<clr-datagrid (clrDgRefresh)="retrieve($event)">
|
||||
<clr-dg-column>{{'AUDIT_LOG.USERNAME' | translate}}</clr-dg-column>
|
||||
<clr-dg-column>{{'AUDIT_LOG.REPOSITORY_NAME' | translate}}</clr-dg-column>
|
||||
|
@ -30,9 +30,10 @@ class FilterOption {
|
||||
}
|
||||
|
||||
@Component({
|
||||
moduleId: module.id,
|
||||
selector: 'audit-log',
|
||||
templateUrl: './audit-log.component.html',
|
||||
styleUrls: [ 'audit-log.css' ]
|
||||
styleUrls: [ './audit-log.component.css' ]
|
||||
})
|
||||
export class AuditLogComponent implements OnInit {
|
||||
|
||||
|
@ -1,3 +0,0 @@
|
||||
.advance-option {
|
||||
font-size: 12px;
|
||||
}
|
@ -1,15 +1,9 @@
|
||||
<clr-modal [(clrModalOpen)]="createProjectOpened">
|
||||
<h3 class="modal-title">{{'PROJECT.NEW_PROJECT' | translate}}</h3>
|
||||
<inline-alert class="modal-title" (confirmEvt)="confirmCancel($event)"></inline-alert>
|
||||
<div class="modal-body">
|
||||
<form #projectForm="ngForm">
|
||||
<section class="form-block">
|
||||
<clr-alert [clrAlertType]="'alert-danger'" [(clrAlertClosed)]="!errorMessageOpened" (clrAlertClosedChange)="onErrorMessageClose()">
|
||||
<div class="alert-item">
|
||||
<span class="alert-text">
|
||||
{{errorMessage}}
|
||||
</span>
|
||||
</div>
|
||||
</clr-alert>
|
||||
<div class="form-group">
|
||||
<label for="create_project_name" class="col-md-4">{{'PROJECT.NAME' | translate}}</label>
|
||||
<label for="create_project_name" aria-haspopup="true" role="tooltip" [class.invalid]="projectName.invalid && (projectName.dirty || projectName.touched)" [class.valid]="projectName.valid" class="tooltip tooltip-validation tooltip-sm tooltip-bottom-right">
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { Component, EventEmitter, Output } from '@angular/core';
|
||||
import { Component, EventEmitter, Output, ViewChild } from '@angular/core';
|
||||
import { Response } from '@angular/http';
|
||||
|
||||
import { Project } from '../project';
|
||||
@ -8,6 +8,8 @@ import { ProjectService } from '../project.service';
|
||||
import { MessageService } from '../../global-message/message.service';
|
||||
import { AlertType } from '../../shared/shared.const';
|
||||
|
||||
import { InlineAlertComponent } from '../../shared/inline-alert/inline-alert.component';
|
||||
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
|
||||
@Component({
|
||||
@ -22,9 +24,11 @@ export class CreateProjectComponent {
|
||||
|
||||
errorMessageOpened: boolean;
|
||||
errorMessage: string;
|
||||
|
||||
|
||||
@Output() create = new EventEmitter<boolean>();
|
||||
|
||||
@ViewChild(InlineAlertComponent)
|
||||
private inlineAlert: InlineAlertComponent;
|
||||
|
||||
constructor(private projectService: ProjectService,
|
||||
private messageService: MessageService,
|
||||
private translateService: TranslateService) {}
|
||||
@ -53,6 +57,7 @@ export class CreateProjectComponent {
|
||||
this.messageService.announceMessage(error.status, this.errorMessage, AlertType.DANGER);
|
||||
});
|
||||
}
|
||||
this.inlineAlert.showInlineError(this.errorMessage);
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -68,5 +73,10 @@ export class CreateProjectComponent {
|
||||
this.errorMessageOpened = false;
|
||||
this.errorMessage = '';
|
||||
}
|
||||
|
||||
confirmCancel(event: boolean): void {
|
||||
this.errorMessageOpened = false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
@ -4,20 +4,17 @@
|
||||
<clr-dg-column>{{'PROJECT.REPO_COUNT'| translate}}</clr-dg-column>
|
||||
<clr-dg-column>{{'PROJECT.CREATION_TIME' | translate}}</clr-dg-column>
|
||||
<clr-dg-column>{{'PROJECT.DESCRIPTION' | translate}}</clr-dg-column>
|
||||
<clr-dg-row *ngFor="let p of projects">
|
||||
<clr-dg-row *ngFor="let p of projects" [clrDgItem]="p">
|
||||
<clr-dg-action-overflow *ngIf="listFullMode">
|
||||
<button class="action-item" (click)="newReplicationRule(p)">{{'PROJECT.REPLICATION_RULE' | translate}}</button>
|
||||
<button class="action-item" (click)="toggleProject(p)">{{'PROJECT.MAKE' | translate}} {{(p.public === 0 ? 'PROJECT.PUBLIC' : 'PROJECT.PRIVATE') | translate}} </button>
|
||||
<button class="action-item" (click)="deleteProject(p)">{{'PROJECT.DELETE' | translate}}</button>
|
||||
</clr-dg-action-overflow>
|
||||
<clr-dg-cell><a href="javascript:void(0)" (click)="goToLink(p.project_id)">{{p.name}}</a></clr-dg-cell>
|
||||
<clr-dg-cell>{{ (p.public === 1 ? 'PROJECT.PUBLIC' : 'PROJECT.PRIVATE') | translate}}</clr-dg-cell>
|
||||
<clr-dg-cell>{{p.repo_count}}</clr-dg-cell>
|
||||
<clr-dg-cell>{{p.creation_time}}</clr-dg-cell>
|
||||
<clr-dg-cell>
|
||||
{{p.description}}
|
||||
<harbor-action-overflow *ngIf="listFullMode">
|
||||
<a href="javascript:void(0)" class="dropdown-item">{{'PROJECT.NEW_POLICY' | translate}}</a>
|
||||
<a href="javascript:void(0)" class="dropdown-item" (click)="toggleProject(p)">{{'PROJECT.MAKE' | translate}} {{(p.public === 0 ? 'PROJECT.PUBLIC' : 'PROJECT.PRIVATE') | translate}} </a>
|
||||
<div class="dropdown-divider"></div>
|
||||
<a href="javascript:void(0)" class="dropdown-item" (click)="deleteProject(p)">{{'PROJECT.DELETE' | translate}}</a>
|
||||
</harbor-action-overflow>
|
||||
</clr-dg-cell>
|
||||
<clr-dg-cell>{{p.description}}</clr-dg-cell>
|
||||
</clr-dg-row>
|
||||
<clr-dg-footer>
|
||||
{{totalRecordCount || (projects ? projects.length : 0)}} {{'PROJECT.ITEMS' | translate}}
|
||||
|
@ -5,13 +5,15 @@ import { ProjectService } from '../project.service';
|
||||
|
||||
import { SessionService } from '../../shared/session.service';
|
||||
import { SearchTriggerService } from '../../base/global-search/search-trigger.service';
|
||||
import { signInRoute, ListMode } from '../../shared/shared.const';
|
||||
import { ListMode } from '../../shared/shared.const';
|
||||
|
||||
import { State } from 'clarity-angular';
|
||||
|
||||
@Component({
|
||||
moduleId: module.id,
|
||||
selector: 'list-project',
|
||||
templateUrl: 'list-project.component.html'
|
||||
templateUrl: 'list-project.component.html',
|
||||
styleUrls: ['./list-project.component.css']
|
||||
})
|
||||
export class ListProjectComponent implements OnInit {
|
||||
|
||||
@ -38,19 +40,19 @@ export class ListProjectComponent implements OnInit {
|
||||
}
|
||||
|
||||
public get listFullMode(): boolean {
|
||||
return this.mode === ListMode.FULL;
|
||||
return this.mode === ListMode.FULL && this.session.getCurrentUser() != null;
|
||||
}
|
||||
|
||||
goToLink(proId: number): void {
|
||||
this.searchTrigger.closeSearch(false);
|
||||
|
||||
|
||||
let linkUrl = ['harbor', 'projects', proId, 'repository'];
|
||||
if (!this.session.getCurrentUser()) {
|
||||
let navigatorExtra: NavigationExtras = {
|
||||
queryParams: { "redirect_url": linkUrl.join("/") }
|
||||
queryParams: { "guest": true }
|
||||
};
|
||||
|
||||
this.router.navigate([signInRoute], navigatorExtra);
|
||||
this.router.navigate(linkUrl, navigatorExtra);
|
||||
} else {
|
||||
this.router.navigate(linkUrl);
|
||||
|
||||
@ -61,6 +63,12 @@ export class ListProjectComponent implements OnInit {
|
||||
this.paginate.emit(state);
|
||||
}
|
||||
|
||||
newReplicationRule(p: Project) {
|
||||
if(p) {
|
||||
this.router.navigateByUrl(`/harbor/projects/${p.project_id}/replication?is_create=true`);
|
||||
}
|
||||
}
|
||||
|
||||
toggleProject(p: Project) {
|
||||
this.toggle.emit(p);
|
||||
}
|
||||
|
8
src/ui_ng/src/app/project/member/member.component.css
Normal file
8
src/ui_ng/src/app/project/member/member.component.css
Normal file
@ -0,0 +1,8 @@
|
||||
.option-left {
|
||||
padding-left: 16px;
|
||||
margin-top: 24px;
|
||||
}
|
||||
.option-right {
|
||||
padding-right: 16px;
|
||||
margin-top: 18px;
|
||||
}
|
@ -1,32 +1,31 @@
|
||||
<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="flex-xs-middle">
|
||||
<button class="btn btn-link" (click)="openAddMemberModal()"><clr-icon shape="add"></clr-icon> {{'MEMBER.NEW_MEMBER' | translate }}</button>
|
||||
<div class="flex-xs-middle option-left">
|
||||
<button class="btn btn-primary" (click)="openAddMemberModal()"><clr-icon shape="add"></clr-icon> {{'MEMBER.MEMBER' | translate }}</button>
|
||||
<add-member [projectId]="projectId" (added)="addedMember($event)"></add-member>
|
||||
</div>
|
||||
<div class="flex-xs-middle">
|
||||
<div class="flex-xs-middle option-right">
|
||||
<grid-filter filterPlaceholder='{{"MEMBER.FILTER_PLACEHOLDER" | translate}}' (filter)="doSearch($event)"></grid-filter>
|
||||
<a href="javascript:void(0)" (click)="refresh()">
|
||||
<clr-icon shape="refresh"></clr-icon>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
|
||||
<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-action-overflow [hidden]="u.user_id === currentUser.user_id">
|
||||
<button class="action-item" (click)="changeRole(u.user_id, 1)">{{'MEMBER.PROJECT_ADMIN' | translate}}</button>
|
||||
<button class="action-item" (click)="changeRole(u.user_id, 2)">{{'MEMBER.DEVELOPER' | translate}}</button>
|
||||
<button class="action-item" (click)="changeRole(u.user_id, 3)">{{'MEMBER.GUEST' | translate}}</button>
|
||||
<button class="action-item" (click)="deleteMember(u.user_id)">{{'MEMBER.DELETE' | translate}}</button>
|
||||
</clr-dg-action-overflow>
|
||||
<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-cell>{{roleInfo[u.role_id] | translate}}</clr-dg-cell>
|
||||
</clr-dg-row>
|
||||
<clr-dg-footer>{{ (members ? members.length : 0) }} {{'MEMBER.ITEMS' | translate}}</clr-dg-footer>
|
||||
</clr-datagrid>
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { Component, OnInit, ViewChild } from '@angular/core';
|
||||
import { Component, OnInit, ViewChild, OnDestroy } from '@angular/core';
|
||||
import { ActivatedRoute, Params, Router } from '@angular/router';
|
||||
import { Response } from '@angular/http';
|
||||
|
||||
@ -9,10 +9,10 @@ import { MemberService } from './member.service';
|
||||
import { AddMemberComponent } from './add-member/add-member.component';
|
||||
|
||||
import { MessageService } from '../../global-message/message.service';
|
||||
import { AlertType, DeletionTargets } from '../../shared/shared.const';
|
||||
import { AlertType, ConfirmationTargets, ConfirmationState } from '../../shared/shared.const';
|
||||
|
||||
import { DeletionDialogService } from '../../shared/deletion-dialog/deletion-dialog.service';
|
||||
import { DeletionMessage } from '../../shared/deletion-dialog/deletion-message';
|
||||
import { ConfirmationDialogService } from '../../shared/confirmation-dialog/confirmation-dialog.service';
|
||||
import { ConfirmationMessage } from '../../shared/confirmation-dialog/confirmation-message';
|
||||
import { SessionService } from '../../shared/session.service';
|
||||
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
@ -20,30 +20,36 @@ import 'rxjs/add/operator/switchMap';
|
||||
import 'rxjs/add/operator/catch';
|
||||
import 'rxjs/add/operator/map';
|
||||
import 'rxjs/add/observable/throw';
|
||||
import { Subscription } from 'rxjs/Subscription';
|
||||
|
||||
export const roleInfo: {} = { 1: 'MEMBER.PROJECT_ADMIN', 2: 'MEMBER.DEVELOPER', 3: 'MEMBER.GUEST' };
|
||||
|
||||
@Component({
|
||||
templateUrl: 'member.component.html'
|
||||
moduleId: module.id,
|
||||
templateUrl: 'member.component.html',
|
||||
styleUrls: ['./member.component.css']
|
||||
})
|
||||
export class MemberComponent implements OnInit {
|
||||
export class MemberComponent implements OnInit, OnDestroy {
|
||||
|
||||
currentUser: SessionUser;
|
||||
members: Member[];
|
||||
projectId: number;
|
||||
roleInfo = roleInfo;
|
||||
private delSub: Subscription;
|
||||
|
||||
@ViewChild(AddMemberComponent)
|
||||
addMemberComponent: AddMemberComponent;
|
||||
|
||||
constructor(private route: ActivatedRoute, private router: Router,
|
||||
private memberService: MemberService, private messageService: MessageService,
|
||||
private deletionDialogService: DeletionDialogService,
|
||||
session:SessionService) {
|
||||
private deletionDialogService: ConfirmationDialogService,
|
||||
session: SessionService) {
|
||||
//Get current user from registered resolver.
|
||||
this.currentUser = session.getCurrentUser();
|
||||
deletionDialogService.deletionConfirm$.subscribe(message => {
|
||||
if (message && message.targetId === DeletionTargets.PROJECT_MEMBER) {
|
||||
this.delSub = deletionDialogService.confirmationConfirm$.subscribe(message => {
|
||||
if (message &&
|
||||
message.state === ConfirmationState.CONFIRMED &&
|
||||
message.source === ConfirmationTargets.PROJECT_MEMBER) {
|
||||
this.memberService
|
||||
.deleteMember(this.projectId, message.data)
|
||||
.subscribe(
|
||||
@ -69,6 +75,12 @@ export class MemberComponent implements OnInit {
|
||||
);
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
if (this.delSub) {
|
||||
this.delSub.unsubscribe();
|
||||
}
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
//Get projectId from route params snapshot.
|
||||
this.projectId = +this.route.snapshot.parent.params['id'];
|
||||
@ -98,12 +110,12 @@ export class MemberComponent implements OnInit {
|
||||
}
|
||||
|
||||
deleteMember(userId: number) {
|
||||
let deletionMessage: DeletionMessage = new DeletionMessage(
|
||||
let deletionMessage: ConfirmationMessage = new ConfirmationMessage(
|
||||
'MEMBER.DELETION_TITLE',
|
||||
'MEMBER.DELETION_SUMMARY',
|
||||
userId+"",
|
||||
userId + "",
|
||||
userId,
|
||||
DeletionTargets.PROJECT_MEMBER
|
||||
ConfirmationTargets.PROJECT_MEMBER
|
||||
);
|
||||
this.deletionDialogService.openComfirmDialog(deletionMessage);
|
||||
}
|
||||
|
@ -0,0 +1,8 @@
|
||||
.sub-header-title {
|
||||
margin-top: 12px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.sub-nav-bg-color {
|
||||
background-color: #fafafa;
|
||||
}
|
@ -1,19 +1,19 @@
|
||||
<a style="display: block;" [routerLink]="['/harbor', 'projects']">< {{'PROJECT_DETAIL.PROJECTS' | translate}}</a>
|
||||
<h1 class="display-in-line">{{currentProject.name}}</h1>
|
||||
<nav class="subnav">
|
||||
<h1 class="sub-header-title">{{currentProject.name}}</h1>
|
||||
<nav class="subnav sub-nav-bg-color">
|
||||
<ul class="nav">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" routerLink="repository" routerLinkActive="active">{{'PROJECT_DETAIL.REPOSITORIES' | translate}}</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<li class="nav-item" *ngIf="isSessionValid && isSystemAdmin">
|
||||
<a class="nav-link" routerLink="replication" routerLinkActive="active">{{'PROJECT_DETAIL.REPLICATION' | translate}}</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<li class="nav-item" *ngIf="isSessionValid">
|
||||
<a class="nav-link" routerLink="member" routerLinkActive="active">{{'PROJECT_DETAIL.USERS' | translate}}</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<li class="nav-item" *ngIf="isSessionValid">
|
||||
<a class="nav-link" routerLink="log" routerLinkActive="active">{{'PROJECT_DETAIL.LOGS' | translate}}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
<router-outlet></router-outlet>
|
||||
<router-outlet></router-outlet>
|
@ -3,16 +3,32 @@ import { ActivatedRoute, Router } from '@angular/router';
|
||||
|
||||
import { Project } from '../project';
|
||||
|
||||
import { SessionService } from '../../shared/session.service';
|
||||
|
||||
@Component({
|
||||
selector: 'project-detail',
|
||||
templateUrl: "project-detail.component.html",
|
||||
styleUrls: [ 'project-detail.css' ]
|
||||
styleUrls: [ 'project-detail.component.css' ]
|
||||
})
|
||||
export class ProjectDetailComponent {
|
||||
|
||||
currentProject: Project;
|
||||
|
||||
constructor(private route: ActivatedRoute, private router: Router) {
|
||||
constructor(
|
||||
private route: ActivatedRoute,
|
||||
private router: Router,
|
||||
private sessionService: SessionService) {
|
||||
this.route.data.subscribe(data=>this.currentProject = <Project>data['projectResolver']);
|
||||
|
||||
}
|
||||
|
||||
public get isSystemAdmin(): boolean {
|
||||
let account = this.sessionService.getCurrentUser();
|
||||
return account != null && account.has_admin_role > 0;
|
||||
}
|
||||
|
||||
public get isSessionValid(): boolean {
|
||||
return this.sessionService.getCurrentUser() != null;
|
||||
}
|
||||
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
.display-in-line {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.project-title {
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.pull-right {
|
||||
float: right !important;
|
||||
}
|
11
src/ui_ng/src/app/project/project.component.css
Normal file
11
src/ui_ng/src/app/project/project.component.css
Normal file
@ -0,0 +1,11 @@
|
||||
.header-title {
|
||||
margin-top: 0;
|
||||
}
|
||||
.option-left {
|
||||
padding-left: 12px;
|
||||
margin-top: 12px;
|
||||
}
|
||||
.option-right {
|
||||
padding-right: 16px;
|
||||
margin-top: 18px;
|
||||
}
|
@ -1,24 +1,28 @@
|
||||
<h1>{{'PROJECT.PROJECTS' | translate}}</h1>
|
||||
<div class="row flex-items-xs-between">
|
||||
<div class="flex-items-xs-middle">
|
||||
<button class="btn btn-link" (click)="openModal()"><clr-icon shape="add"></clr-icon> {{'PROJECT.NEW_PROJECT' | translate}}</button>
|
||||
<create-project (create)="createProject($event)"></create-project>
|
||||
</div>
|
||||
<div class="flex-items-xs-middle">
|
||||
<clr-dropdown [clrMenuPosition]="'bottom-left'">
|
||||
<button class="btn btn-link" clrDropdownToggle>
|
||||
{{projectTypes[currentFilteredType] | translate}}
|
||||
<clr-icon shape="caret down"></clr-icon>
|
||||
</button>
|
||||
<div class="dropdown-menu">
|
||||
<a href="javascript:void(0)" clrDropdownItem (click)="doFilterProjects(0)">{{projectTypes[0] | translate}}</a>
|
||||
<a href="javascript:void(0)" clrDropdownItem (click)="doFilterProjects(1)">{{projectTypes[1] | translate}}</a>
|
||||
<div class="row">
|
||||
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
|
||||
<h2 class="header-title">{{'PROJECT.PROJECTS' | translate}}</h2>
|
||||
<div class="row flex-items-xs-between">
|
||||
<div class="option-left">
|
||||
<button class="btn btn-primary" (click)="openModal()"><clr-icon shape="add"></clr-icon> {{'PROJECT.PROJECT' | translate}}</button>
|
||||
<create-project (create)="createProject($event)"></create-project>
|
||||
</div>
|
||||
</clr-dropdown>
|
||||
<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 class="option-right">
|
||||
<clr-dropdown [clrMenuPosition]="'bottom'">
|
||||
<button class="btn btn-link" clrDropdownToggle>
|
||||
{{projectTypes[currentFilteredType] | translate}}
|
||||
<clr-icon shape="caret down"></clr-icon>
|
||||
</button>
|
||||
<div class="dropdown-menu">
|
||||
<a href="javascript:void(0)" clrDropdownItem (click)="doFilterProjects(0)">{{projectTypes[0] | translate}}</a>
|
||||
<a href="javascript:void(0)" clrDropdownItem (click)="doFilterProjects(1)">{{projectTypes[1] | translate}}</a>
|
||||
</div>
|
||||
</clr-dropdown>
|
||||
<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>
|
||||
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
|
||||
<list-project [projects]="changedProjects" (toggle)="toggleProject($event)" (delete)="deleteProject($event)" (paginate)="retrieve($event)" [totalPage]="totalPage" [totalRecordCount]="totalRecordCount"></list-project>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { Component, OnInit, ViewChild } from '@angular/core';
|
||||
import { Component, OnInit, ViewChild, OnDestroy } from '@angular/core';
|
||||
|
||||
import { Router } from '@angular/router';
|
||||
import { Router } from '@angular/router';
|
||||
|
||||
import { Project } from './project';
|
||||
import { ProjectService } from './project.service';
|
||||
@ -15,27 +15,28 @@ import { Message } from '../global-message/message';
|
||||
import { AlertType } from '../shared/shared.const';
|
||||
import { Response } from '@angular/http';
|
||||
|
||||
import { DeletionDialogService } from '../shared/deletion-dialog/deletion-dialog.service';
|
||||
import { DeletionMessage } from '../shared/deletion-dialog/deletion-message';
|
||||
import { DeletionTargets } from '../shared/shared.const';
|
||||
import { ConfirmationDialogService } from '../shared/confirmation-dialog/confirmation-dialog.service';
|
||||
import { ConfirmationMessage } from '../shared/confirmation-dialog/confirmation-message';
|
||||
import { ConfirmationTargets, ConfirmationState } from '../shared/shared.const';
|
||||
|
||||
import { Subscription } from 'rxjs/Subscription';
|
||||
|
||||
import { State } from 'clarity-angular';
|
||||
|
||||
const types: {} = { 0: 'PROJECT.MY_PROJECTS', 1: 'PROJECT.PUBLIC_PROJECTS'};
|
||||
const types: {} = { 0: 'PROJECT.MY_PROJECTS', 1: 'PROJECT.PUBLIC_PROJECTS' };
|
||||
|
||||
@Component({
|
||||
selector: 'project',
|
||||
templateUrl: 'project.component.html',
|
||||
styleUrls: [ 'project.css' ]
|
||||
moduleId: module.id,
|
||||
selector: 'project',
|
||||
templateUrl: 'project.component.html',
|
||||
styleUrls: ['./project.component.css']
|
||||
})
|
||||
export class ProjectComponent implements OnInit {
|
||||
|
||||
export class ProjectComponent implements OnInit, OnDestroy {
|
||||
|
||||
selected = [];
|
||||
changedProjects: Project[];
|
||||
projectTypes = types;
|
||||
|
||||
|
||||
@ViewChild(CreateProjectComponent)
|
||||
creationProject: CreateProjectComponent;
|
||||
|
||||
@ -50,7 +51,7 @@ export class ProjectComponent implements OnInit {
|
||||
isPublic: number;
|
||||
|
||||
page: number = 1;
|
||||
pageSize: number = 15;
|
||||
pageSize: number = 3;
|
||||
|
||||
totalPage: number;
|
||||
totalRecordCount: number;
|
||||
@ -58,51 +59,59 @@ export class ProjectComponent implements OnInit {
|
||||
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();
|
||||
},
|
||||
error=>this.messageService.announceMessage(error.status, error, AlertType.WARNING)
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
private deletionDialogService: ConfirmationDialogService) {
|
||||
this.subscription = deletionDialogService.confirmationConfirm$.subscribe(message => {
|
||||
if (message &&
|
||||
message.state === ConfirmationState.CONFIRMED &&
|
||||
message.source === ConfirmationTargets.PROJECT) {
|
||||
let projectId = message.data;
|
||||
this.projectService
|
||||
.deleteProject(projectId)
|
||||
.subscribe(
|
||||
response => {
|
||||
console.log('Successful delete project with ID:' + projectId);
|
||||
this.retrieve();
|
||||
},
|
||||
error => this.messageService.announceMessage(error.status, error, AlertType.WARNING)
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.projectName = '';
|
||||
this.isPublic = 0;
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
if (this.subscription) {
|
||||
this.subscription.unsubscribe();
|
||||
}
|
||||
}
|
||||
|
||||
retrieve(state?: State): void {
|
||||
if(state) {
|
||||
if (state) {
|
||||
this.page = state.page.to + 1;
|
||||
}
|
||||
this.projectService
|
||||
.listProjects(this.projectName, this.isPublic, this.page, this.pageSize)
|
||||
.subscribe(
|
||||
response => {
|
||||
this.totalRecordCount = response.headers.get('x-total-count');
|
||||
this.totalPage = Math.ceil(this.totalRecordCount / this.pageSize);
|
||||
console.log('TotalRecordCount:' + this.totalRecordCount + ', totalPage:' + this.totalPage);
|
||||
this.changedProjects = response.json();
|
||||
},
|
||||
error => this.messageService.announceAppLevelMessage(error.status, error, AlertType.WARNING)
|
||||
);
|
||||
.listProjects(this.projectName, this.isPublic, this.page, this.pageSize)
|
||||
.subscribe(
|
||||
response => {
|
||||
this.totalRecordCount = response.headers.get('x-total-count');
|
||||
this.totalPage = Math.ceil(this.totalRecordCount / this.pageSize);
|
||||
console.log('TotalRecordCount:' + this.totalRecordCount + ', totalPage:' + this.totalPage);
|
||||
this.changedProjects = response.json();
|
||||
},
|
||||
error => this.messageService.announceAppLevelMessage(error.status, error, AlertType.WARNING)
|
||||
);
|
||||
}
|
||||
|
||||
openModal(): void {
|
||||
this.creationProject.newProject();
|
||||
}
|
||||
|
||||
|
||||
createProject(created: boolean) {
|
||||
if(created) {
|
||||
if (created) {
|
||||
this.retrieve();
|
||||
}
|
||||
}
|
||||
@ -116,6 +125,7 @@ export class ProjectComponent implements OnInit {
|
||||
doFilterProjects(filteredType: number): void {
|
||||
console.log('Filter projects with type:' + types[filteredType]);
|
||||
this.isPublic = filteredType;
|
||||
this.currentFilteredType = filteredType;
|
||||
this.retrieve();
|
||||
}
|
||||
|
||||
@ -125,19 +135,19 @@ export class ProjectComponent implements OnInit {
|
||||
this.projectService
|
||||
.toggleProjectPublic(p.project_id, p.public)
|
||||
.subscribe(
|
||||
response=>console.log('Successful toggled project_id:' + p.project_id),
|
||||
error=>this.messageService.announceMessage(error.status, error, AlertType.WARNING)
|
||||
response => console.log('Successful toggled project_id:' + p.project_id),
|
||||
error => this.messageService.announceMessage(error.status, error, AlertType.WARNING)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
deleteProject(p: Project) {
|
||||
let deletionMessage = new DeletionMessage(
|
||||
let deletionMessage = new ConfirmationMessage(
|
||||
'PROJECT.DELETION_TITLE',
|
||||
'PROJECT.DELETION_SUMMARY',
|
||||
p.name,
|
||||
p.project_id,
|
||||
DeletionTargets.PROJECT
|
||||
ConfirmationTargets.PROJECT
|
||||
);
|
||||
this.deletionDialogService.openComfirmDialog(deletionMessage);
|
||||
}
|
||||
|
@ -0,0 +1,8 @@
|
||||
.option-left {
|
||||
padding-left: 16px;
|
||||
margin-top: 24px;
|
||||
}
|
||||
.option-right {
|
||||
padding-right: 16px;
|
||||
margin-top: 36px;
|
||||
}
|
@ -1,30 +1,33 @@
|
||||
<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="flex-items-xs-middle">
|
||||
<button class="btn btn-link" (click)="openModal()"><clr-icon shape="add"></clr-icon> {{'DESTINATION.NEW_ENDPOINT' | translate}}</button>
|
||||
<create-edit-destination (reload)="reload($event)"></create-edit-destination>
|
||||
</div>
|
||||
<div class="flex-items-xs-middle">
|
||||
<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 class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
|
||||
<div class="row flex-items-xs-between">
|
||||
<div class="flex-items-xs-middle option-left">
|
||||
<button class="btn btn-primary" (click)="openModal()"><clr-icon shape="add"></clr-icon> {{'DESTINATION.ENDPOINT' | translate}}</button>
|
||||
<create-edit-destination (reload)="reload($event)"></create-edit-destination>
|
||||
</div>
|
||||
<div class="flex-items-xs-middle option-right">
|
||||
<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>
|
||||
</div>
|
||||
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
|
||||
<clr-datagrid>
|
||||
<clr-dg-column>{{'DESTINATION.NAME' | translate}}</clr-dg-column>
|
||||
<clr-dg-column>{{'DESTINATION.URL' | translate}}</clr-dg-column>
|
||||
<clr-dg-column>{{'DESTINATION.CREATION_TIME' | translate}}</clr-dg-column>
|
||||
<clr-dg-row *ngFor="let t of targets" [clrDgItem]='t'>
|
||||
<clr-dg-action-overflow>
|
||||
<button class="action-item" (click)="editTarget(t)">{{'DESTINATION.TITLE_EDIT' | translate}}</button>
|
||||
<button class="action-item" (click)="deleteTarget(t)">{{'DESTINATION.DELETE' | translate}}</button>
|
||||
</clr-dg-action-overflow>
|
||||
<clr-dg-cell>{{t.name}}</clr-dg-cell>
|
||||
<clr-dg-cell>{{t.endpoint}}</clr-dg-cell>
|
||||
<clr-dg-cell>{{t.creation_time}}</clr-dg-cell>
|
||||
</clr-dg-row>
|
||||
<clr-dg-footer>{{ (targets ? targets.length : 0) }} {{'DESTINATION.ITEMS' | translate}}</clr-dg-footer>
|
||||
</clr-datagrid>
|
||||
</div>
|
||||
<clr-datagrid>
|
||||
<clr-dg-column>{{'DESTINATION.NAME' | translate}}</clr-dg-column>
|
||||
<clr-dg-column>{{'DESTINATION.URL' | translate}}</clr-dg-column>
|
||||
<clr-dg-column>{{'DESTINATION.CREATION_TIME' | translate}}</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)">{{'DESTINATION.TITLE_EDIT' | translate}}</a>
|
||||
<a href="javascript:void(0)" class="dropdown-item" (click)="deleteTarget(t)">{{'DESTINATION.DELETE' | translate}}</a>
|
||||
</harbor-action-overflow>
|
||||
</clr-dg-cell>
|
||||
</clr-dg-row>
|
||||
<clr-dg-footer>{{ (targets ? targets.length : 0) }} {{'DESTINATION.ITEMS' | translate}}</clr-dg-footer>
|
||||
</clr-datagrid>
|
||||
</div>
|
||||
</div>
|
@ -4,50 +4,56 @@ 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 { ConfirmationDialogService } from '../../shared/confirmation-dialog/confirmation-dialog.service';
|
||||
import { ConfirmationMessage } from '../../shared/confirmation-dialog/confirmation-message';
|
||||
|
||||
import { DeletionTargets } from '../../shared/shared.const';
|
||||
import { ConfirmationTargets, ConfirmationState } from '../../shared/shared.const';
|
||||
|
||||
import { Subscription } from 'rxjs/Subscription';
|
||||
|
||||
import { CreateEditDestinationComponent } from '../create-edit-destination/create-edit-destination.component';
|
||||
|
||||
@Component({
|
||||
moduleId: module.id,
|
||||
selector: 'destination',
|
||||
templateUrl: 'destination.component.html'
|
||||
templateUrl: 'destination.component.html',
|
||||
styleUrls: ['./destination.component.css']
|
||||
})
|
||||
export class DestinationComponent implements OnInit {
|
||||
|
||||
@ViewChild(CreateEditDestinationComponent)
|
||||
createEditDestinationComponent: CreateEditDestinationComponent;
|
||||
@ViewChild(CreateEditDestinationComponent)
|
||||
createEditDestinationComponent: CreateEditDestinationComponent;
|
||||
|
||||
targets: Target[];
|
||||
target: Target;
|
||||
|
||||
targetName: string;
|
||||
subscription : Subscription;
|
||||
subscription: Subscription;
|
||||
|
||||
constructor(
|
||||
private replicationService: ReplicationService,
|
||||
private messageService: MessageService,
|
||||
private deletionDialogService: DeletionDialogService) {
|
||||
this.subscription = this.deletionDialogService.deletionConfirm$.subscribe(message=>{
|
||||
private deletionDialogService: ConfirmationDialogService) {
|
||||
this.subscription = this.deletionDialogService.confirmationConfirm$.subscribe(message => {
|
||||
if (message &&
|
||||
message.source === ConfirmationTargets.TARGET &&
|
||||
message.state === ConfirmationState.CONFIRMED) {
|
||||
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)
|
||||
);
|
||||
});
|
||||
}
|
||||
.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 = '';
|
||||
@ -55,18 +61,18 @@ export class DestinationComponent implements OnInit {
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
if(this.subscription) {
|
||||
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)
|
||||
);
|
||||
.listTargets(targetName)
|
||||
.subscribe(
|
||||
targets => this.targets = targets,
|
||||
error => this.messageService.announceMessage(error.status, 'Failed to get targets:' + error, AlertType.DANGER)
|
||||
);
|
||||
}
|
||||
|
||||
doSearchTargets(targetName: string) {
|
||||
@ -88,15 +94,20 @@ export class DestinationComponent implements OnInit {
|
||||
}
|
||||
|
||||
editTarget(target: Target) {
|
||||
if(target) {
|
||||
if (target) {
|
||||
this.createEditDestinationComponent.openCreateEditTarget(target.id);
|
||||
}
|
||||
}
|
||||
|
||||
deleteTarget(target: Target) {
|
||||
if(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);
|
||||
let deletionMessage = new ConfirmationMessage(
|
||||
'REPLICATION.DELETION_TITLE_TARGET',
|
||||
'REPLICATION.DELETION_SUMMARY_TARGET',
|
||||
target.name,
|
||||
target.id,
|
||||
ConfirmationTargets.TARGET);
|
||||
this.deletionDialogService.openComfirmDialog(deletionMessage);
|
||||
}
|
||||
}
|
||||
|
@ -1,20 +1,24 @@
|
||||
<clr-datagrid (clrDgRefresh)="refresh($event)">
|
||||
<clr-dg-column>{{'REPLICATION.NAME' | translate}}</clr-dg-column>
|
||||
<clr-dg-column>{{'REPLICATION.STATUS' | translate}}</clr-dg-column>
|
||||
<clr-dg-column>{{'REPLICATION.OPERATION' | translate}}</clr-dg-column>
|
||||
<clr-dg-column>{{'REPLICATION.CREATION_TIME' | translate}}</clr-dg-column>
|
||||
<clr-dg-column>{{'REPLICATION.END_TIME' | translate}}</clr-dg-column>
|
||||
<clr-dg-column>{{'REPLICATION.LOGS' | translate}}</clr-dg-column>
|
||||
<clr-dg-row *ngFor="let j of jobs">
|
||||
<clr-dg-cell>{{j.repository}}</clr-dg-cell>
|
||||
<clr-dg-cell>{{j.status}}</clr-dg-cell>
|
||||
<clr-dg-cell>{{j.operation}}</clr-dg-cell>
|
||||
<clr-dg-cell>{{j.creation_time}}</clr-dg-cell>
|
||||
<clr-dg-cell>{{j.update_time}}</clr-dg-cell>
|
||||
<clr-dg-cell><a href="/api/jobs/replication/{{j.id}}/log" target="_BLANK"><clr-icon shape="clipboard"></clr-icon></a></clr-dg-cell>
|
||||
</clr-dg-row>
|
||||
<clr-dg-footer>
|
||||
{{ totalRecordCount }} {{'REPLICATION.ITEMS' | translate}}
|
||||
<clr-dg-pagination [clrDgPageSize]="pageOffset" [clrDgTotalItems]="totalPage"></clr-dg-pagination>
|
||||
</clr-dg-footer>
|
||||
<clr-datagrid (clrDgRefresh)="refresh($event)">
|
||||
<clr-dg-column>{{'REPLICATION.NAME' | translate}}</clr-dg-column>
|
||||
<clr-dg-column>{{'REPLICATION.STATUS' | translate}}</clr-dg-column>
|
||||
<clr-dg-column>{{'REPLICATION.OPERATION' | translate}}</clr-dg-column>
|
||||
<clr-dg-column>{{'REPLICATION.CREATION_TIME' | translate}}</clr-dg-column>
|
||||
<clr-dg-column>{{'REPLICATION.END_TIME' | translate}}</clr-dg-column>
|
||||
<clr-dg-column>{{'REPLICATION.LOGS' | translate}}</clr-dg-column>
|
||||
<clr-dg-row *ngFor="let j of jobs" [clrDgItem]='j'>
|
||||
<clr-dg-cell>{{j.repository}}</clr-dg-cell>
|
||||
<clr-dg-cell>{{j.status}}</clr-dg-cell>
|
||||
<clr-dg-cell>{{j.operation}}</clr-dg-cell>
|
||||
<clr-dg-cell>{{j.creation_time}}</clr-dg-cell>
|
||||
<clr-dg-cell>{{j.update_time}}</clr-dg-cell>
|
||||
<clr-dg-cell>
|
||||
<a href="/api/jobs/replication/{{j.id}}/log" target="_BLANK">
|
||||
<clr-icon shape="clipboard"></clr-icon>
|
||||
</a>
|
||||
</clr-dg-cell>
|
||||
</clr-dg-row>
|
||||
<clr-dg-footer>
|
||||
{{ totalRecordCount }} {{'REPLICATION.ITEMS' | translate}}
|
||||
<clr-dg-pagination [clrDgPageSize]="pageOffset" [clrDgTotalItems]="totalPage"></clr-dg-pagination>
|
||||
</clr-dg-footer>
|
||||
</clr-datagrid>
|
@ -0,0 +1,8 @@
|
||||
.sub-header-title {
|
||||
margin-top: 0;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.sub-nav-bg-color {
|
||||
background-color: #fafafa;
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
<h2>{{'SIDE_NAV.SYSTEM_MGMT.REPLICATION' | translate}}</h2>
|
||||
<h2 class="sub-header-title">{{'SIDE_NAV.SYSTEM_MGMT.REPLICATION' | translate}}</h2>
|
||||
<nav class="subnav">
|
||||
<ul class="nav">
|
||||
<ul class="nav sub-nav-bg-color">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" routerLink="endpoints" routerLinkActive="active">{{'REPLICATION.ENDPOINTS' | translate}}</a>
|
||||
</li>
|
||||
|
@ -1,8 +1,9 @@
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
moduleId: module.id,
|
||||
selector: 'replication-management',
|
||||
templateUrl: 'replication-management.component.html',
|
||||
styleUrls: [ 'replication-management.css' ]
|
||||
styleUrls: [ './replication-management.component.css' ]
|
||||
})
|
||||
export class ReplicationManagementComponent {}
|
@ -1,32 +0,0 @@
|
||||
.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;
|
||||
}
|
17
src/ui_ng/src/app/replication/replication.component.css
Normal file
17
src/ui_ng/src/app/replication/replication.component.css
Normal file
@ -0,0 +1,17 @@
|
||||
.option-left {
|
||||
padding-left: 16px;
|
||||
margin-top: 24px;
|
||||
}
|
||||
.option-right {
|
||||
padding-right: 16px;
|
||||
margin-top: 18px;
|
||||
}
|
||||
|
||||
.option-left-down {
|
||||
margin-top: 36px;
|
||||
}
|
||||
|
||||
.option-right-down {
|
||||
padding-right: 16px;
|
||||
margin-top: 24px;
|
||||
}
|
@ -1,11 +1,11 @@
|
||||
<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="flex-xs-middle">
|
||||
<button class="btn btn-link" (click)="openModal()"><clr-icon shape="add"></clr-icon> {{'REPLICATION.NEW_REPLICATION_RULE' | translate}}</button>
|
||||
<div class="flex-xs-middle option-left">
|
||||
<button class="btn btn-primary" (click)="openModal()"><clr-icon shape="add"></clr-icon> {{'REPLICATION.REPLICATION_RULE' | translate}}</button>
|
||||
<create-edit-policy [projectId]="projectId" (reload)="reloadPolicies($event)"></create-edit-policy>
|
||||
</div>
|
||||
<div class="flex-xs-middle">
|
||||
<div class="flex-xs-middle option-right">
|
||||
<clr-dropdown [clrMenuPosition]="'bottom-left'">
|
||||
<button class="btn btn-link" clrDropdownToggle>
|
||||
{{currentRuleStatus.description | translate}}
|
||||
@ -19,16 +19,20 @@
|
||||
<a href="javascript:void(0)" (click)="refreshPolicies()"><clr-icon shape="refresh"></clr-icon></a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
|
||||
<list-policy [policies]="changedPolicies" [projectless]="false" [selectedId]="initSelectedId" (selectOne)="selectOne($event)" (editOne)="openEditPolicy($event)" (reload)="reloadPolicies($event)"></list-policy>
|
||||
</div>
|
||||
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
|
||||
<div class="row flex-items-xs-between">
|
||||
<h5 class="flex-items-xs-bottom" style="margin-left: 14px;">{{'REPLICATION.REPLICATION_JOBS' | translate}}</h5>
|
||||
<div class="flex-items-xs-bottom">
|
||||
<h5 class="flex-items-xs-bottom option-left-down" style="margin-left: 14px;">{{'REPLICATION.REPLICATION_JOBS' | translate}}</h5>
|
||||
<div class="flex-items-xs-bottom option-right-down">
|
||||
<button class="btn btn-link" (click)="toggleSearchJobOptionalName(currentJobSearchOption)">{{toggleJobSearchOption[currentJobSearchOption] | translate}}</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">
|
||||
<div class="row flex-items-xs-right option-right" [hidden]="currentJobSearchOption === 0">
|
||||
<clr-dropdown [clrMenuPosition]="'bottom-left'">
|
||||
<button class="btn btn-link" clrDropdownToggle>
|
||||
{{currentJobStatus.description | translate}}
|
||||
@ -43,6 +47,8 @@
|
||||
<clr-icon shape="date"></clr-icon><input type="date" #toTime (change)="doJobSearchByTimeRange(toTime.value, 'end')">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
|
||||
<list-job [jobs]="changedJobs" [totalPage]="jobsTotalPage" [totalRecordCount]="jobsTotalRecordCount" (paginate)="fetchPolicyJobs($event)"></list-job>
|
||||
</div>
|
||||
</div>
|
@ -48,8 +48,10 @@ class SearchOption {
|
||||
}
|
||||
|
||||
@Component({
|
||||
moduleId: module.id,
|
||||
selector: 'replicaton',
|
||||
templateUrl: 'replication.component.html'
|
||||
templateUrl: 'replication.component.html',
|
||||
styleUrls: ['./replication.component.css']
|
||||
})
|
||||
export class ReplicationComponent implements OnInit {
|
||||
|
||||
@ -96,6 +98,11 @@ export class ReplicationComponent implements OnInit {
|
||||
this.currentJobStatus = this.jobStatus[0];
|
||||
this.currentJobSearchOption = 0;
|
||||
this.retrievePolicies();
|
||||
|
||||
let isCreate = this.route.snapshot.parent.queryParams['is_create'];
|
||||
if (isCreate && <boolean>isCreate) {
|
||||
this.openModal();
|
||||
}
|
||||
}
|
||||
|
||||
retrievePolicies(): void {
|
||||
@ -154,7 +161,7 @@ export class ReplicationComponent implements OnInit {
|
||||
this.fetchPolicyJobs();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
doSearchPolicies(policyName: string) {
|
||||
this.search.policyName = policyName;
|
||||
this.retrievePolicies();
|
||||
|
@ -0,0 +1,5 @@
|
||||
.option-right {
|
||||
padding-right: 16px;
|
||||
margin-top: 36px;
|
||||
margin-bottom: 11px;
|
||||
}
|
@ -1,12 +1,15 @@
|
||||
<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="row flex-items-xs-right option-right">
|
||||
<div class="flex-items-xs-middle">
|
||||
<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>
|
||||
<create-edit-policy [projectId]="projectId" (reload)="reloadPolicies($event)"></create-edit-policy>
|
||||
</div>
|
||||
<create-edit-policy [projectId]="projectId" (reload)="reloadPolicies($event)"></create-edit-policy>
|
||||
|
||||
</div>
|
||||
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
|
||||
<list-policy [policies]="changedPolicies" [projectless]="true" (editOne)="openEditPolicy($event)" (selectOne)="selectPolicy($event)" (reload)="reloadPolicies($event)"></list-policy>
|
||||
</div>
|
||||
</div>
|
@ -9,9 +9,11 @@ import { AlertType } from '../../shared/shared.const';
|
||||
import { Policy } from '../../replication/policy';
|
||||
|
||||
@Component({
|
||||
moduleId: module.id,
|
||||
selector: 'total-replication',
|
||||
templateUrl: 'total-replication.component.html',
|
||||
providers: [ ReplicationService ]
|
||||
providers: [ ReplicationService ],
|
||||
styleUrls: ['./total-replication.component.css']
|
||||
})
|
||||
export class TotalReplicationComponent implements OnInit {
|
||||
|
||||
@ -58,7 +60,7 @@ export class TotalReplicationComponent implements OnInit {
|
||||
this.projectId = policy.project_id;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
refreshPolicies() {
|
||||
this.retrievePolicies();
|
||||
}
|
||||
|
@ -2,17 +2,15 @@
|
||||
<clr-dg-column>{{'REPOSITORY.NAME' | translate}}</clr-dg-column>
|
||||
<clr-dg-column>{{'REPOSITORY.TAGS_COUNT' | translate}}</clr-dg-column>
|
||||
<clr-dg-column>{{'REPOSITORY.PULL_COUNT' | translate}}</clr-dg-column>
|
||||
<clr-dg-row *ngFor="let r of repositories">
|
||||
<clr-dg-row *ngFor="let r of repositories" [clrDgItem]='r'>
|
||||
<clr-dg-action-overflow *ngIf="listFullMode">
|
||||
<button class="action-item">{{'REPOSITORY.COPY_ID' | translate}}</button>
|
||||
<button class="action-item">{{'REPOSITORY.COPY_PARENT_ID' | translate}}</button>
|
||||
<button class="action-item" (click)="deleteRepo(r.name)">{{'REPOSITORY.DELETE' | translate}}</button>
|
||||
</clr-dg-action-overflow>
|
||||
<clr-dg-cell><a href="javascript:void(0)" (click)="gotoLink(projectId || r.project_id, r.name || r.repository_name)">{{r.name || r.repository_name}}</a></clr-dg-cell>
|
||||
<clr-dg-cell>{{r.tags_count}}</clr-dg-cell>
|
||||
<clr-dg-cell>{{r.pull_count}}
|
||||
<harbor-action-overflow *ngIf="listFullMode">
|
||||
<a href="javascript:void(0)" class="dropdown-item">{{'REPOSITORY.COPY_ID' | translate}}</a>
|
||||
<a href="javascript:void(0)" class="dropdown-item">{{'REPOSITORY.COPY_PARENT_ID' | translate}}</a>
|
||||
<div class="dropdown-divider"></div>
|
||||
<a href="javascript:void(0)" class="dropdown-item" (click)="deleteRepo(r.name)">{{'REPOSITORY.DELETE' | translate}}</a>
|
||||
</harbor-action-overflow>
|
||||
</clr-dg-cell>
|
||||
<clr-dg-cell>{{r.pull_count}}</clr-dg-cell>
|
||||
</clr-dg-row>
|
||||
<clr-dg-footer>
|
||||
{{totalRecordCount || (repositories ? repositories.length : 0)}} {{'REPOSITORY.ITEMS' | translate}}
|
||||
|
@ -5,7 +5,7 @@ import { State } from 'clarity-angular';
|
||||
|
||||
import { SearchTriggerService } from '../../base/global-search/search-trigger.service';
|
||||
import { SessionService } from '../../shared/session.service';
|
||||
import { signInRoute, ListMode } from '../../shared/shared.const';
|
||||
import { ListMode } from '../../shared/shared.const';
|
||||
|
||||
@Component({
|
||||
selector: 'list-repository',
|
||||
@ -32,16 +32,16 @@ export class ListRepositoryComponent {
|
||||
|
||||
deleteRepo(repoName: string) {
|
||||
this.delete.emit(repoName);
|
||||
}
|
||||
}
|
||||
|
||||
refresh(state: State) {
|
||||
if(this.repositories) {
|
||||
if (this.repositories) {
|
||||
this.paginate.emit(state);
|
||||
}
|
||||
}
|
||||
|
||||
public get listFullMode(): boolean {
|
||||
return this.mode === ListMode.FULL;
|
||||
return this.mode === ListMode.FULL && this.session.getCurrentUser() != null;
|
||||
}
|
||||
|
||||
public gotoLink(projectId: number, repoName: string): void {
|
||||
@ -50,10 +50,9 @@ export class ListRepositoryComponent {
|
||||
let linkUrl = ['harbor', 'tags', projectId, repoName];
|
||||
if (!this.session.getCurrentUser()) {
|
||||
let navigatorExtra: NavigationExtras = {
|
||||
queryParams: { "redirect_url": linkUrl.join("/") }
|
||||
queryParams: { "guest": true }
|
||||
};
|
||||
|
||||
this.router.navigate([signInRoute], navigatorExtra);
|
||||
this.router.navigate(linkUrl, navigatorExtra);
|
||||
} else {
|
||||
this.router.navigate(linkUrl);
|
||||
}
|
||||
|
5
src/ui_ng/src/app/repository/repository.component.css
Normal file
5
src/ui_ng/src/app/repository/repository.component.css
Normal file
@ -0,0 +1,5 @@
|
||||
.option-right {
|
||||
padding-right: 16px;
|
||||
margin-top: 32px;
|
||||
margin-bottom: 12px;
|
||||
}
|
@ -1,11 +1,13 @@
|
||||
<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="row flex-items-xs-right option-right">
|
||||
<div class="flex-xs-middle">
|
||||
<grid-filter filterPlaceholder="{{'REPOSITORY.FILTER_FOR_REPOSITORIES' | translate}}" (filter)="doSearchRepoNames($event)"></grid-filter>
|
||||
<a href="javascript:void(0)" (click)="refresh()"><clr-icon shape="refresh"></clr-icon></a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
|
||||
<list-repository [projectId]="projectId" [repositories]="changedRepositories" (delete)="deleteRepo($event)" [totalPage]="totalPage" [totalRecordCount]="totalRecordCount" (paginate)="retrieve($event)"></list-repository>
|
||||
</div>
|
||||
</div>
|
@ -5,11 +5,11 @@ import { RepositoryService } from './repository.service';
|
||||
import { Repository } from './repository';
|
||||
|
||||
import { MessageService } from '../global-message/message.service';
|
||||
import { AlertType, DeletionTargets } from '../shared/shared.const';
|
||||
import { AlertType, ConfirmationState, ConfirmationTargets } from '../shared/shared.const';
|
||||
|
||||
|
||||
import { DeletionDialogService } from '../shared/deletion-dialog/deletion-dialog.service';
|
||||
import { DeletionMessage } from '../shared/deletion-dialog/deletion-message';
|
||||
import { ConfirmationDialogService } from '../shared/confirmation-dialog/confirmation-dialog.service';
|
||||
import { ConfirmationMessage } from '../shared/confirmation-dialog/confirmation-message';
|
||||
import { Subscription } from 'rxjs/Subscription';
|
||||
|
||||
import { State } from 'clarity-angular';
|
||||
@ -20,8 +20,10 @@ const repositoryTypes = [
|
||||
];
|
||||
|
||||
@Component({
|
||||
moduleId: module.id,
|
||||
selector: 'repository',
|
||||
templateUrl: 'repository.component.html'
|
||||
templateUrl: 'repository.component.html',
|
||||
styleUrls: ['./repository.component.css']
|
||||
})
|
||||
export class RepositoryComponent implements OnInit {
|
||||
changedRepositories: Repository[];
|
||||
@ -43,24 +45,28 @@ export class RepositoryComponent implements OnInit {
|
||||
private route: ActivatedRoute,
|
||||
private repositoryService: RepositoryService,
|
||||
private messageService: MessageService,
|
||||
private deletionDialogService: DeletionDialogService
|
||||
private deletionDialogService: ConfirmationDialogService
|
||||
) {
|
||||
this.subscription = this.deletionDialogService
|
||||
.deletionConfirm$
|
||||
.subscribe(
|
||||
message=>{
|
||||
let repoName = message.data;
|
||||
this.repositoryService
|
||||
.deleteRepository(repoName)
|
||||
.subscribe(
|
||||
response=>{
|
||||
this.refresh();
|
||||
console.log('Successful deleted repo:' + repoName);
|
||||
},
|
||||
error=>this.messageService.announceMessage(error.status, 'Failed to delete repo:' + repoName, AlertType.DANGER)
|
||||
);
|
||||
}
|
||||
);
|
||||
.confirmationConfirm$
|
||||
.subscribe(
|
||||
message => {
|
||||
if (message &&
|
||||
message.source === ConfirmationTargets.REPOSITORY &&
|
||||
message.state === ConfirmationState.CONFIRMED) {
|
||||
let repoName = message.data;
|
||||
this.repositoryService
|
||||
.deleteRepository(repoName)
|
||||
.subscribe(
|
||||
response => {
|
||||
this.refresh();
|
||||
console.log('Successful deleted repo:' + repoName);
|
||||
},
|
||||
error => this.messageService.announceMessage(error.status, 'Failed to delete repo:' + repoName, AlertType.DANGER)
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
@ -71,43 +77,45 @@ export class RepositoryComponent implements OnInit {
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
if(this.subscription) {
|
||||
if (this.subscription) {
|
||||
this.subscription.unsubscribe();
|
||||
}
|
||||
}
|
||||
|
||||
retrieve(state?: State) {
|
||||
if(state) {
|
||||
if (state) {
|
||||
this.page = state.page.to + 1;
|
||||
}
|
||||
this.repositoryService
|
||||
.listRepositories(this.projectId, this.lastFilteredRepoName, this.page, this.pageSize)
|
||||
.subscribe(
|
||||
response=>{
|
||||
this.totalRecordCount = response.headers.get('x-total-count');
|
||||
this.totalPage = Math.ceil(this.totalRecordCount / this.pageSize);
|
||||
console.log('TotalRecordCount:' + this.totalRecordCount + ', totalPage:' + this.totalPage);
|
||||
this.changedRepositories=response.json();
|
||||
},
|
||||
error=>this.messageService.announceMessage(error.status, 'Failed to list repositories.', AlertType.DANGER)
|
||||
);
|
||||
.listRepositories(this.projectId, this.lastFilteredRepoName, this.page, this.pageSize)
|
||||
.subscribe(
|
||||
response => {
|
||||
this.totalRecordCount = response.headers.get('x-total-count');
|
||||
this.totalPage = Math.ceil(this.totalRecordCount / this.pageSize);
|
||||
console.log('TotalRecordCount:' + this.totalRecordCount + ', totalPage:' + this.totalPage);
|
||||
this.changedRepositories = response.json();
|
||||
},
|
||||
error => this.messageService.announceMessage(error.status, 'Failed to list repositories.', AlertType.DANGER)
|
||||
);
|
||||
}
|
||||
|
||||
doFilterRepositoryByType(type: string) {
|
||||
this.currentRepositoryType = this.repositoryTypes.find(r=>r.key == type);
|
||||
this.currentRepositoryType = this.repositoryTypes.find(r => r.key == type);
|
||||
}
|
||||
|
||||
|
||||
doSearchRepoNames(repoName: string) {
|
||||
this.lastFilteredRepoName = repoName;
|
||||
this.retrieve();
|
||||
|
||||
|
||||
}
|
||||
|
||||
deleteRepo(repoName: string) {
|
||||
let message = new DeletionMessage(
|
||||
'REPOSITORY.DELETION_TITLE_REPO',
|
||||
'REPOSITORY.DELETION_SUMMARY_REPO',
|
||||
repoName, repoName, DeletionTargets.REPOSITORY);
|
||||
let message = new ConfirmationMessage(
|
||||
'REPOSITORY.DELETION_TITLE_REPO',
|
||||
'REPOSITORY.DELETION_SUMMARY_REPO',
|
||||
repoName,
|
||||
repoName,
|
||||
ConfirmationTargets.REPOSITORY);
|
||||
this.deletionDialogService.openComfirmDialog(message);
|
||||
}
|
||||
|
||||
|
@ -40,25 +40,24 @@ export class RepositoryService {
|
||||
}
|
||||
|
||||
listTagsWithVerifiedSignatures(repoName: string): Observable<Tag[]> {
|
||||
return this.http
|
||||
.get(`/api/repositories/signatures?repo_name=${repoName}`)
|
||||
.map(response=>response)
|
||||
.flatMap(res=>
|
||||
this.listTags(repoName)
|
||||
.map((tags: Tag[])=>{
|
||||
let signatures = res.json();
|
||||
tags.forEach(t=>{
|
||||
for(let i = 0; i < signatures.length; i++) {
|
||||
if(signatures[i].tag === t.tag) {
|
||||
t.verified = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
return tags;
|
||||
})
|
||||
.catch(error=>Observable.throw(error))
|
||||
)
|
||||
return this.listTags(repoName)
|
||||
.map(res=>res)
|
||||
.flatMap(tags=>{
|
||||
return this.listNotarySignatures(repoName).map(signatures=>{
|
||||
tags.forEach(t=>{
|
||||
for(let i = 0; i < signatures.length; i++) {
|
||||
if(signatures[i].tag === t.tag) {
|
||||
t.signed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
return tags;
|
||||
})
|
||||
.catch(error=>{
|
||||
return tags;
|
||||
})
|
||||
})
|
||||
.catch(error=>Observable.throw(error));
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,3 @@
|
||||
.sub-header-title {
|
||||
margin-top: 12px;
|
||||
}
|
@ -1,30 +1,29 @@
|
||||
<a [routerLink]="['/harbor', 'projects', projectId, 'repository']">< {{'REPOSITORY.REPOSITORIES' | translate}}</a>
|
||||
<h2>{{repoName}} <span class="badge">{{tags ? tags.length : 0}}</span></h2>
|
||||
<h2 class="sub-header-title">{{repoName}} <span class="badge">{{tags ? tags.length : 0}}</span></h2>
|
||||
<clr-datagrid>
|
||||
<clr-dg-column>{{'REPOSITORY.TAG' | translate}}</clr-dg-column>
|
||||
<clr-dg-column>{{'REPOSITORY.PULL_COMMAND' | translate}}</clr-dg-column>
|
||||
<clr-dg-column>{{'REPOSITORY.VERIFIED' | translate}}</clr-dg-column>
|
||||
<clr-dg-column>{{'REPOSITORY.AUTHOR' | translate}}</clr-dg-column>
|
||||
<clr-dg-column>{{'REPOSITORY.CREATED' | translate}}</clr-dg-column>
|
||||
<clr-dg-column>{{'REPOSITORY.DOCKER_VERSION' | translate}}</clr-dg-column>
|
||||
<clr-dg-column>{{'REPOSITORY.ARCHITECTURE' | translate}}</clr-dg-column>
|
||||
<clr-dg-column>{{'REPOSITORY.OS' | translate}}</clr-dg-column>
|
||||
<clr-dg-row *ngFor="let t of tags">
|
||||
<clr-dg-cell>{{t.tag}}</clr-dg-cell>
|
||||
<clr-dg-cell>{{t.pullCommand}}</clr-dg-cell>
|
||||
<clr-dg-cell>
|
||||
<clr-icon shape="check" *ngIf="t.verified" style="color: #1D5100;"></clr-icon>
|
||||
<clr-icon shape="close" *ngIf="!t.verified" style="color: #C92100;"></clr-icon>
|
||||
</clr-dg-cell>
|
||||
<clr-dg-cell>{{t.author}}</clr-dg-cell>
|
||||
<clr-dg-cell>{{t.created | date: 'yyyy/MM/dd'}}</clr-dg-cell>
|
||||
<clr-dg-cell>{{t.dockerVersion}}</clr-dg-cell>
|
||||
<clr-dg-cell>{{t.architecture}}</clr-dg-cell>
|
||||
<clr-dg-cell>{{t.os}}
|
||||
<harbor-action-overflow>
|
||||
<a href="javascript:void(0)" class="dropdown-item" (click)="deleteTag(t)">{{'REPOSITORY.DELETE' | translate}}</a>
|
||||
</harbor-action-overflow>
|
||||
</clr-dg-cell>
|
||||
<clr-dg-column>{{'REPOSITORY.TAG' | translate}}</clr-dg-column>
|
||||
<clr-dg-column>{{'REPOSITORY.PULL_COMMAND' | translate}}</clr-dg-column>
|
||||
<clr-dg-column>{{'REPOSITORY.SIGNED' | translate}}</clr-dg-column>
|
||||
<clr-dg-column>{{'REPOSITORY.AUTHOR' | translate}}</clr-dg-column>
|
||||
<clr-dg-column>{{'REPOSITORY.CREATED' | translate}}</clr-dg-column>
|
||||
<clr-dg-column>{{'REPOSITORY.DOCKER_VERSION' | translate}}</clr-dg-column>
|
||||
<clr-dg-column>{{'REPOSITORY.ARCHITECTURE' | translate}}</clr-dg-column>
|
||||
<clr-dg-column>{{'REPOSITORY.OS' | translate}}</clr-dg-column>
|
||||
<clr-dg-row *ngFor="let t of tags" [clrDgItem]='t'>
|
||||
<clr-dg-action-overflow>
|
||||
<button class="action-item" (click)="deleteTag(t)">{{'REPOSITORY.DELETE' | translate}}</button>
|
||||
</clr-dg-action-overflow>
|
||||
<clr-dg-cell>{{t.tag}}</clr-dg-cell>
|
||||
<clr-dg-cell>{{t.pullCommand}}</clr-dg-cell>
|
||||
<clr-dg-cell>
|
||||
<clr-icon shape="check" *ngIf="t.verified" style="color: #1D5100;"></clr-icon>
|
||||
<clr-icon shape="close" *ngIf="!t.verified" style="color: #C92100;"></clr-icon>
|
||||
</clr-dg-cell>
|
||||
<clr-dg-cell>{{t.author}}</clr-dg-cell>
|
||||
<clr-dg-cell>{{t.created | date: 'yyyy/MM/dd'}}</clr-dg-cell>
|
||||
<clr-dg-cell>{{t.dockerVersion}}</clr-dg-cell>
|
||||
<clr-dg-cell>{{t.architecture}}</clr-dg-cell>
|
||||
<clr-dg-cell>{{t.os}}</clr-dg-cell>
|
||||
</clr-dg-row>
|
||||
<clr-dg-footer>{{tags ? tags.length : 0}} {{'REPOSITORY.ITEMS' | translate}}</clr-dg-footer>
|
||||
<clr-dg-footer>{{tags ? tags.length : 0}} {{'REPOSITORY.ITEMS' | translate}}</clr-dg-footer>
|
||||
</clr-datagrid>
|
@ -3,18 +3,22 @@ import { ActivatedRoute } from '@angular/router';
|
||||
|
||||
import { RepositoryService } from '../repository.service';
|
||||
import { MessageService } from '../../global-message/message.service';
|
||||
import { AlertType, DeletionTargets } from '../../shared/shared.const';
|
||||
import { AlertType, ConfirmationTargets, ConfirmationState } from '../../shared/shared.const';
|
||||
|
||||
import { DeletionDialogService } from '../../shared/deletion-dialog/deletion-dialog.service';
|
||||
import { DeletionMessage } from '../../shared/deletion-dialog/deletion-message';
|
||||
import { ConfirmationDialogService } from '../../shared/confirmation-dialog/confirmation-dialog.service';
|
||||
import { ConfirmationMessage } from '../../shared/confirmation-dialog/confirmation-message';
|
||||
|
||||
import { Subscription } from 'rxjs/Subscription';
|
||||
|
||||
import { TagView } from '../tag-view';
|
||||
|
||||
import { AppConfigService } from '../../app-config.service';
|
||||
|
||||
@Component({
|
||||
moduleId: module.id,
|
||||
selector: 'tag-repository',
|
||||
templateUrl: 'tag-repository.component.html'
|
||||
templateUrl: 'tag-repository.component.html',
|
||||
styleUrls: ['./tag-repository.component.css']
|
||||
})
|
||||
export class TagRepositoryComponent implements OnInit, OnDestroy {
|
||||
|
||||
@ -28,70 +32,74 @@ export class TagRepositoryComponent implements OnInit, OnDestroy {
|
||||
constructor(
|
||||
private route: ActivatedRoute,
|
||||
private messageService: MessageService,
|
||||
private deletionDialogService: DeletionDialogService,
|
||||
private repositoryService: RepositoryService) {
|
||||
this.subscription = this.deletionDialogService.deletionConfirm$.subscribe(
|
||||
message=>{
|
||||
private deletionDialogService: ConfirmationDialogService,
|
||||
private repositoryService: RepositoryService,
|
||||
private appConfigService: AppConfigService) {
|
||||
this.subscription = this.deletionDialogService.confirmationConfirm$.subscribe(
|
||||
message => {
|
||||
if (message &&
|
||||
message.source === ConfirmationTargets.TAG
|
||||
&& message.state === ConfirmationState.CONFIRMED) {
|
||||
let tag = message.data;
|
||||
if(tag) {
|
||||
if(tag.verified) {
|
||||
if (tag) {
|
||||
if (tag.verified) {
|
||||
return;
|
||||
} else {
|
||||
let tagName = tag.tag;
|
||||
this.repositoryService
|
||||
.deleteRepoByTag(this.repoName, tagName)
|
||||
.subscribe(
|
||||
response=>{
|
||||
this.retrieve();
|
||||
console.log('Deleted repo:' + this.repoName + ' with tag:' + tagName);
|
||||
},
|
||||
error=>this.messageService.announceMessage(error.status, 'Failed to delete tag:' + tagName + ' under repo:' + this.repoName, AlertType.DANGER)
|
||||
);
|
||||
.deleteRepoByTag(this.repoName, tagName)
|
||||
.subscribe(
|
||||
response => {
|
||||
this.retrieve();
|
||||
console.log('Deleted repo:' + this.repoName + ' with tag:' + tagName);
|
||||
},
|
||||
error => this.messageService.announceMessage(error.status, 'Failed to delete tag:' + tagName + ' under repo:' + this.repoName, AlertType.DANGER)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
ngOnInit() {
|
||||
this.projectId = this.route.snapshot.params['id'];
|
||||
this.projectId = this.route.snapshot.params['id'];
|
||||
this.repoName = this.route.snapshot.params['repo'];
|
||||
this.tags = [];
|
||||
this.retrieve();
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
if(this.subscription) {
|
||||
if (this.subscription) {
|
||||
this.subscription.unsubscribe();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
retrieve() {
|
||||
this.tags = [];
|
||||
this.repositoryService
|
||||
.listTagsWithVerifiedSignatures(this.repoName)
|
||||
.subscribe(
|
||||
items=>{
|
||||
items.forEach(t=>{
|
||||
let tag = new TagView();
|
||||
tag.tag = t.tag;
|
||||
let data = JSON.parse(t.manifest.history[0].v1Compatibility);
|
||||
tag.architecture = data['architecture'];
|
||||
tag.author = data['author'];
|
||||
tag.verified = t.verified || false;
|
||||
tag.created = data['created'];
|
||||
tag.dockerVersion = data['docker_version'];
|
||||
tag.pullCommand = 'docker pull ' + t.manifest.name + ':' + t.tag;
|
||||
tag.os = data['os'];
|
||||
this.tags.push(tag);
|
||||
});
|
||||
},
|
||||
error=>this.messageService.announceMessage(error.status, 'Failed to list tags with repo:' + this.repoName, AlertType.DANGER));
|
||||
.listTagsWithVerifiedSignatures(this.repoName)
|
||||
.subscribe(
|
||||
items => {
|
||||
items.forEach(t => {
|
||||
let tag = new TagView();
|
||||
tag.tag = t.tag;
|
||||
let data = JSON.parse(t.manifest.history[0].v1Compatibility);
|
||||
tag.architecture = data['architecture'];
|
||||
tag.author = data['author'];
|
||||
tag.verified = t.signed;
|
||||
tag.created = data['created'];
|
||||
tag.dockerVersion = data['docker_version'];
|
||||
tag.pullCommand = 'docker pull ' + t.manifest.name + ':' + t.tag;
|
||||
tag.os = data['os'];
|
||||
this.tags.push(tag);
|
||||
});
|
||||
},
|
||||
error => this.messageService.announceMessage(error.status, 'Failed to list tags with repo:' + this.repoName, AlertType.DANGER));
|
||||
}
|
||||
|
||||
deleteTag(tag: TagView) {
|
||||
if(tag) {
|
||||
if (tag) {
|
||||
let titleKey: string, summaryKey: string;
|
||||
if (tag.verified) {
|
||||
titleKey = 'REPOSITORY.DELETION_TITLE_TAG_DENIED';
|
||||
@ -100,7 +108,12 @@ export class TagRepositoryComponent implements OnInit, OnDestroy {
|
||||
titleKey = 'REPOSITORY.DELETION_TITLE_TAG';
|
||||
summaryKey = 'REPOSITORY.DELETION_SUMMARY_TAG';
|
||||
}
|
||||
let message = new DeletionMessage(titleKey, summaryKey, tag.tag, tag, DeletionTargets.TAG);
|
||||
let message = new ConfirmationMessage(
|
||||
titleKey,
|
||||
summaryKey,
|
||||
tag.tag,
|
||||
tag,
|
||||
ConfirmationTargets.TAG);
|
||||
this.deletionDialogService.openComfirmDialog(message);
|
||||
}
|
||||
}
|
||||
|
@ -23,5 +23,5 @@ export class Tag {
|
||||
}
|
||||
];
|
||||
};
|
||||
verified: boolean;
|
||||
signed: boolean;
|
||||
}
|
@ -0,0 +1,4 @@
|
||||
.repo-wrapper {
|
||||
margin-left: 48px;
|
||||
margin-right: 48px;
|
||||
}
|
@ -1,4 +1,8 @@
|
||||
<div class="card card-block">
|
||||
<h3 class="card-title">Popular Repositories</h3>
|
||||
<list-repository [repositories]="topRepos" [mode]="listMode"></list-repository>
|
||||
<div class="repo-wrapper">
|
||||
<div>
|
||||
<h3 style="margin-top: 0px;">{{'REPOSITORY.POP_REPOS' | translate}}</h3>
|
||||
</div>
|
||||
<div>
|
||||
<list-repository [repositories]="topRepos" [mode]="listMode"></list-repository>
|
||||
</div>
|
||||
</div>
|
@ -9,6 +9,7 @@ import { Repository } from '../repository';
|
||||
@Component({
|
||||
selector: 'top-repo',
|
||||
templateUrl: "top-repo.component.html",
|
||||
styleUrls: ['top-repo.component.css'],
|
||||
|
||||
providers: [TopRepoService]
|
||||
})
|
||||
@ -32,11 +33,7 @@ export class TopRepoComponent implements OnInit{
|
||||
//Get top popular repositories
|
||||
getTopRepos() {
|
||||
this.topRepoService.getTopRepos()
|
||||
.then(repos => repos.forEach(item => {
|
||||
let repo: Repository = new Repository(item.name, item.count);
|
||||
repo.pull_count = 0;
|
||||
this.topRepos.push(repo);
|
||||
}))
|
||||
.then(repos => this.topRepos = repos )
|
||||
.catch(error => {
|
||||
this.msgService.announceMessage(error.status, errorHandler(error), AlertType.WARNING);
|
||||
})
|
||||
|
@ -2,9 +2,9 @@ import { Injectable } from '@angular/core';
|
||||
import { Headers, Http, RequestOptions } from '@angular/http';
|
||||
import 'rxjs/add/operator/toPromise';
|
||||
|
||||
import { TopRepo } from './top-repository';
|
||||
import { Repository } from '../repository';
|
||||
|
||||
export const topRepoEndpoint = "/api/repositories/top";
|
||||
export const topRepoEndpoint = "/api/repositories/top?detail=1";
|
||||
/**
|
||||
* Declare service to handle the top repositories
|
||||
*
|
||||
@ -31,9 +31,9 @@ export class TopRepoService {
|
||||
*
|
||||
* @memberOf GlobalSearchService
|
||||
*/
|
||||
getTopRepos(): Promise<TopRepo[]> {
|
||||
getTopRepos(): Promise<Repository[]> {
|
||||
return this.http.get(topRepoEndpoint, this.options).toPromise()
|
||||
.then(response => response.json() as TopRepo[])
|
||||
.then(response => response.json() as Repository[])
|
||||
.catch(error => Promise.reject(error));
|
||||
}
|
||||
}
|
@ -1,14 +1,14 @@
|
||||
.deletion-icon-inline {
|
||||
.confirmation-icon-inline {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.deletion-title {
|
||||
.confirmation-title {
|
||||
line-height: 24px;
|
||||
color: #000000;
|
||||
font-size: 22px;
|
||||
}
|
||||
|
||||
.deletion-content {
|
||||
.confirmation-content {
|
||||
font-size: 14px;
|
||||
color: #565656;
|
||||
line-height: 24px;
|
@ -1,13 +1,13 @@
|
||||
<clr-modal [(clrModalOpen)]="opened" [clrModalClosable]="false" [clrModalStaticBackdrop]="true">
|
||||
<h3 class="modal-title" class="deletion-title" style="margin-top: 0px;">{{dialogTitle}}</h3>
|
||||
<h3 class="modal-title" class="confirmation-title" style="margin-top: 0px;">{{dialogTitle}}</h3>
|
||||
<div class="modal-body">
|
||||
<div class="deletion-icon-inline">
|
||||
<div class="confirmation-icon-inline">
|
||||
<clr-icon shape="warning" class="is-warning" size="64"></clr-icon>
|
||||
</div>
|
||||
<div class="deletion-content">{{dialogContent}}</div>
|
||||
<div class="confirmation-content">{{dialogContent}}</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-outline" (click)="close()">{{'BUTTON.CANCEL' | translate}}</button>
|
||||
<button type="button" class="btn btn-outline" (click)="cancel()">{{'BUTTON.CANCEL' | translate}}</button>
|
||||
<button type="button" class="btn btn-primary" (click)="confirm()">{{'BUTTON.CONFIRM' | translate}}</button>
|
||||
</div>
|
||||
</clr-modal>
|
@ -0,0 +1,83 @@
|
||||
import { Component, OnDestroy } from '@angular/core';
|
||||
import { Subscription } from 'rxjs/Subscription';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
|
||||
import { ConfirmationDialogService } from './confirmation-dialog.service';
|
||||
import { ConfirmationMessage } from './confirmation-message';
|
||||
import { ConfirmationAcknowledgement } from './confirmation-state-message';
|
||||
import { ConfirmationState, ConfirmationTargets } from '../shared.const';
|
||||
|
||||
@Component({
|
||||
selector: 'confiramtion-dialog',
|
||||
templateUrl: 'confirmation-dialog.component.html',
|
||||
styleUrls: ['confirmation-dialog.component.css']
|
||||
})
|
||||
|
||||
export class ConfirmationDialogComponent implements OnDestroy {
|
||||
opened: boolean = false;
|
||||
dialogTitle: string = "";
|
||||
dialogContent: string = "";
|
||||
message: ConfirmationMessage;
|
||||
private annouceSubscription: Subscription;
|
||||
|
||||
constructor(
|
||||
private confirmationService: ConfirmationDialogService,
|
||||
private translate: TranslateService) {
|
||||
this.annouceSubscription = confirmationService.confirmationAnnouced$.subscribe(msg => {
|
||||
this.dialogTitle = msg.title;
|
||||
this.dialogContent = msg.message;
|
||||
this.message = msg;
|
||||
|
||||
this.translate.get(this.dialogTitle).subscribe((res: string) => this.dialogTitle = res);
|
||||
this.translate.get(this.dialogContent, { 'param': msg.param }).subscribe((res: string) => this.dialogContent = res);
|
||||
//Open dialog
|
||||
this.open();
|
||||
});
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
if (this.annouceSubscription) {
|
||||
this.annouceSubscription.unsubscribe();
|
||||
}
|
||||
}
|
||||
|
||||
open(): void {
|
||||
this.opened = true;
|
||||
}
|
||||
|
||||
close(): void {
|
||||
this.opened = false;
|
||||
}
|
||||
|
||||
cancel(): void {
|
||||
if(!this.message){//Inproper condition
|
||||
this.close();
|
||||
return;
|
||||
}
|
||||
|
||||
let data: any = this.message.data ? this.message.data : {};
|
||||
let target = this.message.targetId ? this.message.targetId : ConfirmationTargets.EMPTY;
|
||||
this.confirmationService.cancel(new ConfirmationAcknowledgement(
|
||||
ConfirmationState.CANCEL,
|
||||
data,
|
||||
target
|
||||
));
|
||||
this.close();
|
||||
}
|
||||
|
||||
confirm(): void {
|
||||
if(!this.message){//Inproper condition
|
||||
this.close();
|
||||
return;
|
||||
}
|
||||
|
||||
let data: any = this.message.data ? this.message.data : {};
|
||||
let target = this.message.targetId ? this.message.targetId : ConfirmationTargets.EMPTY;
|
||||
this.confirmationService.confirm(new ConfirmationAcknowledgement(
|
||||
ConfirmationState.CONFIRMED,
|
||||
data,
|
||||
target
|
||||
));
|
||||
this.close();
|
||||
}
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
import { Injectable } from '@angular/core'
|
||||
import { Subject } from 'rxjs/Subject';
|
||||
|
||||
import { ConfirmationMessage } from './confirmation-message';
|
||||
import { ConfirmationState } from '../shared.const';
|
||||
import { ConfirmationAcknowledgement } from './confirmation-state-message';
|
||||
|
||||
@Injectable()
|
||||
export class ConfirmationDialogService {
|
||||
private confirmationAnnoucedSource = new Subject<ConfirmationMessage>();
|
||||
private confirmationConfirmSource = new Subject<ConfirmationAcknowledgement>();
|
||||
|
||||
confirmationAnnouced$ = this.confirmationAnnoucedSource.asObservable();
|
||||
confirmationConfirm$ = this.confirmationConfirmSource.asObservable();
|
||||
|
||||
//User confirm the action
|
||||
public confirm(ack: ConfirmationAcknowledgement): void {
|
||||
this.confirmationConfirmSource.next(ack);
|
||||
}
|
||||
|
||||
//User cancel the action
|
||||
public cancel(ack: ConfirmationAcknowledgement): void {
|
||||
this.confirm(ack);
|
||||
}
|
||||
|
||||
//Open the confirmation dialog
|
||||
public openComfirmDialog(message: ConfirmationMessage): void {
|
||||
this.confirmationAnnoucedSource.next(message);
|
||||
}
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
import { DeletionTargets } from '../../shared/shared.const';
|
||||
import { ConfirmationTargets } from '../../shared/shared.const';
|
||||
|
||||
export class DeletionMessage {
|
||||
public constructor(title: string, message: string, param: string, data: any, targetId: DeletionTargets) {
|
||||
export class ConfirmationMessage {
|
||||
public constructor(title: string, message: string, param: string, data: any, targetId: ConfirmationTargets) {
|
||||
this.title = title;
|
||||
this.message = message;
|
||||
this.data = data;
|
||||
@ -10,7 +10,7 @@ export class DeletionMessage {
|
||||
}
|
||||
title: string;
|
||||
message: string;
|
||||
data: any;
|
||||
targetId: DeletionTargets = DeletionTargets.EMPTY;
|
||||
data: any = {};//default is empty
|
||||
targetId: ConfirmationTargets = ConfirmationTargets.EMPTY;
|
||||
param: string;
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
import { ConfirmationState, ConfirmationTargets } from '../shared.const';
|
||||
|
||||
export class ConfirmationAcknowledgement {
|
||||
constructor(state: ConfirmationState, data: any, source: ConfirmationTargets) {
|
||||
this.state = state;
|
||||
this.data = data;
|
||||
this.source = source;
|
||||
}
|
||||
|
||||
state: ConfirmationState = ConfirmationState.NA;
|
||||
data: any = {};
|
||||
source: ConfirmationTargets = ConfirmationTargets.EMPTY;
|
||||
}
|
@ -1,54 +0,0 @@
|
||||
import { Component, OnDestroy } from '@angular/core';
|
||||
import { Subscription } from 'rxjs/Subscription';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
|
||||
import { DeletionDialogService } from './deletion-dialog.service';
|
||||
import { DeletionMessage } from './deletion-message';
|
||||
|
||||
@Component({
|
||||
selector: 'deletion-dialog',
|
||||
templateUrl: 'deletion-dialog.component.html',
|
||||
styleUrls: ['deletion-dialog.component.css']
|
||||
})
|
||||
|
||||
export class DeletionDialogComponent implements OnDestroy{
|
||||
opened: boolean = false;
|
||||
dialogTitle: string = "";
|
||||
dialogContent: string = "";
|
||||
message: DeletionMessage;
|
||||
private annouceSubscription: Subscription;
|
||||
|
||||
constructor(
|
||||
private delService: DeletionDialogService,
|
||||
private translate: TranslateService) {
|
||||
this.annouceSubscription = delService.deletionAnnouced$.subscribe(msg => {
|
||||
this.dialogTitle = msg.title;
|
||||
this.dialogContent = msg.message;
|
||||
this.message = msg;
|
||||
|
||||
this.translate.get(this.dialogTitle).subscribe((res: string) => this.dialogTitle = res);
|
||||
this.translate.get(this.dialogContent, { 'param': msg.param }).subscribe((res: string) => this.dialogContent = res);
|
||||
//Open dialog
|
||||
this.open();
|
||||
});
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
if(this.annouceSubscription){
|
||||
this.annouceSubscription.unsubscribe();
|
||||
}
|
||||
}
|
||||
|
||||
open(): void {
|
||||
this.opened = true;
|
||||
}
|
||||
|
||||
close(): void {
|
||||
this.opened = false;
|
||||
}
|
||||
|
||||
confirm(): void {
|
||||
this.delService.confirmDeletion(this.message);
|
||||
this.close();
|
||||
}
|
||||
}
|
@ -1,21 +0,0 @@
|
||||
import { Injectable } from '@angular/core'
|
||||
import { Subject } from 'rxjs/Subject';
|
||||
|
||||
import { DeletionMessage } from './deletion-message';
|
||||
|
||||
@Injectable()
|
||||
export class DeletionDialogService {
|
||||
private deletionAnnoucedSource = new Subject<DeletionMessage>();
|
||||
private deletionConfirmSource = new Subject<DeletionMessage>();
|
||||
|
||||
deletionAnnouced$ = this.deletionAnnoucedSource.asObservable();
|
||||
deletionConfirm$ = this.deletionConfirmSource.asObservable();
|
||||
|
||||
confirmDeletion(message: any): void {
|
||||
this.deletionConfirmSource.next(message);
|
||||
}
|
||||
|
||||
openComfirmDialog(message: DeletionMessage): void {
|
||||
this.deletionAnnoucedSource.next(message);
|
||||
}
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
<span style="float: right; margin-right: 24px;">
|
||||
<clr-dropdown [clrMenuPosition]="'bottom-right'" [clrCloseMenuOnItemClick]="true" style="position: absolute;">
|
||||
<button clrDropdownToggle>
|
||||
<clr-icon shape="ellipses-vertical"></clr-icon>
|
||||
</button>
|
||||
<div class="dropdown-menu">
|
||||
<ng-content></ng-content>
|
||||
</div>
|
||||
</clr-dropdown>
|
||||
</span>
|
@ -1,9 +0,0 @@
|
||||
import {Component} from "@angular/core";
|
||||
|
||||
@Component({
|
||||
selector: "harbor-action-overflow",
|
||||
templateUrl: "harbor-action-overflow.html"
|
||||
})
|
||||
|
||||
export class HarborActionOverflow {
|
||||
}
|
@ -4,7 +4,7 @@
|
||||
{{errorMessage}}
|
||||
</span>
|
||||
<div class="alert-actions" *ngIf="showCancelAction">
|
||||
<button class="btn alert-action" (click)="confirmCancel()">{{'BUTTON.CONFIRM' | translate}}</button>
|
||||
<button class="btn alert-action" (click)="confirmCancel()">{{'BUTTON.YES' | translate}}</button>
|
||||
</div>
|
||||
</div>
|
||||
</clr-alert>
|
@ -44,7 +44,7 @@ export class InlineAlertComponent {
|
||||
this.showCancelAction = true;
|
||||
this.inlineAlertClosable = true;
|
||||
this.alertClose = false;
|
||||
this.useAppLevelStyle = true;
|
||||
this.useAppLevelStyle = false;
|
||||
}
|
||||
|
||||
//Show inline sccess info
|
||||
|
@ -1,24 +1,24 @@
|
||||
<clr-datagrid>
|
||||
<clr-dg-column>{{'REPLICATION.NAME' | translate}}</clr-dg-column>
|
||||
<clr-dg-column *ngIf="projectless">{{'REPLICATION.PROJECT' | translate}}</clr-dg-column>
|
||||
<clr-dg-column>{{'REPLICATION.DESCRIPTION' | translate}}</clr-dg-column>
|
||||
<clr-dg-column>{{'REPLICATION.DESTINATION_NAME' | translate}}</clr-dg-column>
|
||||
<clr-dg-column>{{'REPLICATION.LAST_START_TIME' | translate}}</clr-dg-column>
|
||||
<clr-dg-column>{{'REPLICATION.ACTIVATION' | translate}}</clr-dg-column>
|
||||
<clr-dg-row *ngFor="let p of policies;let i = index;" (click)="selectPolicy(p)" [style.backgroundColor]="(!projectless && selectedId === p.id) ? '#eee' : ''">
|
||||
<clr-dg-cell>{{p.name}}</clr-dg-cell>
|
||||
<clr-dg-cell *ngIf="projectless">{{p.project_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 === 1 ? 'REPLICATION.ENABLED' : 'REPLICATION.DISABLED') | translate}}
|
||||
<harbor-action-overflow>
|
||||
<a href="javascript:void(0)" class="dropdown-item" (click)="editPolicy(p)">{{'REPLICATION.EDIT_POLICY' | translate}}</a>
|
||||
<a href="javascript:void(0)" class="dropdown-item" (click)="enablePolicy(p)">{{ (p.enabled === 0 ? 'REPLICATION.ENABLE' : 'REPLICATION.DISABLE') | translate}}</a>
|
||||
<a href="javascript:void(0)" class="dropdown-item" (click)="deletePolicy(p)">{{'REPLICATION.DELETE_POLICY' | translate}}</a>
|
||||
</harbor-action-overflow>
|
||||
</clr-dg-cell>
|
||||
</clr-dg-row>
|
||||
<clr-dg-footer>{{ (policies ? policies.length : 0) }} {{'REPLICATION.ITEMS' | translate}}</clr-dg-footer>
|
||||
<clr-dg-column>{{'REPLICATION.NAME' | translate}}</clr-dg-column>
|
||||
<clr-dg-column *ngIf="projectless">{{'REPLICATION.PROJECT' | translate}}</clr-dg-column>
|
||||
<clr-dg-column>{{'REPLICATION.DESCRIPTION' | translate}}</clr-dg-column>
|
||||
<clr-dg-column>{{'REPLICATION.DESTINATION_NAME' | translate}}</clr-dg-column>
|
||||
<clr-dg-column>{{'REPLICATION.LAST_START_TIME' | translate}}</clr-dg-column>
|
||||
<clr-dg-column>{{'REPLICATION.ACTIVATION' | translate}}</clr-dg-column>
|
||||
<clr-dg-row *ngFor="let p of policies;let i = index;" [clrDgItem]="p" (click)="selectPolicy(p)" [style.backgroundColor]="(!projectless && selectedId === p.id) ? '#eee' : ''">
|
||||
<clr-dg-action-overflow>
|
||||
<button class="action-item" (click)="editPolicy(p)">{{'REPLICATION.EDIT_POLICY' | translate}}</button>
|
||||
<button class="action-item" (click)="togglePolicy(p)">{{ (p.enabled === 0 ? 'REPLICATION.ENABLE' : 'REPLICATION.DISABLE') | translate}}</button>
|
||||
<button class="action-item" (click)="deletePolicy(p)">{{'REPLICATION.DELETE_POLICY' | translate}}</button>
|
||||
</clr-dg-action-overflow>
|
||||
<clr-dg-cell>{{p.name}}</clr-dg-cell>
|
||||
<clr-dg-cell *ngIf="projectless">{{p.project_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 === 1 ? 'REPLICATION.ENABLED' : 'REPLICATION.DISABLED') | translate}}
|
||||
</clr-dg-cell>
|
||||
</clr-dg-row>
|
||||
<clr-dg-footer>{{ (policies ? policies.length : 0) }} {{'REPLICATION.ITEMS' | translate}}</clr-dg-footer>
|
||||
</clr-datagrid>
|
@ -3,10 +3,10 @@ import { Component, Input, Output, EventEmitter, ViewChild, OnDestroy } from '@a
|
||||
import { ReplicationService } from '../../replication/replication.service';
|
||||
import { Policy } from '../../replication/policy';
|
||||
|
||||
import { DeletionDialogService } from '../../shared/deletion-dialog/deletion-dialog.service';
|
||||
import { DeletionMessage } from '../../shared/deletion-dialog/deletion-message';
|
||||
import { ConfirmationDialogService } from '../../shared/confirmation-dialog/confirmation-dialog.service';
|
||||
import { ConfirmationMessage } from '../../shared/confirmation-dialog/confirmation-message';
|
||||
|
||||
import { DeletionTargets } from '../../shared/shared.const';
|
||||
import { ConfirmationState, ConfirmationTargets } from '../../shared/shared.const';
|
||||
|
||||
import { MessageService } from '../../global-message/message.service';
|
||||
import { AlertType } from '../../shared/shared.const';
|
||||
@ -18,7 +18,7 @@ import { Subscription } from 'rxjs/Subscription';
|
||||
templateUrl: 'list-policy.component.html',
|
||||
})
|
||||
export class ListPolicyComponent implements OnDestroy {
|
||||
|
||||
|
||||
@Input() policies: Policy[];
|
||||
@Input() projectless: boolean;
|
||||
@Input() selectedId: number;
|
||||
@ -26,35 +26,38 @@ export class ListPolicyComponent implements OnDestroy {
|
||||
@Output() reload = new EventEmitter<boolean>();
|
||||
@Output() selectOne = new EventEmitter<Policy>();
|
||||
@Output() editOne = new EventEmitter<number>();
|
||||
|
||||
@Output() toggleOne = new EventEmitter<Policy>();
|
||||
|
||||
subscription: Subscription;
|
||||
|
||||
constructor(
|
||||
private replicationService: ReplicationService,
|
||||
private deletionDialogService: DeletionDialogService,
|
||||
private deletionDialogService: ConfirmationDialogService,
|
||||
private messageService: MessageService) {
|
||||
|
||||
|
||||
this.subscription = this.subscription = this.deletionDialogService
|
||||
.deletionConfirm$
|
||||
.subscribe(
|
||||
message=>{
|
||||
if(message && message.targetId === DeletionTargets.POLICY) {
|
||||
this.replicationService
|
||||
.deletePolicy(message.data)
|
||||
.subscribe(
|
||||
response=>{
|
||||
console.log('Successful delete policy with ID:' + message.data);
|
||||
this.reload.emit(true);
|
||||
},
|
||||
error=>this.messageService.announceMessage(error.status, 'Failed to delete policy with ID:' + message.data, AlertType.DANGER)
|
||||
);
|
||||
}
|
||||
});
|
||||
.confirmationConfirm$
|
||||
.subscribe(
|
||||
message => {
|
||||
if (message &&
|
||||
message.source === ConfirmationTargets.POLICY &&
|
||||
message.state === ConfirmationState.CONFIRMED) {
|
||||
this.replicationService
|
||||
.deletePolicy(message.data)
|
||||
.subscribe(
|
||||
response => {
|
||||
console.log('Successful delete policy with ID:' + message.data);
|
||||
this.reload.emit(true);
|
||||
},
|
||||
error => this.messageService.announceMessage(error.status, 'Failed to delete policy with ID:' + message.data, AlertType.DANGER)
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
if(this.subscription) {
|
||||
if (this.subscription) {
|
||||
this.subscription.unsubscribe();
|
||||
}
|
||||
}
|
||||
@ -69,15 +72,24 @@ export class ListPolicyComponent implements OnDestroy {
|
||||
console.log('Open modal to edit policy.');
|
||||
this.editOne.emit(policy.id);
|
||||
}
|
||||
|
||||
enablePolicy(policy: Policy): void {
|
||||
|
||||
togglePolicy(policy: Policy) {
|
||||
policy.enabled = policy.enabled === 0 ? 1 : 0;
|
||||
console.log('Enable policy ID:' + policy.id + ' with activation status ' + policy.enabled);
|
||||
policy.enabled = policy.enabled === 0 ? 1 : 0;
|
||||
this.replicationService.enablePolicy(policy.id, policy.enabled);
|
||||
this.replicationService.enablePolicy(policy.id, policy.enabled)
|
||||
.subscribe(
|
||||
res => console.log('Successful toggled policy status'),
|
||||
error => this.messageService.announceMessage(error.status, "Failed to toggle policy status.", AlertType.DANGER)
|
||||
);
|
||||
}
|
||||
|
||||
deletePolicy(policy: Policy) {
|
||||
let deletionMessage: DeletionMessage = new DeletionMessage('REPLICATION.DELETION_TITLE', 'REPLICATION.DELETION_SUMMARY', policy.name, policy.id, DeletionTargets.POLICY);
|
||||
let deletionMessage: ConfirmationMessage = new ConfirmationMessage(
|
||||
'REPLICATION.DELETION_TITLE',
|
||||
'REPLICATION.DELETION_SUMMARY',
|
||||
policy.name,
|
||||
policy.id,
|
||||
ConfirmationTargets.POLICY);
|
||||
this.deletionDialogService.openComfirmDialog(deletionMessage);
|
||||
}
|
||||
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user