Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
5a33735
added basic certificate model
johnnyzhang82 Mar 14, 2018
bea3902
-Added certificate proxy class
johnnyzhang82 Mar 15, 2018
a873991
Added certificate service class and dto model
johnnyzhang82 Mar 15, 2018
2bf6d41
added certificate and related decorater
johnnyzhang82 Mar 15, 2018
7186adb
First round added certificate home list pages
johnnyzhang82 Mar 15, 2018
3cb113a
Updated certificate filter as a client search
johnnyzhang82 Mar 15, 2018
2a9270e
added certificate configuration
johnnyzhang82 Mar 15, 2018
b5fe035
Remove console.log
johnnyzhang82 Mar 15, 2018
cc1c1a4
added delete certificate and reactivate certifcate actions
johnnyzhang82 Mar 16, 2018
f105125
Link delete/reactivate actions to cert list and detail page button an…
johnnyzhang82 Mar 16, 2018
678a5d3
Basic certificate create form
johnnyzhang82 Mar 16, 2018
238a278
added file picker
johnnyzhang82 Mar 16, 2018
1fdf525
added function that get cert thumprint based uploaded cert
johnnyzhang82 Mar 16, 2018
379fdb1
added password validation
johnnyzhang82 Mar 16, 2018
b41c951
Fixed action errors
johnnyzhang82 Mar 16, 2018
86dadcd
Merge branch 'master' into feature/cert
johnnyzhang82 Mar 17, 2018
abbdc2d
Merge branch 'master' into feature/cert
johnnyzhang82 Mar 19, 2018
1544951
Code review update
johnnyzhang82 Mar 19, 2018
c34f3b3
Merge branch 'master' into feature/cert
johnnyzhang82 Mar 20, 2018
45cbb7d
Merge branch 'master' into feature/cert
johnnyzhang82 Mar 21, 2018
2af7d87
Merge branch 'master' into feature/cert
johnnyzhang82 Mar 21, 2018
01064e7
Merge branch 'master' into feature/cert
johnnyzhang82 Mar 22, 2018
05e877e
update prefix of node-forge version
johnnyzhang82 Mar 22, 2018
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion app/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { MainNavigationComponent } from "app/components/shared/main-navigation.c
import { BaseModule } from "@batch-flask/ui";
import { AccountModule } from "app/components/account/account.module";
import { ApplicationModule } from "app/components/application/application.module";
import { CertificateModule } from "app/components/certificate/certificate.module";
import { DataModule } from "app/components/data/data.module";
import { FileModule } from "app/components/file/file.module";
import { JobScheduleModule } from "app/components/job-schedule/job-schedule.module";
Expand Down Expand Up @@ -52,6 +53,7 @@ import {
BatchClientService,
BatchLabsService,
CacheDataService,
CertificateService,
CommandService,
ComputeService,
FileService,
Expand Down Expand Up @@ -89,7 +91,7 @@ import {
} from "./services";

const modules = [
AccountModule, ApplicationModule, DataModule,
AccountModule, ApplicationModule, CertificateModule, DataModule,
FileModule, JobModule, JobScheduleModule, NodeModule, PoolModule,
SettingsModule, TaskModule, MarketModule, LayoutModule,
MiscModule,
Expand Down Expand Up @@ -134,6 +136,7 @@ const graphApiServices = [AADApplicationService, AADGraphHttpService, MsGraphHtt
BatchClientService,
BatchLabsService,
CacheDataService,
CertificateService,
CommandService,
CommonModule,
ComputeService,
Expand Down
11 changes: 11 additions & 0 deletions app/app.routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ import { AccountHomeComponent } from "./components/account/home/account-home.com
import { AccountMonitoringHomeComponent } from "./components/account/monitoring";
import { ApplicationDefaultComponent, ApplicationDetailsComponent } from "./components/application/details";
import { ApplicationHomeComponent } from "./components/application/home/application-home.component";
import { CertificateDefaultComponent, CertificateDetailsComponent } from "./components/certificate/details";
import { CertificateHomeComponent } from "./components/certificate/home/certificate-home.component";
import { DataDefaultComponent, DataDetailsComponent } from "./components/data/details";
import { DataHomeComponent } from "./components/data/home/data-home.component";
import { JobScheduleDefaultComponent, JobScheduleDetailsComponent } from "./components/job-schedule/details";
Expand Down Expand Up @@ -73,6 +75,15 @@ export const routes: Routes = [
{ path: ":id", component: PoolDetailsComponent }, // pools/{pool.id}
],
},
{
path: "certificates",
canActivate: [NavigationGuard],
component: CertificateHomeComponent,
children: [
{ path: "", component: CertificateDefaultComponent }, // certificates/
{ path: ":thumbprint", component: CertificateDetailsComponent }, // certificate/{certificate.thumbprint}
],
},
{
path: "market",
canActivate: [NavigationGuard],
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import { Component } from "@angular/core";
import { FormBuilder, FormControl, FormGroup, Validators } from "@angular/forms";
import { Observable } from "rxjs";

import { autobind } from "@batch-flask/core";
import { NotificationService } from "@batch-flask/ui/notifications";
import { SidebarRef } from "@batch-flask/ui/sidebar";
import { CertificateFormat, CertificateService } from "app/services";
import { Constants, FileUrlUtils } from "app/utils";

import "./certificate-create-dialog.scss";

@Component({
selector: "bl-certificate-create-dialog",
templateUrl: "certificate-create-dialog.html",
})
export class CertificateCreateDialogComponent {
public file: File;
public form: FormGroup;

constructor(
private formBuilder: FormBuilder,
public sidebarRef: SidebarRef<CertificateCreateDialogComponent>,
private certificateService: CertificateService,
private notificationService: NotificationService) {
const validation = Constants.forms.validation;

this.form = this.formBuilder.group({
certificate: ["", [
Validators.required,
Validators.pattern(validation.regex.certificateFileName),
]],
password: [null, this._passwordValidator()],
});
}

@autobind()
public submit(): Observable<any> {
const formData = this.form.value;
const obs = this.certificateService.parseCertificate(this.file, formData.password);

obs.subscribe({
next: (certificate: any) => {
const obs = this.certificateService.add(certificate);
obs.subscribe({
next: () => {
this.certificateService.onCertificateAdded.next(certificate.thumbprint);
this.notificationService.success("Certificate added!",
`Certificate '${certificate.thumbprint}' was created successfully!`);
},
error: () => null,
});
},
error: (response: Response) => {
this.notificationService.error(
"Certificate creation failed",
response.toString(),
);
},
});
return obs;
}

public fileSelected(changeEvent: Event) {
const element = changeEvent.srcElement as any;
this.form.controls["certificate"].markAsTouched();
if (element.files.length > 0) {
this.file = element.files[0];
this.form.controls["certificate"].setValue(this.file.name);
} else {
this.file = null;
this.form.controls["certificate"].setValue(null);
}
this.form.controls["password"].setValue(null);
}

public get showPassword() {
return this.file && FileUrlUtils.getFileExtension(this.file.name) === CertificateFormat.pfx;
}

private _passwordValidator() {
return (control: FormControl): {[key: string]: any} => {
if (this.showPassword) {
if (!control.value) {
return {
pfxPasswordRequired: {
value: true,
},
};
}
}
return null;
};
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<bl-complex-form [formGroup]="form" [submit]="submit" [containerRef]="sidebarRef">
<bl-form-page main-form-page title="Create certificate" [formGroup]="form">
<bl-form-section title="Certificate" subtitle="Select a certificate (pfx or cer) to upload">
<div class="form-element">
<input type="file" (change)="fileSelected($event)" />
<bl-error controlName="certificate" code="required">Please select a valid certificate file</bl-error>
<bl-error controlName="certificate" code="pattern">Certificate need to be valid CER or PFX files only</bl-error>
</div>
</bl-form-section>
<bl-form-section *ngIf="showPassword" title="Password" subtitle="Type password for pfx certificate format">
<div class="form-element">
<mat-form-field>
<input matInput formControlName="password" placeholder="Password" type="password" />
</mat-form-field>
<bl-error controlName="password" code="pfxPasswordRequired">Password is required if the certificate format is pfx</bl-error>
</div>
</bl-form-section>
</bl-form-page>
</bl-complex-form>
Empty file.
1 change: 1 addition & 0 deletions app/components/certificate/action/add/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./certificate-create-dialog.component";
21 changes: 21 additions & 0 deletions app/components/certificate/action/certificate-action.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { NgModule } from "@angular/core";

import { BaseModule } from "@batch-flask/ui";
import { commonModules } from "app/common";
import { JobActionModule } from "app/components/job/action";
import { CertificateCreateDialogComponent } from "./add/certificate-create-dialog.component";
import { DeleteCertificateDialogComponent } from "./delete/delete-certificate-dialog.component";
import { ReactivateCertificateDialogComponent } from "./reactivate/reactivate-certificate-dialog.component";

const components = [
DeleteCertificateDialogComponent, ReactivateCertificateDialogComponent, CertificateCreateDialogComponent,
];

@NgModule({
declarations: components,
exports: components,
imports: [...commonModules, JobActionModule, BaseModule],
entryComponents: components,
})
export class CertificateActionModule {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { BehaviorSubject } from "rxjs";

import { BackgroundTaskService } from "@batch-flask/ui/background-task";
import { WaitForDeletePoller } from "app/components/core/pollers";
import { Certificate } from "app/models";
import { CertificateService } from "app/services";
import { LongRunningDeleteAction } from "app/services/core";

export class DeleteCertificateAction extends LongRunningDeleteAction {
constructor(private certificateService: CertificateService, certificateThumbprints: string[]) {
super("certificate", certificateThumbprints);
}

public deleteAction(thumbprint: string) {
return this.certificateService.delete(thumbprint);
}

protected waitForDelete(thumbprint: string, taskManager?: BackgroundTaskService) {
this.certificateService.get(thumbprint).subscribe({
next: (certificate: Certificate) => {
const task = new WaitForDeletePoller(() => this.certificateService.get(thumbprint));
if (taskManager) {
taskManager.startTask(`Deleting Certificate '${thumbprint}'`, (bTask) => {
return task.start(bTask.progress);
});
} else {
task.start(new BehaviorSubject<any>(-1)).subscribe({
complete: () => {
this.markItemAsDeleted();
},
});
}
},
error: (error) => {
// No need to watch for Certificate it is already deleted
this.markItemAsDeleted();
},
});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { ChangeDetectionStrategy, ChangeDetectorRef, Component } from "@angular/core";
import { MatDialogRef } from "@angular/material";

import { autobind } from "@batch-flask/core";
import { BackgroundTaskService } from "@batch-flask/ui/background-task";
import { DeleteCertificateAction } from "app/components/certificate/action/delete/delete-certificate-action";
import { CertificateService } from "app/services";

@Component({
selector: "bl-delete-certificate-dialog",
templateUrl: "delete-certificate-dialog.html",
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class DeleteCertificateDialogComponent {
public set certificateThumbprint(certificateThumbprint: string) {
this._certificateThumbprint = certificateThumbprint;
this.changeDetector.detectChanges();
}
public get certificateThumbprint() { return this._certificateThumbprint; }

private _certificateThumbprint: string;

constructor(
public dialogRef: MatDialogRef<DeleteCertificateDialogComponent>,
private certificateService: CertificateService,
private taskManager: BackgroundTaskService,
private changeDetector: ChangeDetectorRef) {
}

@autobind()
public destroyCertificate() {
const task = new DeleteCertificateAction(this.certificateService, [this.certificateThumbprint]);
task.startAndWaitAsync(this.taskManager);
return task.actionDone;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<bl-simple-form [submit]="destroyCertificate" [containerRef]="dialogRef" actionColor="warn" actionName="Delete" [multiUse]="false" size="small" title="Delete certificate">
<div class="message">
<p>Do you want to delete the certificate: '<b>{{certificateThumbprint}}</b>'?</p>
<p>
Warning! Deleting certificate'<b>{{certificateThumbprint}}</b>' is irreversible. The action you're about to take can't be undone. Clicking
OK will delete this certificate permanently.
</p>
</div>
</bl-simple-form>
2 changes: 2 additions & 0 deletions app/components/certificate/action/delete/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from "./delete-certificate-action";
export * from "./delete-certificate-dialog.component";
4 changes: 4 additions & 0 deletions app/components/certificate/action/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@

export * from "./delete";
export * from "./reactivate";
export * from "./certificate-action.module";
1 change: 1 addition & 0 deletions app/components/certificate/action/reactivate/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./reactivate-certificate-dialog.component";
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { ChangeDetectionStrategy, Component } from "@angular/core";
import { MatDialogRef } from "@angular/material";

import { autobind } from "@batch-flask/core";
import { CertificateService } from "app/services";

@Component({
selector: "bl-reactivate-certificate-dialog",
templateUrl: "reactivate-certificate-dialog.html",
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ReactivateCertificateDialogComponent {
public certificateThumbprint: string;

constructor(
public dialogRef: MatDialogRef<ReactivateCertificateDialogComponent>,
private certificateService: CertificateService) {
}

@autobind()
public ok() {
const options: any = {};

return this.certificateService.cancelDelete(this.certificateThumbprint, options);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<bl-simple-form [submit]="ok" [containerRef]="dialogRef" [multiUse]="false" size="small" actionName="Enable" title="Reactivate certificate">
<p>Do you want to reactivate the certificate '<b>{{certificateThumbprint}}</b>'?</p>
</bl-simple-form>
Loading