Skip to content

Commit

Permalink
Merge pull request #324 from openziti/323-more-actions-extensions
Browse files Browse the repository at this point in the history
Allow ZAC extensions to define additional items for the "More Actions" drop down
  • Loading branch information
rgallettonf authored Apr 29, 2024
2 parents cecc90e + adf71cb commit 79c5d9c
Show file tree
Hide file tree
Showing 20 changed files with 168 additions and 46 deletions.
4 changes: 3 additions & 1 deletion projects/app-ziti-console/src/app/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,8 @@ import {
ZAC_LOGIN_SERVICE,
EDGE_ROUTER_EXTENSION_SERVICE,
SERVICE_EXTENSION_SERVICE,
ExtensionsNoopService
ExtensionsNoopService,
IDENTITY_EXTENSION_SERVICE
} from "ziti-console-lib";

import {AppRoutingModule} from "./app-routing.module";
Expand Down Expand Up @@ -112,6 +113,7 @@ if (environment.nodeIntegration) {
{provide: ZAC_LOGIN_SERVICE, useClass: loginService},
{provide: SETTINGS_SERVICE, useClass: settingsService},
{provide: EDGE_ROUTER_EXTENSION_SERVICE, useClass: ExtensionsNoopService},
{provide: IDENTITY_EXTENSION_SERVICE, useClass: ExtensionsNoopService},
{provide: SERVICE_EXTENSION_SERVICE, useClass: ExtensionsNoopService},
],
bootstrap: [AppComponent]
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Extension Service

Below is an example of how to define and use an extension service.

In the file `example-extension.service.ts` is an exported class that implements the `ExtensionService` interface.

Each create/edit form (ie. `identity-form.component.ts`, `edge-router-form.component.ts` etc...) injects an instance of an `ExtensionService` implementation.

To use a specific implementation of an extension service, you must define a provider for it in `app.module.ts`. See below for an example of how to do this:

```
// In the "providers" secion of app.module.ts, use the relevant injection token to decalre which instance of the extesnsion service to provide
import { ExampleExtensionService } from "./examples/extension-service/example-extension.service";
@NgModule({
declarations: [AppComponent],
providers: [
// Here we can chose to use the example-extension.service.ts
{ provide: EDGE_ROUTER_EXTENSION_SERVICE, useClass: ExampleExtensionService },
]
})
```

In `example-extension.service.ts` are examples of how the various properties, functions, and events are defined. If you open the EdgeRouter create/edit form
(either by clicking on an existing router from the list page, or by adding a new one), you should then see the "Edit Form Action" item display as an option in the "More Actions" dropdown
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import {EventEmitter, Injectable} from '@angular/core';
import {ExtensionService} from "ziti-console-lib";
import {BehaviorSubject} from "rxjs";

