Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow ZAC extensions to define additional items for the "More Actions" drop down #324

Merged
merged 3 commits into from
Apr 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
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