@Injectable({ providedIn: 'root' })
export class ExampleExtensionService implements ExtensionService {

moreActions = [
{label: 'Example Action', action: 'more-action', callback: this.executeMoreAction.bind(this)},
]
listActions = [
{label: 'Example Action', action: 'list-action', callback: this.executeListAction.bind(this)},
]
closeAfterSave: boolean;
closed: EventEmitter<any>;
formDataChanged: BehaviorSubject<any>;

extendAfterViewInits(extentionPoints: any): void {
// This function will execute after the edit form is initialized
// Uncomment the alert below to see when this function is called
//alert('ExampleExtensionService "extendAfterViewInits()" function executed');
}

formDataSaved(data: any): Promise<any> {
// This function will execute before form data is saved in order to do check if extension data is valid
// Uncomment the alert below to see when this function is called
// alert('ExampleExtensionService "formDataSaved()" function executed');
return Promise.resolve(undefined);
}

updateFormData(data: any): void {
// This function will pass in a reference to the root data model of the entity being edited
// Uncomment the alert below to see when this function is called
// alert('ExampleExtensionService "updateFormData()" function executed');
}

validateData(): Promise<any> {
// This function will execute before form data is saved in order to do check if extension data is valid
// Uncomment the alert below to see when this function is called
// alert('ExampleExtensionService "validateData()" function executed');
return Promise.resolve(undefined);
}

executeMoreAction() {
alert('Example action executed from "More Actions" drop down');
}

executeListAction() {
alert('Example action executed from list page menu items');
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,9 @@ export class CardListComponent extends ProjectableForm {
public svc: ServiceFormService,
@Inject(ZITI_DATA_SERVICE) private zitiService: ZitiDataService,
growlerService: GrowlerService,
@Inject(SERVICE_EXTENSION_SERVICE) private extService: ExtensionService
@Inject(SERVICE_EXTENSION_SERVICE) extService: ExtensionService
) {
super(growlerService);
super(growlerService, extService);
}
clear(): void {
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
class="tActionRow"
id="ResetTableButton"
>
{{headerAction.name}}
{{headerAction.name || headerAction.label}}
</div>
</div>
<div
Expand All @@ -59,7 +59,7 @@
class="tActionRow"
id="TableActionButton_{{menuItem.action}}"
>
{{menuItem.name}}
{{menuItem.name || menuItem.label}}
</div>
</div>
<div
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ export interface ExtensionService {
formDataChanged: BehaviorSubject<any>;
closed: EventEmitter<any>;
closeAfterSave: boolean;
moreActions?: any[];
listActions?: any[];
extendAfterViewInits(extentionPoints: any): void;
updateFormData(data: any): void;
validateData(): Promise<any>;
Expand All @@ -37,6 +39,7 @@ export class ExtensionsNoopService implements ExtensionService {
formDataChanged = new BehaviorSubject<any>({isEmpty: true});
closed: EventEmitter<any> = new EventEmitter<any>();
closeAfterSave = true;
moreActions = [];

constructor() { }

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
import {
Component,
ComponentRef,
EventEmitter,
EventEmitter, Inject,
Input,
OnDestroy,
OnInit, Output,
Expand All @@ -31,6 +31,8 @@ import {ProjectableForm} from "../projectable-form.class";
import {JsonEditorComponent, JsonEditorOptions} from 'ang-jsoneditor';
import _ from "lodash";
import {GrowlerService} from "../../messaging/growler.service";
import {ExtensionService, SHAREDZ_EXTENSION} from "../../extendable/extensions-noop.service";
import {SERVICE_EXTENSION_SERVICE} from "../service/service-form.service";


@Component({
Expand Down Expand Up @@ -74,8 +76,9 @@ export class ConfigurationFormComponent extends ProjectableForm implements OnIni

constructor(private svc: ConfigurationService,
private schemaSvc: SchemaService,
growlerService: GrowlerService) {
super(growlerService);
growlerService: GrowlerService,
@Inject(SHAREDZ_EXTENSION) extService: ExtensionService) {
super(growlerService, extService);
}

async createForm() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<lib-form-header
[data]="formData"
[title]="formData.id ? 'Edit Edge Router: ' : 'Create New Edge Router'"
[moreActions]="formData.moreActions"
[moreActions]="moreActions"
(actionRequested)="headerActionRequested($event)"
[(formView)]="formView"
></lib-form-header>
Expand Down Expand Up @@ -34,7 +34,7 @@
[placeholder]="'Add attributes to group Edge Routers'"
></lib-tag-selector>
</lib-form-field-container>
<ng-content select="[slot=provider]"></ng-content>
<ng-content select="[slot=column-1-slot-1]"></ng-content>
<lib-form-field-toggle [(toggleOn)]="showMore" style="margin: 0px 10px"></lib-form-field-toggle>
<div *ngIf="showMore" class="form-group-column">
<lib-form-field-container
Expand Down Expand Up @@ -94,7 +94,9 @@
*ngIf="hasEnrolmentToken"
[showHeader]="false"
>
<ng-content select="[slot=column-2-slot-1]"></ng-content>
<lib-qr-code
*ngIf="!extService['hideZitiRegistration']"
[identity]="formData"
[jwt]="formData.jwt"
[token]="formData.enrollmentToken"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,9 @@ export class EdgeRouterFormComponent extends ProjectableForm implements OnInit,
public svc: EdgeRouterFormService,
@Inject(ZITI_DATA_SERVICE) private zitiService: ZitiDataService,
growlerService: GrowlerService,
@Inject(EDGE_ROUTER_EXTENSION_SERVICE) private extService: ExtensionService
@Inject(EDGE_ROUTER_EXTENSION_SERVICE) extService: ExtensionService
) {
super(growlerService);
super(growlerService, extService);
}

ngOnInit(): void {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<div class="form-header-container" [ngClass]="{'show-more-actions': moreActions && moreActions.length > 0}">
<div class="form-header-group">
<div class="form-title-container">
<div class="form-title-back-button" (click)="requestAction('close')">
<div class="form-title-back-button" (click)="requestAction({name: 'close'})">
<div class="form-title-back-button-image"></div>
<div class="form-title-back-button-line"></div>
</div>
Expand All @@ -15,11 +15,11 @@
<span
class="toggle-option-text"
[ngClass]="{'toggle-option-selected': formView === 'simple'}"
(click)="requestAction('toggle-view', 'raw')"
(click)="requestAction({name: 'toggle-view', data: 'raw'})"
>
FORM
</span>
<div class="form-header-toggle" (click)="requestAction('toggle-view', formView)">
<div class="form-header-toggle" (click)="requestAction({name: 'toggle-view', data: formView})">
<div
class="form-toggle-switch"
[ngClass]="{'toggle-left': formView === 'simple', 'toggle-right': formView === 'raw'}"
Expand All @@ -31,7 +31,7 @@
<span
class="toggle-option-text"
[ngClass]="{'toggle-option-selected': formView === 'raw'}"
(click)="requestAction('toggle-view', 'simple')"
(click)="requestAction({name:'toggle-view', data: 'simple'})"
>
JSON
</span>
Expand All @@ -47,14 +47,14 @@
<div
*ngFor="let action of moreActions"
class="more-actions-item"
(click)="requestAction(action.name, action.data)"
(click)="requestAction(action)"
>
{{action.label}}
</div>
</div>
</div>
<div class="save-button"
(click)="requestAction('save')"
(click)="requestAction({name: 'save'})"
[ngClass]="{'save-disabled': saveDisabled}"
matTooltip="{{saveDisabled ? saveTooltip : ''}}"
matTooltipPosition="below"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,17 +38,20 @@ export class FormHeaderComponent {

constructor() {}

requestAction(action, data?: any) {
if (action === 'toggle-view') {
if (data === 'simple') {
requestAction(action) {
if (action.name === 'toggle-view') {
if (action.data === 'simple') {
this.formView = 'raw';
} else {
this.formView = 'simple';
}
data = this.formView;
action.data = this.formView;
this.formViewChange.emit();
}
this.actionRequested.emit({name: action, data: data})
if (action.callback) {
action.callback();
}
this.actionRequested.emit({name: action.name, data: action.data})
this.showActionsDropDown = false;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<lib-form-header
[data]="formData"
[title]="formData.id ? 'Edit Identity: ' : 'Create New Identity'"
[moreActions]="formData.moreActions"
[moreActions]="moreActions"
(actionRequested)="headerActionRequested($event)"
[(formView)]="formView"
></lib-form-header>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,21 +22,20 @@ import {
Output,
OnChanges,
SimpleChanges,
ViewChild,
ElementRef,
AfterViewInit, Inject
AfterViewInit,
Inject
} from '@angular/core';
import {ProjectableForm} from "../projectable-form.class";
import {SETTINGS_SERVICE, SettingsService} from "../../../services/settings.service";

import {isEmpty, isNil, forEach, delay, unset, keys, forOwn, cloneDeep, isEqual, set, result} from 'lodash';
import {isEmpty, isNil, forOwn, cloneDeep, set} from 'lodash';
import {ZITI_DATA_SERVICE, ZitiDataService} from "../../../services/ziti-data.service";
import {GrowlerService} from "../../messaging/growler.service";
import {GrowlerModel} from "../../messaging/growler.model";
import {Identity} from "../../../models/identity";
import { IdentityFormService } from './identity-form.service';
import {IDENTITY_EXTENSION_SERVICE, IdentityFormService} from './identity-form.service';
import {MatDialogRef} from "@angular/material/dialog";
import {IdentitiesPageService} from "../../../pages/identities/identities-page.service";
import {ExtensionService} from "../../extendable/extensions-noop.service";


@Component({
selector: 'lib-identity-form',
Expand Down Expand Up @@ -84,9 +83,10 @@ export class IdentityFormComponent extends ProjectableForm implements OnInit, On
public svc: IdentityFormService,
public identitiesService: IdentitiesPageService,
@Inject(ZITI_DATA_SERVICE) private zitiService: ZitiDataService,
growlerService: GrowlerService
growlerService: GrowlerService,
@Inject(IDENTITY_EXTENSION_SERVICE) extService: ExtensionService
) {
super(growlerService);
super(growlerService, extService);
this.identityRoleAttributes = [];
}

Expand All @@ -103,6 +103,7 @@ export class IdentityFormComponent extends ProjectableForm implements OnInit, On
this.getCertificateAuthorities();
this.initData = cloneDeep(this.formData);
this.loadTags();
this.extService.updateFormData(this.formData);
}

override ngAfterViewInit() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
limitations under the License.
*/

import {Injectable, Inject} from "@angular/core";
import {Injectable, Inject, InjectionToken} from "@angular/core";
import moment from 'moment';

import {isEmpty, unset, keys} from 'lodash';
Expand All @@ -24,6 +24,10 @@ import {GrowlerModel} from "../../messaging/growler.model";
import {Identity} from "../../../models/identity";
import {SETTINGS_SERVICE, SettingsService} from "../../../services/settings.service";


export const IDENTITY_EXTENSION_SERVICE = new InjectionToken<any>('IDENTITY_EXTENSION_SERVICE');


@Injectable({
providedIn: 'root'
})
Expand Down
Loading

0 comments on commit 79c5d9c

Please sign in to comment.