http-request-manager 18.11.10 → 18.11.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -1,58 +1,17 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { inject, Injectable, APP_ID, Inject, InjectionToken, isDevMode, signal, effect, computed, Injector, Optional,
|
|
2
|
+
import { inject, Injectable, APP_ID, Inject, InjectionToken, isDevMode, signal, effect, computed, Injector, Optional, NgModule } from '@angular/core';
|
|
3
3
|
import { ComponentStore } from '@ngrx/component-store';
|
|
4
4
|
import { map, catchError, filter, tap, finalize, takeWhile, retry, startWith, mergeMap, takeUntil, concatMap, toArray, withLatestFrom, switchMap, delay, take, scan, distinctUntilChanged } from 'rxjs/operators';
|
|
5
5
|
import { HttpClient, HttpHeaders, HttpEventType, HttpHeaderResponse, HttpErrorResponse, HTTP_INTERCEPTORS } from '@angular/common/http';
|
|
6
6
|
import * as CryptoJS from 'crypto-js';
|
|
7
|
-
import { from, BehaviorSubject, EMPTY, throwError, defer, interval, timer, Subject, of, merge, Subscription, take as take$1, catchError as catchError$1
|
|
7
|
+
import { from, BehaviorSubject, EMPTY, throwError, defer, interval, timer, Subject, of, merge, Subscription, take as take$1, catchError as catchError$1 } from 'rxjs';
|
|
8
8
|
import { toObservable } from '@angular/core/rxjs-interop';
|
|
9
9
|
import { ToastMessageDisplayService, ToastDisplay, ToastColors, ToastMessageDisplayModule } from 'toast-message-display';
|
|
10
10
|
import Dexie from 'dexie';
|
|
11
11
|
import * as i1 from '@ngx-translate/core';
|
|
12
12
|
import { TranslateModule } from '@ngx-translate/core';
|
|
13
|
-
import * as i1$1 from '@angular/common';
|
|
14
13
|
import { CommonModule } from '@angular/common';
|
|
15
|
-
import
|
|
16
|
-
import { FormBuilder, Validators, FormsModule, ReactiveFormsModule } from '@angular/forms';
|
|
17
|
-
import { MatAutocompleteModule } from '@angular/material/autocomplete';
|
|
18
|
-
import * as i3 from '@angular/material/button';
|
|
19
|
-
import { MatButtonModule } from '@angular/material/button';
|
|
20
|
-
import * as i3$1 from '@angular/material/chips';
|
|
21
|
-
import { MatChipsModule } from '@angular/material/chips';
|
|
22
|
-
import * as i12 from '@angular/material/divider';
|
|
23
|
-
import { MatDividerModule } from '@angular/material/divider';
|
|
24
|
-
import * as i4 from '@angular/material/form-field';
|
|
25
|
-
import { MatFormFieldModule } from '@angular/material/form-field';
|
|
26
|
-
import * as i8 from '@angular/material/icon';
|
|
27
|
-
import { MatIconModule } from '@angular/material/icon';
|
|
28
|
-
import * as i13 from '@angular/material/input';
|
|
29
|
-
import { MatInputModule } from '@angular/material/input';
|
|
30
|
-
import * as i8$1 from '@angular/material/progress-bar';
|
|
31
|
-
import { MatProgressBarModule } from '@angular/material/progress-bar';
|
|
32
|
-
import * as i2 from '@angular/material/progress-spinner';
|
|
33
|
-
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
|
|
34
|
-
import * as i5 from '@angular/material/select';
|
|
35
|
-
import { MatSelectModule } from '@angular/material/select';
|
|
36
|
-
import { MatSidenavModule } from '@angular/material/sidenav';
|
|
37
|
-
import * as i11 from '@angular/material/slide-toggle';
|
|
38
|
-
import { MatSlideToggleModule } from '@angular/material/slide-toggle';
|
|
39
|
-
import * as i7 from '@angular/material/menu';
|
|
40
|
-
import { MatMenuModule } from '@angular/material/menu';
|
|
41
|
-
import * as i3$2 from '@angular/material/toolbar';
|
|
42
|
-
import { MatToolbarModule } from '@angular/material/toolbar';
|
|
43
|
-
import * as i9 from '@angular/material/table';
|
|
44
|
-
import { MatTableModule } from '@angular/material/table';
|
|
45
|
-
import * as i10 from '@angular/material/button-toggle';
|
|
46
|
-
import { MatButtonToggleModule } from '@angular/material/button-toggle';
|
|
47
|
-
import * as i9$1 from '@angular/material/datepicker';
|
|
48
|
-
import { MatDatepickerModule } from '@angular/material/datepicker';
|
|
49
|
-
import * as i6 from '@angular/material/core';
|
|
50
|
-
import { MatNativeDateModule } from '@angular/material/core';
|
|
51
|
-
import * as i16 from '@angular/material/card';
|
|
52
|
-
import { MatCardModule } from '@angular/material/card';
|
|
53
|
-
import * as i1$2 from '@angular/material/tabs';
|
|
54
|
-
import { MatTabsModule } from '@angular/material/tabs';
|
|
55
|
-
import { DataSource } from '@angular/cdk/collections';
|
|
14
|
+
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
|
56
15
|
|
|
57
16
|
var StorageType;
|
|
58
17
|
(function (StorageType) {
|
|
@@ -7278,227 +7237,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
7278
7237
|
args: [PROXY_CONFIG]
|
|
7279
7238
|
}] }] });
|
|
7280
7239
|
|
|
7281
|
-
class DownloadFileComponent {
|
|
7282
|
-
constructor() {
|
|
7283
|
-
this.subscriptions = new Subscription();
|
|
7284
|
-
this.displayError = 3; // seconds
|
|
7285
|
-
this.diameter = 32;
|
|
7286
|
-
this.mode = 'determinate';
|
|
7287
|
-
this.isPending = false;
|
|
7288
|
-
this.active = false;
|
|
7289
|
-
this.disabled = false;
|
|
7290
|
-
this.error = new EventEmitter();
|
|
7291
|
-
this._progress = 0;
|
|
7292
|
-
this._hasError = false;
|
|
7293
|
-
this.errorTimerActive = false;
|
|
7294
|
-
}
|
|
7295
|
-
set progress(value) {
|
|
7296
|
-
this._progress = value ?? 0;
|
|
7297
|
-
}
|
|
7298
|
-
get progress() {
|
|
7299
|
-
return this._progress;
|
|
7300
|
-
}
|
|
7301
|
-
set hasError(value) {
|
|
7302
|
-
this._hasError = !!value;
|
|
7303
|
-
if (this._hasError && !this.errorTimerActive) {
|
|
7304
|
-
this.errorTimerActive = true;
|
|
7305
|
-
this.active = false;
|
|
7306
|
-
this.error.emit();
|
|
7307
|
-
this.subscriptions.add(timer(this.displayError * 1000)
|
|
7308
|
-
.subscribe((err) => {
|
|
7309
|
-
this._hasError = false;
|
|
7310
|
-
this.errorTimerActive = false;
|
|
7311
|
-
}));
|
|
7312
|
-
}
|
|
7313
|
-
}
|
|
7314
|
-
get hasError() {
|
|
7315
|
-
return this._hasError;
|
|
7316
|
-
}
|
|
7317
|
-
ngOnInit() { }
|
|
7318
|
-
onAction() {
|
|
7319
|
-
this.isPending = false;
|
|
7320
|
-
if (this.event)
|
|
7321
|
-
this.event();
|
|
7322
|
-
}
|
|
7323
|
-
ngOnDestroy() {
|
|
7324
|
-
this.subscriptions.unsubscribe();
|
|
7325
|
-
}
|
|
7326
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: DownloadFileComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
7327
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: DownloadFileComponent, selector: "app-download-file", inputs: { event: "event", displayError: "displayError", diameter: "diameter", mode: "mode", isPending: "isPending", active: "active", disabled: "disabled", progress: "progress", hasError: "hasError" }, outputs: { error: "error" }, ngImport: i0, template: "@if (!isPending) {\n @if (hasError) {\n <div class=\"width center-txt\" style=\"margin-bottom: 4px;\">\n <mat-icon color=\"warn\" class=\"warn-icon\">warning</mat-icon>\n </div>\n } @else {\n @if (active) {\n <div class=\"container-obj\">\n <div class=\"centered-obj-div\">\n <mat-progress-spinner\n color=\"primary\"\n mode=\"indeterminate\"\n [diameter]=\"44\"\n ></mat-progress-spinner>\n </div>\n </div>\n } @else {\n <button data-tracking=\"export-btn\" mat-icon-button (click)=\"onAction()\" class=\"icon-button\" [disabled]=\"disabled\">\n <mat-icon class=\"custom-icon\">file_download</mat-icon>\n </button>\n }\n }\n} @else {\n @if ((progress > 0 && progress < 100)) {\n <div\n class=\"spinner-container\"\n >\n <div class=\"spinner-background\">\n {{progress}}%\n </div>\n <mat-progress-spinner\n color=\"primary\"\n [mode]=\"mode\"\n [value]=\"progress\"\n [diameter]=\"44\"\n ></mat-progress-spinner>\n </div>\n } @else {\n <div class=\"container-obj\">\n <div class=\"centered-obj-div\">\n <mat-progress-spinner\n color=\"primary\"\n mode=\"indeterminate\"\n [diameter]=\"44\"\n ></mat-progress-spinner>\n </div>\n </div>\n }\n}\n\n\n\n\n", styles: [":not(spinner-container).spinner-container{position:relative}:not(spinner-container).spinner-container .spinner-background{position:absolute;width:44px;height:44px;font-size:12px;line-height:32px;text-align:center;overflow:hidden;border-radius:50%;border:solid 5px whitesmoke}.center-txt{align-content:center;text-align:-webkit-center}.width{width:48px;height:48px}.icon-button{display:flex;align-items:center;justify-content:center;width:48px;height:48px;padding:0}.container-obj{display:flex;justify-content:center;align-items:center;width:48px;height:48px}.centered-obj-div{text-align:center}\n"], dependencies: [{ kind: "component", type: i8.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: i2.MatProgressSpinner, selector: "mat-progress-spinner, mat-spinner", inputs: ["color", "mode", "value", "diameter", "strokeWidth"], exportAs: ["matProgressSpinner"] }, { kind: "component", type: i3.MatIconButton, selector: "button[mat-icon-button]", exportAs: ["matButton"] }], encapsulation: i0.ViewEncapsulation.None }); }
|
|
7328
|
-
}
|
|
7329
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: DownloadFileComponent, decorators: [{
|
|
7330
|
-
type: Component,
|
|
7331
|
-
args: [{ selector: 'app-download-file', encapsulation: ViewEncapsulation.None, standalone: false, template: "@if (!isPending) {\n @if (hasError) {\n <div class=\"width center-txt\" style=\"margin-bottom: 4px;\">\n <mat-icon color=\"warn\" class=\"warn-icon\">warning</mat-icon>\n </div>\n } @else {\n @if (active) {\n <div class=\"container-obj\">\n <div class=\"centered-obj-div\">\n <mat-progress-spinner\n color=\"primary\"\n mode=\"indeterminate\"\n [diameter]=\"44\"\n ></mat-progress-spinner>\n </div>\n </div>\n } @else {\n <button data-tracking=\"export-btn\" mat-icon-button (click)=\"onAction()\" class=\"icon-button\" [disabled]=\"disabled\">\n <mat-icon class=\"custom-icon\">file_download</mat-icon>\n </button>\n }\n }\n} @else {\n @if ((progress > 0 && progress < 100)) {\n <div\n class=\"spinner-container\"\n >\n <div class=\"spinner-background\">\n {{progress}}%\n </div>\n <mat-progress-spinner\n color=\"primary\"\n [mode]=\"mode\"\n [value]=\"progress\"\n [diameter]=\"44\"\n ></mat-progress-spinner>\n </div>\n } @else {\n <div class=\"container-obj\">\n <div class=\"centered-obj-div\">\n <mat-progress-spinner\n color=\"primary\"\n mode=\"indeterminate\"\n [diameter]=\"44\"\n ></mat-progress-spinner>\n </div>\n </div>\n }\n}\n\n\n\n\n", styles: [":not(spinner-container).spinner-container{position:relative}:not(spinner-container).spinner-container .spinner-background{position:absolute;width:44px;height:44px;font-size:12px;line-height:32px;text-align:center;overflow:hidden;border-radius:50%;border:solid 5px whitesmoke}.center-txt{align-content:center;text-align:-webkit-center}.width{width:48px;height:48px}.icon-button{display:flex;align-items:center;justify-content:center;width:48px;height:48px;padding:0}.container-obj{display:flex;justify-content:center;align-items:center;width:48px;height:48px}.centered-obj-div{text-align:center}\n"] }]
|
|
7332
|
-
}], propDecorators: { event: [{
|
|
7333
|
-
type: Input
|
|
7334
|
-
}], displayError: [{
|
|
7335
|
-
type: Input
|
|
7336
|
-
}], diameter: [{
|
|
7337
|
-
type: Input
|
|
7338
|
-
}], mode: [{
|
|
7339
|
-
type: Input
|
|
7340
|
-
}], isPending: [{
|
|
7341
|
-
type: Input
|
|
7342
|
-
}], active: [{
|
|
7343
|
-
type: Input
|
|
7344
|
-
}], disabled: [{
|
|
7345
|
-
type: Input
|
|
7346
|
-
}], error: [{
|
|
7347
|
-
type: Output
|
|
7348
|
-
}], progress: [{
|
|
7349
|
-
type: Input
|
|
7350
|
-
}], hasError: [{
|
|
7351
|
-
type: Input
|
|
7352
|
-
}] } });
|
|
7353
|
-
|
|
7354
|
-
class DownloadLabels {
|
|
7355
|
-
constructor(error = '', action = '', icon = 'error') {
|
|
7356
|
-
this.error = error;
|
|
7357
|
-
this.action = action;
|
|
7358
|
-
this.icon = icon;
|
|
7359
|
-
}
|
|
7360
|
-
static adapt(item) {
|
|
7361
|
-
return new DownloadLabels(item?.error, item?.action, item?.icon);
|
|
7362
|
-
}
|
|
7363
|
-
}
|
|
7364
|
-
|
|
7365
|
-
class FileDownloaderComponent extends HTTPManagerService {
|
|
7366
|
-
set labels(value) {
|
|
7367
|
-
this._labels = (value) ? DownloadLabels.adapt(value) : DownloadLabels.adapt();
|
|
7368
|
-
}
|
|
7369
|
-
get labels() {
|
|
7370
|
-
return this._labels;
|
|
7371
|
-
}
|
|
7372
|
-
constructor() {
|
|
7373
|
-
super();
|
|
7374
|
-
this.delayError = 3;
|
|
7375
|
-
this.apiRequest = ApiRequest.adapt();
|
|
7376
|
-
this.displayErrorMessage = false;
|
|
7377
|
-
this._labels = DownloadLabels.adapt();
|
|
7378
|
-
this.active = false;
|
|
7379
|
-
this.subscription = new Subscription();
|
|
7380
|
-
this.completed = new EventEmitter();
|
|
7381
|
-
this.failed = new EventEmitter();
|
|
7382
|
-
this.disabled = false;
|
|
7383
|
-
}
|
|
7384
|
-
ngOnInit() {
|
|
7385
|
-
}
|
|
7386
|
-
onDownloadStreaming() {
|
|
7387
|
-
if (this.active)
|
|
7388
|
-
return;
|
|
7389
|
-
this.active = true;
|
|
7390
|
-
return this.downloadRequest(this.apiRequest, [])
|
|
7391
|
-
.pipe(distinctUntilChanged(), catchError((err) => {
|
|
7392
|
-
this.onError(err.message);
|
|
7393
|
-
this.active = false;
|
|
7394
|
-
this.failed.emit(err);
|
|
7395
|
-
return throwError(() => err);
|
|
7396
|
-
}), finalize(() => {
|
|
7397
|
-
if (!this.active)
|
|
7398
|
-
return;
|
|
7399
|
-
this.active = false;
|
|
7400
|
-
this.completed.emit();
|
|
7401
|
-
}))
|
|
7402
|
-
.subscribe();
|
|
7403
|
-
}
|
|
7404
|
-
onError(message) {
|
|
7405
|
-
if (!message || !this.displayErrorMessage)
|
|
7406
|
-
return;
|
|
7407
|
-
const display = ToastDisplay.adapt({
|
|
7408
|
-
message,
|
|
7409
|
-
action: 'Ok',
|
|
7410
|
-
color: ToastColors.ERROR,
|
|
7411
|
-
icon: 'error',
|
|
7412
|
-
});
|
|
7413
|
-
this.active = false;
|
|
7414
|
-
this.toastMessage.toastMessage(display);
|
|
7415
|
-
}
|
|
7416
|
-
OnDestroy() {
|
|
7417
|
-
this.subscription.unsubscribe();
|
|
7418
|
-
}
|
|
7419
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: FileDownloaderComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
7420
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: FileDownloaderComponent, selector: "app-file-downloader", inputs: { delayError: "delayError", apiRequest: "apiRequest", displayErrorMessage: "displayErrorMessage", saveFileAs: "saveFileAs", labels: "labels", disabled: "disabled" }, outputs: { completed: "completed", failed: "failed" }, usesInheritance: true, ngImport: i0, template: "<app-download-file\n [disabled]=\"disabled\"\n [displayError]=\"3\"\n [event]=\"onDownloadStreaming.bind(this)\"\n [isPending]=\"(isPending$ | async) || false\"\n [progress]=\"(progress$ | async)\"\n [hasError]=\"(error$ | async)\"\n (error)=\"onError(labels.error)\"\n [active]=\"active\"\n></app-download-file>\n", styles: [".snackBarInfo{background-color:#f44336;color:#fff}.mat-simple-snackbar>span{font-weight:700}.mat-simple-snackbar-action .mat-button .mat-button-wrapper{color:#fff}.cdk-overlay-pane>.mat-snack-bar-container{width:100%}.mat-snack-bar-container{max-width:100%!important;width:100%}\n"], dependencies: [{ kind: "component", type: DownloadFileComponent, selector: "app-download-file", inputs: ["event", "displayError", "diameter", "mode", "isPending", "active", "disabled", "progress", "hasError"], outputs: ["error"] }, { kind: "pipe", type: i1$1.AsyncPipe, name: "async" }], encapsulation: i0.ViewEncapsulation.None }); }
|
|
7421
|
-
}
|
|
7422
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: FileDownloaderComponent, decorators: [{
|
|
7423
|
-
type: Component,
|
|
7424
|
-
args: [{ selector: 'app-file-downloader', encapsulation: ViewEncapsulation.None, standalone: false, template: "<app-download-file\n [disabled]=\"disabled\"\n [displayError]=\"3\"\n [event]=\"onDownloadStreaming.bind(this)\"\n [isPending]=\"(isPending$ | async) || false\"\n [progress]=\"(progress$ | async)\"\n [hasError]=\"(error$ | async)\"\n (error)=\"onError(labels.error)\"\n [active]=\"active\"\n></app-download-file>\n", styles: [".snackBarInfo{background-color:#f44336;color:#fff}.mat-simple-snackbar>span{font-weight:700}.mat-simple-snackbar-action .mat-button .mat-button-wrapper{color:#fff}.cdk-overlay-pane>.mat-snack-bar-container{width:100%}.mat-snack-bar-container{max-width:100%!important;width:100%}\n"] }]
|
|
7425
|
-
}], ctorParameters: () => [], propDecorators: { delayError: [{
|
|
7426
|
-
type: Input
|
|
7427
|
-
}], apiRequest: [{
|
|
7428
|
-
type: Input
|
|
7429
|
-
}], displayErrorMessage: [{
|
|
7430
|
-
type: Input
|
|
7431
|
-
}], saveFileAs: [{
|
|
7432
|
-
type: Input
|
|
7433
|
-
}], labels: [{
|
|
7434
|
-
type: Input
|
|
7435
|
-
}], completed: [{
|
|
7436
|
-
type: Output
|
|
7437
|
-
}], failed: [{
|
|
7438
|
-
type: Output
|
|
7439
|
-
}], disabled: [{
|
|
7440
|
-
type: Input
|
|
7441
|
-
}] } });
|
|
7442
|
-
|
|
7443
|
-
class SpinnerComponent {
|
|
7444
|
-
constructor() {
|
|
7445
|
-
this.value = 0;
|
|
7446
|
-
}
|
|
7447
|
-
ngOnInit() {
|
|
7448
|
-
}
|
|
7449
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SpinnerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
7450
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: SpinnerComponent, selector: "app-spinner", inputs: { color: "color", diameter: "diameter", display: "display", mode: "mode", strokeWidth: "strokeWidth", value: "value" }, ngImport: i0, template: "<div class=\"spinner-background\">{{display}}</div>\n<mat-progress-spinner\n [color]=\"color\"\n [diameter]=\"diameter\"\n [mode]=\"mode || 'indeterminate'\"\n [strokeWidth]=\"strokeWidth\"\n [value]=\"value\">\n</mat-progress-spinner>\n", styles: [".example-h2{margin:24px 0}:not(spinner-container).spinner-container{position:relative}:not(spinner-container).spinner-container .spinner-background{position:absolute;width:80px;height:80px;line-height:80px;text-align:center;overflow:hidden;border-color:#673ab71f;border-radius:50%;border-style:solid;border-width:10px}\n"], dependencies: [{ kind: "component", type: i2.MatProgressSpinner, selector: "mat-progress-spinner, mat-spinner", inputs: ["color", "mode", "value", "diameter", "strokeWidth"], exportAs: ["matProgressSpinner"] }] }); }
|
|
7451
|
-
}
|
|
7452
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SpinnerComponent, decorators: [{
|
|
7453
|
-
type: Component,
|
|
7454
|
-
args: [{ selector: 'app-spinner', standalone: false, template: "<div class=\"spinner-background\">{{display}}</div>\n<mat-progress-spinner\n [color]=\"color\"\n [diameter]=\"diameter\"\n [mode]=\"mode || 'indeterminate'\"\n [strokeWidth]=\"strokeWidth\"\n [value]=\"value\">\n</mat-progress-spinner>\n", styles: [".example-h2{margin:24px 0}:not(spinner-container).spinner-container{position:relative}:not(spinner-container).spinner-container .spinner-background{position:absolute;width:80px;height:80px;line-height:80px;text-align:center;overflow:hidden;border-color:#673ab71f;border-radius:50%;border-style:solid;border-width:10px}\n"] }]
|
|
7455
|
-
}], ctorParameters: () => [], propDecorators: { color: [{
|
|
7456
|
-
type: Input
|
|
7457
|
-
}], diameter: [{
|
|
7458
|
-
type: Input
|
|
7459
|
-
}], display: [{
|
|
7460
|
-
type: Input
|
|
7461
|
-
}], mode: [{
|
|
7462
|
-
type: Input
|
|
7463
|
-
}], strokeWidth: [{
|
|
7464
|
-
type: Input
|
|
7465
|
-
}], value: [{
|
|
7466
|
-
type: Input
|
|
7467
|
-
}] } });
|
|
7468
|
-
|
|
7469
|
-
class FileDownloaderModule {
|
|
7470
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: FileDownloaderModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
|
|
7471
|
-
static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "18.2.14", ngImport: i0, type: FileDownloaderModule, declarations: [SpinnerComponent,
|
|
7472
|
-
DownloadFileComponent,
|
|
7473
|
-
FileDownloaderComponent], imports: [CommonModule,
|
|
7474
|
-
MatIconModule,
|
|
7475
|
-
MatProgressSpinnerModule,
|
|
7476
|
-
MatButtonModule], exports: [FileDownloaderComponent] }); }
|
|
7477
|
-
static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: FileDownloaderModule, imports: [CommonModule,
|
|
7478
|
-
MatIconModule,
|
|
7479
|
-
MatProgressSpinnerModule,
|
|
7480
|
-
MatButtonModule] }); }
|
|
7481
|
-
}
|
|
7482
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: FileDownloaderModule, decorators: [{
|
|
7483
|
-
type: NgModule,
|
|
7484
|
-
args: [{
|
|
7485
|
-
imports: [
|
|
7486
|
-
CommonModule,
|
|
7487
|
-
MatIconModule,
|
|
7488
|
-
MatProgressSpinnerModule,
|
|
7489
|
-
MatButtonModule,
|
|
7490
|
-
],
|
|
7491
|
-
declarations: [
|
|
7492
|
-
SpinnerComponent,
|
|
7493
|
-
DownloadFileComponent,
|
|
7494
|
-
FileDownloaderComponent,
|
|
7495
|
-
],
|
|
7496
|
-
exports: [
|
|
7497
|
-
FileDownloaderComponent
|
|
7498
|
-
]
|
|
7499
|
-
}]
|
|
7500
|
-
}] });
|
|
7501
|
-
|
|
7502
7240
|
class DisplayConfig {
|
|
7503
7241
|
constructor(type = 'snackbar', supportsMarkdown, stackable, queueBehavior, autoDismiss, width, height) {
|
|
7504
7242
|
this.type = type;
|
|
@@ -7705,3284 +7443,37 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
7705
7443
|
args: [{ providedIn: 'root' }]
|
|
7706
7444
|
}] });
|
|
7707
7445
|
|
|
7708
|
-
|
|
7709
|
-
|
|
7710
|
-
|
|
7711
|
-
|
|
7712
|
-
|
|
7713
|
-
|
|
7714
|
-
|
|
7715
|
-
|
|
7716
|
-
|
|
7717
|
-
}
|
|
7718
|
-
};
|
|
7719
|
-
|
|
7720
|
-
let ClientInfoMapper$2 = class ClientInfoMapper {
|
|
7721
|
-
constructor(id = 0, first_name = '', last_name = '', email = '') {
|
|
7722
|
-
this.id = id;
|
|
7723
|
-
this.first_name = first_name;
|
|
7724
|
-
this.last_name = last_name;
|
|
7725
|
-
this.email = email;
|
|
7726
|
-
}
|
|
7727
|
-
static adapt(item) {
|
|
7728
|
-
const first_name = (item?.name) ? item.name.split(' ')[0] : '';
|
|
7729
|
-
const last_name = (item?.name) ? item.name.split(' ')[1] : '';
|
|
7730
|
-
return new ClientInfoMapper(item?.id, first_name, last_name, item?.email);
|
|
7731
|
-
}
|
|
7732
|
-
};
|
|
7733
|
-
|
|
7734
|
-
let AIPrompt$2 = class AIPrompt {
|
|
7735
|
-
constructor(response = '') {
|
|
7736
|
-
this.response = response;
|
|
7737
|
-
}
|
|
7738
|
-
static adapt(item) {
|
|
7739
|
-
return new AIPrompt(item?.response);
|
|
7740
|
-
}
|
|
7741
|
-
};
|
|
7742
|
-
|
|
7743
|
-
class RequestManagerBasicDemoComponent {
|
|
7744
|
-
// Dynamic columns based on data structure
|
|
7745
|
-
getColumnsFromData(data) {
|
|
7746
|
-
if (!data || data.length === 0) {
|
|
7747
|
-
return [];
|
|
7748
|
-
}
|
|
7749
|
-
const firstRecord = data[0];
|
|
7750
|
-
if (!firstRecord || typeof firstRecord !== 'object') {
|
|
7751
|
-
return [];
|
|
7752
|
-
}
|
|
7753
|
-
// Extract all keys from the first record, excluding null/undefined
|
|
7754
|
-
return Object.keys(firstRecord).filter(key => firstRecord[key] !== null && firstRecord[key] !== undefined);
|
|
7755
|
-
}
|
|
7756
|
-
// Update displayed columns when data changes
|
|
7757
|
-
updateDisplayedColumns(data) {
|
|
7758
|
-
this.displayedColumns = this.getColumnsFromData(data);
|
|
7759
|
-
console.log('[DEMO] Updated columns:', this.displayedColumns);
|
|
7760
|
-
}
|
|
7761
|
-
// Helper to check if value is an object
|
|
7762
|
-
isObject(value) {
|
|
7763
|
-
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
7764
|
-
}
|
|
7765
|
-
get retry() {
|
|
7766
|
-
return this.requestForm.get('retry')?.value;
|
|
7767
|
-
}
|
|
7768
|
-
get headers() {
|
|
7769
|
-
return this.requestForm.get('headers');
|
|
7770
|
-
}
|
|
7771
|
-
get isValid() {
|
|
7772
|
-
this.requestForm.markAllAsTouched();
|
|
7773
|
-
return this.requestForm.valid;
|
|
7774
|
-
}
|
|
7775
|
-
constructor() {
|
|
7776
|
-
this.server = 'http://localhost:8080';
|
|
7777
|
-
this.displayedColumns = [];
|
|
7778
|
-
this.fb = inject(FormBuilder);
|
|
7779
|
-
this.toastMessage = inject(ToastMessageDisplayService);
|
|
7780
|
-
this.questionControl = this.fb.control("", [Validators.required]);
|
|
7781
|
-
this.httpManagerService = inject(HTTPManagerService);
|
|
7782
|
-
this.isPending$ = this.httpManagerService.isPending$;
|
|
7783
|
-
this.countdown$ = this.httpManagerService.countdown$;
|
|
7784
|
-
this.GET_error$ = new BehaviorSubject('');
|
|
7785
|
-
this.POST_error$ = new BehaviorSubject('');
|
|
7786
|
-
this.PUT_error$ = new BehaviorSubject('');
|
|
7787
|
-
this.DELETE_error$ = new BehaviorSubject('');
|
|
7788
|
-
this.STREAM_error$ = new BehaviorSubject('');
|
|
7789
|
-
this.STREAM_AI_error$ = new BehaviorSubject('');
|
|
7790
|
-
this.requestParams = {
|
|
7791
|
-
GET: ApiRequest.adapt(),
|
|
7792
|
-
POST: ApiRequest.adapt(),
|
|
7793
|
-
PUT: ApiRequest.adapt(),
|
|
7794
|
-
DELETE: ApiRequest.adapt(),
|
|
7795
|
-
STREAM: ApiRequest.adapt(),
|
|
7796
|
-
};
|
|
7797
|
-
this.streamTypes = [
|
|
7798
|
-
{ id: 'JSON', value: 'json' },
|
|
7799
|
-
{ id: 'NDJSON', value: 'ndjson' },
|
|
7800
|
-
{ id: 'AI Streaming', value: 'ai_streaming' },
|
|
7801
|
-
{ id: 'Event Stream', value: 'event_stream' },
|
|
7802
|
-
{ id: 'Auto', value: 'auto' },
|
|
7803
|
-
];
|
|
7804
|
-
this.streamType = 'Auto';
|
|
7805
|
-
this.downloadRequest = ApiRequest.adapt({
|
|
7806
|
-
server: 'assets/images',
|
|
7807
|
-
path: ['lego.png'],
|
|
7808
|
-
saveAs: 'john.jpg', // Optional
|
|
7809
|
-
headers: { 'Authorization': 'Bearer Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6InJ0c0ZULWItN0x1WTdEVlllU05LY0lKN1ZuYyJ9.eyJhdWQiOiI4ODhhMzFjZS01OWQzLTRhMTItYTU5Ni04ZDYyZjY0MWI1MDUiLCJpc3MiOiJodHRwczovL2xvZ2luLm1pY3Jvc29mdG9ubGluZS5jb20vNmY0MTE5ODItY2YyMy00ZTQ1LTk0NDktNGI2MDdiN2E4OGVjL3YyLjAiLCJpYXQiOjE3NjU4MzQxNDksIm5iZiI6MTc2NTgzNDE0OSwiZXhwIjoxNzY1ODM5NzQ2LCJhY2N0IjowLCJhaW8iOiJBV1FBbS84YUFBQUFVUEZOYUhSZkZ4ZVkwRWthcFBzdWZaNG1QYXV6YVRoODRRZUtQRzkrbEFIVE5XanVwQy9ZSjEwK2IrMktWVlJSSXZRNFpwS2xyWTFGd0xRZmtXOTNLbkRNckxSeEMzWTVOOGlQREZ4b1liZExucW1QL1N1ZE1pRW1Va05tTklSWSIsImF6cCI6Ijg4OGEzMWNlLTU5ZDMtNGExMi1hNTk2LThkNjJmNjQxYjUwNSIsImF6cGFjciI6IjAiLCJncm91cHMiOlsiZWE2ZDk5YjAtNDgyNC00MDU0LTk0MTQtZDNhOTZkZDA3MjRiIl0sIm5hbWUiOiJHVExDIEFwcCBTdXBlciBBZG1pbiIsIm9pZCI6IjI2NzUxY2I2LWNlMDEtNDMzMC05OTc0LWZjMzgxMjQ3YTEzYyIsInByZWZlcnJlZF91c2VybmFtZSI6Imd0bGMwMDNAZ3RsYy5jYSIsInJoIjoiMS5BUzRBZ2hsQmJ5UFBSVTZVU1V0Z2UzcUk3TTR4aW9qVFdSSktwWmFOWXZaQnRRVm1BYTR1QUEuIiwicm9sZXMiOlsiZ3RsYy5hY2wuc3VwZXJhZG1pbiJdLCJzY3AiOiJHVExDX0FwcGxpY2F0aW9uIiwic2lkIjoiMDAzZjVhZDktZmJmNi1jZTkyLTg3MjItMWEzNDExYjMzMDJiIiwic3ViIjoiS0RYQ3drVlhUbTRUUmcwaTkxbXZDZzgtQ29uLXpWbk5FQ2Y5LVg0dkpzUSIsInRpZCI6IjZmNDExOTgyLWNmMjMtNGU0NS05NDQ5LTRiNjA3YjdhODhlYyIsInVwbiI6Imd0bGMwMDNAZ3RsYy5jYSIsInV0aSI6InpBaTJoMmpVUUVHbVY0RTJXMWxqQUEiLCJ2ZXIiOiIyLjAiLCJ3aWRzIjpbImI3OWZiZjRkLTNlZjktNDY4OS04MTQzLTc2YjE5NGU4NTUwOSJdLCJ4bXNfZnRkIjoiUDJraW1ZM0tqc1BTRndxNHlZdmJ0bGhrSUk3d2tXNmFpcW1FQ1BaNEdja0JkWE5sWVhOMExXUnpiWE0ifQ.LzppoggMm27smSAy9SamtPN95vCzdELCAfhtOj5n_T_H6g9xCmNRLS9FaUFQMau6Qvl0lROKl7WDklTswLFkfxbIxCBWtXdL-LTqT5cDURSJAll8vC3zlN3Hg9pAFBUVZFRolt6Z7LvPdI3pvUOQs0yFwVzp9k6cLF8aemKdwKQrMX3XXua1MfBWZcqQ4WiBVNmKh8w6yQB35I4u5WqdFnu33nUGb-kvc18SOpoUfiJnlV-PudaEzFXdU3CjAaMEcuPFv5xLwWJKuhU73dNH4EyQDFMVGtcIHNnieOfiY_nK2_0-5DM6aI40UIRK6Bt-HmMQpnbhLps5y3ep6Z7RNw' } // Optional
|
|
7810
|
-
});
|
|
7811
|
-
// downloadRequest = ApiRequest.adapt({
|
|
7812
|
-
// server: 'oidc/ai/file'
|
|
7813
|
-
// })
|
|
7814
|
-
this.sampleClientData = {
|
|
7815
|
-
id: 0,
|
|
7816
|
-
name: "Old School Dates",
|
|
7817
|
-
domain: "osd.com",
|
|
7818
|
-
service: "osd",
|
|
7819
|
-
spiffe: "osd.com/osd",
|
|
7820
|
-
secret: "SMOPECXP-OS4P-USOG-X2II-3XMD1FQDR3IJX",
|
|
7821
|
-
created: 1693003138,
|
|
7822
|
-
modified: 1693003138,
|
|
7823
|
-
icon: "",
|
|
7824
|
-
imageFile: "",
|
|
7825
|
-
email: "wavecoders@gmail.com"
|
|
7826
|
-
};
|
|
7827
|
-
this.requestForm = this.fb.group({
|
|
7828
|
-
path: this.fb.control("ai/"),
|
|
7829
|
-
headers: this.fb.array([]),
|
|
7830
|
-
adapter: [null],
|
|
7831
|
-
mapper: [null],
|
|
7832
|
-
retry: this.fb.group({
|
|
7833
|
-
times: [3],
|
|
7834
|
-
delay: [3],
|
|
7835
|
-
}),
|
|
7836
|
-
polling: [3],
|
|
7837
|
-
});
|
|
7838
|
-
this.AIType = 0;
|
|
7839
|
-
this.sampleAdaptors = [
|
|
7840
|
-
{ label: "ClientInfo Basic", value: ClientInfo$2.adapt },
|
|
7841
|
-
{ label: "AI Prompt", value: AIPrompt$2.adapt },
|
|
7842
|
-
];
|
|
7843
|
-
this.sampleMappers = [
|
|
7844
|
-
{ label: "Mapper Basic", value: ClientInfoMapper$2.adapt },
|
|
7845
|
-
{ label: "AI Prompt", value: AIPrompt$2.adapt },
|
|
7846
|
-
];
|
|
7847
|
-
this.hasId = (arr) => {
|
|
7848
|
-
if (arr.length === 0)
|
|
7849
|
-
return false;
|
|
7850
|
-
return !isNaN(arr[arr.length - 1]);
|
|
7851
|
-
};
|
|
7852
|
-
this.props = (adapter) => {
|
|
7853
|
-
return (adapter) ? adapter() : null;
|
|
7854
|
-
};
|
|
7855
|
-
// server = `http://sample-endpoint/as/authorization.oauth2`
|
|
7856
|
-
this.arrayObjectsToObjects = (arr) => {
|
|
7857
|
-
return Array.isArray(arr) ? arr.reduce((obj, item) => Object.assign(obj, { [item.key]: item.value }), {}) : {};
|
|
7858
|
-
};
|
|
7859
|
-
}
|
|
7860
|
-
ngOnInit() {
|
|
7861
|
-
// const reqGet2 = ApiRequest.adapt({
|
|
7862
|
-
// server,
|
|
7863
|
-
// path: ['clients'],
|
|
7864
|
-
// headers: { authentication: "Bearer <KEY>" },
|
|
7865
|
-
// adapter: ClientInfo,
|
|
7866
|
-
// dataType: DataType.OBJECT,
|
|
7867
|
-
// // concurrent: false,
|
|
7868
|
-
// // polling: 3, //seconds
|
|
7869
|
-
// })
|
|
7870
|
-
// const req2 = [1024,1025,1026].map(item => {
|
|
7871
|
-
// return this.httpManagerService.getRequest<ClientInfo[]>(reqGet2, [item])
|
|
7872
|
-
// .pipe(
|
|
7873
|
-
// catchError(error => {
|
|
7874
|
-
// return throwError(() => new Error(error.error.message))
|
|
7875
|
-
// })
|
|
7876
|
-
// )
|
|
7877
|
-
// })
|
|
7878
|
-
// forkJoin(req2)
|
|
7879
|
-
// .subscribe(res => console.log(res))
|
|
7880
|
-
}
|
|
7881
|
-
onStreamType(type) {
|
|
7882
|
-
this.streamType = type;
|
|
7883
|
-
}
|
|
7884
|
-
addHeader() {
|
|
7885
|
-
const header = this.fb.group({
|
|
7886
|
-
key: ['', Validators.required],
|
|
7887
|
-
value: ['']
|
|
7888
|
-
});
|
|
7889
|
-
this.headers.push(header);
|
|
7890
|
-
}
|
|
7891
|
-
removeHeader(index) {
|
|
7892
|
-
this.headers.removeAt(index);
|
|
7893
|
-
}
|
|
7894
|
-
compileRequest() {
|
|
7895
|
-
const requestParams = this.requestForm.value;
|
|
7896
|
-
requestParams.headers = this.arrayObjectsToObjects(requestParams.headers || []);
|
|
7897
|
-
const pathReq = (requestParams.path === "") ? [] : (requestParams.path || "").split("/");
|
|
7898
|
-
if (!this.pollingState.checked)
|
|
7899
|
-
requestParams.polling = 0;
|
|
7900
|
-
if (!this.failedState.checked) {
|
|
7901
|
-
requestParams.retry = { times: 0, delay: 0 };
|
|
7902
|
-
}
|
|
7903
|
-
const apiOptions = ApiRequest.adapt(requestParams);
|
|
7904
|
-
apiOptions.path = [];
|
|
7905
|
-
apiOptions.server = this.server;
|
|
7906
|
-
apiOptions.adapter = this.adapter;
|
|
7907
|
-
apiOptions.mapper = this.mapper;
|
|
7908
|
-
return { apiOptions: apiOptions, path: pathReq };
|
|
7909
|
-
}
|
|
7910
|
-
onGetRequest() {
|
|
7911
|
-
if (!this.isValid)
|
|
7912
|
-
return;
|
|
7913
|
-
const reqParams = this.compileRequest();
|
|
7914
|
-
this.requestParams.GET = reqParams.apiOptions;
|
|
7915
|
-
this.GET$ = EMPTY; //Cancels Previous
|
|
7916
|
-
this.GET_error$.next('');
|
|
7917
|
-
this.GET$ = this.httpManagerService.getRequest({ ...reqParams.apiOptions }, reqParams.path)
|
|
7918
|
-
.pipe(
|
|
7919
|
-
// tap((data) => console.log("API GET response", data)),
|
|
7920
|
-
catchError(error => {
|
|
7921
|
-
return throwError(() => this.errorHandling(error, 'GET'));
|
|
7922
|
-
}));
|
|
7923
|
-
}
|
|
7924
|
-
onCreateRequest() {
|
|
7925
|
-
if (!this.isValid)
|
|
7926
|
-
return;
|
|
7927
|
-
const reqParams = this.compileRequest();
|
|
7928
|
-
this.requestParams.POST = reqParams.apiOptions;
|
|
7929
|
-
this.POST$ = EMPTY; //Cancels Previous
|
|
7930
|
-
this.POST_error$.next('');
|
|
7931
|
-
console.log("POST", this.sampleClientData);
|
|
7932
|
-
console.log("POST", reqParams.apiOptions);
|
|
7933
|
-
console.log("POST", reqParams.path);
|
|
7934
|
-
this.POST$ = this.httpManagerService.postRequest(this.sampleClientData, reqParams.apiOptions, reqParams.path)
|
|
7935
|
-
.pipe(
|
|
7936
|
-
// tap((data) => console.log("API POST response", data)),
|
|
7937
|
-
catchError(error => {
|
|
7938
|
-
return throwError(() => this.errorHandling(error, 'POST'));
|
|
7939
|
-
}));
|
|
7940
|
-
}
|
|
7941
|
-
onUpdateRequest() {
|
|
7942
|
-
if (!this.isValid)
|
|
7943
|
-
return;
|
|
7944
|
-
const reqParams = this.compileRequest();
|
|
7945
|
-
if (!this.hasId(reqParams.path)) {
|
|
7946
|
-
console.log("Missing ID");
|
|
7947
|
-
return;
|
|
7948
|
-
}
|
|
7949
|
-
this.sampleClientData.id = parseInt(reqParams.path[reqParams.path.length - 1]);
|
|
7950
|
-
this.requestParams.PUT = reqParams.apiOptions;
|
|
7951
|
-
this.PUT$ = EMPTY; //Cancels Previous
|
|
7952
|
-
this.PUT_error$.next('');
|
|
7953
|
-
this.PUT$ = this.httpManagerService.putRequest(this.sampleClientData, reqParams.apiOptions, reqParams.path)
|
|
7954
|
-
.pipe(
|
|
7955
|
-
// tap((data) => console.log("API PUT response", data)),
|
|
7956
|
-
catchError(error => {
|
|
7957
|
-
return throwError(() => this.errorHandling(error, 'PUT'));
|
|
7958
|
-
}));
|
|
7959
|
-
}
|
|
7960
|
-
onDeleteRequest() {
|
|
7961
|
-
if (!this.isValid)
|
|
7962
|
-
return;
|
|
7963
|
-
const reqParams = this.compileRequest();
|
|
7964
|
-
this.requestParams.DELETE = reqParams.apiOptions;
|
|
7965
|
-
if (!this.hasId(reqParams.path)) {
|
|
7966
|
-
console.log("Missing ID");
|
|
7967
|
-
return;
|
|
7968
|
-
}
|
|
7969
|
-
this.sampleClientData.id = parseInt(reqParams.path[reqParams.path.length - 1]);
|
|
7970
|
-
this.requestParams.DELETE = reqParams.apiOptions;
|
|
7971
|
-
this.DELETE$ = EMPTY; //Cancels Previous
|
|
7972
|
-
this.DELETE_error$.next('');
|
|
7973
|
-
this.DELETE$ = this.httpManagerService.deleteRequest(reqParams.apiOptions, reqParams.path)
|
|
7974
|
-
.pipe(
|
|
7975
|
-
// tap((data) => console.log("API DELETE response", data)),
|
|
7976
|
-
catchError(error => {
|
|
7977
|
-
return throwError(() => this.errorHandling(error, 'DELETE'));
|
|
7978
|
-
}));
|
|
7979
|
-
}
|
|
7980
|
-
onStreamPostRequest() {
|
|
7981
|
-
if (!this.isValid)
|
|
7982
|
-
return;
|
|
7983
|
-
const reqParams = this.compileRequest();
|
|
7984
|
-
let payload = {};
|
|
7985
|
-
let apiPath = reqParams.path;
|
|
7986
|
-
let apiOptions = reqParams.apiOptions;
|
|
7987
|
-
let responseMapper = (items) => items.response;
|
|
7988
|
-
if (this.AIType === 0) {
|
|
7989
|
-
// API request
|
|
7990
|
-
payload = { prompt: this.questionControl.value };
|
|
7991
|
-
}
|
|
7992
|
-
else {
|
|
7993
|
-
// Local Ollama request
|
|
7994
|
-
apiOptions.server = "api";
|
|
7995
|
-
apiPath = ["generate"];
|
|
7996
|
-
apiOptions.stream = true;
|
|
7997
|
-
apiOptions.streamType = this.streamType;
|
|
7998
|
-
payload = {
|
|
7999
|
-
model: "phi3:latest",
|
|
8000
|
-
prompt: this.questionControl.value,
|
|
8001
|
-
stream: true,
|
|
8002
|
-
};
|
|
8003
|
-
responseMapper = (items) => items.map((word) => word.response).flat().join('');
|
|
8004
|
-
}
|
|
8005
|
-
this.requestParams.STREAM = apiOptions;
|
|
8006
|
-
this.STREAM_AI$ = EMPTY;
|
|
8007
|
-
this.STREAM_AI_error$.next('');
|
|
8008
|
-
this.STREAM_AI$ = this.httpManagerService.postRequest(payload, apiOptions, apiPath).pipe(map(responseMapper), tap(() => this.questionControl.reset()), catchError(error => throwError(() => this.errorHandling(error, 'STREAM'))));
|
|
8009
|
-
}
|
|
8010
|
-
onStreamRequest() {
|
|
8011
|
-
if (!this.isValid)
|
|
8012
|
-
return;
|
|
8013
|
-
const reqParams = this.compileRequest();
|
|
8014
|
-
reqParams.apiOptions.stream = true;
|
|
8015
|
-
reqParams.apiOptions.streamType = StreamType.NDJSON;
|
|
8016
|
-
this.requestParams.GET = reqParams.apiOptions;
|
|
8017
|
-
this.STREAM$ = this.httpManagerService.getRequest(reqParams.apiOptions, reqParams.path)
|
|
8018
|
-
.pipe(tap((data) => {
|
|
8019
|
-
console.log("API STREAM response", data);
|
|
8020
|
-
if (data && data.length > 0) {
|
|
8021
|
-
this.updateDisplayedColumns(data);
|
|
8022
|
-
}
|
|
8023
|
-
}), catchError(error => {
|
|
8024
|
-
return throwError(() => this.errorHandling(error, 'STREAM'));
|
|
8025
|
-
}));
|
|
8026
|
-
}
|
|
8027
|
-
onDownloadCompleted() {
|
|
8028
|
-
const message = "Download Completed";
|
|
8029
|
-
const display = ToastDisplay.adapt({
|
|
8030
|
-
message,
|
|
8031
|
-
action: 'Ok',
|
|
8032
|
-
color: ToastColors.SUCCESS,
|
|
8033
|
-
icon: 'sentiment_satisfied_alt',
|
|
8034
|
-
});
|
|
8035
|
-
this.toastMessage.toastMessage(display);
|
|
8036
|
-
}
|
|
8037
|
-
onDownloadFailed(err) {
|
|
8038
|
-
const message = "Download Failed";
|
|
8039
|
-
const display = ToastDisplay.adapt({
|
|
8040
|
-
message,
|
|
8041
|
-
action: 'Ok',
|
|
8042
|
-
color: ToastColors.ERROR,
|
|
8043
|
-
icon: 'warning',
|
|
8044
|
-
});
|
|
8045
|
-
this.toastMessage.toastMessage(display);
|
|
8046
|
-
}
|
|
8047
|
-
errorHandling(err, type) {
|
|
8048
|
-
if (type === 'GET')
|
|
8049
|
-
this.GET_error$.next(err.message);
|
|
8050
|
-
if (type === 'POST')
|
|
8051
|
-
this.POST_error$.next(err.message);
|
|
8052
|
-
if (type === 'PUT')
|
|
8053
|
-
this.PUT_error$.next(err.message);
|
|
8054
|
-
if (type === 'DELETE')
|
|
8055
|
-
this.DELETE_error$.next(err.message);
|
|
8056
|
-
if (type === 'STREAM')
|
|
8057
|
-
this.STREAM_error$.next(err.message);
|
|
8058
|
-
}
|
|
8059
|
-
onSelectAIType(type) {
|
|
8060
|
-
this.AIType = type;
|
|
8061
|
-
}
|
|
8062
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: RequestManagerBasicDemoComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
8063
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: RequestManagerBasicDemoComponent, selector: "app-request-manager-basic-demo", providers: [HTTPManagerService], viewQueries: [{ propertyName: "failedState", first: true, predicate: ["failedState"], descendants: true, static: true }, { propertyName: "pollingState", first: true, predicate: ["pollingState"], descendants: true, static: true }], ngImport: i0, template: "<div style=\"margin: 2rem;\">\n\n <h2>\n HTTP Basic Request Manager Setup\n </h2>\n\n <div style=\"margin-bottom: 1rem; margin-top: 2rem;\">\n @if ((isPending$ | async)) {\n <mat-progress-bar mode=\"indeterminate\"\n ></mat-progress-bar>\n }\n @if (pollingState.checked && !(isPending$ | async)) {\n <mat-progress-bar mode=\"determinate\"\n [value]=\"(this.countdown$ | async)\"\n ></mat-progress-bar>\n }\n </div>\n\n <div style=\"margin-top: 2rem\">\n <div style=\"display: flex;\">\n <h2 style=\"flex:1\">GET Request</h2>\n <div>\n <button mat-raised-button (click)=\"onGetRequest()\" class=\"btn\">Request</button>\n </div>\n </div>\n\n @if ((GET_error$ | async); as get_error) {\n <div style=\"margin-top: .5rem;\">\n <mat-error>{{ get_error }}</mat-error>\n </div>\n }\n\n @if ((GET$ | async); as dataRecord) {\n <div style=\"margin-top: 1rem;\">\n <!-- <div [innerHTML]=\"(GET$ | async) | jsonv\"></div> -->\n {{ dataRecord | json }}\n </div>\n }\n\n </div>\n\n <div style=\"margin-top: 2rem\">\n <mat-divider></mat-divider>\n </div>\n\n <div style=\"margin-top: 2rem\">\n <div style=\"display: flex;\">\n <h2 style=\"flex:1\">POST Request</h2>\n <div>\n <button mat-raised-button (click)=\"onCreateRequest()\" class=\"btn\">Request</button>\n </div>\n </div>\n\n @if ((POST_error$ | async); as post_error) {\n <div style=\"margin-top: .5rem;\">\n <mat-error>{{ post_error }}</mat-error>\n </div>\n }\n\n @if ((POST$ | async); as dataRecord) {\n <div style=\"margin-top: 1rem;\">\n <!-- <div [innerHTML]=\"(POST$ | async) | jsonv\"></div> -->\n {{ dataRecord | json }}\n </div>\n }\n\n </div>\n\n <div style=\"margin-top: 2rem\">\n <mat-divider></mat-divider>\n </div>\n\n <div style=\"margin-top: 2rem\">\n <div style=\"display: flex;\">\n <h2 style=\"flex:1\">PUT Request</h2>\n <div>\n <button mat-raised-button (click)=\"onUpdateRequest()\" class=\"btn\">Request</button>\n </div>\n </div>\n\n @if ((PUT_error$ | async); as put_error) {\n <div style=\"margin-top: .5rem;\">\n <mat-error>{{ put_error }}</mat-error>\n </div>\n }\n\n <h3>Include Record ID in the RestPath</h3>\n\n @if ((PUT$ | async); as dataRecord) {\n <div style=\"margin-top: 1rem;\">\n <!-- <div [innerHTML]=\"(PUT$ | async) | jsonv\"></div> -->\n {{ dataRecord | json }}\n </div>\n }\n\n </div>\n\n <div style=\"margin-top: 2rem\">\n <mat-divider></mat-divider>\n </div>\n\n <div style=\"margin-top: 2rem\">\n <div style=\"display: flex;\">\n <h2 style=\"flex:1\">DELETE Request</h2>\n <div>\n <button mat-raised-button (click)=\"onDeleteRequest()\" class=\"btn\">Request</button>\n </div>\n </div>\n\n <h3>Include Record ID in the RestPath</h3>\n\n @if ((DELETE_error$ | async); as delete_error) {\n <div style=\"margin-top: .5rem;\">\n <mat-error>{{ delete_error }}</mat-error>\n </div>\n }\n\n @if ((DELETE$ | async); as dataRecord) {\n <div style=\"margin-top: 1rem;\">\n <!-- <div [innerHTML]=\"(DELETE$ | async) | jsonv\"></div> -->\n {{ dataRecord | json }}\n </div>\n }\n\n </div>\n\n <div style=\"margin-top: 2rem\">\n <mat-divider></mat-divider>\n </div>\n\n <div style=\"margin-top: 2rem\">\n <div style=\"display: flex;\">\n <h2 style=\"flex:1\">Streaming GET Request</h2>\n <div style=\"display: flex; gap: 1rem; align-items: center;\">\n <div style=\"display: flex; gap: 1rem; align-items: center;\">\n {{ streamType }}\n <button mat-icon-button [matMenuTriggerFor]=\"menu\">\n <mat-icon>data_usage</mat-icon>\n </button>\n </div>\n <mat-menu #menu=\"matMenu\">\n <button mat-menu-item *ngFor=\"let item of streamTypes\" (click)=\"onStreamType(item.id)\">{{ item.value }}</button>\n </mat-menu>\n <button mat-raised-button (click)=\"onStreamRequest()\" class=\"btn\">Request</button>\n </div>\n </div>\n\n <!-- <div *ngIf=\"(STREAM_error$ | async) as stream_error\" style=\"margin-top: .5rem;\">\n <mat-error>{{ stream_error }}</mat-error>\n </div> -->\n\n <div style=\"margin-top: 1rem;\">\n @if ((STREAM$ | async); as data) {\n <div class=\"container\">\n <table mat-table [dataSource]=\"data\" class=\"mat-elevation-z8\">\n\n <!-- Dynamic columns -->\n <ng-container *ngFor=\"let column of displayedColumns\" [matColumnDef]=\"column\">\n <th mat-header-cell *matHeaderCellDef> {{ column | titlecase }} </th>\n <td mat-cell *matCellDef=\"let element\">\n @if (isObject(element[column]); as objValue) {\n <pre style=\"margin: 0; font-size: 0.8em; white-space: pre-wrap;\">{{ objValue | json }}</pre>\n } @else {\n {{ element[column] }}\n }\n </td>\n </ng-container>\n\n <tr mat-header-row *matHeaderRowDef=\"displayedColumns\"></tr>\n <tr mat-row *matRowDef=\"let row; columns: displayedColumns;\"></tr>\n </table>\n\n <!-- Debug info -->\n <div style=\"margin-top: 1rem; font-size: 0.8em; color: #666;\">\n Columns: {{ displayedColumns.join(', ') }} | Records: {{ data.length }}\n </div>\n </div>\n }\n </div>\n\n</div>\n\n<div style=\"margin-top: 2rem\">\n <mat-divider></mat-divider>\n</div>\n\n<div style=\"margin-top: 2rem\">\n <div style=\"display: flex;\">\n <h2 style=\"flex:1; padding-top: .5rem;\">AI -@if (AIType === 1) {\n <span>STREAMING</span>\n } POST Request</h2>\n <div style=\"display: flex; gap: 1rem;\">\n <button mat-raised-button [matMenuTriggerFor]=\"menu\" style=\"min-width: 120px;\">\n <mat-icon>lan</mat-icon>\n @if (AIType === 0) {\n <span>Server</span>\n } @else {\n Local\n }\n </button>\n <mat-menu #menu=\"matMenu\">\n <button mat-menu-item (click)=\"onSelectAIType(0)\">Server</button>\n <button mat-menu-item (click)=\"onSelectAIType(1)\">Local</button>\n </mat-menu>\n <div>\n <button mat-raised-button (click)=\"onStreamPostRequest()\" class=\"btn\">Ask Me</button>\n </div>\n </div>\n </div>\n\n <div style=\"display: flex;\">\n <mat-form-field appearance=\"outline\" style=\"flex:1\">\n <mat-label>Ask me a Question</mat-label>\n <textarea matInput placeholder=\"Why is the sky blue?\" [formControl]=\"questionControl\"></textarea>\n </mat-form-field>\n </div>\n\n @if ((STREAM_AI_error$ | async); as stream_error) {\n <div style=\"margin-top: .5rem;\">\n <mat-error>{{ stream_error }}</mat-error>\n </div>\n }\n\n @if (AIType === 1) {\n <div style=\"color: red;\">\n You must have Ollama active and the 'phi3:latest' model to use this feature.\n </div>\n } @else {\n <span style=\"color: gray;\">\n Define the RestPath to the API endpoint that will handle the AI request.\n Use: 'ai/chat' for server\n </span>\n }\n\n <div>\n @if ((STREAM_AI$ | async); as data) {\n <div style=\"margin-top: 1rem; font-size: 1.2rem; border-radius: 1rem; border: black 1px solid; padding: 2rem;\">\n <p style=\"margin-bottom: .5rem; white-space:pre-wrap; line-height: 1.6rem;\">{{data}}</p>\n </div>\n }\n </div>\n\n</div>\n\n<div style=\"margin-top: 1.5rem; margin-bottom: 1rem; line-height: 1.5rem;\">\n <mat-divider></mat-divider>\n</div>\n\n<div>\n <div style=\"display: flex;\">\n <h2 style=\"flex:1; margin-bottom: 0; padding-top: .5rem; display: flex;\">\n <div>\n Download File\n </div>\n <div style=\"flex:1; margin-left: 1rem;\">\n <mat-slide-toggle #disable>\n @if (disable.checked) {\n <span>\n Enable\n </span>\n } @else {\n Disable\n }\n </mat-slide-toggle>\n </div>\n </h2>\n <div>\n <app-file-downloader\n [disabled]=\"disable.checked\"\n [delayError]=\"3\"\n [apiRequest]=\"downloadRequest\"\n (completed)=\"onDownloadCompleted()\"\n (failed)=\"onDownloadFailed($event)\"\n ></app-file-downloader>\n </div>\n </div>\n</div>\n\n</div>\n", styles: [".btn{min-width:120px}.mat-mdc-row .mat-mdc-cell{border-bottom:1px solid transparent;border-top:1px solid transparent;cursor:pointer}.mat-mdc-row:hover .mat-mdc-cell{border-color:currentColor;background-color:#f5f5f5}.container{height:400px;overflow:auto}.box{padding:10px;border:1px solid #ccc}\n"], dependencies: [{ kind: "directive", type: i1$1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i2$1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2$1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "component", type: i3.MatButton, selector: " button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button] ", exportAs: ["matButton"] }, { kind: "component", type: i3.MatIconButton, selector: "button[mat-icon-button]", exportAs: ["matButton"] }, { kind: "component", type: i4.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i4.MatLabel, selector: "mat-label" }, { kind: "directive", type: i4.MatError, selector: "mat-error, [matError]", inputs: ["id"] }, { kind: "component", type: i7.MatMenu, selector: "mat-menu", inputs: ["backdropClass", "aria-label", "aria-labelledby", "aria-describedby", "xPosition", "yPosition", "overlapTrigger", "hasBackdrop", "class", "classList"], outputs: ["closed", "close"], exportAs: ["matMenu"] }, { kind: "component", type: i7.MatMenuItem, selector: "[mat-menu-item]", inputs: ["role", "disabled", "disableRipple"], exportAs: ["matMenuItem"] }, { kind: "directive", type: i7.MatMenuTrigger, selector: "[mat-menu-trigger-for], [matMenuTriggerFor]", inputs: ["mat-menu-trigger-for", "matMenuTriggerFor", "matMenuTriggerData", "matMenuTriggerRestoreFocus"], outputs: ["menuOpened", "onMenuOpen", "menuClosed", "onMenuClose"], exportAs: ["matMenuTrigger"] }, { kind: "component", type: i8.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: i9.MatTable, selector: "mat-table, table[mat-table]", exportAs: ["matTable"] }, { kind: "directive", type: i9.MatHeaderCellDef, selector: "[matHeaderCellDef]" }, { kind: "directive", type: i9.MatHeaderRowDef, selector: "[matHeaderRowDef]", inputs: ["matHeaderRowDef", "matHeaderRowDefSticky"] }, { kind: "directive", type: i9.MatColumnDef, selector: "[matColumnDef]", inputs: ["matColumnDef"] }, { kind: "directive", type: i9.MatCellDef, selector: "[matCellDef]" }, { kind: "directive", type: i9.MatRowDef, selector: "[matRowDef]", inputs: ["matRowDefColumns", "matRowDefWhen"] }, { kind: "directive", type: i9.MatHeaderCell, selector: "mat-header-cell, th[mat-header-cell]" }, { kind: "directive", type: i9.MatCell, selector: "mat-cell, td[mat-cell]" }, { kind: "component", type: i9.MatHeaderRow, selector: "mat-header-row, tr[mat-header-row]", exportAs: ["matHeaderRow"] }, { kind: "component", type: i9.MatRow, selector: "mat-row, tr[mat-row]", exportAs: ["matRow"] }, { kind: "component", type: i8$1.MatProgressBar, selector: "mat-progress-bar", inputs: ["color", "value", "bufferValue", "mode"], outputs: ["animationEnd"], exportAs: ["matProgressBar"] }, { kind: "component", type: i11.MatSlideToggle, selector: "mat-slide-toggle", inputs: ["name", "id", "labelPosition", "aria-label", "aria-labelledby", "aria-describedby", "required", "color", "disabled", "disableRipple", "tabIndex", "checked", "hideIcon", "disabledInteractive"], outputs: ["change", "toggleChange"], exportAs: ["matSlideToggle"] }, { kind: "component", type: i12.MatDivider, selector: "mat-divider", inputs: ["vertical", "inset"] }, { kind: "directive", type: i13.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly"], exportAs: ["matInput"] }, { kind: "component", type: FileDownloaderComponent, selector: "app-file-downloader", inputs: ["delayError", "apiRequest", "displayErrorMessage", "saveFileAs", "labels", "disabled"], outputs: ["completed", "failed"] }, { kind: "pipe", type: i1$1.AsyncPipe, name: "async" }, { kind: "pipe", type: i1$1.JsonPipe, name: "json" }, { kind: "pipe", type: i1$1.TitleCasePipe, name: "titlecase" }] }); }
|
|
8064
|
-
}
|
|
8065
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: RequestManagerBasicDemoComponent, decorators: [{
|
|
8066
|
-
type: Component,
|
|
8067
|
-
args: [{ selector: 'app-request-manager-basic-demo', providers: [HTTPManagerService], standalone: false, template: "<div style=\"margin: 2rem;\">\n\n <h2>\n HTTP Basic Request Manager Setup\n </h2>\n\n <div style=\"margin-bottom: 1rem; margin-top: 2rem;\">\n @if ((isPending$ | async)) {\n <mat-progress-bar mode=\"indeterminate\"\n ></mat-progress-bar>\n }\n @if (pollingState.checked && !(isPending$ | async)) {\n <mat-progress-bar mode=\"determinate\"\n [value]=\"(this.countdown$ | async)\"\n ></mat-progress-bar>\n }\n </div>\n\n <div style=\"margin-top: 2rem\">\n <div style=\"display: flex;\">\n <h2 style=\"flex:1\">GET Request</h2>\n <div>\n <button mat-raised-button (click)=\"onGetRequest()\" class=\"btn\">Request</button>\n </div>\n </div>\n\n @if ((GET_error$ | async); as get_error) {\n <div style=\"margin-top: .5rem;\">\n <mat-error>{{ get_error }}</mat-error>\n </div>\n }\n\n @if ((GET$ | async); as dataRecord) {\n <div style=\"margin-top: 1rem;\">\n <!-- <div [innerHTML]=\"(GET$ | async) | jsonv\"></div> -->\n {{ dataRecord | json }}\n </div>\n }\n\n </div>\n\n <div style=\"margin-top: 2rem\">\n <mat-divider></mat-divider>\n </div>\n\n <div style=\"margin-top: 2rem\">\n <div style=\"display: flex;\">\n <h2 style=\"flex:1\">POST Request</h2>\n <div>\n <button mat-raised-button (click)=\"onCreateRequest()\" class=\"btn\">Request</button>\n </div>\n </div>\n\n @if ((POST_error$ | async); as post_error) {\n <div style=\"margin-top: .5rem;\">\n <mat-error>{{ post_error }}</mat-error>\n </div>\n }\n\n @if ((POST$ | async); as dataRecord) {\n <div style=\"margin-top: 1rem;\">\n <!-- <div [innerHTML]=\"(POST$ | async) | jsonv\"></div> -->\n {{ dataRecord | json }}\n </div>\n }\n\n </div>\n\n <div style=\"margin-top: 2rem\">\n <mat-divider></mat-divider>\n </div>\n\n <div style=\"margin-top: 2rem\">\n <div style=\"display: flex;\">\n <h2 style=\"flex:1\">PUT Request</h2>\n <div>\n <button mat-raised-button (click)=\"onUpdateRequest()\" class=\"btn\">Request</button>\n </div>\n </div>\n\n @if ((PUT_error$ | async); as put_error) {\n <div style=\"margin-top: .5rem;\">\n <mat-error>{{ put_error }}</mat-error>\n </div>\n }\n\n <h3>Include Record ID in the RestPath</h3>\n\n @if ((PUT$ | async); as dataRecord) {\n <div style=\"margin-top: 1rem;\">\n <!-- <div [innerHTML]=\"(PUT$ | async) | jsonv\"></div> -->\n {{ dataRecord | json }}\n </div>\n }\n\n </div>\n\n <div style=\"margin-top: 2rem\">\n <mat-divider></mat-divider>\n </div>\n\n <div style=\"margin-top: 2rem\">\n <div style=\"display: flex;\">\n <h2 style=\"flex:1\">DELETE Request</h2>\n <div>\n <button mat-raised-button (click)=\"onDeleteRequest()\" class=\"btn\">Request</button>\n </div>\n </div>\n\n <h3>Include Record ID in the RestPath</h3>\n\n @if ((DELETE_error$ | async); as delete_error) {\n <div style=\"margin-top: .5rem;\">\n <mat-error>{{ delete_error }}</mat-error>\n </div>\n }\n\n @if ((DELETE$ | async); as dataRecord) {\n <div style=\"margin-top: 1rem;\">\n <!-- <div [innerHTML]=\"(DELETE$ | async) | jsonv\"></div> -->\n {{ dataRecord | json }}\n </div>\n }\n\n </div>\n\n <div style=\"margin-top: 2rem\">\n <mat-divider></mat-divider>\n </div>\n\n <div style=\"margin-top: 2rem\">\n <div style=\"display: flex;\">\n <h2 style=\"flex:1\">Streaming GET Request</h2>\n <div style=\"display: flex; gap: 1rem; align-items: center;\">\n <div style=\"display: flex; gap: 1rem; align-items: center;\">\n {{ streamType }}\n <button mat-icon-button [matMenuTriggerFor]=\"menu\">\n <mat-icon>data_usage</mat-icon>\n </button>\n </div>\n <mat-menu #menu=\"matMenu\">\n <button mat-menu-item *ngFor=\"let item of streamTypes\" (click)=\"onStreamType(item.id)\">{{ item.value }}</button>\n </mat-menu>\n <button mat-raised-button (click)=\"onStreamRequest()\" class=\"btn\">Request</button>\n </div>\n </div>\n\n <!-- <div *ngIf=\"(STREAM_error$ | async) as stream_error\" style=\"margin-top: .5rem;\">\n <mat-error>{{ stream_error }}</mat-error>\n </div> -->\n\n <div style=\"margin-top: 1rem;\">\n @if ((STREAM$ | async); as data) {\n <div class=\"container\">\n <table mat-table [dataSource]=\"data\" class=\"mat-elevation-z8\">\n\n <!-- Dynamic columns -->\n <ng-container *ngFor=\"let column of displayedColumns\" [matColumnDef]=\"column\">\n <th mat-header-cell *matHeaderCellDef> {{ column | titlecase }} </th>\n <td mat-cell *matCellDef=\"let element\">\n @if (isObject(element[column]); as objValue) {\n <pre style=\"margin: 0; font-size: 0.8em; white-space: pre-wrap;\">{{ objValue | json }}</pre>\n } @else {\n {{ element[column] }}\n }\n </td>\n </ng-container>\n\n <tr mat-header-row *matHeaderRowDef=\"displayedColumns\"></tr>\n <tr mat-row *matRowDef=\"let row; columns: displayedColumns;\"></tr>\n </table>\n\n <!-- Debug info -->\n <div style=\"margin-top: 1rem; font-size: 0.8em; color: #666;\">\n Columns: {{ displayedColumns.join(', ') }} | Records: {{ data.length }}\n </div>\n </div>\n }\n </div>\n\n</div>\n\n<div style=\"margin-top: 2rem\">\n <mat-divider></mat-divider>\n</div>\n\n<div style=\"margin-top: 2rem\">\n <div style=\"display: flex;\">\n <h2 style=\"flex:1; padding-top: .5rem;\">AI -@if (AIType === 1) {\n <span>STREAMING</span>\n } POST Request</h2>\n <div style=\"display: flex; gap: 1rem;\">\n <button mat-raised-button [matMenuTriggerFor]=\"menu\" style=\"min-width: 120px;\">\n <mat-icon>lan</mat-icon>\n @if (AIType === 0) {\n <span>Server</span>\n } @else {\n Local\n }\n </button>\n <mat-menu #menu=\"matMenu\">\n <button mat-menu-item (click)=\"onSelectAIType(0)\">Server</button>\n <button mat-menu-item (click)=\"onSelectAIType(1)\">Local</button>\n </mat-menu>\n <div>\n <button mat-raised-button (click)=\"onStreamPostRequest()\" class=\"btn\">Ask Me</button>\n </div>\n </div>\n </div>\n\n <div style=\"display: flex;\">\n <mat-form-field appearance=\"outline\" style=\"flex:1\">\n <mat-label>Ask me a Question</mat-label>\n <textarea matInput placeholder=\"Why is the sky blue?\" [formControl]=\"questionControl\"></textarea>\n </mat-form-field>\n </div>\n\n @if ((STREAM_AI_error$ | async); as stream_error) {\n <div style=\"margin-top: .5rem;\">\n <mat-error>{{ stream_error }}</mat-error>\n </div>\n }\n\n @if (AIType === 1) {\n <div style=\"color: red;\">\n You must have Ollama active and the 'phi3:latest' model to use this feature.\n </div>\n } @else {\n <span style=\"color: gray;\">\n Define the RestPath to the API endpoint that will handle the AI request.\n Use: 'ai/chat' for server\n </span>\n }\n\n <div>\n @if ((STREAM_AI$ | async); as data) {\n <div style=\"margin-top: 1rem; font-size: 1.2rem; border-radius: 1rem; border: black 1px solid; padding: 2rem;\">\n <p style=\"margin-bottom: .5rem; white-space:pre-wrap; line-height: 1.6rem;\">{{data}}</p>\n </div>\n }\n </div>\n\n</div>\n\n<div style=\"margin-top: 1.5rem; margin-bottom: 1rem; line-height: 1.5rem;\">\n <mat-divider></mat-divider>\n</div>\n\n<div>\n <div style=\"display: flex;\">\n <h2 style=\"flex:1; margin-bottom: 0; padding-top: .5rem; display: flex;\">\n <div>\n Download File\n </div>\n <div style=\"flex:1; margin-left: 1rem;\">\n <mat-slide-toggle #disable>\n @if (disable.checked) {\n <span>\n Enable\n </span>\n } @else {\n Disable\n }\n </mat-slide-toggle>\n </div>\n </h2>\n <div>\n <app-file-downloader\n [disabled]=\"disable.checked\"\n [delayError]=\"3\"\n [apiRequest]=\"downloadRequest\"\n (completed)=\"onDownloadCompleted()\"\n (failed)=\"onDownloadFailed($event)\"\n ></app-file-downloader>\n </div>\n </div>\n</div>\n\n</div>\n", styles: [".btn{min-width:120px}.mat-mdc-row .mat-mdc-cell{border-bottom:1px solid transparent;border-top:1px solid transparent;cursor:pointer}.mat-mdc-row:hover .mat-mdc-cell{border-color:currentColor;background-color:#f5f5f5}.container{height:400px;overflow:auto}.box{padding:10px;border:1px solid #ccc}\n"] }]
|
|
8068
|
-
}], ctorParameters: () => [], propDecorators: { failedState: [{
|
|
8069
|
-
type: ViewChild,
|
|
8070
|
-
args: ["failedState", { static: true }]
|
|
8071
|
-
}], pollingState: [{
|
|
8072
|
-
type: ViewChild,
|
|
8073
|
-
args: ["pollingState", { static: true }]
|
|
8074
|
-
}] } });
|
|
8075
|
-
|
|
8076
|
-
let ClientInfo$1 = class ClientInfo {
|
|
8077
|
-
constructor(domain = '', service = '', id = 0, name = '') {
|
|
8078
|
-
this.domain = domain;
|
|
8079
|
-
this.service = service;
|
|
8080
|
-
this.id = id;
|
|
8081
|
-
this.name = name;
|
|
8082
|
-
}
|
|
8083
|
-
static adapt(item) {
|
|
8084
|
-
return new ClientInfo(item?.domain, item?.service, item?.id, (item?.first_name || item?.last_name) ? `${item?.first_name} ${item?.last_name}` : '');
|
|
8085
|
-
}
|
|
8086
|
-
};
|
|
8087
|
-
|
|
8088
|
-
let ClientInfoMapper$1 = class ClientInfoMapper {
|
|
8089
|
-
constructor(id = 0, first_name = '', last_name = '', email = '') {
|
|
8090
|
-
this.id = id;
|
|
8091
|
-
this.first_name = first_name;
|
|
8092
|
-
this.last_name = last_name;
|
|
8093
|
-
this.email = email;
|
|
8094
|
-
}
|
|
8095
|
-
static adapt(item) {
|
|
8096
|
-
const first_name = (item?.name) ? item.name.split(' ')[0] : '';
|
|
8097
|
-
const last_name = (item?.name) ? item.name.split(' ')[1] : '';
|
|
8098
|
-
return new ClientInfoMapper(item?.id, first_name, last_name, item?.email);
|
|
8099
|
-
}
|
|
8100
|
-
};
|
|
8101
|
-
|
|
8102
|
-
class StateManagerDemoService extends HTTPManagerStateService {
|
|
8103
|
-
constructor() {
|
|
8104
|
-
super(ApiRequest.adapt({
|
|
8105
|
-
server: "",
|
|
8106
|
-
path: [],
|
|
8107
|
-
headers: {},
|
|
8108
|
-
adapter: ClientInfo$1.adapt,
|
|
8109
|
-
mapper: ClientInfoMapper$1.adapt,
|
|
8110
|
-
stream: false,
|
|
8111
|
-
}), DataType.ARRAY, DatabaseStorage.adapt());
|
|
8112
|
-
}
|
|
8113
|
-
setAPIOptions(apiOptions, dataType, database) {
|
|
8114
|
-
this.setApiRequestOptions(apiOptions, dataType, database);
|
|
8115
|
-
}
|
|
8116
|
-
getClients() {
|
|
8117
|
-
// const headers = {
|
|
8118
|
-
// auth: "sample-auth-token"
|
|
8119
|
-
// }
|
|
8120
|
-
// const sampleOptions = RequestOptions.adapt({ path: ["id", 12], headers, sample: true })
|
|
8121
|
-
this.fetchRecords();
|
|
8122
|
-
}
|
|
8123
|
-
createClient(data) {
|
|
8124
|
-
// const headers = {
|
|
8125
|
-
// auth: "sample-auth-token"
|
|
8126
|
-
// }
|
|
8127
|
-
// const sampleOptions = RequestOptions.adapt({ path: ["id", 12], headers, sample: true })
|
|
8128
|
-
this.createRecord(data);
|
|
8129
|
-
}
|
|
8130
|
-
updateClient(data) {
|
|
8131
|
-
// const headers = {
|
|
8132
|
-
// auth: "sample-auth-token"
|
|
8133
|
-
// }
|
|
8134
|
-
data.id = 1031;
|
|
8135
|
-
const sampleOptions = RequestOptions.adapt({ path: [data.id] });
|
|
8136
|
-
this.updateRecord(data, sampleOptions);
|
|
8137
|
-
}
|
|
8138
|
-
deleteClient(data) {
|
|
8139
|
-
// const headers = {
|
|
8140
|
-
// auth: "sample-auth-token"
|
|
8141
|
-
// }
|
|
8142
|
-
data.id = 1031;
|
|
8143
|
-
const sampleOptions = RequestOptions.adapt({ path: [data.id] });
|
|
8144
|
-
this.deleteRecord(sampleOptions);
|
|
8145
|
-
}
|
|
8146
|
-
streamRequest() {
|
|
8147
|
-
console.log('[DEMO SERVICE] streamRequest called');
|
|
8148
|
-
const headers = {
|
|
8149
|
-
auth: "sample-auth-token"
|
|
8150
|
-
};
|
|
8151
|
-
console.log('[DEMO SERVICE] Calling fetchStream with headers:', headers);
|
|
8152
|
-
this.fetchStream();
|
|
8153
|
-
}
|
|
8154
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: StateManagerDemoService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
8155
|
-
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: StateManagerDemoService }); }
|
|
8156
|
-
}
|
|
8157
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: StateManagerDemoService, decorators: [{
|
|
8158
|
-
type: Injectable
|
|
8159
|
-
}], ctorParameters: () => [] });
|
|
8160
|
-
|
|
8161
|
-
let AIPrompt$1 = class AIPrompt {
|
|
8162
|
-
constructor(response = '') {
|
|
8163
|
-
this.response = response;
|
|
8164
|
-
}
|
|
8165
|
-
static adapt(item) {
|
|
8166
|
-
return new AIPrompt(item?.response);
|
|
8167
|
-
}
|
|
8168
|
-
};
|
|
8169
|
-
|
|
8170
|
-
class RequestManagerStateDemoComponent {
|
|
8171
|
-
// Dynamic columns based on data structure
|
|
8172
|
-
getColumnsFromData(data) {
|
|
8173
|
-
if (!data || data.length === 0) {
|
|
8174
|
-
return [];
|
|
8175
|
-
}
|
|
8176
|
-
const firstRecord = data[0];
|
|
8177
|
-
if (!firstRecord || typeof firstRecord !== 'object') {
|
|
8178
|
-
return [];
|
|
8179
|
-
}
|
|
8180
|
-
// Extract all keys from the first record, excluding null/undefined
|
|
8181
|
-
return Object.keys(firstRecord).filter(key => firstRecord[key] !== null && firstRecord[key] !== undefined);
|
|
8182
|
-
}
|
|
8183
|
-
// Update displayed columns when data changes
|
|
8184
|
-
updateDisplayedColumns(data) {
|
|
8185
|
-
this.displayedColumns = this.getColumnsFromData(data);
|
|
8186
|
-
console.log('[STATE DEMO] Updated columns:', this.displayedColumns);
|
|
8187
|
-
}
|
|
8188
|
-
// Helper to check if value is an object
|
|
8189
|
-
isObject(value) {
|
|
8190
|
-
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
8191
|
-
}
|
|
8192
|
-
// Helper to safely get data length
|
|
8193
|
-
getDataLength(data) {
|
|
8194
|
-
if (Array.isArray(data)) {
|
|
8195
|
-
return data.length;
|
|
8196
|
-
}
|
|
8197
|
-
return 0;
|
|
8198
|
-
}
|
|
8199
|
-
get dataObservable$() {
|
|
8200
|
-
switch (this.requestType) {
|
|
8201
|
-
case 'GET':
|
|
8202
|
-
return this.GET$;
|
|
8203
|
-
case 'PUT':
|
|
8204
|
-
return this.PUT$;
|
|
8205
|
-
case 'POST':
|
|
8206
|
-
return this.POST$;
|
|
8207
|
-
case 'DELETE':
|
|
8208
|
-
return this.DELETE$;
|
|
8209
|
-
case 'STREAM':
|
|
8210
|
-
return this.STREAM;
|
|
8211
|
-
case 'STREAM_AI':
|
|
8212
|
-
return this.STREAM_AI;
|
|
8213
|
-
default:
|
|
8214
|
-
return this.GET$;
|
|
8215
|
-
break;
|
|
8216
|
-
}
|
|
8217
|
-
}
|
|
8218
|
-
get hasChanged() {
|
|
8219
|
-
return this.requestForm.dirty || this.requestForm.untouched;
|
|
8220
|
-
}
|
|
8221
|
-
get dataType() {
|
|
8222
|
-
return this.requestForm.get("datatype")?.value;
|
|
8223
|
-
}
|
|
8224
|
-
get database() {
|
|
8225
|
-
return this.requestForm.get("database")?.value;
|
|
8226
|
-
}
|
|
8227
|
-
get retry() {
|
|
8228
|
-
return this.requestForm.get("retry")?.value;
|
|
8229
|
-
}
|
|
8230
|
-
get headers() {
|
|
8231
|
-
return this.requestForm.get('headers');
|
|
8232
|
-
}
|
|
8233
|
-
get isValid() {
|
|
8234
|
-
this.requestForm.markAllAsTouched();
|
|
8235
|
-
return this.requestForm.valid;
|
|
8236
|
-
}
|
|
8237
|
-
constructor() {
|
|
8238
|
-
this.server = 'http://localhost:8080';
|
|
8239
|
-
this.stateManagerDemoService = inject(StateManagerDemoService);
|
|
8240
|
-
this.displayedColumns = [];
|
|
8241
|
-
this.fb = inject(FormBuilder);
|
|
8242
|
-
this.streamTypes = [
|
|
8243
|
-
{ id: 'JSON', value: 'json' },
|
|
8244
|
-
{ id: 'NDJSON', value: 'ndjson' },
|
|
8245
|
-
{ id: 'AI Streaming', value: 'ai_streaming' },
|
|
8246
|
-
{ id: 'Event Stream', value: 'event_stream' },
|
|
8247
|
-
{ id: 'Auto', value: 'auto' },
|
|
8248
|
-
];
|
|
8249
|
-
this.streamType = 'Auto';
|
|
8250
|
-
this.httpManagerService = inject(HTTPManagerService);
|
|
8251
|
-
this.isPending$ = this.stateManagerDemoService.isPending$;
|
|
8252
|
-
this.error$ = this.stateManagerDemoService.error$;
|
|
8253
|
-
this.countdown$ = this.httpManagerService.countdown$;
|
|
8254
|
-
this.GET_error$ = new BehaviorSubject('');
|
|
8255
|
-
this.POST_error$ = new BehaviorSubject('');
|
|
8256
|
-
this.PUT_error$ = new BehaviorSubject('');
|
|
8257
|
-
this.DELETE_error$ = new BehaviorSubject('');
|
|
8258
|
-
this.STREAM_error$ = new BehaviorSubject('');
|
|
8259
|
-
this.STREAM_AI_error$ = new BehaviorSubject('');
|
|
8260
|
-
this.GET$ = new BehaviorSubject(null);
|
|
8261
|
-
this.POST$ = new BehaviorSubject(null);
|
|
8262
|
-
this.PUT$ = new BehaviorSubject(null);
|
|
8263
|
-
this.DELETE$ = new BehaviorSubject(null);
|
|
8264
|
-
this.STREAM = new BehaviorSubject(null);
|
|
8265
|
-
this.STREAM$ = this.STREAM.asObservable();
|
|
8266
|
-
this.STREAM_AI = new BehaviorSubject([]);
|
|
8267
|
-
this.STREAM_AI$ = this.STREAM_AI.asObservable()
|
|
8268
|
-
.pipe(map$1((items) => (items) ? items.map((item) => item.response) : []), map$1((items) => items.join('\n').trim()));
|
|
8269
|
-
this.questionControl = this.fb.control("", [Validators.required]);
|
|
8270
|
-
this.requestType = '';
|
|
8271
|
-
this.prompts = [];
|
|
8272
|
-
this.AIType = 0;
|
|
8273
|
-
this.sampleClientData = {
|
|
8274
|
-
id: 0,
|
|
8275
|
-
name: "Old School Dates",
|
|
8276
|
-
domain: "osd.com",
|
|
8277
|
-
service: "osd",
|
|
8278
|
-
spiffe: "osd.com/osd",
|
|
8279
|
-
secret: "SMOPECXP-OS4P-USOG-X2II-3XMD1FQDR3IJX",
|
|
8280
|
-
created: 1693003138,
|
|
8281
|
-
modified: 1693003138,
|
|
8282
|
-
icon: "",
|
|
8283
|
-
imageFile: "",
|
|
8284
|
-
};
|
|
8285
|
-
this.selectedRecord = this.fb.control(null);
|
|
8286
|
-
this.requestForm = this.fb.group({
|
|
8287
|
-
datatype: this.fb.control('ARRAY'),
|
|
8288
|
-
path: this.fb.control("ai/"),
|
|
8289
|
-
headers: this.fb.array([]),
|
|
8290
|
-
adapter: [null],
|
|
8291
|
-
mapper: [null],
|
|
8292
|
-
retry: this.fb.group({
|
|
8293
|
-
times: [3],
|
|
8294
|
-
delay: [3],
|
|
8295
|
-
}),
|
|
8296
|
-
polling: [3],
|
|
8297
|
-
database: this.fb.group({
|
|
8298
|
-
table: [''],
|
|
8299
|
-
expiresIn: ['1m'],
|
|
8300
|
-
})
|
|
8301
|
-
});
|
|
8302
|
-
this.sampleAdaptors = [
|
|
8303
|
-
{ label: "ClientInfo Basic", value: ClientInfo$1.adapt },
|
|
8304
|
-
{ label: "AI Prompt", value: AIPrompt$1.adapt },
|
|
8305
|
-
];
|
|
8306
|
-
this.sampleMappers = [
|
|
8307
|
-
{ label: "Mapper Basic", value: ClientInfoMapper$1.adapt },
|
|
8308
|
-
{ label: "AI Prompt", value: AIPrompt$1.adapt },
|
|
8309
|
-
];
|
|
8310
|
-
// server = `http://sample-endpoint/as/authorization.oauth2`
|
|
8311
|
-
this.arrayObjectsToObjects = (arr) => {
|
|
8312
|
-
return Array.isArray(arr) ? arr.reduce((obj, item) => Object.assign(obj, { [item.key]: item.value }), {}) : {};
|
|
8313
|
-
};
|
|
8314
|
-
this.props = (adapter) => {
|
|
8315
|
-
return (adapter) ? adapter() : null;
|
|
7446
|
+
class HttpRequestManagerModule {
|
|
7447
|
+
static forRoot(config = ConfigOptions.adapt()) {
|
|
7448
|
+
return {
|
|
7449
|
+
ngModule: HttpRequestManagerModule,
|
|
7450
|
+
providers: [
|
|
7451
|
+
{ provide: CONFIG_SETTINGS_TOKEN, useValue: ConfigOptions.adapt(config) },
|
|
7452
|
+
HTTPManagerService,
|
|
7453
|
+
LocalStorageManagerService, // all services that need access to config
|
|
7454
|
+
],
|
|
8316
7455
|
};
|
|
8317
7456
|
}
|
|
8318
|
-
|
|
8319
|
-
|
|
8320
|
-
|
|
8321
|
-
|
|
8322
|
-
|
|
8323
|
-
|
|
8324
|
-
|
|
8325
|
-
|
|
8326
|
-
|
|
8327
|
-
.
|
|
8328
|
-
|
|
8329
|
-
|
|
8330
|
-
|
|
8331
|
-
|
|
8332
|
-
|
|
8333
|
-
|
|
8334
|
-
|
|
8335
|
-
|
|
8336
|
-
|
|
8337
|
-
|
|
8338
|
-
case 'PUT':
|
|
8339
|
-
console.log('[COMPONENT] Updating PUT$ with data:', data);
|
|
8340
|
-
this.PUT$.next(data);
|
|
8341
|
-
break;
|
|
8342
|
-
case 'POST':
|
|
8343
|
-
console.log('[COMPONENT] Updating POST$ with data:', data);
|
|
8344
|
-
this.POST$.next(data);
|
|
8345
|
-
break;
|
|
8346
|
-
case 'DELETE':
|
|
8347
|
-
console.log('[COMPONENT] Updating DELETE$ with data:', data);
|
|
8348
|
-
this.DELETE$.next(data);
|
|
8349
|
-
break;
|
|
8350
|
-
case 'STREAM':
|
|
8351
|
-
console.log('[COMPONENT] Updating STREAM$ with data:', data);
|
|
8352
|
-
this.STREAM.next(data);
|
|
8353
|
-
// Update table columns dynamically based on streaming data
|
|
8354
|
-
if (data && Array.isArray(data) && data.length > 0) {
|
|
8355
|
-
this.updateDisplayedColumns(data);
|
|
8356
|
-
}
|
|
8357
|
-
break;
|
|
8358
|
-
case 'STREAM_AI':
|
|
8359
|
-
console.log('[COMPONENT] Updating STREAM_AI$ with data:', data);
|
|
8360
|
-
this.STREAM_AI.next(data);
|
|
8361
|
-
break;
|
|
8362
|
-
default:
|
|
8363
|
-
console.log('[COMPONENT] No requestType set, ignoring data');
|
|
8364
|
-
break;
|
|
8365
|
-
}
|
|
8366
|
-
})).subscribe();
|
|
8367
|
-
}
|
|
8368
|
-
onStreamType(type) {
|
|
8369
|
-
this.streamType = type;
|
|
8370
|
-
}
|
|
8371
|
-
addHeader() {
|
|
8372
|
-
const header = this.fb.group({
|
|
8373
|
-
key: ['', Validators.required],
|
|
8374
|
-
value: ['']
|
|
8375
|
-
});
|
|
8376
|
-
this.headers.push(header);
|
|
8377
|
-
}
|
|
8378
|
-
removeHeader(index) {
|
|
8379
|
-
this.headers.removeAt(index);
|
|
8380
|
-
}
|
|
8381
|
-
compileRequest() {
|
|
8382
|
-
const requestParams = this.requestForm.value;
|
|
8383
|
-
requestParams.headers = this.arrayObjectsToObjects(requestParams.headers || []);
|
|
8384
|
-
const pathReq = (requestParams.path === "") ? [] : (requestParams.path || "").split("/");
|
|
8385
|
-
if (!this.pollingState.checked)
|
|
8386
|
-
requestParams.polling = 0;
|
|
8387
|
-
if (!this.failedState.checked) {
|
|
8388
|
-
requestParams.retry = { times: 0, delay: 0 };
|
|
8389
|
-
}
|
|
8390
|
-
const currentOptions = ApiRequest.adapt(requestParams);
|
|
8391
|
-
currentOptions.path = [];
|
|
8392
|
-
currentOptions.server = this.server;
|
|
8393
|
-
currentOptions.adapter = this.adapter;
|
|
8394
|
-
currentOptions.mapper = this.mapper;
|
|
8395
|
-
const apiOptions = ApiRequest.adapt({ ...currentOptions, path: pathReq });
|
|
8396
|
-
return { apiOptions: apiOptions, path: pathReq };
|
|
8397
|
-
}
|
|
8398
|
-
onSetStateOptions() {
|
|
8399
|
-
if (!this.isValid)
|
|
8400
|
-
return;
|
|
8401
|
-
const reqParams = this.compileRequest();
|
|
8402
|
-
const db = DatabaseStorage.adapt(this.database);
|
|
8403
|
-
const type = this.dataType === "ARRAY" ? DataType.ARRAY : DataType.OBJECT;
|
|
8404
|
-
this.stateManagerDemoService.setAPIOptions(reqParams.apiOptions, type, db);
|
|
8405
|
-
this.requestForm.markAsPristine();
|
|
8406
|
-
}
|
|
8407
|
-
onClearRecords() {
|
|
8408
|
-
this.stateManagerDemoService.clearRecords();
|
|
8409
|
-
}
|
|
8410
|
-
onGetRequest() {
|
|
8411
|
-
this.requestType = 'GET';
|
|
8412
|
-
this.stateManagerDemoService.getClients();
|
|
8413
|
-
}
|
|
8414
|
-
onCreateRequest() {
|
|
8415
|
-
this.requestType = 'POST';
|
|
8416
|
-
this.stateManagerDemoService.createClient(this.sampleClientData);
|
|
8417
|
-
}
|
|
8418
|
-
onUpdateRequest() {
|
|
8419
|
-
this.requestType = 'PUT';
|
|
8420
|
-
this.stateManagerDemoService.updateClient(this.sampleClientData);
|
|
8421
|
-
}
|
|
8422
|
-
onDeleteRequest() {
|
|
8423
|
-
this.requestType = 'DELETE';
|
|
8424
|
-
this.stateManagerDemoService.deleteClient(this.sampleClientData);
|
|
8425
|
-
}
|
|
8426
|
-
onStreamRequest() {
|
|
8427
|
-
console.log('[COMPONENT] onStreamRequest called');
|
|
8428
|
-
if (!this.isValid) {
|
|
8429
|
-
console.log('[COMPONENT] Form invalid, aborting');
|
|
8430
|
-
return;
|
|
8431
|
-
}
|
|
8432
|
-
console.log('[COMPONENT] Compiling request...');
|
|
8433
|
-
const reqParams = this.compileRequest();
|
|
8434
|
-
console.log('[COMPONENT] Setting stream options:', reqParams.apiOptions);
|
|
8435
|
-
reqParams.apiOptions.stream = true;
|
|
8436
|
-
reqParams.apiOptions.streamType = this.streamType;
|
|
8437
|
-
this.requestType = 'STREAM';
|
|
8438
|
-
console.log('[COMPONENT] Calling streamRequest...');
|
|
8439
|
-
this.stateManagerDemoService.streamRequest();
|
|
8440
|
-
}
|
|
8441
|
-
errorHandling(err, type) {
|
|
8442
|
-
console.log(err, type);
|
|
8443
|
-
if (type === 'GET')
|
|
8444
|
-
this.GET_error$.next(err.message);
|
|
8445
|
-
if (type === 'POST')
|
|
8446
|
-
this.POST_error$.next(err.message);
|
|
8447
|
-
if (type === 'PUT')
|
|
8448
|
-
this.PUT_error$.next(err.message);
|
|
8449
|
-
if (type === 'DELETE')
|
|
8450
|
-
this.DELETE_error$.next(err.message);
|
|
8451
|
-
if (type === 'STREAM')
|
|
8452
|
-
this.STREAM_error$.next(err.message);
|
|
8453
|
-
if (type === 'STREAM_AI')
|
|
8454
|
-
this.STREAM_AI_error$.next(err.message);
|
|
8455
|
-
}
|
|
8456
|
-
onSelectAIType(type) {
|
|
8457
|
-
this.AIType = type;
|
|
8458
|
-
}
|
|
8459
|
-
onClearHistory() {
|
|
8460
|
-
this.prompts = [];
|
|
8461
|
-
}
|
|
8462
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: RequestManagerStateDemoComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
8463
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: RequestManagerStateDemoComponent, selector: "app-request-manager-state-demo", inputs: { server: "server", adapter: "adapter", mapper: "mapper" }, providers: [StateManagerDemoService, HTTPManagerService], viewQueries: [{ propertyName: "failedState", first: true, predicate: ["failedState"], descendants: true, static: true }, { propertyName: "pollingState", first: true, predicate: ["pollingState"], descendants: true, static: true }], ngImport: i0, template: "<div style=\"margin: 2rem;\">\n\n <h2>\n HTTP Request State Manager\n </h2>\n\n <div [formGroup]=\"requestForm\" style=\"margin-top: 2rem;\">\n <div style=\"display: flex; gap: .5rem\">\n <mat-form-field appearance=\"outline\">\n <mat-label>State Data Type</mat-label>\n <mat-select formControlName=\"datatype\">\n <mat-option value=\"ARRAY\">Array</mat-option>\n <mat-option value=\"OBJECT\">Object</mat-option>\n </mat-select>\n </mat-form-field>\n </div>\n <div style=\"display: flex; gap: .5rem\">\n <mat-form-field appearance=\"outline\">\n <mat-label>Adapter (Model)</mat-label>\n <mat-select formControlName=\"adapter\" #adapterSelect>\n <mat-option>None</mat-option>\n @for (adapter of sampleAdaptors; track adapter) {\n <mat-option [value]=\"adapter.value\">\n {{adapter.label}}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Mapper (Model)</mat-label>\n <mat-select formControlName=\"mapper\" #mapperSelect>\n <mat-option>None</mat-option>\n @for (mapper of sampleMappers; track mapper) {\n <mat-option [value]=\"mapper.value\">\n {{mapper.label}}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n </div>\n\n @if (adapterSelect.value || mapperSelect.value) {\n <div style=\"display: flex; margin-bottom: 2rem;\">\n <div style=\"flex:1\" class=\"box\">\n <h3>Adapter (Incoming)</h3>\n @if (adapterSelect.value) {\n <div>\n {{ props(adapterSelect.value) | json }}\n </div>\n } @else {\n No Transformation\n }\n </div>\n <div style=\"flex:1\" class=\"box\">\n <h3>Mapper (Outgoing)</h3>\n @if (mapperSelect.value) {\n <div>\n {{ props(mapperSelect.value) | json }}\n </div>\n } @else {\n No Transformation\n }\n </div>\n </div>\n }\n\n <div>\n <mat-form-field appearance=\"outline\">\n <mat-label>RestPath (/ delimited)</mat-label>\n <input matInput placeholder=\"clients/list\" formControlName=\"path\">\n </mat-form-field>\n </div>\n <div>\n <div formArrayName=\"headers\">\n @for (task of headers.controls; track task; let i = $index) {\n <div [formGroupName]=\"i\">\n <div style=\"display: flex; gap: .5rem\">\n <mat-form-field appearance=\"outline\">\n <mat-label>Key</mat-label>\n <input matInput placeholder=\"authentication\" formControlName=\"key\">\n </mat-form-field>\n <mat-form-field appearance=\"outline\" style=\"flex:1\">\n <mat-label>Value</mat-label>\n <input matInput placeholder=\"sample\" formControlName=\"value\">\n </mat-form-field>\n <div style=\"margin-top: .5rem;\">\n <button mat-icon-button (click)=\"removeHeader(i)\">\n <mat-icon>close</mat-icon>\n </button>\n </div>\n </div>\n </div>\n }\n </div>\n <button mat-stroked-button (click)=\"addHeader()\">Add Header</button>\n </div>\n <div style=\"margin-top: 2rem; display: flex; flex-direction:column; gap: 1rem;\">\n <div>\n <mat-slide-toggle #failedState>Retry on Failed</mat-slide-toggle>\n @if (failedState.checked) {\n <div style=\"display: flex; gap: .5rem; margin-top: 1rem;\" formGroupName=\"retry\">\n <mat-form-field appearance=\"outline\">\n <mat-label>#of Times</mat-label>\n <input matInput placeholder=\"3\" formControlName=\"times\" value=\"3\">\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Delay Until Next</mat-label>\n <input matInput placeholder=\"3\" formControlName=\"delay\" value=\"3\">\n </mat-form-field>\n </div>\n }\n </div>\n <div>\n <mat-slide-toggle #pollingState>Polling</mat-slide-toggle>\n @if (pollingState.checked) {\n <div>\n <mat-form-field appearance=\"outline\" style=\"margin-top: 1rem\">\n <mat-label>#of Seconds</mat-label>\n <input matInput placeholder=\"3\" formControlName=\"polling\" value=\"3\">\n </mat-form-field>\n </div>\n }\n </div>\n <div>\n <mat-slide-toggle #DBState>Database Storage</mat-slide-toggle>\n @if (DBState.checked) {\n <div style=\"display: flex; gap: .5rem; margin-top: 1rem\" formGroupName=\"database\">\n <mat-form-field appearance=\"outline\">\n <mat-label>Table Name</mat-label>\n <input matInput placeholder=\"table\" formControlName=\"table\" value=\"\">\n </mat-form-field>\n <div>\n <mat-form-field appearance=\"outline\">\n <mat-label>Expires In</mat-label>\n <mat-select formControlName=\"expiresIn\">\n <mat-option value=\"1m\">One Minute</mat-option>\n <mat-option value=\"1h\">One Hour</mat-option>\n <mat-option value=\"1d\">One Day</mat-option>\n </mat-select>\n </mat-form-field>\n </div>\n </div>\n }\n </div>\n <div style=\"margin-top: 1rem; display: flex;\">\n <span style=\"flex:1\"></span>\n <button mat-stroked-button (click)=\"onSetStateOptions()\" [disabled]=\"!hasChanged\">\n Set API Request Options\n </button>\n </div>\n <div>\n @if ((error$ | async); as error) {\n <mat-error>\n {{ error }}\n </mat-error>\n }\n </div>\n </div>\n </div>\n\n <div style=\"margin-bottom: 1rem; margin-top: 2rem;\">\n @if ((isPending$ | async)) {\n <mat-progress-bar mode=\"indeterminate\"\n ></mat-progress-bar>\n }\n @if (pollingState.checked) {\n <div>\n @if (!(isPending$ | async) && (this.countdown$ | async) || -1 > 0) {\n <mat-progress-bar mode=\"determinate\"\n [value]=\"(this.countdown$ | async)\"\n ></mat-progress-bar>\n }\n </div>\n }\n\n </div>\n\n @if ((GET$ | async); as data) {\n <div style=\"margin-top: 2rem\">\n <div style=\"display: flex;\">\n <h2 style=\"flex:1;\">CURRENT STATE ({{ dataType }})</h2>\n </div>\n @if (data.length > 1) {\n <div>\n <mat-form-field appearance=\"outline\">\n <mat-label>Records</mat-label>\n <mat-select [formControl]=\"selectedRecord\" [disabled]=\"data === null && data?.length === 0\">\n <mat-option [value]=\"\">None</mat-option>\n @for (item of data; track item) {\n <mat-option [value]=\"item\">\n {{item.name || (item.first_name) | titlecase}}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n </div>\n }\n @if ((dataObservable$ | async); as dataRecord) {\n <div>\n @if ((selectedRecord$ | async); as record) {\n <div>\n {{ record | json }}\n </div>\n } @else {\n No Record Selected from State\n }\n </div>\n }\n <div style=\"margin-top: 1rem;\">\n @if (data !== null && data?.length > 0) {\n <span>\n State contains a Total of {{ data.length }} Records\n </span>\n } @else {\n No Records\n }\n <div style=\"display: flex; gap: .5rem; text-align: end; justify-content: flex-end;\">\n <button class=\"btn\" mat-stroked-button (click)=\"onClearRecords()\">Clear</button>\n </div>\n </div>\n </div>\n }\n\n\n\n\n <div style=\"margin-top: 1rem\">\n <mat-divider></mat-divider>\n </div>\n\n <div style=\"margin-top: 2rem\">\n <div style=\"display: flex;\">\n <h2 style=\"flex:1\">GET Request ({{ dataType }})</h2>\n <div>\n <button mat-raised-button (click)=\"onGetRequest()\" [disabled]=\"hasChanged\" class=\"btn\">Request</button>\n </div>\n </div>\n\n @if ((GET_error$ | async); as get_error) {\n <div style=\"margin-top: .5rem;\">\n <mat-error>{{ get_error }}</mat-error>\n </div>\n }\n\n <div style=\"margin-top: 1rem;\">\n <!-- <div [innerHTML]=\"(GET$ | async) | jsonv\"></div> -->\n @if ((GET$ | async); as getData) {\n <div>{{ getData | json }}</div>\n }\n </div>\n\n </div>\n\n <div style=\"margin-top: 2rem\">\n <mat-divider></mat-divider>\n </div>\n\n <div style=\"margin-top: 2rem\">\n <div style=\"display: flex;\">\n <h2 style=\"flex:1\">POST Request</h2>\n <div>\n <button mat-raised-button (click)=\"onCreateRequest()\" [disabled]=\"hasChanged\" class=\"btn\">Request</button>\n </div>\n </div>\n\n @if ((POST_error$ | async); as post_error) {\n <div style=\"margin-top: .5rem;\">\n <mat-error>{{ post_error }}</mat-error>\n </div>\n }\n\n <div style=\"margin-top: 1rem;\">\n <!-- <div [innerHTML]=\"(POST$ | async) | jsonv\"></div> -->\n @if ((POST$ | async); as postData) {\n <div>{{ postData | json }}</div>\n }\n </div>\n\n </div>\n\n <div style=\"margin-top: 2rem\">\n <mat-divider></mat-divider>\n </div>\n\n <div style=\"margin-top: 2rem\">\n <div style=\"display: flex;\">\n <h2 style=\"flex:1\">PUT Request</h2>\n <div>\n <button mat-raised-button (click)=\"onUpdateRequest()\" [disabled]=\"hasChanged\" class=\"btn\">Request</button>\n </div>\n </div>\n\n @if ((PUT_error$ | async); as put_error) {\n <div style=\"margin-top: .5rem;\">\n <mat-error>{{ put_error }}</mat-error>\n </div>\n }\n\n <div style=\"margin-top: 1rem;\">\n <!-- <div [innerHTML]=\"(PUT$ | async) | jsonv\"></div> -->\n @if ((PUT$ | async); as putData) {\n <div>{{ putData | json }}</div>\n }\n </div>\n\n </div>\n\n <div style=\"margin-top: 2rem\">\n <mat-divider></mat-divider>\n </div>\n\n <div style=\"margin-top: 2rem\">\n <div style=\"display: flex;\">\n <h2 style=\"flex:1\">DELETE Request</h2>\n <div>\n <button mat-raised-button (click)=\"onDeleteRequest()\" [disabled]=\"hasChanged\" class=\"btn\">Request</button>\n </div>\n </div>\n\n @if ((DELETE_error$ | async); as delete_error) {\n <div style=\"margin-top: .5rem;\">\n <mat-error>{{ delete_error }}</mat-error>\n </div>\n }\n\n <div style=\"margin-top: 1rem;\">\n <!-- <div [innerHTML]=\"(DELETE$ | async) | jsonv\"></div> -->\n @if ((DELETE$ | async); as deleteData) {\n <div>{{ deleteData | json }}</div>\n }\n </div>\n\n </div>\n\n <div style=\"margin-top: 2rem\">\n <mat-divider></mat-divider>\n </div>\n\n <div style=\"margin-top: 2rem\">\n <div style=\"display: flex;\">\n <h2 style=\"flex:1\">Streaming GET Request</h2>\n <div style=\"display: flex; gap: 1rem; align-items: center;\">\n <div style=\"display: flex; gap: 1rem; align-items: center;\">\n {{ streamType }}\n <button mat-icon-button [matMenuTriggerFor]=\"menu\">\n <mat-icon>data_usage</mat-icon>\n </button>\n </div>\n <mat-menu #menu=\"matMenu\">\n <button mat-menu-item *ngFor=\"let item of streamTypes\" (click)=\"onStreamType(item.id)\">{{ item.value }}</button>\n </mat-menu>\n <button mat-raised-button (click)=\"onStreamRequest()\" class=\"btn\" [disabled]=\"hasChanged\">Request</button>\n </div>\n </div>\n\n @if ((STREAM_error$ | async); as stream_error) {\n <div style=\"margin-top: .5rem;\">\n <mat-error>{{ stream_error }}</mat-error>\n </div>\n }\n\n <div style=\"margin-top: 1rem;\">\n @if ((STREAM$ | async); as data) {\n <div class=\"container\">\n <table mat-table [dataSource]=\"data\" class=\"mat-elevation-z8\">\n\n <!-- Dynamic columns -->\n <ng-container *ngFor=\"let column of displayedColumns\" [matColumnDef]=\"column\">\n <th mat-header-cell *matHeaderCellDef> {{ column | titlecase }} </th>\n <td mat-cell *matCellDef=\"let element\">\n @if (isObject(element[column]); as objValue) {\n <pre style=\"margin: 0; font-size: 0.8em; white-space: pre-wrap;\">{{ objValue | json }}</pre>\n } @else {\n {{ element[column] }}\n }\n </td>\n </ng-container>\n\n <tr mat-header-row *matHeaderRowDef=\"displayedColumns\"></tr>\n <tr mat-row *matRowDef=\"let row; columns: displayedColumns;\"></tr>\n </table>\n\n <!-- Debug info -->\n <div style=\"margin-top: 1rem; font-size: 0.8em; color: #666;\">\n Columns: {{ displayedColumns.join(', ') }} | Data received\n </div>\n </div>\n }\n </div>\n\n </div>\n\n</div>\n", styles: [".btn{min-width:120px}.mat-mdc-row .mat-mdc-cell{border-bottom:1px solid transparent;border-top:1px solid transparent;cursor:pointer}.mat-mdc-row:hover .mat-mdc-cell{border-color:currentColor;background-color:#f5f5f5}.container{height:400px;overflow:auto}.box{padding:10px;border:1px solid #ccc}\n"], dependencies: [{ kind: "directive", type: i1$1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i2$1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2$1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i2$1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "directive", type: i2$1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i2$1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "directive", type: i2$1.FormGroupName, selector: "[formGroupName]", inputs: ["formGroupName"] }, { kind: "directive", type: i2$1.FormArrayName, selector: "[formArrayName]", inputs: ["formArrayName"] }, { kind: "component", type: i3.MatButton, selector: " button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button] ", exportAs: ["matButton"] }, { kind: "component", type: i3.MatIconButton, selector: "button[mat-icon-button]", exportAs: ["matButton"] }, { kind: "component", type: i4.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i4.MatLabel, selector: "mat-label" }, { kind: "directive", type: i4.MatError, selector: "mat-error, [matError]", inputs: ["id"] }, { kind: "component", type: i5.MatSelect, selector: "mat-select", inputs: ["aria-describedby", "panelClass", "disabled", "disableRipple", "tabIndex", "hideSingleSelectionIndicator", "placeholder", "required", "multiple", "disableOptionCentering", "compareWith", "value", "aria-label", "aria-labelledby", "errorStateMatcher", "typeaheadDebounceInterval", "sortComparator", "id", "panelWidth"], outputs: ["openedChange", "opened", "closed", "selectionChange", "valueChange"], exportAs: ["matSelect"] }, { kind: "component", type: i6.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "component", type: i7.MatMenu, selector: "mat-menu", inputs: ["backdropClass", "aria-label", "aria-labelledby", "aria-describedby", "xPosition", "yPosition", "overlapTrigger", "hasBackdrop", "class", "classList"], outputs: ["closed", "close"], exportAs: ["matMenu"] }, { kind: "component", type: i7.MatMenuItem, selector: "[mat-menu-item]", inputs: ["role", "disabled", "disableRipple"], exportAs: ["matMenuItem"] }, { kind: "directive", type: i7.MatMenuTrigger, selector: "[mat-menu-trigger-for], [matMenuTriggerFor]", inputs: ["mat-menu-trigger-for", "matMenuTriggerFor", "matMenuTriggerData", "matMenuTriggerRestoreFocus"], outputs: ["menuOpened", "onMenuOpen", "menuClosed", "onMenuClose"], exportAs: ["matMenuTrigger"] }, { kind: "component", type: i8.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: i9.MatTable, selector: "mat-table, table[mat-table]", exportAs: ["matTable"] }, { kind: "directive", type: i9.MatHeaderCellDef, selector: "[matHeaderCellDef]" }, { kind: "directive", type: i9.MatHeaderRowDef, selector: "[matHeaderRowDef]", inputs: ["matHeaderRowDef", "matHeaderRowDefSticky"] }, { kind: "directive", type: i9.MatColumnDef, selector: "[matColumnDef]", inputs: ["matColumnDef"] }, { kind: "directive", type: i9.MatCellDef, selector: "[matCellDef]" }, { kind: "directive", type: i9.MatRowDef, selector: "[matRowDef]", inputs: ["matRowDefColumns", "matRowDefWhen"] }, { kind: "directive", type: i9.MatHeaderCell, selector: "mat-header-cell, th[mat-header-cell]" }, { kind: "directive", type: i9.MatCell, selector: "mat-cell, td[mat-cell]" }, { kind: "component", type: i9.MatHeaderRow, selector: "mat-header-row, tr[mat-header-row]", exportAs: ["matHeaderRow"] }, { kind: "component", type: i9.MatRow, selector: "mat-row, tr[mat-row]", exportAs: ["matRow"] }, { kind: "component", type: i8$1.MatProgressBar, selector: "mat-progress-bar", inputs: ["color", "value", "bufferValue", "mode"], outputs: ["animationEnd"], exportAs: ["matProgressBar"] }, { kind: "component", type: i11.MatSlideToggle, selector: "mat-slide-toggle", inputs: ["name", "id", "labelPosition", "aria-label", "aria-labelledby", "aria-describedby", "required", "color", "disabled", "disableRipple", "tabIndex", "checked", "hideIcon", "disabledInteractive"], outputs: ["change", "toggleChange"], exportAs: ["matSlideToggle"] }, { kind: "component", type: i12.MatDivider, selector: "mat-divider", inputs: ["vertical", "inset"] }, { kind: "directive", type: i13.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly"], exportAs: ["matInput"] }, { kind: "pipe", type: i1$1.AsyncPipe, name: "async" }, { kind: "pipe", type: i1$1.JsonPipe, name: "json" }, { kind: "pipe", type: i1$1.TitleCasePipe, name: "titlecase" }] }); }
|
|
8464
|
-
}
|
|
8465
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: RequestManagerStateDemoComponent, decorators: [{
|
|
8466
|
-
type: Component,
|
|
8467
|
-
args: [{ selector: 'app-request-manager-state-demo', providers: [StateManagerDemoService, HTTPManagerService], standalone: false, template: "<div style=\"margin: 2rem;\">\n\n <h2>\n HTTP Request State Manager\n </h2>\n\n <div [formGroup]=\"requestForm\" style=\"margin-top: 2rem;\">\n <div style=\"display: flex; gap: .5rem\">\n <mat-form-field appearance=\"outline\">\n <mat-label>State Data Type</mat-label>\n <mat-select formControlName=\"datatype\">\n <mat-option value=\"ARRAY\">Array</mat-option>\n <mat-option value=\"OBJECT\">Object</mat-option>\n </mat-select>\n </mat-form-field>\n </div>\n <div style=\"display: flex; gap: .5rem\">\n <mat-form-field appearance=\"outline\">\n <mat-label>Adapter (Model)</mat-label>\n <mat-select formControlName=\"adapter\" #adapterSelect>\n <mat-option>None</mat-option>\n @for (adapter of sampleAdaptors; track adapter) {\n <mat-option [value]=\"adapter.value\">\n {{adapter.label}}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Mapper (Model)</mat-label>\n <mat-select formControlName=\"mapper\" #mapperSelect>\n <mat-option>None</mat-option>\n @for (mapper of sampleMappers; track mapper) {\n <mat-option [value]=\"mapper.value\">\n {{mapper.label}}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n </div>\n\n @if (adapterSelect.value || mapperSelect.value) {\n <div style=\"display: flex; margin-bottom: 2rem;\">\n <div style=\"flex:1\" class=\"box\">\n <h3>Adapter (Incoming)</h3>\n @if (adapterSelect.value) {\n <div>\n {{ props(adapterSelect.value) | json }}\n </div>\n } @else {\n No Transformation\n }\n </div>\n <div style=\"flex:1\" class=\"box\">\n <h3>Mapper (Outgoing)</h3>\n @if (mapperSelect.value) {\n <div>\n {{ props(mapperSelect.value) | json }}\n </div>\n } @else {\n No Transformation\n }\n </div>\n </div>\n }\n\n <div>\n <mat-form-field appearance=\"outline\">\n <mat-label>RestPath (/ delimited)</mat-label>\n <input matInput placeholder=\"clients/list\" formControlName=\"path\">\n </mat-form-field>\n </div>\n <div>\n <div formArrayName=\"headers\">\n @for (task of headers.controls; track task; let i = $index) {\n <div [formGroupName]=\"i\">\n <div style=\"display: flex; gap: .5rem\">\n <mat-form-field appearance=\"outline\">\n <mat-label>Key</mat-label>\n <input matInput placeholder=\"authentication\" formControlName=\"key\">\n </mat-form-field>\n <mat-form-field appearance=\"outline\" style=\"flex:1\">\n <mat-label>Value</mat-label>\n <input matInput placeholder=\"sample\" formControlName=\"value\">\n </mat-form-field>\n <div style=\"margin-top: .5rem;\">\n <button mat-icon-button (click)=\"removeHeader(i)\">\n <mat-icon>close</mat-icon>\n </button>\n </div>\n </div>\n </div>\n }\n </div>\n <button mat-stroked-button (click)=\"addHeader()\">Add Header</button>\n </div>\n <div style=\"margin-top: 2rem; display: flex; flex-direction:column; gap: 1rem;\">\n <div>\n <mat-slide-toggle #failedState>Retry on Failed</mat-slide-toggle>\n @if (failedState.checked) {\n <div style=\"display: flex; gap: .5rem; margin-top: 1rem;\" formGroupName=\"retry\">\n <mat-form-field appearance=\"outline\">\n <mat-label>#of Times</mat-label>\n <input matInput placeholder=\"3\" formControlName=\"times\" value=\"3\">\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Delay Until Next</mat-label>\n <input matInput placeholder=\"3\" formControlName=\"delay\" value=\"3\">\n </mat-form-field>\n </div>\n }\n </div>\n <div>\n <mat-slide-toggle #pollingState>Polling</mat-slide-toggle>\n @if (pollingState.checked) {\n <div>\n <mat-form-field appearance=\"outline\" style=\"margin-top: 1rem\">\n <mat-label>#of Seconds</mat-label>\n <input matInput placeholder=\"3\" formControlName=\"polling\" value=\"3\">\n </mat-form-field>\n </div>\n }\n </div>\n <div>\n <mat-slide-toggle #DBState>Database Storage</mat-slide-toggle>\n @if (DBState.checked) {\n <div style=\"display: flex; gap: .5rem; margin-top: 1rem\" formGroupName=\"database\">\n <mat-form-field appearance=\"outline\">\n <mat-label>Table Name</mat-label>\n <input matInput placeholder=\"table\" formControlName=\"table\" value=\"\">\n </mat-form-field>\n <div>\n <mat-form-field appearance=\"outline\">\n <mat-label>Expires In</mat-label>\n <mat-select formControlName=\"expiresIn\">\n <mat-option value=\"1m\">One Minute</mat-option>\n <mat-option value=\"1h\">One Hour</mat-option>\n <mat-option value=\"1d\">One Day</mat-option>\n </mat-select>\n </mat-form-field>\n </div>\n </div>\n }\n </div>\n <div style=\"margin-top: 1rem; display: flex;\">\n <span style=\"flex:1\"></span>\n <button mat-stroked-button (click)=\"onSetStateOptions()\" [disabled]=\"!hasChanged\">\n Set API Request Options\n </button>\n </div>\n <div>\n @if ((error$ | async); as error) {\n <mat-error>\n {{ error }}\n </mat-error>\n }\n </div>\n </div>\n </div>\n\n <div style=\"margin-bottom: 1rem; margin-top: 2rem;\">\n @if ((isPending$ | async)) {\n <mat-progress-bar mode=\"indeterminate\"\n ></mat-progress-bar>\n }\n @if (pollingState.checked) {\n <div>\n @if (!(isPending$ | async) && (this.countdown$ | async) || -1 > 0) {\n <mat-progress-bar mode=\"determinate\"\n [value]=\"(this.countdown$ | async)\"\n ></mat-progress-bar>\n }\n </div>\n }\n\n </div>\n\n @if ((GET$ | async); as data) {\n <div style=\"margin-top: 2rem\">\n <div style=\"display: flex;\">\n <h2 style=\"flex:1;\">CURRENT STATE ({{ dataType }})</h2>\n </div>\n @if (data.length > 1) {\n <div>\n <mat-form-field appearance=\"outline\">\n <mat-label>Records</mat-label>\n <mat-select [formControl]=\"selectedRecord\" [disabled]=\"data === null && data?.length === 0\">\n <mat-option [value]=\"\">None</mat-option>\n @for (item of data; track item) {\n <mat-option [value]=\"item\">\n {{item.name || (item.first_name) | titlecase}}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n </div>\n }\n @if ((dataObservable$ | async); as dataRecord) {\n <div>\n @if ((selectedRecord$ | async); as record) {\n <div>\n {{ record | json }}\n </div>\n } @else {\n No Record Selected from State\n }\n </div>\n }\n <div style=\"margin-top: 1rem;\">\n @if (data !== null && data?.length > 0) {\n <span>\n State contains a Total of {{ data.length }} Records\n </span>\n } @else {\n No Records\n }\n <div style=\"display: flex; gap: .5rem; text-align: end; justify-content: flex-end;\">\n <button class=\"btn\" mat-stroked-button (click)=\"onClearRecords()\">Clear</button>\n </div>\n </div>\n </div>\n }\n\n\n\n\n <div style=\"margin-top: 1rem\">\n <mat-divider></mat-divider>\n </div>\n\n <div style=\"margin-top: 2rem\">\n <div style=\"display: flex;\">\n <h2 style=\"flex:1\">GET Request ({{ dataType }})</h2>\n <div>\n <button mat-raised-button (click)=\"onGetRequest()\" [disabled]=\"hasChanged\" class=\"btn\">Request</button>\n </div>\n </div>\n\n @if ((GET_error$ | async); as get_error) {\n <div style=\"margin-top: .5rem;\">\n <mat-error>{{ get_error }}</mat-error>\n </div>\n }\n\n <div style=\"margin-top: 1rem;\">\n <!-- <div [innerHTML]=\"(GET$ | async) | jsonv\"></div> -->\n @if ((GET$ | async); as getData) {\n <div>{{ getData | json }}</div>\n }\n </div>\n\n </div>\n\n <div style=\"margin-top: 2rem\">\n <mat-divider></mat-divider>\n </div>\n\n <div style=\"margin-top: 2rem\">\n <div style=\"display: flex;\">\n <h2 style=\"flex:1\">POST Request</h2>\n <div>\n <button mat-raised-button (click)=\"onCreateRequest()\" [disabled]=\"hasChanged\" class=\"btn\">Request</button>\n </div>\n </div>\n\n @if ((POST_error$ | async); as post_error) {\n <div style=\"margin-top: .5rem;\">\n <mat-error>{{ post_error }}</mat-error>\n </div>\n }\n\n <div style=\"margin-top: 1rem;\">\n <!-- <div [innerHTML]=\"(POST$ | async) | jsonv\"></div> -->\n @if ((POST$ | async); as postData) {\n <div>{{ postData | json }}</div>\n }\n </div>\n\n </div>\n\n <div style=\"margin-top: 2rem\">\n <mat-divider></mat-divider>\n </div>\n\n <div style=\"margin-top: 2rem\">\n <div style=\"display: flex;\">\n <h2 style=\"flex:1\">PUT Request</h2>\n <div>\n <button mat-raised-button (click)=\"onUpdateRequest()\" [disabled]=\"hasChanged\" class=\"btn\">Request</button>\n </div>\n </div>\n\n @if ((PUT_error$ | async); as put_error) {\n <div style=\"margin-top: .5rem;\">\n <mat-error>{{ put_error }}</mat-error>\n </div>\n }\n\n <div style=\"margin-top: 1rem;\">\n <!-- <div [innerHTML]=\"(PUT$ | async) | jsonv\"></div> -->\n @if ((PUT$ | async); as putData) {\n <div>{{ putData | json }}</div>\n }\n </div>\n\n </div>\n\n <div style=\"margin-top: 2rem\">\n <mat-divider></mat-divider>\n </div>\n\n <div style=\"margin-top: 2rem\">\n <div style=\"display: flex;\">\n <h2 style=\"flex:1\">DELETE Request</h2>\n <div>\n <button mat-raised-button (click)=\"onDeleteRequest()\" [disabled]=\"hasChanged\" class=\"btn\">Request</button>\n </div>\n </div>\n\n @if ((DELETE_error$ | async); as delete_error) {\n <div style=\"margin-top: .5rem;\">\n <mat-error>{{ delete_error }}</mat-error>\n </div>\n }\n\n <div style=\"margin-top: 1rem;\">\n <!-- <div [innerHTML]=\"(DELETE$ | async) | jsonv\"></div> -->\n @if ((DELETE$ | async); as deleteData) {\n <div>{{ deleteData | json }}</div>\n }\n </div>\n\n </div>\n\n <div style=\"margin-top: 2rem\">\n <mat-divider></mat-divider>\n </div>\n\n <div style=\"margin-top: 2rem\">\n <div style=\"display: flex;\">\n <h2 style=\"flex:1\">Streaming GET Request</h2>\n <div style=\"display: flex; gap: 1rem; align-items: center;\">\n <div style=\"display: flex; gap: 1rem; align-items: center;\">\n {{ streamType }}\n <button mat-icon-button [matMenuTriggerFor]=\"menu\">\n <mat-icon>data_usage</mat-icon>\n </button>\n </div>\n <mat-menu #menu=\"matMenu\">\n <button mat-menu-item *ngFor=\"let item of streamTypes\" (click)=\"onStreamType(item.id)\">{{ item.value }}</button>\n </mat-menu>\n <button mat-raised-button (click)=\"onStreamRequest()\" class=\"btn\" [disabled]=\"hasChanged\">Request</button>\n </div>\n </div>\n\n @if ((STREAM_error$ | async); as stream_error) {\n <div style=\"margin-top: .5rem;\">\n <mat-error>{{ stream_error }}</mat-error>\n </div>\n }\n\n <div style=\"margin-top: 1rem;\">\n @if ((STREAM$ | async); as data) {\n <div class=\"container\">\n <table mat-table [dataSource]=\"data\" class=\"mat-elevation-z8\">\n\n <!-- Dynamic columns -->\n <ng-container *ngFor=\"let column of displayedColumns\" [matColumnDef]=\"column\">\n <th mat-header-cell *matHeaderCellDef> {{ column | titlecase }} </th>\n <td mat-cell *matCellDef=\"let element\">\n @if (isObject(element[column]); as objValue) {\n <pre style=\"margin: 0; font-size: 0.8em; white-space: pre-wrap;\">{{ objValue | json }}</pre>\n } @else {\n {{ element[column] }}\n }\n </td>\n </ng-container>\n\n <tr mat-header-row *matHeaderRowDef=\"displayedColumns\"></tr>\n <tr mat-row *matRowDef=\"let row; columns: displayedColumns;\"></tr>\n </table>\n\n <!-- Debug info -->\n <div style=\"margin-top: 1rem; font-size: 0.8em; color: #666;\">\n Columns: {{ displayedColumns.join(', ') }} | Data received\n </div>\n </div>\n }\n </div>\n\n </div>\n\n</div>\n", styles: [".btn{min-width:120px}.mat-mdc-row .mat-mdc-cell{border-bottom:1px solid transparent;border-top:1px solid transparent;cursor:pointer}.mat-mdc-row:hover .mat-mdc-cell{border-color:currentColor;background-color:#f5f5f5}.container{height:400px;overflow:auto}.box{padding:10px;border:1px solid #ccc}\n"] }]
|
|
8468
|
-
}], ctorParameters: () => [], propDecorators: { server: [{
|
|
8469
|
-
type: Input
|
|
8470
|
-
}], adapter: [{
|
|
8471
|
-
type: Input
|
|
8472
|
-
}], mapper: [{
|
|
8473
|
-
type: Input
|
|
8474
|
-
}], failedState: [{
|
|
8475
|
-
type: ViewChild,
|
|
8476
|
-
args: ["failedState", { static: true }]
|
|
8477
|
-
}], pollingState: [{
|
|
8478
|
-
type: ViewChild,
|
|
8479
|
-
args: ["pollingState", { static: true }]
|
|
8480
|
-
}] } });
|
|
8481
|
-
|
|
8482
|
-
class RequestManagerDemoComponent {
|
|
8483
|
-
// Dynamic columns based on data structure
|
|
8484
|
-
getColumnsFromData(data) {
|
|
8485
|
-
if (!data || data.length === 0) {
|
|
8486
|
-
return [];
|
|
8487
|
-
}
|
|
8488
|
-
const firstRecord = data[0];
|
|
8489
|
-
if (!firstRecord || typeof firstRecord !== 'object') {
|
|
8490
|
-
return [];
|
|
8491
|
-
}
|
|
8492
|
-
// Extract all keys from the first record, excluding null/undefined
|
|
8493
|
-
return Object.keys(firstRecord).filter(key => firstRecord[key] !== null && firstRecord[key] !== undefined);
|
|
8494
|
-
}
|
|
8495
|
-
// Update displayed columns when data changes
|
|
8496
|
-
updateDisplayedColumns(data) {
|
|
8497
|
-
this.displayedColumns = this.getColumnsFromData(data);
|
|
8498
|
-
console.log('[DEMO] Updated columns:', this.displayedColumns);
|
|
8499
|
-
}
|
|
8500
|
-
// Helper to check if value is an object
|
|
8501
|
-
isObject(value) {
|
|
8502
|
-
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
8503
|
-
}
|
|
8504
|
-
// Computed properties for filtered states (removes undefined)
|
|
8505
|
-
get filteredParallelStates() {
|
|
8506
|
-
return this.parallelBatchStates.filter(s => s !== undefined);
|
|
8507
|
-
}
|
|
8508
|
-
get filteredSequentialStates() {
|
|
8509
|
-
return this.sequentialBatchStates.filter(s => s !== undefined);
|
|
8510
|
-
}
|
|
8511
|
-
get filteredStreamStates() {
|
|
8512
|
-
return this.streamBatchStates.filter(s => s !== undefined);
|
|
8513
|
-
}
|
|
8514
|
-
get retry() {
|
|
8515
|
-
return this.requestForm.get('retry')?.value;
|
|
8516
|
-
}
|
|
8517
|
-
get headers() {
|
|
8518
|
-
return this.requestForm.get('headers');
|
|
8519
|
-
}
|
|
8520
|
-
get isValid() {
|
|
8521
|
-
this.requestForm.markAllAsTouched();
|
|
8522
|
-
return this.requestForm.valid;
|
|
8523
|
-
}
|
|
8524
|
-
constructor() {
|
|
8525
|
-
this.server = 'http://localhost:8080';
|
|
8526
|
-
this.displayedColumns = [];
|
|
8527
|
-
this.fb = inject(FormBuilder);
|
|
8528
|
-
this.toastMessage = inject(ToastMessageDisplayService);
|
|
8529
|
-
this.questionControl = this.fb.control("", [Validators.required]);
|
|
8530
|
-
this.httpManagerService = inject(HTTPManagerService);
|
|
8531
|
-
this.isPending$ = this.httpManagerService.isPending$;
|
|
8532
|
-
this.countdown$ = this.httpManagerService.countdown$;
|
|
8533
|
-
this.GET_error$ = new BehaviorSubject('');
|
|
8534
|
-
this.POST_error$ = new BehaviorSubject('');
|
|
8535
|
-
this.PUT_error$ = new BehaviorSubject('');
|
|
8536
|
-
this.DELETE_error$ = new BehaviorSubject('');
|
|
8537
|
-
this.STREAM_error$ = new BehaviorSubject('');
|
|
8538
|
-
this.STREAM_AI_error$ = new BehaviorSubject('');
|
|
8539
|
-
this.isParallelLoading = false;
|
|
8540
|
-
this.isSequentialLoading = false;
|
|
8541
|
-
// Parallel batch states for individual request tracking
|
|
8542
|
-
this.parallelBatchStates = [];
|
|
8543
|
-
// Sequential batch states for individual request tracking
|
|
8544
|
-
this.sequentialBatchStates = [];
|
|
8545
|
-
// Stream batch request properties
|
|
8546
|
-
this.streamBatchStates = [];
|
|
8547
|
-
this.isStreamLoading = false;
|
|
8548
|
-
this.requestParams = {
|
|
8549
|
-
GET: ApiRequest.adapt(),
|
|
8550
|
-
POST: ApiRequest.adapt(),
|
|
8551
|
-
PUT: ApiRequest.adapt(),
|
|
8552
|
-
DELETE: ApiRequest.adapt(),
|
|
8553
|
-
STREAM: ApiRequest.adapt(),
|
|
8554
|
-
};
|
|
8555
|
-
this.streamTypes = [
|
|
8556
|
-
{ id: 'JSON', value: 'json' },
|
|
8557
|
-
{ id: 'NDJSON', value: 'ndjson' },
|
|
8558
|
-
{ id: 'AI Streaming', value: 'ai_streaming' },
|
|
8559
|
-
{ id: 'Event Stream', value: 'event_stream' },
|
|
8560
|
-
{ id: 'Auto', value: 'auto' },
|
|
8561
|
-
];
|
|
8562
|
-
this.streamType = 'Auto';
|
|
8563
|
-
this.downloadRequest = ApiRequest.adapt({
|
|
8564
|
-
server: 'assets/images',
|
|
8565
|
-
path: ['lego.png'],
|
|
8566
|
-
saveAs: 'john.jpg', // Optional
|
|
8567
|
-
headers: { 'Authorization': 'Bearer Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6InJ0c0ZULWItN0x1WTdEVlllU05LY0lKN1ZuYyJ9.eyJhdWQiOiI4ODhhMzFjZS01OWQzLTRhMTItYTU5Ni04ZDYyZjY0MWI1MDUiLCJpc3MiOiJodHRwczovL2xvZ2luLm1pY3Jvc29mdG9ubGluZS5jb20vNmY0MTE5ODItY2YyMy00ZTQ1LTk0NDktNGI2MDdiN2E4OGVjL3YyLjAiLCJpYXQiOjE3NjU4MzQxNDksIm5iZiI6MTc2NTgzNDE0OSwiZXhwIjoxNzY1ODM5NzQ2LCJhY2N0IjowLCJhaW8iOiJBV1FBbS84YUFBQUFVUEZOYUhSZkZ4ZVkwRWthcFBzdWZaNG1QYXV6YVRoODRRZUtQRzkrbEFIVE5XanVwQy9ZSjEwK2IrMktWVlJSSXZRNFpwS2xyWTFGd0xRZmtXOTNLbkRNckxSeEMzWTVOOGlQREZ4b1liZExucW1QL1N1ZE1pRW1Va05tTklSWSIsImF6cCI6Ijg4OGEzMWNlLTU5ZDMtNGExMi1hNTk2LThkNjJmNjQxYjUwNSIsImF6cGFjciI6IjAiLCJncm91cHMiOlsiZWE2ZDk5YjAtNDgyNC00MDU0LTk0MTQtZDNhOTZkZDA3MjRiIl0sIm5hbWUiOiJHVExDIEFwcCBTdXBlciBBZG1pbiIsIm9pZCI6IjI2NzUxY2I2LWNlMDEtNDMzMC05OTc0LWZjMzgxMjQ3YTEzYyIsInByZWZlcnJlZF91c2VybmFtZSI6Imd0bGMwMDNAZ3RsYy5jYSIsInJoIjoiMS5BUzRBZ2hsQmJ5UFBSVTZVU1V0Z2UzcUk3TTR4aW9qVFdSSktwWmFOWXZaQnRRVm1BYTR1QUEuIiwicm9sZXMiOlsiZ3RsYy5hY2wuc3VwZXJhZG1pbiJdLCJzY3AiOiJHVExDX0FwcGxpY2F0aW9uIiwic2lkIjoiMDAzZjVhZDktZmJmNi1jZTkyLTg3MjItMWEzNDExYjMzMDJiIiwic3ViIjoiS0RYQ3drVlhUbTRUUmcwaTkxbXZDZzgtQ29uLXpWbk5FQ2Y5LVg0dkpzUSIsInRpZCI6IjZmNDExOTgyLWNmMjMtNGU0NS05NDQ5LTRiNjA3YjdhODhlYyIsInVwbiI6Imd0bGMwMDNAZ3RsYy5jYSIsInV0aSI6InpBaTJoMmpVUUVHbVY0RTJXMWxqQUEiLCJ2ZXIiOiIyLjAiLCJ3aWRzIjpbImI3OWZiZjRkLTNlZjktNDY4OS04MTQzLTc2YjE5NGU4NTUwOSJdLCJ4bXNfZnRkIjoiUDJraW1ZM0tqc1BTRndxNHlZdmJ0bGhrSUk3d2tXNmFpcW1FQ1BaNEdja0JkWE5sWVhOMExXUnpiWE0ifQ.LzppoggMm27smSAy9SamtPN95vCzdELCAfhtOj5n_T_H6g9xCmNRLS9FaUFQMau6Qvl0lROKl7WDklTswLFkfxbIxCBWtXdL-LTqT5cDURSJAll8vC3zlN3Hg9pAFBUVZFRolt6Z7LvPdI3pvUOQs0yFwVzp9k6cLF8aemKdwKQrMX3XXua1MfBWZcqQ4WiBVNmKh8w6yQB35I4u5WqdFnu33nUGb-kvc18SOpoUfiJnlV-PudaEzFXdU3CjAaMEcuPFv5xLwWJKuhU73dNH4EyQDFMVGtcIHNnieOfiY_nK2_0-5DM6aI40UIRK6Bt-HmMQpnbhLps5y3ep6Z7RNw' } // Optional
|
|
8568
|
-
});
|
|
8569
|
-
// downloadRequest = ApiRequest.adapt({
|
|
8570
|
-
// server: 'oidc/ai/file'
|
|
8571
|
-
// })
|
|
8572
|
-
this.sampleClientData = {
|
|
8573
|
-
id: 0,
|
|
8574
|
-
name: "Old School Dates",
|
|
8575
|
-
domain: "osd.com",
|
|
8576
|
-
service: "osd",
|
|
8577
|
-
spiffe: "osd.com/osd",
|
|
8578
|
-
secret: "SMOPECXP-OS4P-USOG-X2II-3XMD1FQDR3IJX",
|
|
8579
|
-
created: 1693003138,
|
|
8580
|
-
modified: 1693003138,
|
|
8581
|
-
icon: "",
|
|
8582
|
-
imageFile: "",
|
|
8583
|
-
email: "wavecoders@gmail.com"
|
|
8584
|
-
};
|
|
8585
|
-
this.requestForm = this.fb.group({
|
|
8586
|
-
path: this.fb.control("ai/"),
|
|
8587
|
-
headers: this.fb.array([]),
|
|
8588
|
-
adapter: [null],
|
|
8589
|
-
mapper: [null],
|
|
8590
|
-
retry: this.fb.group({
|
|
8591
|
-
times: [3],
|
|
8592
|
-
delay: [3],
|
|
8593
|
-
}),
|
|
8594
|
-
polling: [3],
|
|
8595
|
-
});
|
|
8596
|
-
this.AIType = 0;
|
|
8597
|
-
this.sampleAdaptors = [
|
|
8598
|
-
{ label: "ClientInfo Basic", value: ClientInfo$1.adapt },
|
|
8599
|
-
{ label: "AI Prompt", value: AIPrompt$1.adapt },
|
|
8600
|
-
];
|
|
8601
|
-
this.sampleMappers = [
|
|
8602
|
-
{ label: "Mapper Basic", value: ClientInfoMapper$1.adapt },
|
|
8603
|
-
{ label: "AI Prompt", value: AIPrompt$1.adapt },
|
|
8604
|
-
];
|
|
8605
|
-
this.hasId = (arr) => {
|
|
8606
|
-
if (arr.length === 0)
|
|
8607
|
-
return false;
|
|
8608
|
-
return !isNaN(arr[arr.length - 1]);
|
|
8609
|
-
};
|
|
8610
|
-
this.props = (adapter) => {
|
|
8611
|
-
return (adapter) ? adapter() : null;
|
|
8612
|
-
};
|
|
8613
|
-
// server = `http://sample-endpoint/as/authorization.oauth2`
|
|
8614
|
-
this.arrayObjectsToObjects = (arr) => {
|
|
8615
|
-
return Array.isArray(arr) ? arr.reduce((obj, item) => Object.assign(obj, { [item.key]: item.value }), {}) : {};
|
|
8616
|
-
};
|
|
8617
|
-
}
|
|
8618
|
-
ngOnInit() {
|
|
8619
|
-
// const reqGet2 = ApiRequest.adapt({
|
|
8620
|
-
// server,
|
|
8621
|
-
// path: ['clients'],
|
|
8622
|
-
// headers: { authentication: "Bearer <KEY>" },
|
|
8623
|
-
// adapter: ClientInfo,
|
|
8624
|
-
// dataType: DataType.OBJECT,
|
|
8625
|
-
// // concurrent: false,
|
|
8626
|
-
// // polling: 3, //seconds
|
|
8627
|
-
// })
|
|
8628
|
-
// const req2 = [1024,1025,1026].map(item => {
|
|
8629
|
-
// return this.httpManagerService.getRequest<ClientInfo[]>(reqGet2, [item])
|
|
8630
|
-
// .pipe(
|
|
8631
|
-
// catchError(error => {
|
|
8632
|
-
// return throwError(() => new Error(error.error.message))
|
|
8633
|
-
// })
|
|
8634
|
-
// )
|
|
8635
|
-
// })
|
|
8636
|
-
// forkJoin(req2)
|
|
8637
|
-
// .subscribe(res => console.log(res))
|
|
8638
|
-
}
|
|
8639
|
-
onStreamType(type) {
|
|
8640
|
-
this.streamType = type;
|
|
8641
|
-
}
|
|
8642
|
-
addHeader() {
|
|
8643
|
-
const header = this.fb.group({
|
|
8644
|
-
key: ['', Validators.required],
|
|
8645
|
-
value: ['']
|
|
8646
|
-
});
|
|
8647
|
-
this.headers.push(header);
|
|
8648
|
-
}
|
|
8649
|
-
removeHeader(index) {
|
|
8650
|
-
this.headers.removeAt(index);
|
|
8651
|
-
}
|
|
8652
|
-
compileRequest() {
|
|
8653
|
-
const requestParams = this.requestForm.value;
|
|
8654
|
-
requestParams.headers = this.arrayObjectsToObjects(requestParams.headers || []);
|
|
8655
|
-
const pathReq = (requestParams.path === "") ? [] : (requestParams.path || "").split("/");
|
|
8656
|
-
if (!this.pollingState.checked)
|
|
8657
|
-
requestParams.polling = 0;
|
|
8658
|
-
if (!this.failedState.checked) {
|
|
8659
|
-
requestParams.retry = { times: 0, delay: 0 };
|
|
8660
|
-
}
|
|
8661
|
-
const apiOptions = ApiRequest.adapt(requestParams);
|
|
8662
|
-
apiOptions.path = [];
|
|
8663
|
-
apiOptions.server = this.server;
|
|
8664
|
-
apiOptions.adapter = this.adapter;
|
|
8665
|
-
apiOptions.mapper = this.mapper;
|
|
8666
|
-
return { apiOptions: apiOptions, path: pathReq };
|
|
8667
|
-
}
|
|
8668
|
-
onGetRequest() {
|
|
8669
|
-
if (!this.isValid)
|
|
8670
|
-
return;
|
|
8671
|
-
const reqParams = this.compileRequest();
|
|
8672
|
-
this.requestParams.GET = reqParams.apiOptions;
|
|
8673
|
-
this.GET$ = EMPTY; //Cancels Previous
|
|
8674
|
-
this.GET_error$.next('');
|
|
8675
|
-
this.GET$ = this.httpManagerService.getRequest({ ...reqParams.apiOptions }, reqParams.path)
|
|
8676
|
-
.pipe(
|
|
8677
|
-
// tap((data) => console.log("API GET response", data)),
|
|
8678
|
-
catchError(error => {
|
|
8679
|
-
return throwError(() => this.errorHandling(error, 'GET'));
|
|
8680
|
-
}));
|
|
8681
|
-
}
|
|
8682
|
-
onCreateRequest() {
|
|
8683
|
-
if (!this.isValid)
|
|
8684
|
-
return;
|
|
8685
|
-
const reqParams = this.compileRequest();
|
|
8686
|
-
this.requestParams.POST = reqParams.apiOptions;
|
|
8687
|
-
this.POST$ = EMPTY; //Cancels Previous
|
|
8688
|
-
this.POST_error$.next('');
|
|
8689
|
-
console.log("POST", this.sampleClientData);
|
|
8690
|
-
console.log("POST", reqParams.apiOptions);
|
|
8691
|
-
console.log("POST", reqParams.path);
|
|
8692
|
-
this.POST$ = this.httpManagerService.postRequest(this.sampleClientData, reqParams.apiOptions, reqParams.path)
|
|
8693
|
-
.pipe(
|
|
8694
|
-
// tap((data) => console.log("API POST response", data)),
|
|
8695
|
-
catchError(error => {
|
|
8696
|
-
return throwError(() => this.errorHandling(error, 'POST'));
|
|
8697
|
-
}));
|
|
8698
|
-
}
|
|
8699
|
-
onUpdateRequest() {
|
|
8700
|
-
if (!this.isValid)
|
|
8701
|
-
return;
|
|
8702
|
-
const reqParams = this.compileRequest();
|
|
8703
|
-
if (!this.hasId(reqParams.path)) {
|
|
8704
|
-
console.log("Missing ID");
|
|
8705
|
-
return;
|
|
8706
|
-
}
|
|
8707
|
-
this.sampleClientData.id = parseInt(reqParams.path[reqParams.path.length - 1]);
|
|
8708
|
-
this.requestParams.PUT = reqParams.apiOptions;
|
|
8709
|
-
this.PUT$ = EMPTY; //Cancels Previous
|
|
8710
|
-
this.PUT_error$.next('');
|
|
8711
|
-
this.PUT$ = this.httpManagerService.putRequest(this.sampleClientData, reqParams.apiOptions, reqParams.path)
|
|
8712
|
-
.pipe(
|
|
8713
|
-
// tap((data) => console.log("API PUT response", data)),
|
|
8714
|
-
catchError(error => {
|
|
8715
|
-
return throwError(() => this.errorHandling(error, 'PUT'));
|
|
8716
|
-
}));
|
|
8717
|
-
}
|
|
8718
|
-
onDeleteRequest() {
|
|
8719
|
-
if (!this.isValid)
|
|
8720
|
-
return;
|
|
8721
|
-
const reqParams = this.compileRequest();
|
|
8722
|
-
this.requestParams.DELETE = reqParams.apiOptions;
|
|
8723
|
-
if (!this.hasId(reqParams.path)) {
|
|
8724
|
-
console.log("Missing ID");
|
|
8725
|
-
return;
|
|
8726
|
-
}
|
|
8727
|
-
this.sampleClientData.id = parseInt(reqParams.path[reqParams.path.length - 1]);
|
|
8728
|
-
this.requestParams.DELETE = reqParams.apiOptions;
|
|
8729
|
-
this.DELETE$ = EMPTY; //Cancels Previous
|
|
8730
|
-
this.DELETE_error$.next('');
|
|
8731
|
-
this.DELETE$ = this.httpManagerService.deleteRequest(reqParams.apiOptions, reqParams.path)
|
|
8732
|
-
.pipe(
|
|
8733
|
-
// tap((data) => console.log("API DELETE response", data)),
|
|
8734
|
-
catchError(error => {
|
|
8735
|
-
return throwError(() => this.errorHandling(error, 'DELETE'));
|
|
8736
|
-
}));
|
|
8737
|
-
}
|
|
8738
|
-
onStreamPostRequest() {
|
|
8739
|
-
if (!this.isValid)
|
|
8740
|
-
return;
|
|
8741
|
-
const reqParams = this.compileRequest();
|
|
8742
|
-
let payload = {};
|
|
8743
|
-
let apiPath = reqParams.path;
|
|
8744
|
-
let apiOptions = reqParams.apiOptions;
|
|
8745
|
-
let responseMapper = (items) => items.response;
|
|
8746
|
-
if (this.AIType === 0) {
|
|
8747
|
-
// API request
|
|
8748
|
-
payload = { prompt: this.questionControl.value };
|
|
8749
|
-
}
|
|
8750
|
-
else {
|
|
8751
|
-
// Local Ollama request
|
|
8752
|
-
apiOptions.server = "api";
|
|
8753
|
-
apiPath = ["generate"];
|
|
8754
|
-
apiOptions.stream = true;
|
|
8755
|
-
apiOptions.streamType = this.streamType;
|
|
8756
|
-
payload = {
|
|
8757
|
-
model: "phi3:latest",
|
|
8758
|
-
prompt: this.questionControl.value,
|
|
8759
|
-
stream: true,
|
|
8760
|
-
};
|
|
8761
|
-
responseMapper = (items) => items.map((word) => word.response).flat().join('');
|
|
8762
|
-
}
|
|
8763
|
-
this.requestParams.STREAM = apiOptions;
|
|
8764
|
-
this.STREAM_AI$ = EMPTY;
|
|
8765
|
-
this.STREAM_AI_error$.next('');
|
|
8766
|
-
this.STREAM_AI$ = this.httpManagerService.postRequest(payload, apiOptions, apiPath).pipe(map(responseMapper), tap(() => this.questionControl.reset()), catchError(error => throwError(() => this.errorHandling(error, 'STREAM'))));
|
|
8767
|
-
}
|
|
8768
|
-
onStreamRequest() {
|
|
8769
|
-
if (!this.isValid)
|
|
8770
|
-
return;
|
|
8771
|
-
const reqParams = this.compileRequest();
|
|
8772
|
-
reqParams.apiOptions.stream = true;
|
|
8773
|
-
reqParams.apiOptions.streamType = StreamType.NDJSON;
|
|
8774
|
-
this.requestParams.GET = reqParams.apiOptions;
|
|
8775
|
-
this.STREAM$ = this.httpManagerService.getRequest(reqParams.apiOptions, reqParams.path)
|
|
8776
|
-
.pipe(tap((data) => {
|
|
8777
|
-
console.log("API STREAM response", data);
|
|
8778
|
-
if (data && data.length > 0) {
|
|
8779
|
-
this.updateDisplayedColumns(data);
|
|
8780
|
-
}
|
|
8781
|
-
}), catchError(error => {
|
|
8782
|
-
return throwError(() => this.errorHandling(error, 'STREAM'));
|
|
8783
|
-
}));
|
|
8784
|
-
}
|
|
8785
|
-
onDownloadCompleted() {
|
|
8786
|
-
const message = "Download Completed";
|
|
8787
|
-
const display = ToastDisplay.adapt({
|
|
8788
|
-
message,
|
|
8789
|
-
action: 'Ok',
|
|
8790
|
-
color: ToastColors.SUCCESS,
|
|
8791
|
-
icon: 'sentiment_satisfied_alt',
|
|
8792
|
-
});
|
|
8793
|
-
this.toastMessage.toastMessage(display);
|
|
8794
|
-
}
|
|
8795
|
-
onDownloadFailed(err) {
|
|
8796
|
-
const message = "Download Failed";
|
|
8797
|
-
const display = ToastDisplay.adapt({
|
|
8798
|
-
message,
|
|
8799
|
-
action: 'Ok',
|
|
8800
|
-
color: ToastColors.ERROR,
|
|
8801
|
-
icon: 'warning',
|
|
8802
|
-
});
|
|
8803
|
-
this.toastMessage.toastMessage(display);
|
|
8804
|
-
}
|
|
8805
|
-
errorHandling(err, type) {
|
|
8806
|
-
if (type === 'GET')
|
|
8807
|
-
this.GET_error$.next(err.message);
|
|
8808
|
-
if (type === 'POST')
|
|
8809
|
-
this.POST_error$.next(err.message);
|
|
8810
|
-
if (type === 'PUT')
|
|
8811
|
-
this.PUT_error$.next(err.message);
|
|
8812
|
-
if (type === 'DELETE')
|
|
8813
|
-
this.DELETE_error$.next(err.message);
|
|
8814
|
-
if (type === 'STREAM')
|
|
8815
|
-
this.STREAM_error$.next(err.message);
|
|
8816
|
-
}
|
|
8817
|
-
onSelectAIType(type) {
|
|
8818
|
-
this.AIType = type;
|
|
8819
|
-
}
|
|
8820
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
8821
|
-
// BATCH REQUEST DEMO METHODS
|
|
8822
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
8823
|
-
/**
|
|
8824
|
-
* Execute parallel batch request - fetch 5 users from JSONPlaceholder with individual states
|
|
8825
|
-
*/
|
|
8826
|
-
onExecuteParallelBatch() {
|
|
8827
|
-
this.isParallelLoading = true;
|
|
8828
|
-
this.parallelError = undefined;
|
|
8829
|
-
this.parallelExecutionTime = undefined;
|
|
8830
|
-
this.parallelResults = undefined;
|
|
8831
|
-
this.parallelBatchStates = [];
|
|
8832
|
-
const requests = this.createBatchRequests('https://jsonplaceholder.typicode.com', ['users'], [1, 2, 3, 4, 5]);
|
|
8833
|
-
const startTime = performance.now();
|
|
8834
|
-
this.httpManagerService.getBatchRequestsStream(requests, {
|
|
8835
|
-
mode: 'parallel',
|
|
8836
|
-
concurrency: 3,
|
|
8837
|
-
stopOnError: false,
|
|
8838
|
-
ignoreErrors: true,
|
|
8839
|
-
logErrors: true
|
|
8840
|
-
}).pipe(tap(state => {
|
|
8841
|
-
this.parallelBatchStates[state.index] = state;
|
|
8842
|
-
if (!state.isPending && this.parallelBatchStates.every(s => s && !s.isPending)) {
|
|
8843
|
-
this.parallelExecutionTime = performance.now() - startTime;
|
|
8844
|
-
this.isParallelLoading = false;
|
|
8845
|
-
}
|
|
8846
|
-
}), catchError(error => {
|
|
8847
|
-
this.parallelError = error.message || 'Parallel batch failed';
|
|
8848
|
-
this.isParallelLoading = false;
|
|
8849
|
-
return throwError(() => error);
|
|
8850
|
-
})).subscribe();
|
|
8851
|
-
}
|
|
8852
|
-
/**
|
|
8853
|
-
* Execute sequential batch request - fetch 5 todos from JSONPlaceholder with individual states
|
|
8854
|
-
*/
|
|
8855
|
-
onExecuteSequentialBatch() {
|
|
8856
|
-
this.isSequentialLoading = true;
|
|
8857
|
-
this.sequentialError = undefined;
|
|
8858
|
-
this.sequentialExecutionTime = undefined;
|
|
8859
|
-
this.sequentialResults = undefined;
|
|
8860
|
-
this.sequentialBatchStates = [];
|
|
8861
|
-
const requests = this.createBatchRequests('https://jsonplaceholder.typicode.com', ['todos'], [1, 2, 3, 4, 5]);
|
|
8862
|
-
const startTime = performance.now();
|
|
8863
|
-
this.httpManagerService.getBatchRequestsStream(requests, {
|
|
8864
|
-
mode: 'sequential',
|
|
8865
|
-
stopOnError: false,
|
|
8866
|
-
concurrency: 3,
|
|
8867
|
-
ignoreErrors: true,
|
|
8868
|
-
logErrors: true
|
|
8869
|
-
}).pipe(tap(state => {
|
|
8870
|
-
this.sequentialBatchStates[state.index] = state;
|
|
8871
|
-
if (!state.isPending && this.sequentialBatchStates.every(s => s && !s.isPending)) {
|
|
8872
|
-
this.sequentialExecutionTime = performance.now() - startTime;
|
|
8873
|
-
this.isSequentialLoading = false;
|
|
8874
|
-
}
|
|
8875
|
-
}), catchError(error => {
|
|
8876
|
-
this.sequentialError = error.message || 'Sequential batch failed';
|
|
8877
|
-
this.isSequentialLoading = false;
|
|
8878
|
-
return throwError(() => error);
|
|
8879
|
-
})).subscribe();
|
|
8880
|
-
}
|
|
8881
|
-
/**
|
|
8882
|
-
* Execute stream batch request - fetch 5 posts with real-time state updates
|
|
8883
|
-
*/
|
|
8884
|
-
onExecuteStreamBatch() {
|
|
8885
|
-
this.isStreamLoading = true;
|
|
8886
|
-
this.streamError = undefined;
|
|
8887
|
-
this.streamExecutionTime = undefined;
|
|
8888
|
-
this.streamBatchStates = [];
|
|
8889
|
-
this.streamProgress = undefined;
|
|
8890
|
-
const requests = this.createBatchRequests('https://jsonplaceholder.typicode.com', ['posts'], [1, 2, 3, 4, 5]);
|
|
8891
|
-
const startTime = performance.now();
|
|
8892
|
-
this.httpManagerService.getBatchRequestsStream(requests, {
|
|
8893
|
-
mode: 'parallel',
|
|
8894
|
-
concurrency: 3,
|
|
8895
|
-
stopOnError: false,
|
|
8896
|
-
ignoreErrors: true,
|
|
8897
|
-
logErrors: true
|
|
8898
|
-
}).pipe(tap(state => {
|
|
8899
|
-
this.streamBatchStates[state.index] = state;
|
|
8900
|
-
this.streamProgress = calculateBatchProgress(this.streamBatchStates.filter(s => s !== undefined));
|
|
8901
|
-
if (!state.isPending) {
|
|
8902
|
-
this.streamExecutionTime = performance.now() - startTime;
|
|
8903
|
-
}
|
|
8904
|
-
this.isStreamLoading = this.streamBatchStates.some(s => s && s.isPending);
|
|
8905
|
-
}), catchError(error => {
|
|
8906
|
-
this.streamError = error.message || 'Stream batch failed';
|
|
8907
|
-
this.isStreamLoading = false;
|
|
8908
|
-
return throwError(() => error);
|
|
8909
|
-
})).subscribe();
|
|
8910
|
-
}
|
|
8911
|
-
/**
|
|
8912
|
-
* Type guard for pending state
|
|
8913
|
-
*/
|
|
8914
|
-
isPendingState(state) {
|
|
8915
|
-
return state.isPending === true;
|
|
8916
|
-
}
|
|
8917
|
-
/**
|
|
8918
|
-
* Type guard for success state
|
|
8919
|
-
*/
|
|
8920
|
-
isSuccessState(state) {
|
|
8921
|
-
return state.isPending === false && state.data !== undefined;
|
|
8922
|
-
}
|
|
8923
|
-
/**
|
|
8924
|
-
* Type guard for error state
|
|
8925
|
-
*/
|
|
8926
|
-
isErrorState(state) {
|
|
8927
|
-
return state.isPending === false && state.error !== undefined;
|
|
8928
|
-
}
|
|
8929
|
-
/**
|
|
8930
|
-
* Helper to create batch requests from IDs
|
|
8931
|
-
*/
|
|
8932
|
-
createBatchRequests(baseUrl, basePath, ids) {
|
|
8933
|
-
return ids.map(id => ApiRequest.adapt({
|
|
8934
|
-
server: baseUrl,
|
|
8935
|
-
path: [...basePath, id]
|
|
8936
|
-
}));
|
|
8937
|
-
}
|
|
8938
|
-
/**
|
|
8939
|
-
* Check if result is successful (not undefined)
|
|
8940
|
-
*/
|
|
8941
|
-
isSuccess(result) {
|
|
8942
|
-
return result !== undefined && result !== null;
|
|
8943
|
-
}
|
|
8944
|
-
/**
|
|
8945
|
-
* Format execution time for display
|
|
8946
|
-
*/
|
|
8947
|
-
formatTime(ms) {
|
|
8948
|
-
if (ms < 1000) {
|
|
8949
|
-
return `${Math.round(ms)}ms`;
|
|
8950
|
-
}
|
|
8951
|
-
return `${(ms / 1000).toFixed(2)}s`;
|
|
8952
|
-
}
|
|
8953
|
-
/**
|
|
8954
|
-
* Get success count from results array
|
|
8955
|
-
*/
|
|
8956
|
-
getSuccessCount(results) {
|
|
8957
|
-
if (!results)
|
|
8958
|
-
return 0;
|
|
8959
|
-
return results.filter(r => r !== undefined).length;
|
|
8960
|
-
}
|
|
8961
|
-
/**
|
|
8962
|
-
* Get failure count from results array
|
|
8963
|
-
*/
|
|
8964
|
-
getFailureCount(results) {
|
|
8965
|
-
if (!results)
|
|
8966
|
-
return 0;
|
|
8967
|
-
return results.filter(r => r === undefined).length;
|
|
8968
|
-
}
|
|
8969
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: RequestManagerDemoComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
8970
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: RequestManagerDemoComponent, selector: "app-request-manager-demo", inputs: { server: "server", adapter: "adapter", mapper: "mapper" }, providers: [HTTPManagerService], viewQueries: [{ propertyName: "failedState", first: true, predicate: ["failedState"], descendants: true, static: true }, { propertyName: "pollingState", first: true, predicate: ["pollingState"], descendants: true, static: true }], ngImport: i0, template: "<div style=\"margin: 2rem;\">\n\n <h2>\n HTTP Request Manager\n </h2>\n\n <div [formGroup]=\"requestForm\" style=\"margin-top: 2rem;\">\n <div style=\"display: flex; gap: .5rem\">\n <mat-form-field appearance=\"outline\">\n <mat-label>Adapter (Model)</mat-label>\n <mat-select formControlName=\"adapter\" #adapterSelect>\n <mat-option>None</mat-option>\n @for (adapter of sampleAdaptors; track adapter) {\n <mat-option [value]=\"adapter.value\">\n {{adapter.label}}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Mapper (Model)</mat-label>\n <mat-select formControlName=\"mapper\" #mapperSelect>\n <mat-option>None</mat-option>\n @for (mapper of sampleMappers; track mapper) {\n <mat-option [value]=\"mapper.value\">\n {{mapper.label}}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n </div>\n @if (adapterSelect.value || mapperSelect.value) {\n <div style=\"display: flex; margin-bottom: 2rem;\">\n <div style=\"flex:1\" class=\"box\">\n <h3>Adapter (Incoming)</h3>\n @if (adapterSelect.value) {\n <div>\n {{ props(adapterSelect.value) | json }}\n </div>\n } @else {\n No Transformation\n }\n </div>\n <div style=\"flex:1\" class=\"box\">\n <h3>Mapper (Outgoing)</h3>\n @if (mapperSelect.value) {\n <div>\n {{ props(mapperSelect.value) | json }}\n </div>\n } @else {\n No Transformation\n }\n </div>\n </div>\n }\n <div>\n <mat-form-field appearance=\"outline\">\n <mat-label>RestPath (/ delimited)</mat-label>\n <input matInput placeholder=\"clients/list\" formControlName=\"path\">\n </mat-form-field>\n </div>\n <div>\n <div formArrayName=\"headers\">\n @for (task of headers.controls; track task; let i = $index) {\n <div [formGroupName]=\"i\">\n <div style=\"display: flex; gap: .5rem\">\n <mat-form-field appearance=\"outline\">\n <mat-label>Key</mat-label>\n <input matInput placeholder=\"authentication\" formControlName=\"key\">\n </mat-form-field>\n <mat-form-field appearance=\"outline\" style=\"flex:1\">\n <mat-label>Value</mat-label>\n <input matInput placeholder=\"sample\" formControlName=\"value\">\n </mat-form-field>\n <div style=\"margin-top: .5rem;\">\n <button mat-icon-button (click)=\"removeHeader(i)\">\n <mat-icon>close</mat-icon>\n </button>\n </div>\n </div>\n </div>\n }\n </div>\n <button mat-stroked-button (click)=\"addHeader()\" class=\"btn\">Add Header</button>\n </div>\n <div style=\"margin-top: 2rem; display: flex; flex-direction:column; gap: 1rem;\">\n <div>\n <mat-slide-toggle #failedState>Retry on Failed</mat-slide-toggle>\n @if (failedState.checked) {\n <div style=\"display: flex; gap: .5rem; margin-top: 1rem;\" formGroupName=\"retry\">\n <mat-form-field appearance=\"outline\">\n <mat-label>#of Times</mat-label>\n <input matInput placeholder=\"3\" formControlName=\"times\" value=\"3\">\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Delay Until Next</mat-label>\n <input matInput placeholder=\"3\" formControlName=\"delay\" value=\"3\">\n </mat-form-field>\n </div>\n }\n </div>\n <div>\n <mat-slide-toggle #pollingState>Polling</mat-slide-toggle>\n @if (pollingState.checked) {\n <div>\n <mat-form-field appearance=\"outline\" style=\"margin-top: 1rem\">\n <mat-label>#of Seconds</mat-label>\n <input matInput placeholder=\"3\" formControlName=\"polling\" value=\"3\">\n </mat-form-field>\n </div>\n }\n </div>\n </div>\n </div>\n\n <div style=\"margin-bottom: 1rem; margin-top: 2rem;\">\n @if ((isPending$ | async)) {\n <mat-progress-bar mode=\"indeterminate\"\n ></mat-progress-bar>\n }\n @if (pollingState.checked && !(isPending$ | async)) {\n <mat-progress-bar mode=\"determinate\"\n [value]=\"(this.countdown$ | async)\"\n ></mat-progress-bar>\n }\n </div>\n\n <div style=\"margin-top: 1rem\">\n <mat-divider></mat-divider>\n </div>\n\n <div style=\"margin-top: 2rem\">\n <div style=\"display: flex;\">\n <h2 style=\"flex:1\">GET Request</h2>\n <div>\n <button mat-raised-button (click)=\"onGetRequest()\" class=\"btn\">Request</button>\n </div>\n </div>\n\n @if ((GET_error$ | async); as get_error) {\n <div style=\"margin-top: .5rem;\">\n <mat-error>{{ get_error }}</mat-error>\n </div>\n }\n\n @if ((GET$ | async); as dataRecord) {\n <div style=\"margin-top: 1rem;\">\n <!-- <div [innerHTML]=\"(GET$ | async) | jsonv\"></div> -->\n {{ dataRecord | json }}\n </div>\n }\n\n </div>\n\n <div style=\"margin-top: 2rem\">\n <mat-divider></mat-divider>\n </div>\n\n <div style=\"margin-top: 2rem\">\n <div style=\"display: flex;\">\n <h2 style=\"flex:1\">POST Request</h2>\n <div>\n <button mat-raised-button (click)=\"onCreateRequest()\" class=\"btn\">Request</button>\n </div>\n </div>\n\n @if ((POST_error$ | async); as post_error) {\n <div style=\"margin-top: .5rem;\">\n <mat-error>{{ post_error }}</mat-error>\n </div>\n }\n\n @if ((POST$ | async); as dataRecord) {\n <div style=\"margin-top: 1rem;\">\n <!-- <div [innerHTML]=\"(POST$ | async) | jsonv\"></div> -->\n {{ dataRecord | json }}\n </div>\n }\n\n </div>\n\n <div style=\"margin-top: 2rem\">\n <mat-divider></mat-divider>\n </div>\n\n <div style=\"margin-top: 2rem\">\n <div style=\"display: flex;\">\n <h2 style=\"flex:1\">PUT Request</h2>\n <div>\n <button mat-raised-button (click)=\"onUpdateRequest()\" class=\"btn\">Request</button>\n </div>\n </div>\n\n @if ((PUT_error$ | async); as put_error) {\n <div style=\"margin-top: .5rem;\">\n <mat-error>{{ put_error }}</mat-error>\n </div>\n }\n\n <h3>Include Record ID in the RestPath</h3>\n\n @if ((PUT$ | async); as dataRecord) {\n <div style=\"margin-top: 1rem;\">\n <!-- <div [innerHTML]=\"(PUT$ | async) | jsonv\"></div> -->\n {{ dataRecord | json }}\n </div>\n }\n\n </div>\n\n <div style=\"margin-top: 2rem\">\n <mat-divider></mat-divider>\n </div>\n\n <div style=\"margin-top: 2rem\">\n <div style=\"display: flex;\">\n <h2 style=\"flex:1\">DELETE Request</h2>\n <div>\n <button mat-raised-button (click)=\"onDeleteRequest()\" class=\"btn\">Request</button>\n </div>\n </div>\n\n <h3>Include Record ID in the RestPath</h3>\n\n @if ((DELETE_error$ | async); as delete_error) {\n <div style=\"margin-top: .5rem;\">\n <mat-error>{{ delete_error }}</mat-error>\n </div>\n }\n\n @if ((DELETE$ | async); as dataRecord) {\n <div style=\"margin-top: 1rem;\">\n <!-- <div [innerHTML]=\"(DELETE$ | async) | jsonv\"></div> -->\n {{ dataRecord | json }}\n </div>\n }\n\n </div>\n\n <div style=\"margin-top: 2rem\">\n <mat-divider></mat-divider>\n </div>\n\n <div style=\"margin-top: 2rem\">\n <div style=\"display: flex;\">\n <h2 style=\"flex:1\">Streaming GET Request</h2>\n <div style=\"display: flex; gap: 1rem; align-items: center;\">\n <div style=\"display: flex; gap: 1rem; align-items: center;\">\n {{ streamType }}\n <button mat-icon-button [matMenuTriggerFor]=\"menu\">\n <mat-icon>data_usage</mat-icon>\n </button>\n </div>\n <mat-menu #menu=\"matMenu\">\n <button mat-menu-item *ngFor=\"let item of streamTypes\" (click)=\"onStreamType(item.id)\">{{ item.value }}</button>\n </mat-menu>\n <button mat-raised-button (click)=\"onStreamRequest()\" class=\"btn\">Request</button>\n </div>\n </div>\n\n <!-- <div *ngIf=\"(STREAM_error$ | async) as stream_error\" style=\"margin-top: .5rem;\">\n <mat-error>{{ stream_error }}</mat-error>\n </div> -->\n\n <div style=\"margin-top: 1rem;\">\n @if ((STREAM$ | async); as data) {\n <div class=\"container\">\n <table mat-table [dataSource]=\"data\" class=\"mat-elevation-z8\">\n\n <!-- Dynamic columns -->\n <ng-container *ngFor=\"let column of displayedColumns\" [matColumnDef]=\"column\">\n <th mat-header-cell *matHeaderCellDef> {{ column | titlecase }} </th>\n <td mat-cell *matCellDef=\"let element\">\n @if (isObject(element[column]); as objValue) {\n <pre style=\"margin: 0; font-size: 0.8em; white-space: pre-wrap;\">{{ objValue | json }}</pre>\n } @else {\n {{ element[column] }}\n }\n </td>\n </ng-container>\n\n <tr mat-header-row *matHeaderRowDef=\"displayedColumns\"></tr>\n <tr mat-row *matRowDef=\"let row; columns: displayedColumns;\"></tr>\n </table>\n\n <!-- Debug info -->\n <div style=\"margin-top: 1rem; font-size: 0.8em; color: #666;\">\n Columns: {{ displayedColumns.join(', ') }} | Records: {{ data.length }}\n </div>\n </div>\n }\n </div>\n\n</div>\n\n<div style=\"margin-top: 2rem\">\n <mat-divider></mat-divider>\n</div>\n\n<div style=\"margin-top: 2rem\">\n <!-- Parallel Requests Section -->\n <div>\n <div style=\"display: flex; align-items: center; gap: 1rem; margin-bottom: 1rem;\">\n <h2 style=\"flex: 1; margin: 0;\">Parallel Requests</h2>\n <button\n mat-raised-button\n color=\"primary\"\n (click)=\"onExecuteParallelBatch()\"\n [disabled]=\"isParallelLoading\"\n style=\"min-width: 200px;\">\n <mat-icon>sync</mat-icon>\n <span style=\"margin-left: 8px;\">\n {{ isParallelLoading ? 'Loading...' : 'Fetch 5 Users' }}\n </span>\n </button>\n </div>\n\n <!-- Loading Indicator -->\n @if (isParallelLoading) {\n <div style=\"margin: 1rem 0;\">\n <mat-progress-bar mode=\"indeterminate\"></mat-progress-bar>\n <p style=\"margin-top: 0.5rem; color: #666;\">\n Executing parallel requests (concurrency: 3)...\n </p>\n </div>\n }\n\n <!-- Individual Request States -->\n @if (parallelBatchStates.length > 0) {\n <div style=\"margin: 1rem 0;\">\n <mat-card appearance=\"outlined\">\n <mat-card-content>\n <div style=\"display: grid; gap: 0.5rem;\">\n @for (state of filteredParallelStates; track state.index; let i = $index) {\n <div\n style=\"padding: 0.75rem; border-radius: 4px; border: 1px solid;\n {{ state.isPending ? 'background: #fff3e0; border-color: #ff9800;' :\n state.data ? 'background: #e3f2fd; border-color: #2196f3;' :\n 'background: #ffebee; border-color: #f44336;' }}\">\n @if (state.isPending) {\n <div style=\"display: flex; align-items: center; gap: 0.5rem; justify-content: space-between;\">\n <div style=\"display: flex; align-items: center; gap: 0.5rem;\">\n <mat-spinner diameter=\"16\" style=\"margin-right: 0.5rem;\"></mat-spinner>\n <strong>Request {{ state.index + 1 }}:</strong>\n <span>Loading...</span>\n </div>\n <span style=\"color: #ff9800; font-weight: bold;\">Pending</span>\n </div>\n } @else if (state.data) {\n <div style=\"display: flex; align-items: center; gap: 0.5rem; justify-content: space-between;\">\n <div style=\"display: flex; align-items: center; gap: 0.5rem;\">\n <mat-icon style=\"color: #2196f3; font-size: 18px;\">check_circle</mat-icon>\n <strong>User #{{ state.data.id }}:</strong>\n <span>{{ state.data.name }}</span>\n </div>\n <span style=\"color: #2196f3; font-weight: bold;\">Complete</span>\n </div>\n } @else {\n <div style=\"display: flex; align-items: center; gap: 0.5rem; justify-content: space-between;\">\n <div style=\"display: flex; align-items: center; gap: 0.5rem;\">\n <mat-icon style=\"color: #f44336; font-size: 18px;\">error</mat-icon>\n <strong>Request {{ state.index + 1 }}:</strong>\n <span>Failed</span>\n </div>\n <span style=\"color: #f44336; font-weight: bold;\">Error</span>\n </div>\n }\n </div>\n }\n </div>\n </mat-card-content>\n </mat-card>\n </div>\n }\n\n <!-- Execution Metrics -->\n @if (parallelExecutionTime !== undefined) {\n <div style=\"background: #f5f5f5; padding: 1rem; border-radius: 4px; margin: 1rem 0;\">\n <div style=\"display: flex; gap: 2rem;\">\n <div>\n <strong>\u23F1Execution Time:</strong>\n <span style=\"margin-left: 0.5rem; color: #4caf50;\">\n {{ formatTime(parallelExecutionTime) }}\n </span>\n </div>\n <div>\n <strong>Mode:</strong>\n <span style=\"margin-left: 0.5rem;\">Parallel</span>\n </div>\n </div>\n </div>\n }\n\n <!-- Error Display -->\n @if (parallelError) {\n <div style=\"margin-top: 1rem;\">\n <mat-error>{{ parallelError }}</mat-error>\n </div>\n }\n </div>\n\n <!-- Sequential Requests Section -->\n <div>\n <div style=\"display: flex; align-items: center; gap: 1rem; margin-bottom: 1rem;\">\n <h2 style=\"flex: 1; margin: 0;\">Sequential Requests</h2>\n <button\n mat-raised-button\n color=\"accent\"\n (click)=\"onExecuteSequentialBatch()\"\n [disabled]=\"isSequentialLoading\"\n style=\"min-width: 200px;\">\n <mat-icon>timeline</mat-icon>\n <span style=\"margin-left: 8px;\">\n {{ isSequentialLoading ? 'Loading...' : 'Fetch 5 Todos' }}\n </span>\n </button>\n </div>\n\n <!-- Loading Indicator -->\n @if (isSequentialLoading) {\n <div style=\"margin: 1rem 0;\">\n <mat-progress-bar mode=\"indeterminate\"></mat-progress-bar>\n <p style=\"margin-top: 0.5rem; color: #666;\">\n Executing sequential requests (one at a time)...\n </p>\n </div>\n }\n\n <!-- Individual Request States -->\n @if (sequentialBatchStates.length > 0) {\n <div style=\"margin: 1rem 0;\">\n <mat-card appearance=\"outlined\">\n <mat-card-content>\n <div style=\"display: grid; gap: 0.5rem;\">\n @for (state of filteredSequentialStates; track state.index; let i = $index) {\n <div\n style=\"padding: 0.75rem; border-radius: 4px; border: 1px solid;\n {{ state.isPending ? 'background: #fff3e0; border-color: #ff9800;' :\n state.data ? 'background: #e3f2fd; border-color: #2196f3;' :\n 'background: #ffebee; border-color: #f44336;' }}\">\n @if (state.isPending) {\n <div style=\"display: flex; align-items: center; gap: 0.5rem; justify-content: space-between;\">\n <div style=\"display: flex; align-items: center; gap: 0.5rem;\">\n <mat-spinner diameter=\"16\" style=\"margin-right: 0.5rem;\"></mat-spinner>\n <strong>Request {{ state.index + 1 }}:</strong>\n <span>Loading...</span>\n </div>\n <span style=\"color: #ff9800; font-weight: bold;\">Pending</span>\n </div>\n } @else if (state.data) {\n <div style=\"display: flex; align-items: center; gap: 0.5rem; justify-content: space-between;\">\n <div style=\"display: flex; align-items: center; gap: 0.5rem;\">\n <mat-icon style=\"color: #2196f3; font-size: 18px;\">check_circle</mat-icon>\n <strong>Todo #{{ state.data.id }}:</strong>\n <span style=\"flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;\">\n {{ state.data.title }}\n </span>\n </div>\n </div>\n } @else {\n <div style=\"display: flex; align-items: center; gap: 0.5rem; justify-content: space-between;\">\n <div style=\"display: flex; align-items: center; gap: 0.5rem;\">\n <mat-icon style=\"color: #f44336; font-size: 18px;\">error</mat-icon>\n <strong>Request {{ state.index + 1 }}:</strong>\n <span>Failed</span>\n </div>\n <span style=\"color: #f44336; font-weight: bold;\">Error</span>\n </div>\n }\n </div>\n }\n </div>\n </mat-card-content>\n </mat-card>\n </div>\n }\n\n <!-- Execution Metrics -->\n @if (sequentialExecutionTime !== undefined) {\n <div style=\"background: #f5f5f5; padding: 1rem; border-radius: 4px; margin: 1rem 0;\">\n <div style=\"display: flex; gap: 2rem;\">\n <div>\n <strong>Execution Time:</strong>\n <span style=\"margin-left: 0.5rem; color: #4caf50;\">\n {{ formatTime(sequentialExecutionTime) }}\n </span>\n </div>\n <div>\n <strong>Mode:</strong>\n <span style=\"margin-left: 0.5rem;\">Sequential</span>\n </div>\n </div>\n </div>\n }\n </div>\n\n <!-- Stream Mode Section -->\n <div>\n <div style=\"display: flex; align-items: center; gap: 1rem; margin-bottom: 1rem;\">\n <h2 style=\"flex: 1; margin: 0;\">Stream Mode (Real-time States)</h2>\n <button\n mat-raised-button\n color=\"primary\"\n (click)=\"onExecuteStreamBatch()\"\n [disabled]=\"isStreamLoading\"\n style=\"min-width: 200px;\">\n <mat-icon>auto_awesome</mat-icon>\n <span style=\"margin-left: 8px;\">\n {{ isStreamLoading ? 'Loading...' : 'Fetch 5 Posts' }}\n </span>\n </button>\n </div>\n\n <!-- Loading Indicator -->\n @if (isStreamLoading) {\n <div style=\"margin: 1rem 0;\">\n <mat-progress-bar mode=\"indeterminate\"></mat-progress-bar>\n <p style=\"margin-top: 0.5rem; color: #666;\">\n Executing parallel requests with real-time state updates...\n </p>\n </div>\n }\n\n <!-- Progress Bar -->\n @if (streamProgress) {\n <div style=\"background: #f5f5f5; padding: 1rem; border-radius: 4px; margin: 1rem 0;\">\n <div style=\"display: flex; gap: 2rem; margin-bottom: 0.5rem;\">\n <div>\n <strong>Execution Time:</strong>\n <span style=\"margin-left: 0.5rem; color: #4caf50;\">\n {{ formatTime(streamExecutionTime || 0) }}\n </span>\n </div>\n <div>\n <strong>Progress:</strong>\n <span style=\"margin-left: 0.5rem;\">\n {{ streamProgress.completed }} / {{ streamProgress.total }} completed\n </span>\n </div>\n <div>\n <strong>Pending:</strong>\n <span style=\"margin-left: 0.5rem;\">\n {{ streamProgress.pending }}\n </span>\n </div>\n <div>\n <strong>Failed:</strong>\n <span style=\"margin-left: 0.5rem;\">\n {{ streamProgress.failed }}\n </span>\n </div>\n </div>\n <mat-progress-bar\n mode=\"determinate\"\n [value]=\"streamProgress.percent\"\n color=\"accent\">\n </mat-progress-bar>\n <p style=\"margin-top: 0.5rem; text-align: right; color: #666;\">\n {{ streamProgress.percent }}%\n </p>\n </div>\n }\n\n <!-- Error Display -->\n @if (streamError) {\n <div style=\"margin-top: 1rem;\">\n <mat-error>{{ streamError }}</mat-error>\n </div>\n }\n\n <!-- Individual Request States -->\n @if (streamBatchStates.length > 0) {\n <div style=\"margin-top: 1rem;\">\n <mat-card appearance=\"outlined\">\n <mat-card-content>\n <div style=\"display: grid; gap: 0.5rem;\">\n @for (state of filteredStreamStates; track state.index; let i = $index) {\n <div\n style=\"padding: 0.75rem; border-radius: 4px; border: 1px solid;\n {{ isPendingState(state) ? 'background: #fff3e0; border-color: #ff9800;' :\n isSuccessState(state) ? 'background: #e3f2fd; border-color: #2196f3;' :\n 'background: #ffebee; border-color: #f44336;' }}\">\n @if (isPendingState(state)) {\n <div style=\"display: flex; align-items: center; gap: 0.5rem;\">\n <mat-spinner diameter=\"16\" style=\"margin-right: 0.5rem;\"></mat-spinner>\n <strong>Request {{ state.index + 1 }}:</strong>\n <span>Loading...</span>\n </div>\n } @else if (isSuccessState(state)) {\n <div style=\"display: flex; align-items: center; gap: 0.5rem;\">\n <mat-icon style=\"color: #2196f3; font-size: 18px;\">check_circle</mat-icon>\n <strong>Post #{{ state.data.id }}:</strong>\n <span style=\"flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;\">\n {{ state.data.title }}\n </span>\n </div>\n } @else {\n <div style=\"display: flex; align-items: center; gap: 0.5rem;\">\n <mat-icon style=\"color: #f44336; font-size: 18px;\">error</mat-icon>\n <strong>Request {{ state.index + 1 }}:</strong>\n <span>Failed</span>\n <mat-chip style=\"margin-left: auto; font-size: 0.8em; background: #f44336; color: white;\">\n Error\n </mat-chip>\n </div>\n }\n </div>\n }\n </div>\n </mat-card-content>\n </mat-card>\n </div>\n }\n </div>\n\n <!-- Comparison Info -->\n <div style=\"padding: 1rem; background: whitesmoke; border-radius: 4px; border: 1px solid #ccc;\">\n <h4 style=\"margin-top: 0;\">Key Differences</h4>\n <div style=\"display: grid; grid-template-columns: 1fr 1fr; gap: 1rem;\">\n <div>\n <strong>Parallel:</strong>\n <ul style=\"margin: 0.5rem 0 0 1rem; padding: 0;\">\n <li>Faster execution (concurrent)</li>\n <li>Multiple requests simultaneously</li>\n <li>Results may complete out of order</li>\n <li>Best for: Independent requests</li>\n </ul>\n </div>\n <div>\n <strong>Sequential:</strong>\n <ul style=\"margin: 0.5rem 0 0 1rem; padding: 0;\">\n <li>Slower execution (one-by-one)</li>\n <li>Requests execute in order</li>\n <li>Results always in request order</li>\n <li>Best for: Dependent requests</li>\n </ul>\n </div>\n </div>\n </div>\n</div>\n\n</div>\n", styles: [".btn{min-width:120px}.mat-mdc-row .mat-mdc-cell{border-bottom:1px solid transparent;border-top:1px solid transparent;cursor:pointer}.mat-mdc-row:hover .mat-mdc-cell{border-color:currentColor;background-color:#f5f5f5}.container{height:400px;overflow:auto}.box{padding:10px;border:1px solid #ccc}.batch-demo-section{margin:2rem 0;padding:1.5rem;border:1px solid #e0e0e0;border-radius:8px;background:#fafafa}.batch-result-item{padding:.75rem;border-radius:4px;margin-bottom:.5rem;display:flex;align-items:center;gap:.5rem}.batch-result-item.success{background:#e8f5e9;border-left:3px solid #4caf50}.batch-result-item.error{background:#ffebee;border-left:3px solid #f44336}.execution-metrics{display:flex;gap:2rem;padding:1rem;background:#f5f5f5;border-radius:4px;margin:1rem 0}.execution-metrics>div{display:flex;align-items:center;gap:.5rem}.comparison-box{padding:1rem;background:#fff3e0;border-radius:4px;margin-top:2rem}.comparison-box h4{margin-top:0;margin-bottom:1rem}.comparison-box .comparison-grid{display:grid;grid-template-columns:1fr 1fr;gap:1rem}@media(max-width:768px){.comparison-box .comparison-grid{grid-template-columns:1fr}}.loading-indicator{margin:1rem 0}.result-card{margin-top:1rem}.result-card mat-card-content{padding:1rem}\n"], dependencies: [{ kind: "directive", type: i1$1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i2$1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2$1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i2$1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i2$1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "directive", type: i2$1.FormGroupName, selector: "[formGroupName]", inputs: ["formGroupName"] }, { kind: "directive", type: i2$1.FormArrayName, selector: "[formArrayName]", inputs: ["formArrayName"] }, { kind: "component", type: i3.MatButton, selector: " button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button] ", exportAs: ["matButton"] }, { kind: "component", type: i3.MatIconButton, selector: "button[mat-icon-button]", exportAs: ["matButton"] }, { kind: "component", type: i4.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i4.MatLabel, selector: "mat-label" }, { kind: "directive", type: i4.MatError, selector: "mat-error, [matError]", inputs: ["id"] }, { kind: "component", type: i5.MatSelect, selector: "mat-select", inputs: ["aria-describedby", "panelClass", "disabled", "disableRipple", "tabIndex", "hideSingleSelectionIndicator", "placeholder", "required", "multiple", "disableOptionCentering", "compareWith", "value", "aria-label", "aria-labelledby", "errorStateMatcher", "typeaheadDebounceInterval", "sortComparator", "id", "panelWidth"], outputs: ["openedChange", "opened", "closed", "selectionChange", "valueChange"], exportAs: ["matSelect"] }, { kind: "component", type: i6.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "component", type: i3$1.MatChip, selector: "mat-basic-chip, [mat-basic-chip], mat-chip, [mat-chip]", inputs: ["role", "id", "aria-label", "aria-description", "value", "color", "removable", "highlighted", "disableRipple", "disabled"], outputs: ["removed", "destroyed"], exportAs: ["matChip"] }, { kind: "component", type: i7.MatMenu, selector: "mat-menu", inputs: ["backdropClass", "aria-label", "aria-labelledby", "aria-describedby", "xPosition", "yPosition", "overlapTrigger", "hasBackdrop", "class", "classList"], outputs: ["closed", "close"], exportAs: ["matMenu"] }, { kind: "component", type: i7.MatMenuItem, selector: "[mat-menu-item]", inputs: ["role", "disabled", "disableRipple"], exportAs: ["matMenuItem"] }, { kind: "directive", type: i7.MatMenuTrigger, selector: "[mat-menu-trigger-for], [matMenuTriggerFor]", inputs: ["mat-menu-trigger-for", "matMenuTriggerFor", "matMenuTriggerData", "matMenuTriggerRestoreFocus"], outputs: ["menuOpened", "onMenuOpen", "menuClosed", "onMenuClose"], exportAs: ["matMenuTrigger"] }, { kind: "component", type: i8.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: i9.MatTable, selector: "mat-table, table[mat-table]", exportAs: ["matTable"] }, { kind: "directive", type: i9.MatHeaderCellDef, selector: "[matHeaderCellDef]" }, { kind: "directive", type: i9.MatHeaderRowDef, selector: "[matHeaderRowDef]", inputs: ["matHeaderRowDef", "matHeaderRowDefSticky"] }, { kind: "directive", type: i9.MatColumnDef, selector: "[matColumnDef]", inputs: ["matColumnDef"] }, { kind: "directive", type: i9.MatCellDef, selector: "[matCellDef]" }, { kind: "directive", type: i9.MatRowDef, selector: "[matRowDef]", inputs: ["matRowDefColumns", "matRowDefWhen"] }, { kind: "directive", type: i9.MatHeaderCell, selector: "mat-header-cell, th[mat-header-cell]" }, { kind: "directive", type: i9.MatCell, selector: "mat-cell, td[mat-cell]" }, { kind: "component", type: i9.MatHeaderRow, selector: "mat-header-row, tr[mat-header-row]", exportAs: ["matHeaderRow"] }, { kind: "component", type: i9.MatRow, selector: "mat-row, tr[mat-row]", exportAs: ["matRow"] }, { kind: "component", type: i8$1.MatProgressBar, selector: "mat-progress-bar", inputs: ["color", "value", "bufferValue", "mode"], outputs: ["animationEnd"], exportAs: ["matProgressBar"] }, { kind: "component", type: i2.MatProgressSpinner, selector: "mat-progress-spinner, mat-spinner", inputs: ["color", "mode", "value", "diameter", "strokeWidth"], exportAs: ["matProgressSpinner"] }, { kind: "component", type: i11.MatSlideToggle, selector: "mat-slide-toggle", inputs: ["name", "id", "labelPosition", "aria-label", "aria-labelledby", "aria-describedby", "required", "color", "disabled", "disableRipple", "tabIndex", "checked", "hideIcon", "disabledInteractive"], outputs: ["change", "toggleChange"], exportAs: ["matSlideToggle"] }, { kind: "component", type: i12.MatDivider, selector: "mat-divider", inputs: ["vertical", "inset"] }, { kind: "directive", type: i13.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly"], exportAs: ["matInput"] }, { kind: "component", type: i16.MatCard, selector: "mat-card", inputs: ["appearance"], exportAs: ["matCard"] }, { kind: "directive", type: i16.MatCardContent, selector: "mat-card-content" }, { kind: "pipe", type: i1$1.AsyncPipe, name: "async" }, { kind: "pipe", type: i1$1.JsonPipe, name: "json" }, { kind: "pipe", type: i1$1.TitleCasePipe, name: "titlecase" }] }); }
|
|
8971
|
-
}
|
|
8972
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: RequestManagerDemoComponent, decorators: [{
|
|
8973
|
-
type: Component,
|
|
8974
|
-
args: [{ selector: 'app-request-manager-demo', providers: [HTTPManagerService], standalone: false, template: "<div style=\"margin: 2rem;\">\n\n <h2>\n HTTP Request Manager\n </h2>\n\n <div [formGroup]=\"requestForm\" style=\"margin-top: 2rem;\">\n <div style=\"display: flex; gap: .5rem\">\n <mat-form-field appearance=\"outline\">\n <mat-label>Adapter (Model)</mat-label>\n <mat-select formControlName=\"adapter\" #adapterSelect>\n <mat-option>None</mat-option>\n @for (adapter of sampleAdaptors; track adapter) {\n <mat-option [value]=\"adapter.value\">\n {{adapter.label}}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Mapper (Model)</mat-label>\n <mat-select formControlName=\"mapper\" #mapperSelect>\n <mat-option>None</mat-option>\n @for (mapper of sampleMappers; track mapper) {\n <mat-option [value]=\"mapper.value\">\n {{mapper.label}}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n </div>\n @if (adapterSelect.value || mapperSelect.value) {\n <div style=\"display: flex; margin-bottom: 2rem;\">\n <div style=\"flex:1\" class=\"box\">\n <h3>Adapter (Incoming)</h3>\n @if (adapterSelect.value) {\n <div>\n {{ props(adapterSelect.value) | json }}\n </div>\n } @else {\n No Transformation\n }\n </div>\n <div style=\"flex:1\" class=\"box\">\n <h3>Mapper (Outgoing)</h3>\n @if (mapperSelect.value) {\n <div>\n {{ props(mapperSelect.value) | json }}\n </div>\n } @else {\n No Transformation\n }\n </div>\n </div>\n }\n <div>\n <mat-form-field appearance=\"outline\">\n <mat-label>RestPath (/ delimited)</mat-label>\n <input matInput placeholder=\"clients/list\" formControlName=\"path\">\n </mat-form-field>\n </div>\n <div>\n <div formArrayName=\"headers\">\n @for (task of headers.controls; track task; let i = $index) {\n <div [formGroupName]=\"i\">\n <div style=\"display: flex; gap: .5rem\">\n <mat-form-field appearance=\"outline\">\n <mat-label>Key</mat-label>\n <input matInput placeholder=\"authentication\" formControlName=\"key\">\n </mat-form-field>\n <mat-form-field appearance=\"outline\" style=\"flex:1\">\n <mat-label>Value</mat-label>\n <input matInput placeholder=\"sample\" formControlName=\"value\">\n </mat-form-field>\n <div style=\"margin-top: .5rem;\">\n <button mat-icon-button (click)=\"removeHeader(i)\">\n <mat-icon>close</mat-icon>\n </button>\n </div>\n </div>\n </div>\n }\n </div>\n <button mat-stroked-button (click)=\"addHeader()\" class=\"btn\">Add Header</button>\n </div>\n <div style=\"margin-top: 2rem; display: flex; flex-direction:column; gap: 1rem;\">\n <div>\n <mat-slide-toggle #failedState>Retry on Failed</mat-slide-toggle>\n @if (failedState.checked) {\n <div style=\"display: flex; gap: .5rem; margin-top: 1rem;\" formGroupName=\"retry\">\n <mat-form-field appearance=\"outline\">\n <mat-label>#of Times</mat-label>\n <input matInput placeholder=\"3\" formControlName=\"times\" value=\"3\">\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Delay Until Next</mat-label>\n <input matInput placeholder=\"3\" formControlName=\"delay\" value=\"3\">\n </mat-form-field>\n </div>\n }\n </div>\n <div>\n <mat-slide-toggle #pollingState>Polling</mat-slide-toggle>\n @if (pollingState.checked) {\n <div>\n <mat-form-field appearance=\"outline\" style=\"margin-top: 1rem\">\n <mat-label>#of Seconds</mat-label>\n <input matInput placeholder=\"3\" formControlName=\"polling\" value=\"3\">\n </mat-form-field>\n </div>\n }\n </div>\n </div>\n </div>\n\n <div style=\"margin-bottom: 1rem; margin-top: 2rem;\">\n @if ((isPending$ | async)) {\n <mat-progress-bar mode=\"indeterminate\"\n ></mat-progress-bar>\n }\n @if (pollingState.checked && !(isPending$ | async)) {\n <mat-progress-bar mode=\"determinate\"\n [value]=\"(this.countdown$ | async)\"\n ></mat-progress-bar>\n }\n </div>\n\n <div style=\"margin-top: 1rem\">\n <mat-divider></mat-divider>\n </div>\n\n <div style=\"margin-top: 2rem\">\n <div style=\"display: flex;\">\n <h2 style=\"flex:1\">GET Request</h2>\n <div>\n <button mat-raised-button (click)=\"onGetRequest()\" class=\"btn\">Request</button>\n </div>\n </div>\n\n @if ((GET_error$ | async); as get_error) {\n <div style=\"margin-top: .5rem;\">\n <mat-error>{{ get_error }}</mat-error>\n </div>\n }\n\n @if ((GET$ | async); as dataRecord) {\n <div style=\"margin-top: 1rem;\">\n <!-- <div [innerHTML]=\"(GET$ | async) | jsonv\"></div> -->\n {{ dataRecord | json }}\n </div>\n }\n\n </div>\n\n <div style=\"margin-top: 2rem\">\n <mat-divider></mat-divider>\n </div>\n\n <div style=\"margin-top: 2rem\">\n <div style=\"display: flex;\">\n <h2 style=\"flex:1\">POST Request</h2>\n <div>\n <button mat-raised-button (click)=\"onCreateRequest()\" class=\"btn\">Request</button>\n </div>\n </div>\n\n @if ((POST_error$ | async); as post_error) {\n <div style=\"margin-top: .5rem;\">\n <mat-error>{{ post_error }}</mat-error>\n </div>\n }\n\n @if ((POST$ | async); as dataRecord) {\n <div style=\"margin-top: 1rem;\">\n <!-- <div [innerHTML]=\"(POST$ | async) | jsonv\"></div> -->\n {{ dataRecord | json }}\n </div>\n }\n\n </div>\n\n <div style=\"margin-top: 2rem\">\n <mat-divider></mat-divider>\n </div>\n\n <div style=\"margin-top: 2rem\">\n <div style=\"display: flex;\">\n <h2 style=\"flex:1\">PUT Request</h2>\n <div>\n <button mat-raised-button (click)=\"onUpdateRequest()\" class=\"btn\">Request</button>\n </div>\n </div>\n\n @if ((PUT_error$ | async); as put_error) {\n <div style=\"margin-top: .5rem;\">\n <mat-error>{{ put_error }}</mat-error>\n </div>\n }\n\n <h3>Include Record ID in the RestPath</h3>\n\n @if ((PUT$ | async); as dataRecord) {\n <div style=\"margin-top: 1rem;\">\n <!-- <div [innerHTML]=\"(PUT$ | async) | jsonv\"></div> -->\n {{ dataRecord | json }}\n </div>\n }\n\n </div>\n\n <div style=\"margin-top: 2rem\">\n <mat-divider></mat-divider>\n </div>\n\n <div style=\"margin-top: 2rem\">\n <div style=\"display: flex;\">\n <h2 style=\"flex:1\">DELETE Request</h2>\n <div>\n <button mat-raised-button (click)=\"onDeleteRequest()\" class=\"btn\">Request</button>\n </div>\n </div>\n\n <h3>Include Record ID in the RestPath</h3>\n\n @if ((DELETE_error$ | async); as delete_error) {\n <div style=\"margin-top: .5rem;\">\n <mat-error>{{ delete_error }}</mat-error>\n </div>\n }\n\n @if ((DELETE$ | async); as dataRecord) {\n <div style=\"margin-top: 1rem;\">\n <!-- <div [innerHTML]=\"(DELETE$ | async) | jsonv\"></div> -->\n {{ dataRecord | json }}\n </div>\n }\n\n </div>\n\n <div style=\"margin-top: 2rem\">\n <mat-divider></mat-divider>\n </div>\n\n <div style=\"margin-top: 2rem\">\n <div style=\"display: flex;\">\n <h2 style=\"flex:1\">Streaming GET Request</h2>\n <div style=\"display: flex; gap: 1rem; align-items: center;\">\n <div style=\"display: flex; gap: 1rem; align-items: center;\">\n {{ streamType }}\n <button mat-icon-button [matMenuTriggerFor]=\"menu\">\n <mat-icon>data_usage</mat-icon>\n </button>\n </div>\n <mat-menu #menu=\"matMenu\">\n <button mat-menu-item *ngFor=\"let item of streamTypes\" (click)=\"onStreamType(item.id)\">{{ item.value }}</button>\n </mat-menu>\n <button mat-raised-button (click)=\"onStreamRequest()\" class=\"btn\">Request</button>\n </div>\n </div>\n\n <!-- <div *ngIf=\"(STREAM_error$ | async) as stream_error\" style=\"margin-top: .5rem;\">\n <mat-error>{{ stream_error }}</mat-error>\n </div> -->\n\n <div style=\"margin-top: 1rem;\">\n @if ((STREAM$ | async); as data) {\n <div class=\"container\">\n <table mat-table [dataSource]=\"data\" class=\"mat-elevation-z8\">\n\n <!-- Dynamic columns -->\n <ng-container *ngFor=\"let column of displayedColumns\" [matColumnDef]=\"column\">\n <th mat-header-cell *matHeaderCellDef> {{ column | titlecase }} </th>\n <td mat-cell *matCellDef=\"let element\">\n @if (isObject(element[column]); as objValue) {\n <pre style=\"margin: 0; font-size: 0.8em; white-space: pre-wrap;\">{{ objValue | json }}</pre>\n } @else {\n {{ element[column] }}\n }\n </td>\n </ng-container>\n\n <tr mat-header-row *matHeaderRowDef=\"displayedColumns\"></tr>\n <tr mat-row *matRowDef=\"let row; columns: displayedColumns;\"></tr>\n </table>\n\n <!-- Debug info -->\n <div style=\"margin-top: 1rem; font-size: 0.8em; color: #666;\">\n Columns: {{ displayedColumns.join(', ') }} | Records: {{ data.length }}\n </div>\n </div>\n }\n </div>\n\n</div>\n\n<div style=\"margin-top: 2rem\">\n <mat-divider></mat-divider>\n</div>\n\n<div style=\"margin-top: 2rem\">\n <!-- Parallel Requests Section -->\n <div>\n <div style=\"display: flex; align-items: center; gap: 1rem; margin-bottom: 1rem;\">\n <h2 style=\"flex: 1; margin: 0;\">Parallel Requests</h2>\n <button\n mat-raised-button\n color=\"primary\"\n (click)=\"onExecuteParallelBatch()\"\n [disabled]=\"isParallelLoading\"\n style=\"min-width: 200px;\">\n <mat-icon>sync</mat-icon>\n <span style=\"margin-left: 8px;\">\n {{ isParallelLoading ? 'Loading...' : 'Fetch 5 Users' }}\n </span>\n </button>\n </div>\n\n <!-- Loading Indicator -->\n @if (isParallelLoading) {\n <div style=\"margin: 1rem 0;\">\n <mat-progress-bar mode=\"indeterminate\"></mat-progress-bar>\n <p style=\"margin-top: 0.5rem; color: #666;\">\n Executing parallel requests (concurrency: 3)...\n </p>\n </div>\n }\n\n <!-- Individual Request States -->\n @if (parallelBatchStates.length > 0) {\n <div style=\"margin: 1rem 0;\">\n <mat-card appearance=\"outlined\">\n <mat-card-content>\n <div style=\"display: grid; gap: 0.5rem;\">\n @for (state of filteredParallelStates; track state.index; let i = $index) {\n <div\n style=\"padding: 0.75rem; border-radius: 4px; border: 1px solid;\n {{ state.isPending ? 'background: #fff3e0; border-color: #ff9800;' :\n state.data ? 'background: #e3f2fd; border-color: #2196f3;' :\n 'background: #ffebee; border-color: #f44336;' }}\">\n @if (state.isPending) {\n <div style=\"display: flex; align-items: center; gap: 0.5rem; justify-content: space-between;\">\n <div style=\"display: flex; align-items: center; gap: 0.5rem;\">\n <mat-spinner diameter=\"16\" style=\"margin-right: 0.5rem;\"></mat-spinner>\n <strong>Request {{ state.index + 1 }}:</strong>\n <span>Loading...</span>\n </div>\n <span style=\"color: #ff9800; font-weight: bold;\">Pending</span>\n </div>\n } @else if (state.data) {\n <div style=\"display: flex; align-items: center; gap: 0.5rem; justify-content: space-between;\">\n <div style=\"display: flex; align-items: center; gap: 0.5rem;\">\n <mat-icon style=\"color: #2196f3; font-size: 18px;\">check_circle</mat-icon>\n <strong>User #{{ state.data.id }}:</strong>\n <span>{{ state.data.name }}</span>\n </div>\n <span style=\"color: #2196f3; font-weight: bold;\">Complete</span>\n </div>\n } @else {\n <div style=\"display: flex; align-items: center; gap: 0.5rem; justify-content: space-between;\">\n <div style=\"display: flex; align-items: center; gap: 0.5rem;\">\n <mat-icon style=\"color: #f44336; font-size: 18px;\">error</mat-icon>\n <strong>Request {{ state.index + 1 }}:</strong>\n <span>Failed</span>\n </div>\n <span style=\"color: #f44336; font-weight: bold;\">Error</span>\n </div>\n }\n </div>\n }\n </div>\n </mat-card-content>\n </mat-card>\n </div>\n }\n\n <!-- Execution Metrics -->\n @if (parallelExecutionTime !== undefined) {\n <div style=\"background: #f5f5f5; padding: 1rem; border-radius: 4px; margin: 1rem 0;\">\n <div style=\"display: flex; gap: 2rem;\">\n <div>\n <strong>\u23F1Execution Time:</strong>\n <span style=\"margin-left: 0.5rem; color: #4caf50;\">\n {{ formatTime(parallelExecutionTime) }}\n </span>\n </div>\n <div>\n <strong>Mode:</strong>\n <span style=\"margin-left: 0.5rem;\">Parallel</span>\n </div>\n </div>\n </div>\n }\n\n <!-- Error Display -->\n @if (parallelError) {\n <div style=\"margin-top: 1rem;\">\n <mat-error>{{ parallelError }}</mat-error>\n </div>\n }\n </div>\n\n <!-- Sequential Requests Section -->\n <div>\n <div style=\"display: flex; align-items: center; gap: 1rem; margin-bottom: 1rem;\">\n <h2 style=\"flex: 1; margin: 0;\">Sequential Requests</h2>\n <button\n mat-raised-button\n color=\"accent\"\n (click)=\"onExecuteSequentialBatch()\"\n [disabled]=\"isSequentialLoading\"\n style=\"min-width: 200px;\">\n <mat-icon>timeline</mat-icon>\n <span style=\"margin-left: 8px;\">\n {{ isSequentialLoading ? 'Loading...' : 'Fetch 5 Todos' }}\n </span>\n </button>\n </div>\n\n <!-- Loading Indicator -->\n @if (isSequentialLoading) {\n <div style=\"margin: 1rem 0;\">\n <mat-progress-bar mode=\"indeterminate\"></mat-progress-bar>\n <p style=\"margin-top: 0.5rem; color: #666;\">\n Executing sequential requests (one at a time)...\n </p>\n </div>\n }\n\n <!-- Individual Request States -->\n @if (sequentialBatchStates.length > 0) {\n <div style=\"margin: 1rem 0;\">\n <mat-card appearance=\"outlined\">\n <mat-card-content>\n <div style=\"display: grid; gap: 0.5rem;\">\n @for (state of filteredSequentialStates; track state.index; let i = $index) {\n <div\n style=\"padding: 0.75rem; border-radius: 4px; border: 1px solid;\n {{ state.isPending ? 'background: #fff3e0; border-color: #ff9800;' :\n state.data ? 'background: #e3f2fd; border-color: #2196f3;' :\n 'background: #ffebee; border-color: #f44336;' }}\">\n @if (state.isPending) {\n <div style=\"display: flex; align-items: center; gap: 0.5rem; justify-content: space-between;\">\n <div style=\"display: flex; align-items: center; gap: 0.5rem;\">\n <mat-spinner diameter=\"16\" style=\"margin-right: 0.5rem;\"></mat-spinner>\n <strong>Request {{ state.index + 1 }}:</strong>\n <span>Loading...</span>\n </div>\n <span style=\"color: #ff9800; font-weight: bold;\">Pending</span>\n </div>\n } @else if (state.data) {\n <div style=\"display: flex; align-items: center; gap: 0.5rem; justify-content: space-between;\">\n <div style=\"display: flex; align-items: center; gap: 0.5rem;\">\n <mat-icon style=\"color: #2196f3; font-size: 18px;\">check_circle</mat-icon>\n <strong>Todo #{{ state.data.id }}:</strong>\n <span style=\"flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;\">\n {{ state.data.title }}\n </span>\n </div>\n </div>\n } @else {\n <div style=\"display: flex; align-items: center; gap: 0.5rem; justify-content: space-between;\">\n <div style=\"display: flex; align-items: center; gap: 0.5rem;\">\n <mat-icon style=\"color: #f44336; font-size: 18px;\">error</mat-icon>\n <strong>Request {{ state.index + 1 }}:</strong>\n <span>Failed</span>\n </div>\n <span style=\"color: #f44336; font-weight: bold;\">Error</span>\n </div>\n }\n </div>\n }\n </div>\n </mat-card-content>\n </mat-card>\n </div>\n }\n\n <!-- Execution Metrics -->\n @if (sequentialExecutionTime !== undefined) {\n <div style=\"background: #f5f5f5; padding: 1rem; border-radius: 4px; margin: 1rem 0;\">\n <div style=\"display: flex; gap: 2rem;\">\n <div>\n <strong>Execution Time:</strong>\n <span style=\"margin-left: 0.5rem; color: #4caf50;\">\n {{ formatTime(sequentialExecutionTime) }}\n </span>\n </div>\n <div>\n <strong>Mode:</strong>\n <span style=\"margin-left: 0.5rem;\">Sequential</span>\n </div>\n </div>\n </div>\n }\n </div>\n\n <!-- Stream Mode Section -->\n <div>\n <div style=\"display: flex; align-items: center; gap: 1rem; margin-bottom: 1rem;\">\n <h2 style=\"flex: 1; margin: 0;\">Stream Mode (Real-time States)</h2>\n <button\n mat-raised-button\n color=\"primary\"\n (click)=\"onExecuteStreamBatch()\"\n [disabled]=\"isStreamLoading\"\n style=\"min-width: 200px;\">\n <mat-icon>auto_awesome</mat-icon>\n <span style=\"margin-left: 8px;\">\n {{ isStreamLoading ? 'Loading...' : 'Fetch 5 Posts' }}\n </span>\n </button>\n </div>\n\n <!-- Loading Indicator -->\n @if (isStreamLoading) {\n <div style=\"margin: 1rem 0;\">\n <mat-progress-bar mode=\"indeterminate\"></mat-progress-bar>\n <p style=\"margin-top: 0.5rem; color: #666;\">\n Executing parallel requests with real-time state updates...\n </p>\n </div>\n }\n\n <!-- Progress Bar -->\n @if (streamProgress) {\n <div style=\"background: #f5f5f5; padding: 1rem; border-radius: 4px; margin: 1rem 0;\">\n <div style=\"display: flex; gap: 2rem; margin-bottom: 0.5rem;\">\n <div>\n <strong>Execution Time:</strong>\n <span style=\"margin-left: 0.5rem; color: #4caf50;\">\n {{ formatTime(streamExecutionTime || 0) }}\n </span>\n </div>\n <div>\n <strong>Progress:</strong>\n <span style=\"margin-left: 0.5rem;\">\n {{ streamProgress.completed }} / {{ streamProgress.total }} completed\n </span>\n </div>\n <div>\n <strong>Pending:</strong>\n <span style=\"margin-left: 0.5rem;\">\n {{ streamProgress.pending }}\n </span>\n </div>\n <div>\n <strong>Failed:</strong>\n <span style=\"margin-left: 0.5rem;\">\n {{ streamProgress.failed }}\n </span>\n </div>\n </div>\n <mat-progress-bar\n mode=\"determinate\"\n [value]=\"streamProgress.percent\"\n color=\"accent\">\n </mat-progress-bar>\n <p style=\"margin-top: 0.5rem; text-align: right; color: #666;\">\n {{ streamProgress.percent }}%\n </p>\n </div>\n }\n\n <!-- Error Display -->\n @if (streamError) {\n <div style=\"margin-top: 1rem;\">\n <mat-error>{{ streamError }}</mat-error>\n </div>\n }\n\n <!-- Individual Request States -->\n @if (streamBatchStates.length > 0) {\n <div style=\"margin-top: 1rem;\">\n <mat-card appearance=\"outlined\">\n <mat-card-content>\n <div style=\"display: grid; gap: 0.5rem;\">\n @for (state of filteredStreamStates; track state.index; let i = $index) {\n <div\n style=\"padding: 0.75rem; border-radius: 4px; border: 1px solid;\n {{ isPendingState(state) ? 'background: #fff3e0; border-color: #ff9800;' :\n isSuccessState(state) ? 'background: #e3f2fd; border-color: #2196f3;' :\n 'background: #ffebee; border-color: #f44336;' }}\">\n @if (isPendingState(state)) {\n <div style=\"display: flex; align-items: center; gap: 0.5rem;\">\n <mat-spinner diameter=\"16\" style=\"margin-right: 0.5rem;\"></mat-spinner>\n <strong>Request {{ state.index + 1 }}:</strong>\n <span>Loading...</span>\n </div>\n } @else if (isSuccessState(state)) {\n <div style=\"display: flex; align-items: center; gap: 0.5rem;\">\n <mat-icon style=\"color: #2196f3; font-size: 18px;\">check_circle</mat-icon>\n <strong>Post #{{ state.data.id }}:</strong>\n <span style=\"flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;\">\n {{ state.data.title }}\n </span>\n </div>\n } @else {\n <div style=\"display: flex; align-items: center; gap: 0.5rem;\">\n <mat-icon style=\"color: #f44336; font-size: 18px;\">error</mat-icon>\n <strong>Request {{ state.index + 1 }}:</strong>\n <span>Failed</span>\n <mat-chip style=\"margin-left: auto; font-size: 0.8em; background: #f44336; color: white;\">\n Error\n </mat-chip>\n </div>\n }\n </div>\n }\n </div>\n </mat-card-content>\n </mat-card>\n </div>\n }\n </div>\n\n <!-- Comparison Info -->\n <div style=\"padding: 1rem; background: whitesmoke; border-radius: 4px; border: 1px solid #ccc;\">\n <h4 style=\"margin-top: 0;\">Key Differences</h4>\n <div style=\"display: grid; grid-template-columns: 1fr 1fr; gap: 1rem;\">\n <div>\n <strong>Parallel:</strong>\n <ul style=\"margin: 0.5rem 0 0 1rem; padding: 0;\">\n <li>Faster execution (concurrent)</li>\n <li>Multiple requests simultaneously</li>\n <li>Results may complete out of order</li>\n <li>Best for: Independent requests</li>\n </ul>\n </div>\n <div>\n <strong>Sequential:</strong>\n <ul style=\"margin: 0.5rem 0 0 1rem; padding: 0;\">\n <li>Slower execution (one-by-one)</li>\n <li>Requests execute in order</li>\n <li>Results always in request order</li>\n <li>Best for: Dependent requests</li>\n </ul>\n </div>\n </div>\n </div>\n</div>\n\n</div>\n", styles: [".btn{min-width:120px}.mat-mdc-row .mat-mdc-cell{border-bottom:1px solid transparent;border-top:1px solid transparent;cursor:pointer}.mat-mdc-row:hover .mat-mdc-cell{border-color:currentColor;background-color:#f5f5f5}.container{height:400px;overflow:auto}.box{padding:10px;border:1px solid #ccc}.batch-demo-section{margin:2rem 0;padding:1.5rem;border:1px solid #e0e0e0;border-radius:8px;background:#fafafa}.batch-result-item{padding:.75rem;border-radius:4px;margin-bottom:.5rem;display:flex;align-items:center;gap:.5rem}.batch-result-item.success{background:#e8f5e9;border-left:3px solid #4caf50}.batch-result-item.error{background:#ffebee;border-left:3px solid #f44336}.execution-metrics{display:flex;gap:2rem;padding:1rem;background:#f5f5f5;border-radius:4px;margin:1rem 0}.execution-metrics>div{display:flex;align-items:center;gap:.5rem}.comparison-box{padding:1rem;background:#fff3e0;border-radius:4px;margin-top:2rem}.comparison-box h4{margin-top:0;margin-bottom:1rem}.comparison-box .comparison-grid{display:grid;grid-template-columns:1fr 1fr;gap:1rem}@media(max-width:768px){.comparison-box .comparison-grid{grid-template-columns:1fr}}.loading-indicator{margin:1rem 0}.result-card{margin-top:1rem}.result-card mat-card-content{padding:1rem}\n"] }]
|
|
8975
|
-
}], ctorParameters: () => [], propDecorators: { server: [{
|
|
8976
|
-
type: Input
|
|
8977
|
-
}], adapter: [{
|
|
8978
|
-
type: Input
|
|
8979
|
-
}], mapper: [{
|
|
8980
|
-
type: Input
|
|
8981
|
-
}], failedState: [{
|
|
8982
|
-
type: ViewChild,
|
|
8983
|
-
args: ["failedState", { static: true }]
|
|
8984
|
-
}], pollingState: [{
|
|
8985
|
-
type: ViewChild,
|
|
8986
|
-
args: ["pollingState", { static: true }]
|
|
8987
|
-
}] } });
|
|
8988
|
-
|
|
8989
|
-
class LocalStorageDemoComponent {
|
|
8990
|
-
get type() {
|
|
8991
|
-
return (this.typeControl.value) ? +this.typeControl.value : 0;
|
|
8992
|
-
}
|
|
8993
|
-
get isValid() {
|
|
8994
|
-
return this.newStoreForm.valid;
|
|
8995
|
-
}
|
|
8996
|
-
get isValidData() {
|
|
8997
|
-
return this.storageForm.valid;
|
|
8998
|
-
}
|
|
8999
|
-
constructor(configOptions) {
|
|
9000
|
-
this.configOptions = configOptions;
|
|
9001
|
-
this.fb = inject(FormBuilder);
|
|
9002
|
-
this.utils = inject(UtilsService);
|
|
9003
|
-
this.type$ = new BehaviorSubject(StorageType.GLOBAL);
|
|
9004
|
-
this.typeControl = this.fb.control(StorageType.GLOBAL.toString());
|
|
9005
|
-
this.localStorageManagerService = inject(LocalStorageManagerService);
|
|
9006
|
-
this.settings$ = this.localStorageManagerService.settings$;
|
|
9007
|
-
this.setting$ = (store) => this.localStorageManagerService.setting$(store);
|
|
9008
|
-
this.storageForm = this.fb.group({
|
|
9009
|
-
store: this.fb.control(null),
|
|
9010
|
-
type: 'local',
|
|
9011
|
-
settingType: 'local',
|
|
9012
|
-
encrypted: false,
|
|
9013
|
-
data: this.fb.control('', Validators.required),
|
|
9014
|
-
});
|
|
9015
|
-
this.newStoreForm = this.fb.group({
|
|
9016
|
-
name: this.fb.control(null, Validators.required),
|
|
9017
|
-
storage: 'local',
|
|
9018
|
-
encrypted: false,
|
|
9019
|
-
data: this.fb.control('', Validators.required),
|
|
9020
|
-
expiresIn: this.fb.control('0')
|
|
9021
|
-
});
|
|
9022
|
-
this.storeData$ = this.storageForm.get('store')?.valueChanges
|
|
9023
|
-
.pipe(switchMap((data) => {
|
|
9024
|
-
return data
|
|
9025
|
-
? this.localStorageManagerService.store$(data.name)
|
|
9026
|
-
: of('');
|
|
9027
|
-
}), tap(data => {
|
|
9028
|
-
this.storageForm.get('data')?.patchValue(data, { emitEvent: false });
|
|
9029
|
-
}));
|
|
9030
|
-
this.expiresIn = (epoch) => this.utils.expiresIn(epoch);
|
|
9031
|
-
this.isValidJSON = (str) => {
|
|
9032
|
-
try {
|
|
9033
|
-
JSON.parse(str);
|
|
9034
|
-
return true;
|
|
9035
|
-
}
|
|
9036
|
-
catch (e) {
|
|
9037
|
-
return false;
|
|
9038
|
-
}
|
|
9039
|
-
};
|
|
9040
|
-
this.displayedColumns = ['name', 'id', 'encrypted', 'expires', "option"];
|
|
9041
|
-
this.filterData = (values) => {
|
|
9042
|
-
if (!values)
|
|
9043
|
-
return [];
|
|
9044
|
-
return values.filter((item) => item.options.storage === +this.type);
|
|
9045
|
-
};
|
|
9046
|
-
this.create = false;
|
|
9047
|
-
}
|
|
9048
|
-
ngOnInit() {
|
|
9049
|
-
this.storeProps = this.configOptions?.LocalStorageOptions;
|
|
9050
|
-
this.options = this.storeProps?.options;
|
|
9051
|
-
if (this.options?.storage) {
|
|
9052
|
-
this.typeControl.patchValue(this.options.storage.toString());
|
|
9053
|
-
this.typeControl.disable();
|
|
9054
|
-
}
|
|
9055
|
-
else {
|
|
9056
|
-
this.typeControl.enable();
|
|
9057
|
-
}
|
|
9058
|
-
if (this.options?.expiresIn) {
|
|
9059
|
-
this.newStoreForm.get('expiresIn')?.patchValue(this.options.expiresIn);
|
|
9060
|
-
this.newStoreForm.get('expiresIn')?.disable();
|
|
9061
|
-
}
|
|
9062
|
-
else {
|
|
9063
|
-
this.newStoreForm.get('expiresIn')?.enable();
|
|
9064
|
-
}
|
|
9065
|
-
if (this.options?.encrypted) {
|
|
9066
|
-
this.newStoreForm.get('encrypted')?.patchValue(this.options.encrypted);
|
|
9067
|
-
this.newStoreForm.get('encrypted')?.disable();
|
|
9068
|
-
}
|
|
9069
|
-
else {
|
|
9070
|
-
this.newStoreForm.get('encrypted')?.enable();
|
|
9071
|
-
}
|
|
9072
|
-
}
|
|
9073
|
-
onCreateStore() {
|
|
9074
|
-
if (!this.isValid)
|
|
9075
|
-
return;
|
|
9076
|
-
const store = this.newStoreForm.value;
|
|
9077
|
-
if (!store.name || store.name === '')
|
|
9078
|
-
return;
|
|
9079
|
-
const options = { storage: this.type, encrypted: store.encrypted, expiresIn: store.expiresIn };
|
|
9080
|
-
this.localStorageManagerService.createStore({
|
|
9081
|
-
name: store.name,
|
|
9082
|
-
data: store.data,
|
|
9083
|
-
options: SettingOptions.adapt(options)
|
|
9084
|
-
});
|
|
9085
|
-
this.newStoreForm.reset();
|
|
9086
|
-
this.create = false;
|
|
9087
|
-
}
|
|
9088
|
-
onUpdateStore(store) {
|
|
9089
|
-
if (!this.storageForm.valid)
|
|
9090
|
-
return;
|
|
9091
|
-
const storeData = this.storageForm.value;
|
|
9092
|
-
const data = JSON.parse(storeData.data || '');
|
|
9093
|
-
const type = (storeData.type === 'local') ? StorageType.GLOBAL : StorageType.SESSION;
|
|
9094
|
-
this.localStorageManagerService.updateStore({
|
|
9095
|
-
name: store.name,
|
|
9096
|
-
data
|
|
9097
|
-
});
|
|
9098
|
-
}
|
|
9099
|
-
onSelectedRow(store) {
|
|
9100
|
-
this.store = store;
|
|
9101
|
-
this.data$ = this.localStorageManagerService.store$(store.name).pipe(map(item => JSON.stringify(item)));
|
|
9102
|
-
this.create = false;
|
|
9103
|
-
}
|
|
9104
|
-
onCreate() {
|
|
9105
|
-
this.onCancel();
|
|
9106
|
-
this.create = true;
|
|
9107
|
-
}
|
|
9108
|
-
onDelete(store) {
|
|
9109
|
-
this.localStorageManagerService.deleteStore({
|
|
9110
|
-
name: store.name,
|
|
9111
|
-
});
|
|
9112
|
-
this.onCancel();
|
|
9113
|
-
}
|
|
9114
|
-
onCancel() {
|
|
9115
|
-
this.data$ = EMPTY;
|
|
9116
|
-
this.store = null;
|
|
9117
|
-
this.create = false;
|
|
9118
|
-
}
|
|
9119
|
-
onUpdate(store, data) {
|
|
9120
|
-
this.localStorageManagerService.updateStore({
|
|
9121
|
-
name: store.name,
|
|
9122
|
-
data: JSON.parse(data)
|
|
9123
|
-
});
|
|
9124
|
-
this.onCancel();
|
|
9125
|
-
}
|
|
9126
|
-
onReset() {
|
|
9127
|
-
this.localStorageManagerService.resetStore();
|
|
9128
|
-
}
|
|
9129
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: LocalStorageDemoComponent, deps: [{ token: CONFIG_SETTINGS_TOKEN }], target: i0.ɵɵFactoryTarget.Component }); }
|
|
9130
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: LocalStorageDemoComponent, selector: "app-local-storage-demo", ngImport: i0, template: "<div style=\"margin: 2rem;\">\n\n <div style=\"display: flex; gap: 1rem\">\n <h2 style=\"padding-top: .5rem; display: flex;\">\n <div style=\"padding-top: .5rem;\">Local Storage Manager</div>\n <div>\n <button mat-icon-button [matMenuTriggerFor]=\"menu\">\n <mat-icon>info</mat-icon>\n </button>\n </div>\n </h2>\n <span style=\"flex:1\"></span>\n <div style=\"padding-top: .25rem;\">\n <mat-button-toggle-group name=\"storage\" [formControl]=\"typeControl\" (change)=\"onCancel()\">\n <mat-button-toggle value=\"0\">Local Storage</mat-button-toggle>\n <mat-button-toggle value=\"1\">Session Storage</mat-button-toggle>\n </mat-button-toggle-group>\n </div>\n <div style=\"margin-top: .25rem;\">\n <button mat-icon-button (click)=\"onCreate()\">\n <mat-icon>add</mat-icon>\n </button>\n </div>\n </div>\n\n <ng-container *ngIf=\"storeProps?.storageName || storeProps?.storageSettingsName\">\n <div style=\"display: flex; gap: .5rem; flex-direction: column; border: gray solid thin; background-color: whitesmoke; padding: 1rem;\"\n >\n <div style=\"display: flex;\">\n <div *ngIf=\"storeProps?.storageSettingsName\" style=\"flex:1\">\n Database: <b>{{ storeProps?.storageSettingsName }}</b>\n </div>\n <div *ngIf=\"storeProps?.storageName\" style=\"flex:1\">\n Data: <b>{{ storeProps?.storageName }}</b>\n </div>\n </div>\n </div>\n </ng-container>\n\n <div style=\"margin-top: 2rem;\" [formGroup]=\"newStoreForm\" *ngIf=\"create; else LIST\">\n\n <h2>Create Store</h2>\n\n <div style=\"display: flex; gap: 1rem;\">\n <div style=\"flex:1\">\n <mat-form-field appearance=\"outline\">\n <mat-label>Store Name</mat-label>\n <input matInput placeholder=\"sample\" formControlName=\"name\">\n </mat-form-field>\n </div>\n <div>\n <mat-form-field appearance=\"outline\">\n <mat-label>Expires In...</mat-label>\n <mat-select formControlName=\"expiresIn\">\n <mat-option value=\"0\">None</mat-option>\n <mat-option value=\"30s\">30 Seconds</mat-option>\n <mat-option value=\"1mn\">1 Minute</mat-option>\n <mat-option value=\"1hr\">1 Hour</mat-option>\n <mat-option value=\"1d\">1 Day</mat-option>\n </mat-select>\n </mat-form-field>\n </div>\n <div style=\"margin-top: 1rem;\">\n <mat-slide-toggle formControlName=\"encrypted\">Encrypted</mat-slide-toggle>\n </div>\n </div>\n\n <div style=\"display: flex;\">\n <mat-form-field appearance=\"outline\" style=\"flex: 1;\">\n <mat-label>Data (Must be an Object or Array of any)</mat-label>\n <textarea matInput placeholder=\"[]\" formControlName=\"data\" #json></textarea>\n </mat-form-field>\n </div>\n <mat-error *ngIf=\"!isValidJSON(json.value)\">Not Valid Data</mat-error>\n\n <div style=\"display: flex;\">\n <span style=\"flex:1\"></span>\n <button mat-stroked-button (click)=\"onCreateStore()\" [disabled]=\"!(isValid && isValidJSON(json.value))\">\n Save Store\n </button>\n </div>\n\n </div>\n\n <ng-template #LIST>\n\n <div style=\"margin-top: 1rem;\">\n <mat-divider></mat-divider>\n </div>\n{{ settings$ | async | json }}\n <div *ngIf=\"filterData(settings$ | async) as data\">\n <ng-container *ngIf=\"data.length > 0; else NO_DATA\">\n <table mat-table [dataSource]=\"data\">\n <ng-container matColumnDef=\"name\">\n <th mat-header-cell *matHeaderCellDef> Store </th>\n <td mat-cell *matCellDef=\"let element\" style=\"font-weight: bold; text-transform: uppercase;\"> {{element.name}} </td>\n </ng-container>\n\n <ng-container matColumnDef=\"id\">\n <th mat-header-cell *matHeaderCellDef> ID </th>\n <td mat-cell *matCellDef=\"let element\"> {{element.id}} </td>\n </ng-container>\n\n <ng-container matColumnDef=\"encrypted\">\n <th mat-header-cell *matHeaderCellDef style=\"text-align: center;\"> Encrypted </th>\n <td mat-cell *matCellDef=\"let element\" style=\"text-align: center;\">\n <ng-container *ngIf=\"element.options.encrypted; else NO\">YES</ng-container>\n <ng-template #NO><span style=\"color:gray\">NO</span></ng-template>\n </td>\n </ng-container>\n\n <ng-container matColumnDef=\"expires\">\n <th mat-header-cell *matHeaderCellDef style=\"text-align: center;\"> Expires </th>\n <td mat-cell *matCellDef=\"let element\" style=\"text-align: center;\">\n <ng-container *ngIf=\"element.options.expires !== 0; else NO_DATA\">\n {{expiresIn(element.options.expires)}}\n </ng-container>\n <ng-template #NO_DATA>\n \u221E\n </ng-template>\n </td>\n </ng-container>\n\n <ng-container matColumnDef=\"option\">\n <th mat-header-cell *matHeaderCellDef></th>\n <td mat-cell *matCellDef=\"let element\" style=\"padding-right: 0;\">\n <div style=\"display: flex;justify-content: flex-end;\">\n <button mat-icon-button color=\"warn\" (click)=\"onDelete(element); $event.stopPropagation()\">\n <mat-icon>close</mat-icon>\n </button>\n </div>\n </td>\n </ng-container>\n\n <tr mat-header-row *matHeaderRowDef=\"displayedColumns\"></tr>\n <tr mat-row *matRowDef=\"let row; columns: displayedColumns;\" (click)=\"onSelectedRow(row)\"></tr>\n </table>\n </ng-container>\n\n <ng-template #NO_DATA>\n <h3 style=\"margin-top: 1rem;\">No Data</h3>\n </ng-template>\n\n <div *ngIf=\"(data$ | async) as data\" style=\"margin-top: 2rem;\">\n <div style=\"margin-bottom: 1rem;\">\n <mat-divider></mat-divider>\n </div>\n\n <h3>STORE: <span style=\"font-weight: bold;\">{{ store.name | uppercase }}</span></h3>\n <h3>SETTINGS: {{ setting$(store.name) | async | json }}</h3>\n <div style=\"display: flex;\">\n <mat-form-field appearance=\"outline\" style=\"flex: 1;\">\n <mat-label>Data (Must be an Object or Array of any)</mat-label>\n <textarea matInput placeholder=\"[]\" #json [value]=\"data\"></textarea>\n </mat-form-field>\n </div>\n <mat-error *ngIf=\"!isValidJSON(json.value)\">Not Valid Data</mat-error>\n\n <div style=\"display: flex; gap: .5rem;\">\n <span style=\"flex:1\"></span>\n <button mat-stroked-button (click)=\"onCancel()\">Cancel</button>\n <button mat-stroked-button (click)=\"onUpdate(store, json.value)\" [disabled]=\"!(isValidJSON(json.value))\">Update</button>\n </div>\n </div>\n </div>\n\n </ng-template>\n\n <div style=\"margin-top: 1rem;\">\n <mat-divider></mat-divider>\n </div>\n\n <div style=\"margin-top: 1rem;\">\n <button mat-stroked-button (click)=\"onReset()\">Delete All Stores</button>\n </div>\n\n</div>\n\n\n<mat-menu #menu=\"matMenu\">\n <div style=\"padding: 1rem;\">\n <p>Please note that the LocalStorage (encryption) and management of data is dependant on initializing the APP_ID</p>\n <p>Must provide a value for <b>APP_ID</b> in AppModule->Providers</p>\n <mat-divider></mat-divider>\n <div style=\"font-size: smaller; margin-top: .5rem;\">\n <p>Example: UUID (self.crypto.randomUUID() to generate)</p>\n <div style=\"background-color: whitesmoke; padding: 1rem;\">\n {<br>\n provide: APP_ID,<br>\n useValue: \"056991ac-3537-43ab-b5b9-83edf6554eff\",<br>\n }\n </div>\n </div>\n </div>\n</mat-menu>\n", styles: [".mat-mdc-row .mat-mdc-cell{border-bottom:1px solid transparent;border-top:1px solid transparent;cursor:pointer}.mat-mdc-row:hover .mat-mdc-cell{border-color:currentColor}.demo-row-is-clicked{font-weight:700}.mat-mdc-menu-panel.mat-mdc-menu-panel{max-width:280px!important}\n"], dependencies: [{ kind: "directive", type: i1$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i2$1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2$1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i2$1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "directive", type: i2$1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i2$1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "component", type: i3.MatButton, selector: " button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button] ", exportAs: ["matButton"] }, { kind: "component", type: i3.MatIconButton, selector: "button[mat-icon-button]", exportAs: ["matButton"] }, { kind: "component", type: i4.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i4.MatLabel, selector: "mat-label" }, { kind: "directive", type: i4.MatError, selector: "mat-error, [matError]", inputs: ["id"] }, { kind: "component", type: i5.MatSelect, selector: "mat-select", inputs: ["aria-describedby", "panelClass", "disabled", "disableRipple", "tabIndex", "hideSingleSelectionIndicator", "placeholder", "required", "multiple", "disableOptionCentering", "compareWith", "value", "aria-label", "aria-labelledby", "errorStateMatcher", "typeaheadDebounceInterval", "sortComparator", "id", "panelWidth"], outputs: ["openedChange", "opened", "closed", "selectionChange", "valueChange"], exportAs: ["matSelect"] }, { kind: "component", type: i6.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "component", type: i7.MatMenu, selector: "mat-menu", inputs: ["backdropClass", "aria-label", "aria-labelledby", "aria-describedby", "xPosition", "yPosition", "overlapTrigger", "hasBackdrop", "class", "classList"], outputs: ["closed", "close"], exportAs: ["matMenu"] }, { kind: "directive", type: i7.MatMenuTrigger, selector: "[mat-menu-trigger-for], [matMenuTriggerFor]", inputs: ["mat-menu-trigger-for", "matMenuTriggerFor", "matMenuTriggerData", "matMenuTriggerRestoreFocus"], outputs: ["menuOpened", "onMenuOpen", "menuClosed", "onMenuClose"], exportAs: ["matMenuTrigger"] }, { kind: "component", type: i8.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: i9.MatTable, selector: "mat-table, table[mat-table]", exportAs: ["matTable"] }, { kind: "directive", type: i9.MatHeaderCellDef, selector: "[matHeaderCellDef]" }, { kind: "directive", type: i9.MatHeaderRowDef, selector: "[matHeaderRowDef]", inputs: ["matHeaderRowDef", "matHeaderRowDefSticky"] }, { kind: "directive", type: i9.MatColumnDef, selector: "[matColumnDef]", inputs: ["matColumnDef"] }, { kind: "directive", type: i9.MatCellDef, selector: "[matCellDef]" }, { kind: "directive", type: i9.MatRowDef, selector: "[matRowDef]", inputs: ["matRowDefColumns", "matRowDefWhen"] }, { kind: "directive", type: i9.MatHeaderCell, selector: "mat-header-cell, th[mat-header-cell]" }, { kind: "directive", type: i9.MatCell, selector: "mat-cell, td[mat-cell]" }, { kind: "component", type: i9.MatHeaderRow, selector: "mat-header-row, tr[mat-header-row]", exportAs: ["matHeaderRow"] }, { kind: "component", type: i9.MatRow, selector: "mat-row, tr[mat-row]", exportAs: ["matRow"] }, { kind: "directive", type: i10.MatButtonToggleGroup, selector: "mat-button-toggle-group", inputs: ["appearance", "name", "vertical", "value", "multiple", "disabled", "disabledInteractive", "hideSingleSelectionIndicator", "hideMultipleSelectionIndicator"], outputs: ["valueChange", "change"], exportAs: ["matButtonToggleGroup"] }, { kind: "component", type: i10.MatButtonToggle, selector: "mat-button-toggle", inputs: ["aria-label", "aria-labelledby", "id", "name", "value", "tabIndex", "disableRipple", "appearance", "checked", "disabled", "disabledInteractive"], outputs: ["change"], exportAs: ["matButtonToggle"] }, { kind: "component", type: i11.MatSlideToggle, selector: "mat-slide-toggle", inputs: ["name", "id", "labelPosition", "aria-label", "aria-labelledby", "aria-describedby", "required", "color", "disabled", "disableRipple", "tabIndex", "checked", "hideIcon", "disabledInteractive"], outputs: ["change", "toggleChange"], exportAs: ["matSlideToggle"] }, { kind: "component", type: i12.MatDivider, selector: "mat-divider", inputs: ["vertical", "inset"] }, { kind: "directive", type: i13.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly"], exportAs: ["matInput"] }, { kind: "pipe", type: i1$1.AsyncPipe, name: "async" }, { kind: "pipe", type: i1$1.UpperCasePipe, name: "uppercase" }, { kind: "pipe", type: i1$1.JsonPipe, name: "json" }], encapsulation: i0.ViewEncapsulation.None }); }
|
|
9131
|
-
}
|
|
9132
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: LocalStorageDemoComponent, decorators: [{
|
|
9133
|
-
type: Component,
|
|
9134
|
-
args: [{ selector: 'app-local-storage-demo', encapsulation: ViewEncapsulation.None, standalone: false, template: "<div style=\"margin: 2rem;\">\n\n <div style=\"display: flex; gap: 1rem\">\n <h2 style=\"padding-top: .5rem; display: flex;\">\n <div style=\"padding-top: .5rem;\">Local Storage Manager</div>\n <div>\n <button mat-icon-button [matMenuTriggerFor]=\"menu\">\n <mat-icon>info</mat-icon>\n </button>\n </div>\n </h2>\n <span style=\"flex:1\"></span>\n <div style=\"padding-top: .25rem;\">\n <mat-button-toggle-group name=\"storage\" [formControl]=\"typeControl\" (change)=\"onCancel()\">\n <mat-button-toggle value=\"0\">Local Storage</mat-button-toggle>\n <mat-button-toggle value=\"1\">Session Storage</mat-button-toggle>\n </mat-button-toggle-group>\n </div>\n <div style=\"margin-top: .25rem;\">\n <button mat-icon-button (click)=\"onCreate()\">\n <mat-icon>add</mat-icon>\n </button>\n </div>\n </div>\n\n <ng-container *ngIf=\"storeProps?.storageName || storeProps?.storageSettingsName\">\n <div style=\"display: flex; gap: .5rem; flex-direction: column; border: gray solid thin; background-color: whitesmoke; padding: 1rem;\"\n >\n <div style=\"display: flex;\">\n <div *ngIf=\"storeProps?.storageSettingsName\" style=\"flex:1\">\n Database: <b>{{ storeProps?.storageSettingsName }}</b>\n </div>\n <div *ngIf=\"storeProps?.storageName\" style=\"flex:1\">\n Data: <b>{{ storeProps?.storageName }}</b>\n </div>\n </div>\n </div>\n </ng-container>\n\n <div style=\"margin-top: 2rem;\" [formGroup]=\"newStoreForm\" *ngIf=\"create; else LIST\">\n\n <h2>Create Store</h2>\n\n <div style=\"display: flex; gap: 1rem;\">\n <div style=\"flex:1\">\n <mat-form-field appearance=\"outline\">\n <mat-label>Store Name</mat-label>\n <input matInput placeholder=\"sample\" formControlName=\"name\">\n </mat-form-field>\n </div>\n <div>\n <mat-form-field appearance=\"outline\">\n <mat-label>Expires In...</mat-label>\n <mat-select formControlName=\"expiresIn\">\n <mat-option value=\"0\">None</mat-option>\n <mat-option value=\"30s\">30 Seconds</mat-option>\n <mat-option value=\"1mn\">1 Minute</mat-option>\n <mat-option value=\"1hr\">1 Hour</mat-option>\n <mat-option value=\"1d\">1 Day</mat-option>\n </mat-select>\n </mat-form-field>\n </div>\n <div style=\"margin-top: 1rem;\">\n <mat-slide-toggle formControlName=\"encrypted\">Encrypted</mat-slide-toggle>\n </div>\n </div>\n\n <div style=\"display: flex;\">\n <mat-form-field appearance=\"outline\" style=\"flex: 1;\">\n <mat-label>Data (Must be an Object or Array of any)</mat-label>\n <textarea matInput placeholder=\"[]\" formControlName=\"data\" #json></textarea>\n </mat-form-field>\n </div>\n <mat-error *ngIf=\"!isValidJSON(json.value)\">Not Valid Data</mat-error>\n\n <div style=\"display: flex;\">\n <span style=\"flex:1\"></span>\n <button mat-stroked-button (click)=\"onCreateStore()\" [disabled]=\"!(isValid && isValidJSON(json.value))\">\n Save Store\n </button>\n </div>\n\n </div>\n\n <ng-template #LIST>\n\n <div style=\"margin-top: 1rem;\">\n <mat-divider></mat-divider>\n </div>\n{{ settings$ | async | json }}\n <div *ngIf=\"filterData(settings$ | async) as data\">\n <ng-container *ngIf=\"data.length > 0; else NO_DATA\">\n <table mat-table [dataSource]=\"data\">\n <ng-container matColumnDef=\"name\">\n <th mat-header-cell *matHeaderCellDef> Store </th>\n <td mat-cell *matCellDef=\"let element\" style=\"font-weight: bold; text-transform: uppercase;\"> {{element.name}} </td>\n </ng-container>\n\n <ng-container matColumnDef=\"id\">\n <th mat-header-cell *matHeaderCellDef> ID </th>\n <td mat-cell *matCellDef=\"let element\"> {{element.id}} </td>\n </ng-container>\n\n <ng-container matColumnDef=\"encrypted\">\n <th mat-header-cell *matHeaderCellDef style=\"text-align: center;\"> Encrypted </th>\n <td mat-cell *matCellDef=\"let element\" style=\"text-align: center;\">\n <ng-container *ngIf=\"element.options.encrypted; else NO\">YES</ng-container>\n <ng-template #NO><span style=\"color:gray\">NO</span></ng-template>\n </td>\n </ng-container>\n\n <ng-container matColumnDef=\"expires\">\n <th mat-header-cell *matHeaderCellDef style=\"text-align: center;\"> Expires </th>\n <td mat-cell *matCellDef=\"let element\" style=\"text-align: center;\">\n <ng-container *ngIf=\"element.options.expires !== 0; else NO_DATA\">\n {{expiresIn(element.options.expires)}}\n </ng-container>\n <ng-template #NO_DATA>\n \u221E\n </ng-template>\n </td>\n </ng-container>\n\n <ng-container matColumnDef=\"option\">\n <th mat-header-cell *matHeaderCellDef></th>\n <td mat-cell *matCellDef=\"let element\" style=\"padding-right: 0;\">\n <div style=\"display: flex;justify-content: flex-end;\">\n <button mat-icon-button color=\"warn\" (click)=\"onDelete(element); $event.stopPropagation()\">\n <mat-icon>close</mat-icon>\n </button>\n </div>\n </td>\n </ng-container>\n\n <tr mat-header-row *matHeaderRowDef=\"displayedColumns\"></tr>\n <tr mat-row *matRowDef=\"let row; columns: displayedColumns;\" (click)=\"onSelectedRow(row)\"></tr>\n </table>\n </ng-container>\n\n <ng-template #NO_DATA>\n <h3 style=\"margin-top: 1rem;\">No Data</h3>\n </ng-template>\n\n <div *ngIf=\"(data$ | async) as data\" style=\"margin-top: 2rem;\">\n <div style=\"margin-bottom: 1rem;\">\n <mat-divider></mat-divider>\n </div>\n\n <h3>STORE: <span style=\"font-weight: bold;\">{{ store.name | uppercase }}</span></h3>\n <h3>SETTINGS: {{ setting$(store.name) | async | json }}</h3>\n <div style=\"display: flex;\">\n <mat-form-field appearance=\"outline\" style=\"flex: 1;\">\n <mat-label>Data (Must be an Object or Array of any)</mat-label>\n <textarea matInput placeholder=\"[]\" #json [value]=\"data\"></textarea>\n </mat-form-field>\n </div>\n <mat-error *ngIf=\"!isValidJSON(json.value)\">Not Valid Data</mat-error>\n\n <div style=\"display: flex; gap: .5rem;\">\n <span style=\"flex:1\"></span>\n <button mat-stroked-button (click)=\"onCancel()\">Cancel</button>\n <button mat-stroked-button (click)=\"onUpdate(store, json.value)\" [disabled]=\"!(isValidJSON(json.value))\">Update</button>\n </div>\n </div>\n </div>\n\n </ng-template>\n\n <div style=\"margin-top: 1rem;\">\n <mat-divider></mat-divider>\n </div>\n\n <div style=\"margin-top: 1rem;\">\n <button mat-stroked-button (click)=\"onReset()\">Delete All Stores</button>\n </div>\n\n</div>\n\n\n<mat-menu #menu=\"matMenu\">\n <div style=\"padding: 1rem;\">\n <p>Please note that the LocalStorage (encryption) and management of data is dependant on initializing the APP_ID</p>\n <p>Must provide a value for <b>APP_ID</b> in AppModule->Providers</p>\n <mat-divider></mat-divider>\n <div style=\"font-size: smaller; margin-top: .5rem;\">\n <p>Example: UUID (self.crypto.randomUUID() to generate)</p>\n <div style=\"background-color: whitesmoke; padding: 1rem;\">\n {<br>\n provide: APP_ID,<br>\n useValue: \"056991ac-3537-43ab-b5b9-83edf6554eff\",<br>\n }\n </div>\n </div>\n </div>\n</mat-menu>\n", styles: [".mat-mdc-row .mat-mdc-cell{border-bottom:1px solid transparent;border-top:1px solid transparent;cursor:pointer}.mat-mdc-row:hover .mat-mdc-cell{border-color:currentColor}.demo-row-is-clicked{font-weight:700}.mat-mdc-menu-panel.mat-mdc-menu-panel{max-width:280px!important}\n"] }]
|
|
9135
|
-
}], ctorParameters: () => [{ type: ConfigOptions, decorators: [{
|
|
9136
|
-
type: Inject,
|
|
9137
|
-
args: [CONFIG_SETTINGS_TOKEN]
|
|
9138
|
-
}] }] });
|
|
9139
|
-
|
|
9140
|
-
class OIDCClient {
|
|
9141
|
-
constructor(spiffe = '', id = 0, first_name = '', last_name = '', email = '') {
|
|
9142
|
-
this.spiffe = spiffe;
|
|
9143
|
-
this.id = id;
|
|
9144
|
-
this.first_name = first_name;
|
|
9145
|
-
this.last_name = last_name;
|
|
9146
|
-
this.email = email;
|
|
9147
|
-
}
|
|
9148
|
-
static adapt(item) {
|
|
9149
|
-
return new OIDCClient(item?.spiffe, item?.id, item?.first_name, item?.last_name, item?.email);
|
|
9150
|
-
}
|
|
9151
|
-
}
|
|
9152
|
-
|
|
9153
|
-
/**
|
|
9154
|
-
* StateServiceDemo - Core state management and WebSocket connection service
|
|
9155
|
-
*
|
|
9156
|
-
* Handles:
|
|
9157
|
-
* - WebSocket connection setup
|
|
9158
|
-
* - Base HTTP state management
|
|
9159
|
-
* - Connection status and retry logic
|
|
9160
|
-
*/
|
|
9161
|
-
class StateServiceDemo extends HTTPManagerStateService {
|
|
9162
|
-
constructor() {
|
|
9163
|
-
super(ApiRequest.adapt({
|
|
9164
|
-
server: '',
|
|
9165
|
-
retry: RetryOptions.adapt({
|
|
9166
|
-
times: 3,
|
|
9167
|
-
delay: 1,
|
|
9168
|
-
}),
|
|
9169
|
-
adapter: OIDCClient.adapt,
|
|
9170
|
-
}), DataType.ARRAY, DatabaseStorage.adapt({
|
|
9171
|
-
table: 'ws_demo_data',
|
|
9172
|
-
expiresIn: '5mn',
|
|
9173
|
-
}));
|
|
9174
|
-
this.attempts$ = this.wsRetryAttempts$;
|
|
9175
|
-
this.nextRetry$ = this.wsNextRetry$;
|
|
9176
|
-
}
|
|
9177
|
-
/**
|
|
9178
|
-
* Initialize WebSocket connection with server configuration
|
|
9179
|
-
* @param server - Backend server URL
|
|
9180
|
-
* @param wsServer - WebSocket server URL
|
|
9181
|
-
* @param jwtToken - JWT authentication token
|
|
9182
|
-
* @param user - User information
|
|
9183
|
-
* @param path - Path for constructing channel name (e.g., ['ai','tests'])
|
|
9184
|
-
*/
|
|
9185
|
-
updateConnection(server, wsServer, jwtToken, user, path = ['ai', 'tests']) {
|
|
9186
|
-
// Construct channel name from path: ['ai','tests'] → 'ai/tests'
|
|
9187
|
-
const channelId = path.join('/');
|
|
9188
|
-
this.setApiRequestOptions({
|
|
9189
|
-
server,
|
|
9190
|
-
path, // Set the path for HTTP requests
|
|
9191
|
-
retry: RetryOptions.adapt({
|
|
9192
|
-
times: 3,
|
|
9193
|
-
delay: 1,
|
|
9194
|
-
}),
|
|
9195
|
-
adapter: OIDCClient.adapt,
|
|
9196
|
-
ws: {
|
|
9197
|
-
id: channelId, // Use path-based channel ID instead of hardcoded 'USERS123'
|
|
9198
|
-
wsServer,
|
|
9199
|
-
jwtToken,
|
|
9200
|
-
user,
|
|
9201
|
-
retry: RetryOptions.adapt({
|
|
9202
|
-
times: 10,
|
|
9203
|
-
delay: 5,
|
|
9204
|
-
}),
|
|
9205
|
-
}
|
|
9206
|
-
});
|
|
9207
|
-
this.userAction$.subscribe(user => {
|
|
9208
|
-
console.log('User Action:', user);
|
|
9209
|
-
});
|
|
9210
|
-
}
|
|
9211
|
-
/**
|
|
9212
|
-
* Get all available channels
|
|
9213
|
-
*/
|
|
9214
|
-
getAllChannels() {
|
|
9215
|
-
this.httpManagerService.getAllChannels();
|
|
9216
|
-
}
|
|
9217
|
-
/**
|
|
9218
|
-
* Subscribe to a channel
|
|
9219
|
-
*/
|
|
9220
|
-
subscribeToChannel(channel) {
|
|
9221
|
-
super.subscribeToChannel(channel);
|
|
9222
|
-
}
|
|
9223
|
-
/**
|
|
9224
|
-
* Unsubscribe from a channel
|
|
9225
|
-
*/
|
|
9226
|
-
unsubscribeFromChannel(channel) {
|
|
9227
|
-
super.unsubscribeFromChannel(channel);
|
|
9228
|
-
}
|
|
9229
|
-
// =====================
|
|
9230
|
-
// Notification Channel Methods (MES- prefix)
|
|
9231
|
-
// These override methods from HTTPManagerStateService
|
|
9232
|
-
// =====================
|
|
9233
|
-
/**
|
|
9234
|
-
* Create a notification channel (MES- prefix)
|
|
9235
|
-
*/
|
|
9236
|
-
createNotificationChannel(channel) {
|
|
9237
|
-
super.createNotificationChannel(channel);
|
|
9238
|
-
}
|
|
9239
|
-
/**
|
|
9240
|
-
* Get all notification channels list (in-memory)
|
|
9241
|
-
*/
|
|
9242
|
-
getNotificationChannels() {
|
|
9243
|
-
super.getNotificationChannels();
|
|
9244
|
-
}
|
|
9245
|
-
/**
|
|
9246
|
-
* Get today's notification channels from database
|
|
9247
|
-
* Returns unique channels that have notifications posted today
|
|
9248
|
-
*/
|
|
9249
|
-
getTodaysNotificationChannels() {
|
|
9250
|
-
super.getTodaysNotificationChannels();
|
|
9251
|
-
}
|
|
9252
|
-
/**
|
|
9253
|
-
* Define and load previous day's notification channels from database
|
|
9254
|
-
* Creates channels in memory and broadcasts updated list
|
|
9255
|
-
*/
|
|
9256
|
-
definePreviousNotificationChannels() {
|
|
9257
|
-
const options = ApiRequest.adapt({
|
|
9258
|
-
path: ['notifications', 'channels', 'define-previous']
|
|
9259
|
-
});
|
|
9260
|
-
this.httpManagerService.postRequest({}, options).subscribe({
|
|
9261
|
-
next: (response) => {
|
|
9262
|
-
console.log('📋 Previous day channels loaded:', response);
|
|
9263
|
-
// The server will broadcast updated channel list to all WebSocket clients
|
|
9264
|
-
},
|
|
9265
|
-
error: (error) => {
|
|
9266
|
-
console.error('❌ Failed to load previous day channels:', error);
|
|
9267
|
-
}
|
|
9268
|
-
});
|
|
9269
|
-
}
|
|
9270
|
-
/**
|
|
9271
|
-
* Subscribe to notification channel with optional date filters
|
|
9272
|
-
*/
|
|
9273
|
-
subscribeToNotificationChannel(channel, options, user) {
|
|
9274
|
-
super.subscribeToNotificationChannel(channel, options, user);
|
|
9275
|
-
}
|
|
9276
|
-
/**
|
|
9277
|
-
* Unsubscribe from notification channel
|
|
9278
|
-
*/
|
|
9279
|
-
unsubscribeFromNotificationChannel(channel) {
|
|
9280
|
-
super.unsubscribeFromNotificationChannel(channel);
|
|
9281
|
-
}
|
|
9282
|
-
/**
|
|
9283
|
-
* Send a notification to a channel
|
|
9284
|
-
*/
|
|
9285
|
-
sendNotification(channel, content) {
|
|
9286
|
-
super.sendNotification(channel, content);
|
|
9287
|
-
}
|
|
9288
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: StateServiceDemo, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
9289
|
-
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: StateServiceDemo, providedIn: 'root' }); }
|
|
9290
|
-
}
|
|
9291
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: StateServiceDemo, decorators: [{
|
|
9292
|
-
type: Injectable,
|
|
9293
|
-
args: [{
|
|
9294
|
-
providedIn: 'root'
|
|
9295
|
-
}]
|
|
9296
|
-
}], ctorParameters: () => [] });
|
|
9297
|
-
|
|
9298
|
-
/**
|
|
9299
|
-
* MessageServiceDemo - Channel messaging service (PUB- prefix channels)
|
|
9300
|
-
*
|
|
9301
|
-
* Handles:
|
|
9302
|
-
* - Sending messages to channels
|
|
9303
|
-
* - Channel subscription for messaging
|
|
9304
|
-
* - Real-time message broadcast
|
|
9305
|
-
*/
|
|
9306
|
-
class MessageServiceDemo {
|
|
9307
|
-
constructor() {
|
|
9308
|
-
this.stateService = inject(StateServiceDemo);
|
|
9309
|
-
// Expose observables from state service
|
|
9310
|
-
this.channels$ = this.stateService.channels$;
|
|
9311
|
-
this.subscribedChannels$ = this.stateService.subscribedChannels$;
|
|
9312
|
-
this.communicationMessages$ = this.stateService.communicationMessages$;
|
|
9313
|
-
this.latestCommunicationMessages$ = this.stateService.latestCommunicationMessages$;
|
|
9314
|
-
this.connectionStatus$ = this.stateService.connectionStatus$;
|
|
9315
|
-
this.user$ = this.stateService.user$;
|
|
9316
|
-
this.data$ = this.stateService.data$;
|
|
9317
|
-
}
|
|
9318
|
-
/**
|
|
9319
|
-
* Helper to ensure channel has PUB- prefix
|
|
9320
|
-
*/
|
|
9321
|
-
toPublicChannel(channel) {
|
|
9322
|
-
return channel.startsWith('PUB-') ? channel : `PUB-${channel}`;
|
|
9323
|
-
}
|
|
9324
|
-
/**
|
|
9325
|
-
* Helper to strip PUB- prefix for display
|
|
9326
|
-
*/
|
|
9327
|
-
fromPublicChannel(channel) {
|
|
9328
|
-
return channel.startsWith('PUB-') ? channel.replace('PUB-', '') : channel;
|
|
9329
|
-
}
|
|
9330
|
-
/**
|
|
9331
|
-
* Create a new public channel
|
|
9332
|
-
*/
|
|
9333
|
-
createChannel(channel) {
|
|
9334
|
-
const publicChannel = this.toPublicChannel(channel);
|
|
9335
|
-
this.stateService.createChannel(publicChannel);
|
|
9336
|
-
}
|
|
9337
|
-
/**
|
|
9338
|
-
* Get all available channels
|
|
9339
|
-
*/
|
|
9340
|
-
getAllChannels() {
|
|
9341
|
-
this.stateService.getAllChannels();
|
|
9342
|
-
}
|
|
9343
|
-
/**
|
|
9344
|
-
* Subscribe to a channel to receive messages
|
|
9345
|
-
*/
|
|
9346
|
-
subscribeToChannel(channel) {
|
|
9347
|
-
const publicChannel = this.toPublicChannel(channel);
|
|
9348
|
-
this.stateService.subscribeToChannel(publicChannel);
|
|
9349
|
-
}
|
|
9350
|
-
/**
|
|
9351
|
-
* Unsubscribe from a channel
|
|
9352
|
-
*/
|
|
9353
|
-
unsubscribeFromChannel(channel) {
|
|
9354
|
-
const publicChannel = this.toPublicChannel(channel);
|
|
9355
|
-
this.stateService.unsubscribeFromChannel(publicChannel);
|
|
9356
|
-
}
|
|
9357
|
-
/**
|
|
9358
|
-
* Send a message to one or more channels
|
|
9359
|
-
*/
|
|
9360
|
-
sendMessage(data, channels) {
|
|
9361
|
-
const publicChannels = channels?.map(ch => this.toPublicChannel(ch));
|
|
9362
|
-
console.log('sendMessage', data, 'to channels:', publicChannels);
|
|
9363
|
-
this.stateService.wsMessaging(data, publicChannels);
|
|
9364
|
-
}
|
|
9365
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: MessageServiceDemo, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
9366
|
-
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: MessageServiceDemo, providedIn: 'root' }); }
|
|
9367
|
-
}
|
|
9368
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: MessageServiceDemo, decorators: [{
|
|
9369
|
-
type: Injectable,
|
|
9370
|
-
args: [{
|
|
9371
|
-
providedIn: 'root'
|
|
9372
|
-
}]
|
|
9373
|
-
}] });
|
|
9374
|
-
|
|
9375
|
-
/**
|
|
9376
|
-
* NotificationServiceDemo - Notification channel service (MES- prefix channels)
|
|
9377
|
-
*
|
|
9378
|
-
* Handles:
|
|
9379
|
-
* - Creating notification channels
|
|
9380
|
-
* - Subscribing to notifications with date filters
|
|
9381
|
-
* - Sending notifications (persisted to database)
|
|
9382
|
-
* - Receiving real-time and historical notifications
|
|
9383
|
-
*/
|
|
9384
|
-
class NotificationServiceDemo {
|
|
9385
|
-
constructor() {
|
|
9386
|
-
this.stateService = inject(StateServiceDemo);
|
|
9387
|
-
// Notification channels from state service (in-memory - contains MES- channels from server)
|
|
9388
|
-
this.notificationChannels$ = this.stateService.notificationChannels$;
|
|
9389
|
-
// Today's notification channels from database (channels with data for today)
|
|
9390
|
-
this.todaysNotificationChannels$ = this.stateService.todaysNotificationChannels$;
|
|
9391
|
-
this.subscribedNotificationChannelsSubject = new BehaviorSubject(new Set());
|
|
9392
|
-
this.subscribedNotificationChannels$ = this.subscribedNotificationChannelsSubject.asObservable();
|
|
9393
|
-
// Notification messages from state service
|
|
9394
|
-
this.notificationMessages$ = this.stateService.notificationMessages$;
|
|
9395
|
-
// Latest notification from state service
|
|
9396
|
-
this.latestNotification$ = this.stateService.latestNotification$;
|
|
9397
|
-
// Expose connection status from state service
|
|
9398
|
-
this.connectionStatus$ = this.stateService.connectionStatus$;
|
|
9399
|
-
}
|
|
9400
|
-
/**
|
|
9401
|
-
* Create a notification channel
|
|
9402
|
-
* NOTE: MES- prefix is added automatically by HTTPManagerStateService
|
|
9403
|
-
*/
|
|
9404
|
-
createNotificationChannel(channel) {
|
|
9405
|
-
console.log('📢 createNotificationChannel:', channel);
|
|
9406
|
-
this.stateService.createNotificationChannel(channel);
|
|
9407
|
-
}
|
|
9408
|
-
/**
|
|
9409
|
-
* Get all notification channels (in-memory)
|
|
9410
|
-
*/
|
|
9411
|
-
getNotificationChannels() {
|
|
9412
|
-
console.log('📢 getNotificationChannels');
|
|
9413
|
-
this.stateService.getNotificationChannels();
|
|
9414
|
-
}
|
|
9415
|
-
/**
|
|
9416
|
-
* Get today's notification channels from database
|
|
9417
|
-
* Returns unique channels that have notifications posted today
|
|
9418
|
-
*/
|
|
9419
|
-
getTodaysNotificationChannels() {
|
|
9420
|
-
console.log('📢 getTodaysNotificationChannels (from DB)');
|
|
9421
|
-
this.stateService.getTodaysNotificationChannels();
|
|
9422
|
-
}
|
|
9423
|
-
/**
|
|
9424
|
-
* Define and load previous day's notification channels from database
|
|
9425
|
-
* Creates channels in memory and broadcasts updated list
|
|
9426
|
-
*/
|
|
9427
|
-
definePreviousNotificationChannels() {
|
|
9428
|
-
console.log('📢 definePreviousNotificationChannels');
|
|
9429
|
-
this.stateService.definePreviousNotificationChannels();
|
|
9430
|
-
}
|
|
9431
|
-
/**
|
|
9432
|
-
* Subscribe to a notification channel with optional date filter
|
|
9433
|
-
* NOTE: MES- prefix is added automatically by HTTPManagerStateService
|
|
9434
|
-
* @param channel Base channel name (without MES- prefix)
|
|
9435
|
-
* @param options { startEpoch?, endEpoch? }
|
|
9436
|
-
* @param user User info for subscription
|
|
9437
|
-
*/
|
|
9438
|
-
subscribeToNotificationChannel(channel, options, user) {
|
|
9439
|
-
// Add MES- prefix for local tracking (to match what state service sends)
|
|
9440
|
-
const prefixedChannel = channel.startsWith('MES-') ? channel : `MES-${channel}`;
|
|
9441
|
-
console.log('📢 subscribeToNotificationChannel:', prefixedChannel, options);
|
|
9442
|
-
// Add to local subscribed channels immediately for UI feedback
|
|
9443
|
-
const currentSubs = this.subscribedNotificationChannelsSubject.value;
|
|
9444
|
-
currentSubs.add(prefixedChannel);
|
|
9445
|
-
this.subscribedNotificationChannelsSubject.next(new Set(currentSubs));
|
|
9446
|
-
// Send subscription request to server (state service adds MES- prefix)
|
|
9447
|
-
this.stateService.subscribeToNotificationChannel(channel, options, user);
|
|
9448
|
-
}
|
|
9449
|
-
/**
|
|
9450
|
-
* Unsubscribe from a notification channel
|
|
9451
|
-
* NOTE: MES- prefix is added automatically by HTTPManagerStateService
|
|
9452
|
-
*/
|
|
9453
|
-
unsubscribeFromNotificationChannel(channel) {
|
|
9454
|
-
// Add MES- prefix for local tracking (to match what state service sends)
|
|
9455
|
-
const prefixedChannel = channel.startsWith('MES-') ? channel : `MES-${channel}`;
|
|
9456
|
-
console.log('📢 unsubscribeFromNotificationChannel:', prefixedChannel);
|
|
9457
|
-
// Remove from local subscribed channels immediately for UI feedback
|
|
9458
|
-
const currentSubs = this.subscribedNotificationChannelsSubject.value;
|
|
9459
|
-
currentSubs.delete(prefixedChannel);
|
|
9460
|
-
this.subscribedNotificationChannelsSubject.next(new Set(currentSubs));
|
|
9461
|
-
// Send unsubscription request to server (state service adds MES- prefix)
|
|
9462
|
-
this.stateService.unsubscribeFromNotificationChannel(channel);
|
|
9463
|
-
}
|
|
9464
|
-
/**
|
|
9465
|
-
* Send a notification to a channel
|
|
9466
|
-
* NOTE: MES- prefix is added automatically by HTTPManagerStateService
|
|
9467
|
-
*/
|
|
9468
|
-
sendNotification(channel, content, user) {
|
|
9469
|
-
const notificationContent = {
|
|
9470
|
-
sessionId: {
|
|
9471
|
-
id: user?.id,
|
|
9472
|
-
ldap: user?.ldap,
|
|
9473
|
-
name: user?.name,
|
|
9474
|
-
email: user?.email,
|
|
9475
|
-
color: user?.color,
|
|
9476
|
-
},
|
|
9477
|
-
...content
|
|
9478
|
-
};
|
|
9479
|
-
console.log('📢 sendNotification:', channel, notificationContent);
|
|
9480
|
-
// State service adds MES- prefix
|
|
9481
|
-
this.stateService.sendNotification(channel, notificationContent);
|
|
9482
|
-
}
|
|
9483
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: NotificationServiceDemo, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
9484
|
-
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: NotificationServiceDemo, providedIn: 'root' }); }
|
|
9485
|
-
}
|
|
9486
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: NotificationServiceDemo, decorators: [{
|
|
9487
|
-
type: Injectable,
|
|
9488
|
-
args: [{
|
|
9489
|
-
providedIn: 'root'
|
|
9490
|
-
}]
|
|
9491
|
-
}] });
|
|
9492
|
-
|
|
9493
|
-
class StateDataRequestService extends HTTPManagerStateService {
|
|
9494
|
-
constructor() {
|
|
9495
|
-
super(ApiRequest.adapt({
|
|
9496
|
-
server: '',
|
|
9497
|
-
retry: RetryOptions.adapt({
|
|
9498
|
-
times: 3,
|
|
9499
|
-
delay: 1,
|
|
9500
|
-
}),
|
|
9501
|
-
adapter: OIDCClient.adapt,
|
|
9502
|
-
ws: {
|
|
9503
|
-
id: ``,
|
|
9504
|
-
// wsChannels: ['allChannels'],
|
|
9505
|
-
// access: ['read,write,delete,update'],
|
|
9506
|
-
wsServer: '',
|
|
9507
|
-
jwtToken: '',
|
|
9508
|
-
user: UserData.adapt(),
|
|
9509
|
-
retry: RetryOptions.adapt({
|
|
9510
|
-
times: 3,
|
|
9511
|
-
delay: 10,
|
|
9512
|
-
}),
|
|
9513
|
-
}
|
|
9514
|
-
}), DataType.ARRAY, DatabaseStorage.adapt({
|
|
9515
|
-
table: 'ws_demo_data',
|
|
9516
|
-
expiresIn: '5mn',
|
|
9517
|
-
}));
|
|
9518
|
-
this.attempts$ = this.wsRetryAttempts$;
|
|
9519
|
-
this.nextRetry$ = this.wsNextRetry$;
|
|
9520
|
-
this.path = ['ai', 'tests'];
|
|
9521
|
-
}
|
|
9522
|
-
updateConnection(server, wsServer, jwtToken, user, path = []) {
|
|
9523
|
-
this.path = path;
|
|
9524
|
-
this.setApiRequestOptions({
|
|
9525
|
-
server,
|
|
9526
|
-
retry: RetryOptions.adapt({
|
|
9527
|
-
times: 3,
|
|
9528
|
-
delay: 1,
|
|
9529
|
-
}),
|
|
9530
|
-
adapter: OIDCClient.adapt,
|
|
9531
|
-
ws: {
|
|
9532
|
-
id: this.path.join('/'),
|
|
9533
|
-
wsServer,
|
|
9534
|
-
jwtToken,
|
|
9535
|
-
user, // general info about user
|
|
9536
|
-
retry: RetryOptions.adapt({
|
|
9537
|
-
times: 3,
|
|
9538
|
-
delay: 10,
|
|
9539
|
-
}),
|
|
9540
|
-
}
|
|
9541
|
-
});
|
|
9542
|
-
}
|
|
9543
|
-
addData() {
|
|
9544
|
-
const num = RandomNumber(1000, 9999);
|
|
9545
|
-
const newData = {
|
|
9546
|
-
"spiffe": `sample.com/developer/${num}`,
|
|
9547
|
-
"last_name": "boni",
|
|
9548
|
-
"email": "mikeboni@hotmail.com",
|
|
9549
|
-
"first_name": "mike"
|
|
9550
|
-
};
|
|
9551
|
-
this.createRecord(newData, RequestOptions.adapt({ path: this.path }));
|
|
9552
|
-
}
|
|
9553
|
-
sendMessage(data) {
|
|
9554
|
-
console.log('sendMessage', data);
|
|
9555
|
-
this.wsMessaging(data);
|
|
9556
|
-
}
|
|
9557
|
-
getData() {
|
|
9558
|
-
this.fetchRecords(RequestOptions.adapt({ path: this.path }));
|
|
9559
|
-
}
|
|
9560
|
-
updateData(data) {
|
|
9561
|
-
const sampleOptions = RequestOptions.adapt({ path: [...this.path, data.id] });
|
|
9562
|
-
console.log('updateData', sampleOptions);
|
|
9563
|
-
this.updateRecord(data, sampleOptions);
|
|
9564
|
-
}
|
|
9565
|
-
deleteData(data) {
|
|
9566
|
-
const sampleOptions = RequestOptions.adapt({ path: [...this.path, data.id] });
|
|
9567
|
-
console.log('deleteData', sampleOptions);
|
|
9568
|
-
this.deleteRecord(sampleOptions);
|
|
9569
|
-
}
|
|
9570
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: StateDataRequestService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
9571
|
-
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: StateDataRequestService, providedIn: 'root' }); }
|
|
9572
|
-
}
|
|
9573
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: StateDataRequestService, decorators: [{
|
|
9574
|
-
type: Injectable,
|
|
9575
|
-
args: [{
|
|
9576
|
-
providedIn: 'root'
|
|
9577
|
-
}]
|
|
9578
|
-
}], ctorParameters: () => [] });
|
|
9579
|
-
|
|
9580
|
-
class WsDataControlComponent {
|
|
9581
|
-
constructor() {
|
|
9582
|
-
this.path = ['ai', 'tests'];
|
|
9583
|
-
this.stateDataRequestService = inject(StateDataRequestService);
|
|
9584
|
-
this.webSocketMessageService = inject(WebSocketMessageService);
|
|
9585
|
-
this.user$ = this.stateDataRequestService.user$;
|
|
9586
|
-
this.users$ = this.stateDataRequestService.userList$;
|
|
9587
|
-
this.userAction$ = this.stateDataRequestService.userAction$
|
|
9588
|
-
.pipe(switchMap$1((action) => timer(3 * 1000).pipe(map$1(() => null), startWith$1(action))));
|
|
9589
|
-
this.data$ = this.stateDataRequestService.data$;
|
|
9590
|
-
this.isUser = (user, userItem) => {
|
|
9591
|
-
return user.sessionId === userItem.sessionId;
|
|
9592
|
-
};
|
|
9593
|
-
}
|
|
9594
|
-
ngOnInit() {
|
|
9595
|
-
this.stateDataRequestService.updateConnection(this.server, this.wsServer, this.jwtToken, this.user, this.path);
|
|
9596
|
-
this.stateDataRequestService.getData();
|
|
9597
|
-
}
|
|
9598
|
-
onGetData() {
|
|
9599
|
-
this.stateDataRequestService.getData();
|
|
9600
|
-
}
|
|
9601
|
-
onAddData() {
|
|
9602
|
-
this.stateDataRequestService.addData();
|
|
9603
|
-
}
|
|
9604
|
-
onUpdateData(data) {
|
|
9605
|
-
const num = RandomNumber(1000, 9999);
|
|
9606
|
-
const newData = {
|
|
9607
|
-
"spiffe": `wavecoders.com/developer/${num}`,
|
|
9608
|
-
"id": 63,
|
|
9609
|
-
"last_name": "boni",
|
|
9610
|
-
"email": "mikeboni@hotmail.com",
|
|
9611
|
-
"first_name": "mike"
|
|
9612
|
-
};
|
|
9613
|
-
this.stateDataRequestService.updateData(newData);
|
|
9614
|
-
}
|
|
9615
|
-
onRemoveData(data) {
|
|
9616
|
-
const lastRec = data[data.length - 1];
|
|
9617
|
-
this.stateDataRequestService.deleteData(lastRec);
|
|
9618
|
-
}
|
|
9619
|
-
/**
|
|
9620
|
-
* Test direct state message via WebSocketMessageService
|
|
9621
|
-
* Sends an UPDATE message that should trigger fetchRecord() in the state manager
|
|
9622
|
-
*
|
|
9623
|
-
* @param recordId - The record ID to update (default: 63)
|
|
9624
|
-
* @param useFakeSessionId - If true, uses a fake sessionId to avoid filtering (default: true)
|
|
9625
|
-
* @param customSessionId - Optional custom sessionId (overrides useFakeSessionId if provided)
|
|
9626
|
-
*/
|
|
9627
|
-
onTestDirectStateMessage(recordId = 63, useFakeSessionId = true, customSessionId) {
|
|
9628
|
-
// Get actual sessionId from sessionStorage (stored by HTTPManagerStateService)
|
|
9629
|
-
const actualSessionId = sessionStorage.getItem('WSID') || 'unknown';
|
|
9630
|
-
// Determine sessionId to use
|
|
9631
|
-
let sessionId;
|
|
9632
|
-
if (customSessionId) {
|
|
9633
|
-
sessionId = customSessionId;
|
|
9634
|
-
}
|
|
9635
|
-
else if (useFakeSessionId) {
|
|
9636
|
-
sessionId = `test-${Date.now()}`;
|
|
9637
|
-
}
|
|
9638
|
-
else {
|
|
9639
|
-
// Use actual sessionId (will be filtered out by receiver)
|
|
9640
|
-
sessionId = actualSessionId;
|
|
9641
|
-
}
|
|
9642
|
-
// Build StateMessage with same structure as CRUD operations
|
|
9643
|
-
const stateMessage = {
|
|
9644
|
-
sessionId: {
|
|
9645
|
-
id: sessionId,
|
|
9646
|
-
name: useFakeSessionId ? 'Test Sender' : (this.user?.name || 'Current User')
|
|
9647
|
-
},
|
|
9648
|
-
content: {
|
|
9649
|
-
method: 'UPDATE',
|
|
9650
|
-
path: ['ai', 'tests', recordId],
|
|
9651
|
-
user: {
|
|
9652
|
-
id: sessionId,
|
|
9653
|
-
name: useFakeSessionId ? 'Test Sender' : (this.user?.name || 'Current User')
|
|
9654
|
-
}
|
|
9655
|
-
}
|
|
9656
|
-
};
|
|
9657
|
-
console.log('🧪 [TEST] Sending direct state message:', {
|
|
9658
|
-
recordId,
|
|
9659
|
-
sessionId,
|
|
9660
|
-
actualSessionId,
|
|
9661
|
-
useFakeSessionId,
|
|
9662
|
-
stateMessage
|
|
9663
|
-
});
|
|
9664
|
-
const success = this.webSocketMessageService.sendStateMessage(['ai', 'tests'], stateMessage);
|
|
9665
|
-
console.log('🧪 [TEST] Message sent:', {
|
|
9666
|
-
success,
|
|
9667
|
-
actualSessionId,
|
|
9668
|
-
sentSessionId: sessionId,
|
|
9669
|
-
expectedBehavior: success
|
|
9670
|
-
? 'Server will broadcast to SYS-ai/tests subscribers'
|
|
9671
|
-
: 'Failed to send (check connection)',
|
|
9672
|
-
receiverBehavior: sessionId !== actualSessionId
|
|
9673
|
-
? 'Receiver will process message and call fetchRecord()'
|
|
9674
|
-
: 'Receiver will filter out (sessionId match)'
|
|
9675
|
-
});
|
|
9676
|
-
}
|
|
9677
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: WsDataControlComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
9678
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: WsDataControlComponent, selector: "app-ws-data-control", inputs: { server: "server", wsServer: "wsServer", jwtToken: "jwtToken", user: "user", path: "path" }, ngImport: i0, template: "@if ((data$ | async); as data) {\n <div style=\"margin: 1rem;\">\n @if ((users$ |async); as users) {\n <div>\n @if (users.length > 0) {\n <h3 style=\"margin: 0;\">Connected Users</h3>\n } @else {\n <h3 style=\"margin: 0;\">No Users</h3>\n }\n <mat-chip-set>\n @for (user of users; track $index) {\n <mat-chip\n [class.user-chip--primary]=\"isUser(user, (user$ | async))\"\n [style.color]=\"isUser(user, (user$ | async)) ? '#fff' : null\"\n [disableRipple]=\"true\"\n >\n {{ user.name || user.ldap || user.id || 'Anonymous' }}\n </mat-chip>\n }\n </mat-chip-set>\n </div>\n }\n <div style=\"margin-top: 1rem; margin-bottom: 1rem;\">\n <mat-divider></mat-divider>\n </div>\n\n <div class=\"box\" style=\"margin-bottom: 1rem;\" *ngIf=\"(userAction$ | async) as userAction\">\n {{ userAction?.content?.user?.name }} has {{ userAction?.content?.method }}\n </div>\n\n <h3 style=\"margin: 0;\">Data Actions</h3>\n <div style=\"display: flex; gap: 1rem; margin-bottom: 1rem;\">\n <button mat-stroked-button (click)=\"onGetData()\">Get Data</button>\n <div style=\"flex:1\"></div>\n <button mat-stroked-button color=\"accent\" (click)=\"onUpdateData(data)\">Update Data</button>\n <button mat-stroked-button color=\"warn\" (click)=\"onRemoveData(data)\">Remove Data</button>\n <button mat-stroked-button color=\"primary\" (click)=\"onAddData()\">Add Data</button>\n </div>\n\n <h3 style=\"margin: 0; margin-top: 1rem;\">WebSocketMessageService Test</h3>\n\n <p style=\"font-size: 0.875rem; color: #666; margin: 0;\">\n <strong>Note:</strong> \"Fake SessionId\" will be received and processed. \"Current SessionId\" will be filtered out (self-message).\n @if (data.length > 0) {\n <div>\n <table mat-table [dataSource]=\"data\" style=\"border: 1px solid grey;\">\n <ng-container matColumnDef=\"id\">\n <th mat-header-cell *matHeaderCellDef> ID </th>\n <td mat-cell *matCellDef=\"let element\"> {{element.id}} </td>\n </ng-container>\n <ng-container matColumnDef=\"spiffe\">\n <th mat-header-cell *matHeaderCellDef> Spiffe </th>\n <td mat-cell *matCellDef=\"let element\"> {{element.spiffe}} </td>\n </ng-container>\n <ng-container matColumnDef=\"name\">\n <th mat-header-cell *matHeaderCellDef> Name </th>\n <td mat-cell *matCellDef=\"let element\"> {{element.first_name}} {{element.last_name}}</td>\n </ng-container>\n <ng-container matColumnDef=\"email\">\n <th mat-header-cell *matHeaderCellDef> Email </th>\n <td mat-cell *matCellDef=\"let element\"> {{element.email}} </td>\n </ng-container>\n <tr mat-header-row *matHeaderRowDef=\"['id', 'spiffe', 'name', 'email']\"></tr>\n <tr mat-row *matRowDef=\"let row; columns: ['id', 'spiffe', 'name', 'email'];\"></tr>\n </table>\n <div style=\"border: 1px solid grey; padding: .5rem; border-top: none;\">\n <h3 style=\"margin: 0;\">Total Records {{ data.length }}</h3>\n </div>\n </div>\n } @else {\n <div style=\"margin-top: 1rem; font-style: italic;\">\n No Data Available\n </div>\n }\n\n <div style=\"margin-bottom: 1rem; margin-top: 1rem;\">\n <mat-divider></mat-divider>\n </div>\n\n <div style=\"display: flex; gap: 0.5rem; margin-bottom: 1rem;\">\n <button mat-flat-button color=\"primary\" (click)=\"onTestDirectStateMessage(63, true)\">\n Test UPDATE (Fake Id)\n </button>\n <button mat-stroked-button (click)=\"onTestDirectStateMessage(63, false)\">\n Test UPDATE (Current Id)\n </button>\n <button mat-stroked-button (click)=\"onTestDirectStateMessage(63, false, 'custom-session-123')\">\n Test UPDATE (Custom Id)\n </button>\n </div>\n\n </div>\n}\n\n", styles: [".user-chip--primary{background-color:var(--mdc-theme-primary, var(--md-sys-color-primary, #3f51b5))!important;color:#fff!important;--mdc-evolution-chip-container-color: var(--mdc-theme-primary, var(--md-sys-color-primary, #3f51b5));--mdc-evolution-chip-label-text-color: #fff}.user-chip--primary :is(.mdc-evolution-chip__text-label,.mdc-evolution-chip__action,.mdc-evolution-chip__cell,.mat-mdc-chip-action-label){color:#fff!important}.user-chip--primary,.user-chip--primary *{color:#fff!important}:host ::ng-deep .user-chip--primary{background-color:var(--mdc-theme-primary, var(--md-sys-color-primary, #3f51b5))!important;color:#fff!important;--mdc-evolution-chip-container-color: var(--mdc-theme-primary, var(--md-sys-color-primary, #3f51b5));--mdc-evolution-chip-label-text-color: #fff}:host ::ng-deep .user-chip--primary .mdc-evolution-chip__text-label,:host ::ng-deep .user-chip--primary .mdc-evolution-chip__action,:host ::ng-deep .user-chip--primary .mdc-evolution-chip__cell,:host ::ng-deep .user-chip--primary .mat-mdc-chip-action-label,:host ::ng-deep .user-chip--primary *{color:#fff!important}.box{padding:.5rem;border:1px solid rgb(174,174,13);background-color:#ececaf}\n"], dependencies: [{ kind: "directive", type: i1$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: i3.MatButton, selector: " button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button] ", exportAs: ["matButton"] }, { kind: "component", type: i3$1.MatChip, selector: "mat-basic-chip, [mat-basic-chip], mat-chip, [mat-chip]", inputs: ["role", "id", "aria-label", "aria-description", "value", "color", "removable", "highlighted", "disableRipple", "disabled"], outputs: ["removed", "destroyed"], exportAs: ["matChip"] }, { kind: "component", type: i3$1.MatChipSet, selector: "mat-chip-set", inputs: ["disabled", "role", "tabIndex"] }, { kind: "component", type: i9.MatTable, selector: "mat-table, table[mat-table]", exportAs: ["matTable"] }, { kind: "directive", type: i9.MatHeaderCellDef, selector: "[matHeaderCellDef]" }, { kind: "directive", type: i9.MatHeaderRowDef, selector: "[matHeaderRowDef]", inputs: ["matHeaderRowDef", "matHeaderRowDefSticky"] }, { kind: "directive", type: i9.MatColumnDef, selector: "[matColumnDef]", inputs: ["matColumnDef"] }, { kind: "directive", type: i9.MatCellDef, selector: "[matCellDef]" }, { kind: "directive", type: i9.MatRowDef, selector: "[matRowDef]", inputs: ["matRowDefColumns", "matRowDefWhen"] }, { kind: "directive", type: i9.MatHeaderCell, selector: "mat-header-cell, th[mat-header-cell]" }, { kind: "directive", type: i9.MatCell, selector: "mat-cell, td[mat-cell]" }, { kind: "component", type: i9.MatHeaderRow, selector: "mat-header-row, tr[mat-header-row]", exportAs: ["matHeaderRow"] }, { kind: "component", type: i9.MatRow, selector: "mat-row, tr[mat-row]", exportAs: ["matRow"] }, { kind: "component", type: i12.MatDivider, selector: "mat-divider", inputs: ["vertical", "inset"] }, { kind: "pipe", type: i1$1.AsyncPipe, name: "async" }] }); }
|
|
9679
|
-
}
|
|
9680
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: WsDataControlComponent, decorators: [{
|
|
9681
|
-
type: Component,
|
|
9682
|
-
args: [{ selector: 'app-ws-data-control', standalone: false, template: "@if ((data$ | async); as data) {\n <div style=\"margin: 1rem;\">\n @if ((users$ |async); as users) {\n <div>\n @if (users.length > 0) {\n <h3 style=\"margin: 0;\">Connected Users</h3>\n } @else {\n <h3 style=\"margin: 0;\">No Users</h3>\n }\n <mat-chip-set>\n @for (user of users; track $index) {\n <mat-chip\n [class.user-chip--primary]=\"isUser(user, (user$ | async))\"\n [style.color]=\"isUser(user, (user$ | async)) ? '#fff' : null\"\n [disableRipple]=\"true\"\n >\n {{ user.name || user.ldap || user.id || 'Anonymous' }}\n </mat-chip>\n }\n </mat-chip-set>\n </div>\n }\n <div style=\"margin-top: 1rem; margin-bottom: 1rem;\">\n <mat-divider></mat-divider>\n </div>\n\n <div class=\"box\" style=\"margin-bottom: 1rem;\" *ngIf=\"(userAction$ | async) as userAction\">\n {{ userAction?.content?.user?.name }} has {{ userAction?.content?.method }}\n </div>\n\n <h3 style=\"margin: 0;\">Data Actions</h3>\n <div style=\"display: flex; gap: 1rem; margin-bottom: 1rem;\">\n <button mat-stroked-button (click)=\"onGetData()\">Get Data</button>\n <div style=\"flex:1\"></div>\n <button mat-stroked-button color=\"accent\" (click)=\"onUpdateData(data)\">Update Data</button>\n <button mat-stroked-button color=\"warn\" (click)=\"onRemoveData(data)\">Remove Data</button>\n <button mat-stroked-button color=\"primary\" (click)=\"onAddData()\">Add Data</button>\n </div>\n\n <h3 style=\"margin: 0; margin-top: 1rem;\">WebSocketMessageService Test</h3>\n\n <p style=\"font-size: 0.875rem; color: #666; margin: 0;\">\n <strong>Note:</strong> \"Fake SessionId\" will be received and processed. \"Current SessionId\" will be filtered out (self-message).\n @if (data.length > 0) {\n <div>\n <table mat-table [dataSource]=\"data\" style=\"border: 1px solid grey;\">\n <ng-container matColumnDef=\"id\">\n <th mat-header-cell *matHeaderCellDef> ID </th>\n <td mat-cell *matCellDef=\"let element\"> {{element.id}} </td>\n </ng-container>\n <ng-container matColumnDef=\"spiffe\">\n <th mat-header-cell *matHeaderCellDef> Spiffe </th>\n <td mat-cell *matCellDef=\"let element\"> {{element.spiffe}} </td>\n </ng-container>\n <ng-container matColumnDef=\"name\">\n <th mat-header-cell *matHeaderCellDef> Name </th>\n <td mat-cell *matCellDef=\"let element\"> {{element.first_name}} {{element.last_name}}</td>\n </ng-container>\n <ng-container matColumnDef=\"email\">\n <th mat-header-cell *matHeaderCellDef> Email </th>\n <td mat-cell *matCellDef=\"let element\"> {{element.email}} </td>\n </ng-container>\n <tr mat-header-row *matHeaderRowDef=\"['id', 'spiffe', 'name', 'email']\"></tr>\n <tr mat-row *matRowDef=\"let row; columns: ['id', 'spiffe', 'name', 'email'];\"></tr>\n </table>\n <div style=\"border: 1px solid grey; padding: .5rem; border-top: none;\">\n <h3 style=\"margin: 0;\">Total Records {{ data.length }}</h3>\n </div>\n </div>\n } @else {\n <div style=\"margin-top: 1rem; font-style: italic;\">\n No Data Available\n </div>\n }\n\n <div style=\"margin-bottom: 1rem; margin-top: 1rem;\">\n <mat-divider></mat-divider>\n </div>\n\n <div style=\"display: flex; gap: 0.5rem; margin-bottom: 1rem;\">\n <button mat-flat-button color=\"primary\" (click)=\"onTestDirectStateMessage(63, true)\">\n Test UPDATE (Fake Id)\n </button>\n <button mat-stroked-button (click)=\"onTestDirectStateMessage(63, false)\">\n Test UPDATE (Current Id)\n </button>\n <button mat-stroked-button (click)=\"onTestDirectStateMessage(63, false, 'custom-session-123')\">\n Test UPDATE (Custom Id)\n </button>\n </div>\n\n </div>\n}\n\n", styles: [".user-chip--primary{background-color:var(--mdc-theme-primary, var(--md-sys-color-primary, #3f51b5))!important;color:#fff!important;--mdc-evolution-chip-container-color: var(--mdc-theme-primary, var(--md-sys-color-primary, #3f51b5));--mdc-evolution-chip-label-text-color: #fff}.user-chip--primary :is(.mdc-evolution-chip__text-label,.mdc-evolution-chip__action,.mdc-evolution-chip__cell,.mat-mdc-chip-action-label){color:#fff!important}.user-chip--primary,.user-chip--primary *{color:#fff!important}:host ::ng-deep .user-chip--primary{background-color:var(--mdc-theme-primary, var(--md-sys-color-primary, #3f51b5))!important;color:#fff!important;--mdc-evolution-chip-container-color: var(--mdc-theme-primary, var(--md-sys-color-primary, #3f51b5));--mdc-evolution-chip-label-text-color: #fff}:host ::ng-deep .user-chip--primary .mdc-evolution-chip__text-label,:host ::ng-deep .user-chip--primary .mdc-evolution-chip__action,:host ::ng-deep .user-chip--primary .mdc-evolution-chip__cell,:host ::ng-deep .user-chip--primary .mat-mdc-chip-action-label,:host ::ng-deep .user-chip--primary *{color:#fff!important}.box{padding:.5rem;border:1px solid rgb(174,174,13);background-color:#ececaf}\n"] }]
|
|
9683
|
-
}], propDecorators: { server: [{
|
|
9684
|
-
type: Input
|
|
9685
|
-
}], wsServer: [{
|
|
9686
|
-
type: Input
|
|
9687
|
-
}], jwtToken: [{
|
|
9688
|
-
type: Input
|
|
9689
|
-
}], user: [{
|
|
9690
|
-
type: Input
|
|
9691
|
-
}], path: [{
|
|
9692
|
-
type: Input
|
|
9693
|
-
}] } });
|
|
9694
|
-
|
|
9695
|
-
class WsMessagingComponent {
|
|
9696
|
-
constructor() {
|
|
9697
|
-
this.path = ['ai', 'tests']; // Default path for channel name
|
|
9698
|
-
this.destroy$ = new Subject();
|
|
9699
|
-
this.fb = inject(FormBuilder);
|
|
9700
|
-
this.messageService = inject(MessageServiceDemo);
|
|
9701
|
-
this.stateService = inject(StateServiceDemo);
|
|
9702
|
-
this.messageDisplayService = inject(MessageDisplayRouterService);
|
|
9703
|
-
// Only show public channels (starting with "PUB-"), strip prefix for display
|
|
9704
|
-
// SYS- channels are private/internal and hidden from users
|
|
9705
|
-
this.channels$ = this.messageService.channels$.pipe(map$1(channels => channels
|
|
9706
|
-
?.filter(channel => channel.startsWith('PUB-'))
|
|
9707
|
-
.map(channel => channel.replace('PUB-', '')) || []), startWith$1([]), distinctUntilChanged$1((prev, curr) => prev.length === curr.length && prev.every((v, i) => v === curr[i])));
|
|
9708
|
-
// Track subscribed public channels only, strip prefix for display
|
|
9709
|
-
this.subscribedChannels$ = this.messageService.subscribedChannels$.pipe(map$1(set => Array.from(set)
|
|
9710
|
-
.filter(channel => channel.startsWith('PUB-'))
|
|
9711
|
-
.map(channel => channel.replace('PUB-', ''))), startWith$1([]), distinctUntilChanged$1((prev, curr) => prev.length === curr.length && prev.every((v, i) => v === curr[i])));
|
|
9712
|
-
this.user$ = this.stateService.user$;
|
|
9713
|
-
this.data$ = this.stateService.data$;
|
|
9714
|
-
this.connectionStatus$ = this.stateService.connectionStatus$;
|
|
9715
|
-
// Form for creating new channels
|
|
9716
|
-
this.newChannelName = this.fb.control('', [Validators.required, Validators.minLength(2)]);
|
|
9717
|
-
// Form for selecting channels to send to and message content
|
|
9718
|
-
this.messages = this.fb.group({
|
|
9719
|
-
selectedChannels: this.fb.control([], Validators.required),
|
|
9720
|
-
content: this.fb.control(null, Validators.required),
|
|
9721
|
-
});
|
|
9722
|
-
this.communicationMessages$ = this.messageService.communicationMessages$;
|
|
9723
|
-
this.latestCommunicationMessages$ = this.messageService.latestCommunicationMessages$;
|
|
9724
|
-
this.chat$ = combineLatest([this.user$, this.communicationMessages$])
|
|
9725
|
-
.pipe(map$1(([user, messages]) => ({ user, messages })), map$1(obj => {
|
|
9726
|
-
if (!obj.user)
|
|
9727
|
-
return EMPTY;
|
|
9728
|
-
const mainUser = '';
|
|
9729
|
-
const messages = obj.messages.map((item) => {
|
|
9730
|
-
// Message transformation logic
|
|
9731
|
-
});
|
|
9732
|
-
return { user: mainUser, messages };
|
|
9733
|
-
}));
|
|
9734
|
-
}
|
|
9735
|
-
get selectedChannels() {
|
|
9736
|
-
return this.messages.get('selectedChannels');
|
|
9737
|
-
}
|
|
9738
|
-
get content() {
|
|
9739
|
-
return this.messages.get('content');
|
|
9740
|
-
}
|
|
9741
|
-
ngOnInit() {
|
|
9742
|
-
this.stateService.updateConnection(this.server, this.wsServer, this.jwtToken, this.user, this.path);
|
|
9743
|
-
// Only trigger once when connection becomes true
|
|
9744
|
-
this.connectionStatus$.pipe(filter$1(status => status === true), take$1(1), takeUntil$1(this.destroy$)).subscribe(() => {
|
|
9745
|
-
// Wait a moment for subscription to be processed, then fetch channels
|
|
9746
|
-
setTimeout(() => {
|
|
9747
|
-
console.log('📋 Fetching channels after connection...');
|
|
9748
|
-
this.messageService.getAllChannels();
|
|
9749
|
-
}, 500); // 500ms delay to ensure subscription is processed
|
|
9750
|
-
});
|
|
9751
|
-
// Subscribe to latest messages and display using rule-based routing
|
|
9752
|
-
this.latestCommunicationMessages$.pipe(filter$1(message => !!message), takeUntil$1(this.destroy$)).subscribe((message) => {
|
|
9753
|
-
console.log('🔔 Message received, routing to display:', message);
|
|
9754
|
-
// NEW: Delegate to MessageDisplayRouterService
|
|
9755
|
-
// The message payload determines the display type via rules
|
|
9756
|
-
this.messageDisplayService.display(message);
|
|
9757
|
-
// OLD: Hardcoded snackbar (removed)
|
|
9758
|
-
// const messageContent = message.content?.content?.message || message.content?.message || '';
|
|
9759
|
-
// this.toastService.toastMessage(...);
|
|
9760
|
-
});
|
|
9761
|
-
}
|
|
9762
|
-
ngOnDestroy() {
|
|
9763
|
-
this.destroy$.next();
|
|
9764
|
-
this.destroy$.complete();
|
|
9765
|
-
}
|
|
9766
|
-
/**
|
|
9767
|
-
* Create a new public channel without auto-subscribing
|
|
9768
|
-
* NOTE: PUB- prefix is added by messageService.createChannel
|
|
9769
|
-
*/
|
|
9770
|
-
onCreateChannel() {
|
|
9771
|
-
if (this.newChannelName.invalid)
|
|
9772
|
-
return;
|
|
9773
|
-
const channelName = this.newChannelName.value?.trim();
|
|
9774
|
-
if (channelName) {
|
|
9775
|
-
// Pass base channel name - service adds PUB- prefix
|
|
9776
|
-
this.messageService.createChannel(channelName);
|
|
9777
|
-
this.newChannelName.reset();
|
|
9778
|
-
// Server will broadcast updated channel list to all clients automatically
|
|
9779
|
-
}
|
|
9780
|
-
}
|
|
9781
|
-
/**
|
|
9782
|
-
* Subscribe to a channel to receive messages
|
|
9783
|
-
* NOTE: PUB- prefix is added by messageService.subscribeToChannel
|
|
9784
|
-
*/
|
|
9785
|
-
onSubscribeToChannel(channel) {
|
|
9786
|
-
// Pass base channel name - service adds PUB- prefix
|
|
9787
|
-
this.messageService.subscribeToChannel(channel);
|
|
9788
|
-
// Reset channel selection when subscriptions change
|
|
9789
|
-
this.selectedChannels.reset([]);
|
|
9790
|
-
}
|
|
9791
|
-
/**
|
|
9792
|
-
* Unsubscribe from a channel
|
|
9793
|
-
* NOTE: PUB- prefix is added by messageService.unsubscribeFromChannel
|
|
9794
|
-
*/
|
|
9795
|
-
onUnsubscribeFromChannel(channel) {
|
|
9796
|
-
// Pass base channel name - service adds PUB- prefix
|
|
9797
|
-
this.messageService.unsubscribeFromChannel(channel);
|
|
9798
|
-
// Reset channel selection when subscriptions change
|
|
9799
|
-
this.selectedChannels.reset([]);
|
|
9800
|
-
}
|
|
9801
|
-
/**
|
|
9802
|
-
* Handle chip toggle for subscribe/unsubscribe
|
|
9803
|
-
*/
|
|
9804
|
-
onChipToggle(event, channel) {
|
|
9805
|
-
const isSelected = event.selected;
|
|
9806
|
-
console.log('🎯 Chip toggle event:', {
|
|
9807
|
-
channel,
|
|
9808
|
-
isSelected,
|
|
9809
|
-
option: event.source
|
|
9810
|
-
});
|
|
9811
|
-
if (isSelected) {
|
|
9812
|
-
this.onSubscribeToChannel(channel);
|
|
9813
|
-
}
|
|
9814
|
-
else {
|
|
9815
|
-
this.onUnsubscribeFromChannel(channel);
|
|
9816
|
-
}
|
|
9817
|
-
}
|
|
9818
|
-
/**
|
|
9819
|
-
* Check if currently subscribed to a channel
|
|
9820
|
-
* Compares display names (without PUB- prefix)
|
|
9821
|
-
*/
|
|
9822
|
-
isSubscribed(channel, subscribedChannels) {
|
|
9823
|
-
return subscribedChannels.includes(channel);
|
|
9824
|
-
}
|
|
9825
|
-
onSendMessage() {
|
|
9826
|
-
this.messages.markAllAsTouched();
|
|
9827
|
-
if (this.messages.invalid)
|
|
9828
|
-
return;
|
|
9829
|
-
const channelsToSend = this.selectedChannels.value;
|
|
9830
|
-
if (channelsToSend.length === 0)
|
|
9831
|
-
return;
|
|
9832
|
-
// Get user from observable - subscribe once
|
|
9833
|
-
this.user$.pipe(take$1(1)).subscribe(user => {
|
|
9834
|
-
if (!user) {
|
|
9835
|
-
console.error('❌ No user found');
|
|
9836
|
-
return;
|
|
9837
|
-
}
|
|
9838
|
-
// Pass base channel names - service adds PUB- prefix
|
|
9839
|
-
const message = ChannelMessage.adapt({
|
|
9840
|
-
sessionId: {
|
|
9841
|
-
id: user.id,
|
|
9842
|
-
ldap: user.ldap,
|
|
9843
|
-
name: user.name,
|
|
9844
|
-
email: user.email,
|
|
9845
|
-
},
|
|
9846
|
-
content: { message: this.messages.value.content },
|
|
9847
|
-
});
|
|
9848
|
-
this.messageService.sendMessage(message, channelsToSend);
|
|
9849
|
-
this.content.reset();
|
|
9850
|
-
});
|
|
9851
|
-
}
|
|
9852
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: WsMessagingComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
9853
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: WsMessagingComponent, selector: "app-ws-messaging", inputs: { server: "server", wsServer: "wsServer", jwtToken: "jwtToken", user: "user", path: "path" }, ngImport: i0, template: "\n @if ((data$ | async); as data) {\n @if ((user$ | async); as user) {\n <div style=\"display: flex; gap: 1rem; flex-direction: column; margin-top: 1rem;\">\n\n <!-- Channel Creation Section -->\n <div style=\"padding: 1rem; background: #e3f2fd; border-radius: 4px;\">\n <strong>Create New Channel</strong>\n <div style=\"display: flex; flex-direction: column; margin-top: 0.5rem;\">\n <mat-form-field appearance=\"outline\" style=\"width: 100%;\">\n <mat-label>Channel Name</mat-label>\n <input matInput [formControl]=\"newChannelName\" placeholder=\"Enter channel name\"\n (keydown.enter)=\"onCreateChannel()\">\n </mat-form-field>\n <div style=\"display: flex; justify-content: flex-end; margin-top: -0.5rem;\">\n <button mat-raised-button color=\"primary\" (click)=\"onCreateChannel()\"\n [disabled]=\"newChannelName.invalid\">\n Create Channel\n </button>\n </div>\n </div>\n </div>\n\n <!-- Messaging Section - only show when subscribed to channels -->\n @if ((subscribedChannels$ | async); as subscribedChannels) {\n @if (subscribedChannels.length > 0) {\n <div style=\"padding: 1rem; background: #fff3e0; border-radius: 4px;\" [formGroup]=\"messages\">\n <strong>Send Message</strong>\n <!-- Debug: Show subscribed channels count -->\n <div style=\"font-size: 0.8rem; color: #666; margin-bottom: 0.5rem;\">\n Subscribed to {{ subscribedChannels.length }} channel(s): {{ subscribedChannels | json }}\n </div>\n\n <!-- Channel Selection with Checkboxes -->\n <div style=\"margin-top: 0.5rem;\">\n <mat-form-field appearance=\"outline\" style=\"width: 100%;\">\n <mat-label>Select Channels</mat-label>\n <mat-select formControlName=\"selectedChannels\" multiple>\n <mat-select-trigger>\n @if (selectedChannels.value?.length === 1) {\n {{ selectedChannels.value[0] }}\n } @else if (selectedChannels.value?.length > 1) {\n {{ selectedChannels.value[0] }} (+{{ selectedChannels.value.length - 1 }} {{ selectedChannels.value.length === 2 ? 'other' : 'others' }})\n }\n </mat-select-trigger>\n @for (channel of subscribedChannels; track channel; let i = $index) {\n <mat-option [value]=\"channel\">{{ channel }}</mat-option>\n }\n </mat-select>\n </mat-form-field>\n </div>\n\n <!-- Message Input -->\n <div style=\"display: flex; flex-direction: column;\">\n <mat-form-field style=\"width: 100%;\" appearance=\"outline\">\n <mat-label>Message</mat-label>\n <textarea\n matInput placeholder=\"Type your message...\"\n formControlName=\"content\"\n (keydown.enter)=\"onSendMessage(); $event.preventDefault()\"\n ></textarea>\n </mat-form-field>\n <div style=\"display: flex; justify-content: flex-end; margin-top: -0.5rem;\">\n <button mat-raised-button color=\"primary\" (click)=\"onSendMessage()\"\n [disabled]=\"messages.invalid\">\n Send\n </button>\n </div>\n </div>\n </div>\n }\n }\n\n </div>\n }\n }\n\n <!-- Channel List with Subscription Controls using Chips -->\n @if ((channels$ | async); as channels) {\n @if ((subscribedChannels$ | async); as subscribedChannels) {\n <div style=\"padding: 1rem; background: #f5f5f5; border-radius: 4px; margin-top: 1rem;\">\n <strong>Subscribe to Channel(s)</strong>\n <div style=\"margin-top: 0.5rem;\">\n @if (channels.length === 0) {\n <div style=\"color: #666; font-style: italic;\">No channels available. Create one above.</div>\n } @else {\n <mat-chip-listbox\n aria-label=\"Channel selection\"\n [multiple]=\"true\">\n @for (channel of channels; track channel; let i = $index) {\n <mat-chip-option\n [selected]=\"isSubscribed(channel, subscribedChannels)\"\n (selectionChange)=\"onChipToggle($event, channel)\">\n {{ channel }}\n </mat-chip-option>\n }\n </mat-chip-listbox>\n }\n </div>\n </div>\n }\n }\n", styles: ["button[mat-raised-button],button[mat-stroked-button]{min-width:120px}mat-chip-option{min-width:120px;justify-content:center;border-radius:4px!important;padding:.25rem!important}\n"], dependencies: [{ kind: "directive", type: i2$1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2$1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i2$1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "directive", type: i2$1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i2$1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "component", type: i3.MatButton, selector: " button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button] ", exportAs: ["matButton"] }, { kind: "component", type: i4.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i4.MatLabel, selector: "mat-label" }, { kind: "component", type: i5.MatSelect, selector: "mat-select", inputs: ["aria-describedby", "panelClass", "disabled", "disableRipple", "tabIndex", "hideSingleSelectionIndicator", "placeholder", "required", "multiple", "disableOptionCentering", "compareWith", "value", "aria-label", "aria-labelledby", "errorStateMatcher", "typeaheadDebounceInterval", "sortComparator", "id", "panelWidth"], outputs: ["openedChange", "opened", "closed", "selectionChange", "valueChange"], exportAs: ["matSelect"] }, { kind: "directive", type: i5.MatSelectTrigger, selector: "mat-select-trigger" }, { kind: "component", type: i6.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "component", type: i3$1.MatChipListbox, selector: "mat-chip-listbox", inputs: ["multiple", "aria-orientation", "selectable", "compareWith", "required", "hideSingleSelectionIndicator", "value"], outputs: ["change"] }, { kind: "component", type: i3$1.MatChipOption, selector: "mat-basic-chip-option, [mat-basic-chip-option], mat-chip-option, [mat-chip-option]", inputs: ["selectable", "selected"], outputs: ["selectionChange"] }, { kind: "directive", type: i13.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly"], exportAs: ["matInput"] }, { kind: "pipe", type: i1$1.AsyncPipe, name: "async" }, { kind: "pipe", type: i1$1.JsonPipe, name: "json" }] }); }
|
|
9854
|
-
}
|
|
9855
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: WsMessagingComponent, decorators: [{
|
|
9856
|
-
type: Component,
|
|
9857
|
-
args: [{ selector: 'app-ws-messaging', standalone: false, template: "\n @if ((data$ | async); as data) {\n @if ((user$ | async); as user) {\n <div style=\"display: flex; gap: 1rem; flex-direction: column; margin-top: 1rem;\">\n\n <!-- Channel Creation Section -->\n <div style=\"padding: 1rem; background: #e3f2fd; border-radius: 4px;\">\n <strong>Create New Channel</strong>\n <div style=\"display: flex; flex-direction: column; margin-top: 0.5rem;\">\n <mat-form-field appearance=\"outline\" style=\"width: 100%;\">\n <mat-label>Channel Name</mat-label>\n <input matInput [formControl]=\"newChannelName\" placeholder=\"Enter channel name\"\n (keydown.enter)=\"onCreateChannel()\">\n </mat-form-field>\n <div style=\"display: flex; justify-content: flex-end; margin-top: -0.5rem;\">\n <button mat-raised-button color=\"primary\" (click)=\"onCreateChannel()\"\n [disabled]=\"newChannelName.invalid\">\n Create Channel\n </button>\n </div>\n </div>\n </div>\n\n <!-- Messaging Section - only show when subscribed to channels -->\n @if ((subscribedChannels$ | async); as subscribedChannels) {\n @if (subscribedChannels.length > 0) {\n <div style=\"padding: 1rem; background: #fff3e0; border-radius: 4px;\" [formGroup]=\"messages\">\n <strong>Send Message</strong>\n <!-- Debug: Show subscribed channels count -->\n <div style=\"font-size: 0.8rem; color: #666; margin-bottom: 0.5rem;\">\n Subscribed to {{ subscribedChannels.length }} channel(s): {{ subscribedChannels | json }}\n </div>\n\n <!-- Channel Selection with Checkboxes -->\n <div style=\"margin-top: 0.5rem;\">\n <mat-form-field appearance=\"outline\" style=\"width: 100%;\">\n <mat-label>Select Channels</mat-label>\n <mat-select formControlName=\"selectedChannels\" multiple>\n <mat-select-trigger>\n @if (selectedChannels.value?.length === 1) {\n {{ selectedChannels.value[0] }}\n } @else if (selectedChannels.value?.length > 1) {\n {{ selectedChannels.value[0] }} (+{{ selectedChannels.value.length - 1 }} {{ selectedChannels.value.length === 2 ? 'other' : 'others' }})\n }\n </mat-select-trigger>\n @for (channel of subscribedChannels; track channel; let i = $index) {\n <mat-option [value]=\"channel\">{{ channel }}</mat-option>\n }\n </mat-select>\n </mat-form-field>\n </div>\n\n <!-- Message Input -->\n <div style=\"display: flex; flex-direction: column;\">\n <mat-form-field style=\"width: 100%;\" appearance=\"outline\">\n <mat-label>Message</mat-label>\n <textarea\n matInput placeholder=\"Type your message...\"\n formControlName=\"content\"\n (keydown.enter)=\"onSendMessage(); $event.preventDefault()\"\n ></textarea>\n </mat-form-field>\n <div style=\"display: flex; justify-content: flex-end; margin-top: -0.5rem;\">\n <button mat-raised-button color=\"primary\" (click)=\"onSendMessage()\"\n [disabled]=\"messages.invalid\">\n Send\n </button>\n </div>\n </div>\n </div>\n }\n }\n\n </div>\n }\n }\n\n <!-- Channel List with Subscription Controls using Chips -->\n @if ((channels$ | async); as channels) {\n @if ((subscribedChannels$ | async); as subscribedChannels) {\n <div style=\"padding: 1rem; background: #f5f5f5; border-radius: 4px; margin-top: 1rem;\">\n <strong>Subscribe to Channel(s)</strong>\n <div style=\"margin-top: 0.5rem;\">\n @if (channels.length === 0) {\n <div style=\"color: #666; font-style: italic;\">No channels available. Create one above.</div>\n } @else {\n <mat-chip-listbox\n aria-label=\"Channel selection\"\n [multiple]=\"true\">\n @for (channel of channels; track channel; let i = $index) {\n <mat-chip-option\n [selected]=\"isSubscribed(channel, subscribedChannels)\"\n (selectionChange)=\"onChipToggle($event, channel)\">\n {{ channel }}\n </mat-chip-option>\n }\n </mat-chip-listbox>\n }\n </div>\n </div>\n }\n }\n", styles: ["button[mat-raised-button],button[mat-stroked-button]{min-width:120px}mat-chip-option{min-width:120px;justify-content:center;border-radius:4px!important;padding:.25rem!important}\n"] }]
|
|
9858
|
-
}], propDecorators: { server: [{
|
|
9859
|
-
type: Input
|
|
9860
|
-
}], wsServer: [{
|
|
9861
|
-
type: Input
|
|
9862
|
-
}], jwtToken: [{
|
|
9863
|
-
type: Input
|
|
9864
|
-
}], user: [{
|
|
9865
|
-
type: Input
|
|
9866
|
-
}], path: [{
|
|
9867
|
-
type: Input
|
|
9868
|
-
}] } });
|
|
9869
|
-
|
|
9870
|
-
class WsNotificationsComponent {
|
|
9871
|
-
constructor() {
|
|
9872
|
-
this.destroy$ = new Subject();
|
|
9873
|
-
this.fb = inject(FormBuilder);
|
|
9874
|
-
this.notificationService = inject(NotificationServiceDemo);
|
|
9875
|
-
this.stateService = inject(StateServiceDemo);
|
|
9876
|
-
// Combine in-memory channels and today's DB channels, dedupe and strip prefix for display
|
|
9877
|
-
this.todaysNotificationChannels$ = combineLatest([
|
|
9878
|
-
this.notificationService.notificationChannels$.pipe(startWith$1([])),
|
|
9879
|
-
this.notificationService.todaysNotificationChannels$.pipe(startWith$1([]))
|
|
9880
|
-
]).pipe(tap$1(([inMemory, fromDb]) => console.log('📢 In-memory channels:', inMemory, 'DB channels:', fromDb)), map$1(([inMemory, fromDb]) => {
|
|
9881
|
-
// Merge both sources and dedupe
|
|
9882
|
-
const allChannels = [...(inMemory || []), ...(fromDb || [])];
|
|
9883
|
-
const uniqueChannels = [...new Set(allChannels)]
|
|
9884
|
-
.filter(channel => channel.startsWith('MES-'))
|
|
9885
|
-
.map(channel => channel.replace('MES-', ''));
|
|
9886
|
-
return uniqueChannels.sort();
|
|
9887
|
-
}), tap$1(channels => console.log('📢 Combined channels for display:', channels)), distinctUntilChanged$1((prev, curr) => prev.length === curr.length && prev.every((v, i) => v === curr[i])));
|
|
9888
|
-
// Track subscribed notification channels, strip prefix for display
|
|
9889
|
-
this.subscribedNotificationChannels$ = this.notificationService.subscribedNotificationChannels$.pipe(map$1(set => Array.from(set)
|
|
9890
|
-
.filter(channel => channel.startsWith('MES-'))
|
|
9891
|
-
.map(channel => channel.replace('MES-', ''))), startWith$1([]), distinctUntilChanged$1((prev, curr) => prev.length === curr.length && prev.every((v, i) => v === curr[i])));
|
|
9892
|
-
// Notification messages
|
|
9893
|
-
this.notificationMessages$ = this.notificationService.notificationMessages$;
|
|
9894
|
-
this.connectionStatus$ = this.stateService.connectionStatus$;
|
|
9895
|
-
// Form for creating new notification channels
|
|
9896
|
-
this.newChannelName = this.fb.control('', [Validators.required, Validators.minLength(2)]);
|
|
9897
|
-
// Channel connection selection
|
|
9898
|
-
this.selectedConnectionChannel = this.fb.control(null);
|
|
9899
|
-
this.connectedChannel = null;
|
|
9900
|
-
// Table columns for notifications display
|
|
9901
|
-
this.displayedColumns = ['date', 'user', 'message'];
|
|
9902
|
-
// Form for date filter when subscribing
|
|
9903
|
-
this.dateFilter = this.fb.group({
|
|
9904
|
-
startDate: this.fb.control(null),
|
|
9905
|
-
endDate: this.fb.control(null),
|
|
9906
|
-
});
|
|
9907
|
-
// Form for sending notifications
|
|
9908
|
-
this.notificationForm = this.fb.group({
|
|
9909
|
-
content: this.fb.control(null, Validators.required),
|
|
9910
|
-
});
|
|
9911
|
-
}
|
|
9912
|
-
/**
|
|
9913
|
-
* Helper to ensure channel has MES- prefix for outgoing communication
|
|
9914
|
-
*/
|
|
9915
|
-
toNotificationChannel(channel) {
|
|
9916
|
-
return channel.startsWith('MES-') ? channel : `MES-${channel}`;
|
|
9917
|
-
}
|
|
9918
|
-
get content() {
|
|
9919
|
-
return this.notificationForm.get('content');
|
|
9920
|
-
}
|
|
9921
|
-
/**
|
|
9922
|
-
* Check if currently connected to a channel
|
|
9923
|
-
*/
|
|
9924
|
-
isChannelConnected(subscribedChannels) {
|
|
9925
|
-
return this.connectedChannel !== null && subscribedChannels.includes(this.connectedChannel);
|
|
9926
|
-
}
|
|
9927
|
-
/**
|
|
9928
|
-
* Connect to the selected channel
|
|
9929
|
-
*/
|
|
9930
|
-
onConnectToChannel() {
|
|
9931
|
-
const channel = this.selectedConnectionChannel.value;
|
|
9932
|
-
if (!channel)
|
|
9933
|
-
return;
|
|
9934
|
-
this.connectedChannel = channel;
|
|
9935
|
-
this.onSubscribeToChannel(channel);
|
|
9936
|
-
}
|
|
9937
|
-
/**
|
|
9938
|
-
* Disconnect from the current channel
|
|
9939
|
-
*/
|
|
9940
|
-
onDisconnectFromChannel() {
|
|
9941
|
-
if (this.connectedChannel) {
|
|
9942
|
-
this.onUnsubscribeFromChannel(this.connectedChannel);
|
|
9943
|
-
this.connectedChannel = null;
|
|
9944
|
-
}
|
|
9945
|
-
}
|
|
9946
|
-
ngOnInit() {
|
|
9947
|
-
// Request today's notification channels when connection is established
|
|
9948
|
-
this.connectionStatus$.pipe(filter$1(isConnected => isConnected), takeUntil$1(this.destroy$)).subscribe(() => {
|
|
9949
|
-
// Request the list of today's notification channels from database
|
|
9950
|
-
this.notificationService.getTodaysNotificationChannels();
|
|
9951
|
-
});
|
|
9952
|
-
}
|
|
9953
|
-
ngOnDestroy() {
|
|
9954
|
-
this.destroy$.next();
|
|
9955
|
-
this.destroy$.complete();
|
|
9956
|
-
}
|
|
9957
|
-
/**
|
|
9958
|
-
* Create a new notification channel
|
|
9959
|
-
*/
|
|
9960
|
-
onCreateChannel() {
|
|
9961
|
-
if (this.newChannelName.invalid)
|
|
9962
|
-
return;
|
|
9963
|
-
const channelName = this.newChannelName.value?.trim();
|
|
9964
|
-
if (channelName) {
|
|
9965
|
-
this.notificationService.createNotificationChannel(channelName);
|
|
9966
|
-
this.newChannelName.reset();
|
|
9967
|
-
}
|
|
9968
|
-
}
|
|
9969
|
-
/**
|
|
9970
|
-
* Define and load previous channels
|
|
9971
|
-
*/
|
|
9972
|
-
onDefinePreviousChannels() {
|
|
9973
|
-
this.notificationService.definePreviousNotificationChannels();
|
|
9974
|
-
}
|
|
9975
|
-
/**
|
|
9976
|
-
* Subscribe to a notification channel with optional date filter
|
|
9977
|
-
*/
|
|
9978
|
-
onSubscribeToChannel(channel) {
|
|
9979
|
-
const startDate = this.dateFilter.get('startDate')?.value;
|
|
9980
|
-
const endDate = this.dateFilter.get('endDate')?.value;
|
|
9981
|
-
const options = {};
|
|
9982
|
-
if (startDate) {
|
|
9983
|
-
// Set to start of day
|
|
9984
|
-
const startOfDay = new Date(startDate);
|
|
9985
|
-
startOfDay.setHours(0, 0, 0, 0);
|
|
9986
|
-
options.startEpoch = Math.floor(startOfDay.getTime() / 1000);
|
|
9987
|
-
}
|
|
9988
|
-
if (endDate) {
|
|
9989
|
-
// Set to end of day
|
|
9990
|
-
const endOfDay = new Date(endDate);
|
|
9991
|
-
endOfDay.setHours(23, 59, 59, 999);
|
|
9992
|
-
options.endEpoch = Math.floor(endOfDay.getTime() / 1000);
|
|
9993
|
-
}
|
|
9994
|
-
this.notificationService.subscribeToNotificationChannel(channel, options, this.user);
|
|
9995
|
-
}
|
|
9996
|
-
/**
|
|
9997
|
-
* Unsubscribe from a notification channel
|
|
9998
|
-
*/
|
|
9999
|
-
onUnsubscribeFromChannel(channel) {
|
|
10000
|
-
this.notificationService.unsubscribeFromNotificationChannel(channel);
|
|
10001
|
-
}
|
|
10002
|
-
/**
|
|
10003
|
-
* Check if currently subscribed to a channel
|
|
10004
|
-
*/
|
|
10005
|
-
isSubscribed(channel, subscribedChannels) {
|
|
10006
|
-
return subscribedChannels.includes(channel);
|
|
10007
|
-
}
|
|
10008
|
-
/**
|
|
10009
|
-
* Handle chip click - toggle subscription state
|
|
10010
|
-
*/
|
|
10011
|
-
onChipClick(channel, subscribedChannels) {
|
|
10012
|
-
const isCurrentlySubscribed = this.isSubscribed(channel, subscribedChannels);
|
|
10013
|
-
if (isCurrentlySubscribed) {
|
|
10014
|
-
this.onUnsubscribeFromChannel(channel);
|
|
10015
|
-
}
|
|
10016
|
-
else {
|
|
10017
|
-
this.onSubscribeToChannel(channel);
|
|
10018
|
-
}
|
|
10019
|
-
}
|
|
10020
|
-
/**
|
|
10021
|
-
* Send a notification
|
|
10022
|
-
*/
|
|
10023
|
-
onSendNotification() {
|
|
10024
|
-
this.notificationForm.markAllAsTouched();
|
|
10025
|
-
if (this.notificationForm.invalid)
|
|
10026
|
-
return;
|
|
10027
|
-
const channel = this.connectedChannel;
|
|
10028
|
-
if (!channel)
|
|
10029
|
-
return;
|
|
10030
|
-
const messageContent = {
|
|
10031
|
-
message: this.content.value
|
|
10032
|
-
};
|
|
10033
|
-
this.notificationService.sendNotification(channel, messageContent, this.user);
|
|
10034
|
-
this.content.reset();
|
|
10035
|
-
}
|
|
10036
|
-
/**
|
|
10037
|
-
* Format epoch timestamp to readable date/time
|
|
10038
|
-
*/
|
|
10039
|
-
formatTimestamp(epoch) {
|
|
10040
|
-
return new Date(epoch * 1000).toLocaleString();
|
|
10041
|
-
}
|
|
10042
|
-
/**
|
|
10043
|
-
* Get today's date in YYYY-MM-DD format for date input default
|
|
10044
|
-
*/
|
|
10045
|
-
getTodayDate() {
|
|
10046
|
-
return new Date().toISOString().split('T')[0];
|
|
10047
|
-
}
|
|
10048
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: WsNotificationsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
10049
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: WsNotificationsComponent, selector: "app-ws-notifications", inputs: { server: "server", wsServer: "wsServer", jwtToken: "jwtToken", user: "user" }, ngImport: i0, template: "<div style=\"display: flex; gap: 1rem; flex-direction: column; margin-top: 1rem;\">\n\n <!-- Channel Creation Section -->\n <div style=\"padding: 1rem; background: #e8f5e9; border-radius: 4px;\">\n <strong>Create Notification Channel</strong>\n <div style=\"display: flex; flex-direction: column; margin-top: 0.5rem;\">\n <mat-form-field appearance=\"outline\" style=\"width: 100%;\">\n <mat-label>Channel Name</mat-label>\n <input\n matInput\n [formControl]=\"newChannelName\"\n placeholder=\"Enter notification channel name\"\n (keydown.enter)=\"onCreateChannel()\"\n >\n </mat-form-field>\n <div style=\"display: flex; justify-content: flex-end; gap: 0.5rem;\">\n <!-- <button mat-raised-button color=\"accent\" (click)=\"onDefinePreviousChannels()\">\n Define Previous Channels\n </button> -->\n <button mat-raised-button color=\"primary\" (click)=\"onCreateChannel()\"\n [disabled]=\"newChannelName.invalid\">\n Create Channel 1\n </button>\n </div>\n </div>\n </div>\n\n <!-- Channel Connection Section - Only shown when NOT connected -->\n @if ((subscribedNotificationChannels$ | async); as subscribedChannels) {\n @if (!isChannelConnected(subscribedChannels)) {\n <div style=\"padding: 1rem; background: #e3f2fd; border-radius: 4px;\">\n <strong>Connect to Channel</strong>\n <div style=\"display: flex; gap: 1rem; margin-top: 0.5rem; flex-wrap: wrap; align-items: flex-start;\">\n <!-- Channel Selection (from today's DB data) -->\n <mat-form-field appearance=\"outline\" style=\"flex: 1; min-width: 200px;\">\n <mat-label>Select Channel</mat-label>\n <mat-select [formControl]=\"selectedConnectionChannel\" placeholder=\"Select a notification channel\">\n @for (channel of (todaysNotificationChannels$ | async) || []; track channel) {\n <mat-option [value]=\"channel\">{{ channel }}</mat-option>\n }\n @empty {\n <mat-option disabled>No channels with data for today</mat-option>\n }\n </mat-select>\n </mat-form-field>\n\n <!-- Date Filter -->\n <mat-form-field appearance=\"outline\" style=\"flex: 1; min-width: 200px;\" [formGroup]=\"dateFilter\">\n <mat-label>Start Date</mat-label>\n <input matInput [matDatepicker]=\"startPicker\" formControlName=\"startDate\">\n <mat-datepicker-toggle matIconSuffix [for]=\"startPicker\"></mat-datepicker-toggle>\n <mat-datepicker #startPicker></mat-datepicker>\n </mat-form-field>\n\n <mat-form-field appearance=\"outline\" style=\"flex: 1; min-width: 200px;\" [formGroup]=\"dateFilter\">\n <mat-label>End Date</mat-label>\n <input matInput [matDatepicker]=\"endPicker\" formControlName=\"endDate\">\n <mat-datepicker-toggle matIconSuffix [for]=\"endPicker\"></mat-datepicker-toggle>\n <mat-datepicker #endPicker></mat-datepicker>\n </mat-form-field>\n </div>\n\n <div style=\"display: flex; gap: 0.5rem;\">\n <span style=\"flex:1\"></span>\n <button\n mat-raised-button color=\"primary\"\n (click)=\"onConnectToChannel()\"\n [disabled]=\"!selectedConnectionChannel.value\"\n >\n Connect\n </button>\n </div>\n </div>\n }\n }\n\n <!-- Send Notification Section -->\n @if ((subscribedNotificationChannels$ | async); as subscribedChannels) {\n @if (subscribedChannels.length > 0) {\n <div\n style=\"padding: 1rem; background: #fff8e1; border-radius: 4px;\"\n [formGroup]=\"notificationForm\"\n >\n <strong>Send Notification</strong>\n\n <!-- Notification Content -->\n <div style=\"display: flex; flex-direction: column; margin-top: 0.5rem;\">\n <mat-form-field style=\"width: 100%;\" appearance=\"outline\">\n <mat-label>Notification Message</mat-label>\n <textarea\n matInput placeholder=\"Type your notification...\"\n formControlName=\"content\"\n rows=\"2\"\n (keydown.enter)=\"onSendNotification(); $event.preventDefault()\"\n ></textarea>\n </mat-form-field>\n <div style=\"display: flex; justify-content: flex-end; margin-top: -0.5rem;\">\n <button\n mat-raised-button color=\"accent\"\n (click)=\"onSendNotification()\"\n [disabled]=\"notificationForm.invalid\"\n >\n Send Notification\n </button>\n </div>\n </div>\n </div>\n }\n }\n\n <!-- Notifications Table - Only shown when connected -->\n @if ((subscribedNotificationChannels$ | async); as subscribedChannels) {\n @if (isChannelConnected(subscribedChannels)) {\n @if ((notificationMessages$ | async); as notifications) {\n <div>\n <div style=\"display: flex; justify-content: space-between; align-items: center;\">\n <strong>Notifications ({{ notifications.length }})</strong>\n </div>\n\n @if (notifications.length > 0) {\n <div style=\"margin-top: 0.5rem; height: 400px; overflow: auto; border: 1px solid #888; border-radius: 4px;\">\n <table mat-table [dataSource]=\"notifications\" style=\"width: 100%;\">\n\n <!-- Date Column -->\n <ng-container matColumnDef=\"date\">\n <th mat-header-cell *matHeaderCellDef style=\"width: 180px;\">Date</th>\n <td mat-cell *matCellDef=\"let notification\">\n {{ formatTimestamp(notification.created) | date }}\n </td>\n </ng-container>\n\n <!-- User Column -->\n <ng-container matColumnDef=\"user\">\n <th mat-header-cell *matHeaderCellDef style=\"width: 150px;\">User</th>\n <td mat-cell *matCellDef=\"let notification\">\n <strong>{{ notification.user_name }}</strong>\n </td>\n </ng-container>\n\n <!-- Message Column -->\n <ng-container matColumnDef=\"message\">\n <th mat-header-cell *matHeaderCellDef>Message</th>\n <td mat-cell *matCellDef=\"let notification\">\n <div class=\"pill\" [style.background-color]=\"notification.content.sessionId.color || '#000000'\">\n {{ notification.content?.message || notification.content }}\n </div>\n </td>\n </ng-container>\n\n <tr mat-header-row *matHeaderRowDef=\"displayedColumns; sticky: true\"></tr>\n <tr mat-row *matRowDef=\"let row; columns: displayedColumns;\"></tr>\n </table>\n </div>\n } @else {\n <div style=\"text-align: center; color: #666; padding: 2rem;\">\n <mat-icon style=\"font-size: 48px; height: 48px; width: 48px; opacity: 0.5;\">notifications_none</mat-icon>\n <p>No notifications yet. Waiting for notifications on this channel...</p>\n </div>\n }\n </div>\n\n <!-- Disconnect Button - Below the table -->\n <div style=\"display: flex; justify-content: right; padding: 1rem;\">\n <button mat-raised-button color=\"warn\" (click)=\"onDisconnectFromChannel()\">\n Disconnect from Channel\n </button>\n </div>\n }\n }\n }\n\n</div>\n", styles: [".colored-message{font-weight:500;padding:4px 8px;border-radius:4px;display:inline-block;max-width:100%;word-wrap:break-word}.colored-message.black-text{color:#000!important}.colored-message.white-text{color:#fff!important;text-shadow:0 0 2px rgba(0,0,0,.5)}td.mat-mdc-cell{padding:8px 12px}.pill{border-radius:1rem;padding:.5rem 1rem;color:#fff}\n"], dependencies: [{ kind: "directive", type: i2$1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2$1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i2$1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "directive", type: i2$1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i2$1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "component", type: i3.MatButton, selector: " button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button] ", exportAs: ["matButton"] }, { kind: "component", type: i4.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i4.MatLabel, selector: "mat-label" }, { kind: "directive", type: i4.MatSuffix, selector: "[matSuffix], [matIconSuffix], [matTextSuffix]", inputs: ["matTextSuffix"] }, { kind: "component", type: i5.MatSelect, selector: "mat-select", inputs: ["aria-describedby", "panelClass", "disabled", "disableRipple", "tabIndex", "hideSingleSelectionIndicator", "placeholder", "required", "multiple", "disableOptionCentering", "compareWith", "value", "aria-label", "aria-labelledby", "errorStateMatcher", "typeaheadDebounceInterval", "sortComparator", "id", "panelWidth"], outputs: ["openedChange", "opened", "closed", "selectionChange", "valueChange"], exportAs: ["matSelect"] }, { kind: "component", type: i6.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "component", type: i8.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: i9.MatTable, selector: "mat-table, table[mat-table]", exportAs: ["matTable"] }, { kind: "directive", type: i9.MatHeaderCellDef, selector: "[matHeaderCellDef]" }, { kind: "directive", type: i9.MatHeaderRowDef, selector: "[matHeaderRowDef]", inputs: ["matHeaderRowDef", "matHeaderRowDefSticky"] }, { kind: "directive", type: i9.MatColumnDef, selector: "[matColumnDef]", inputs: ["matColumnDef"] }, { kind: "directive", type: i9.MatCellDef, selector: "[matCellDef]" }, { kind: "directive", type: i9.MatRowDef, selector: "[matRowDef]", inputs: ["matRowDefColumns", "matRowDefWhen"] }, { kind: "directive", type: i9.MatHeaderCell, selector: "mat-header-cell, th[mat-header-cell]" }, { kind: "directive", type: i9.MatCell, selector: "mat-cell, td[mat-cell]" }, { kind: "component", type: i9.MatHeaderRow, selector: "mat-header-row, tr[mat-header-row]", exportAs: ["matHeaderRow"] }, { kind: "component", type: i9.MatRow, selector: "mat-row, tr[mat-row]", exportAs: ["matRow"] }, { kind: "directive", type: i13.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly"], exportAs: ["matInput"] }, { kind: "component", type: i9$1.MatDatepicker, selector: "mat-datepicker", exportAs: ["matDatepicker"] }, { kind: "directive", type: i9$1.MatDatepickerInput, selector: "input[matDatepicker]", inputs: ["matDatepicker", "min", "max", "matDatepickerFilter"], exportAs: ["matDatepickerInput"] }, { kind: "component", type: i9$1.MatDatepickerToggle, selector: "mat-datepicker-toggle", inputs: ["for", "tabIndex", "aria-label", "disabled", "disableRipple"], exportAs: ["matDatepickerToggle"] }, { kind: "pipe", type: i1$1.AsyncPipe, name: "async" }, { kind: "pipe", type: i1$1.DatePipe, name: "date" }] }); }
|
|
10050
|
-
}
|
|
10051
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: WsNotificationsComponent, decorators: [{
|
|
10052
|
-
type: Component,
|
|
10053
|
-
args: [{ selector: 'app-ws-notifications', standalone: false, template: "<div style=\"display: flex; gap: 1rem; flex-direction: column; margin-top: 1rem;\">\n\n <!-- Channel Creation Section -->\n <div style=\"padding: 1rem; background: #e8f5e9; border-radius: 4px;\">\n <strong>Create Notification Channel</strong>\n <div style=\"display: flex; flex-direction: column; margin-top: 0.5rem;\">\n <mat-form-field appearance=\"outline\" style=\"width: 100%;\">\n <mat-label>Channel Name</mat-label>\n <input\n matInput\n [formControl]=\"newChannelName\"\n placeholder=\"Enter notification channel name\"\n (keydown.enter)=\"onCreateChannel()\"\n >\n </mat-form-field>\n <div style=\"display: flex; justify-content: flex-end; gap: 0.5rem;\">\n <!-- <button mat-raised-button color=\"accent\" (click)=\"onDefinePreviousChannels()\">\n Define Previous Channels\n </button> -->\n <button mat-raised-button color=\"primary\" (click)=\"onCreateChannel()\"\n [disabled]=\"newChannelName.invalid\">\n Create Channel 1\n </button>\n </div>\n </div>\n </div>\n\n <!-- Channel Connection Section - Only shown when NOT connected -->\n @if ((subscribedNotificationChannels$ | async); as subscribedChannels) {\n @if (!isChannelConnected(subscribedChannels)) {\n <div style=\"padding: 1rem; background: #e3f2fd; border-radius: 4px;\">\n <strong>Connect to Channel</strong>\n <div style=\"display: flex; gap: 1rem; margin-top: 0.5rem; flex-wrap: wrap; align-items: flex-start;\">\n <!-- Channel Selection (from today's DB data) -->\n <mat-form-field appearance=\"outline\" style=\"flex: 1; min-width: 200px;\">\n <mat-label>Select Channel</mat-label>\n <mat-select [formControl]=\"selectedConnectionChannel\" placeholder=\"Select a notification channel\">\n @for (channel of (todaysNotificationChannels$ | async) || []; track channel) {\n <mat-option [value]=\"channel\">{{ channel }}</mat-option>\n }\n @empty {\n <mat-option disabled>No channels with data for today</mat-option>\n }\n </mat-select>\n </mat-form-field>\n\n <!-- Date Filter -->\n <mat-form-field appearance=\"outline\" style=\"flex: 1; min-width: 200px;\" [formGroup]=\"dateFilter\">\n <mat-label>Start Date</mat-label>\n <input matInput [matDatepicker]=\"startPicker\" formControlName=\"startDate\">\n <mat-datepicker-toggle matIconSuffix [for]=\"startPicker\"></mat-datepicker-toggle>\n <mat-datepicker #startPicker></mat-datepicker>\n </mat-form-field>\n\n <mat-form-field appearance=\"outline\" style=\"flex: 1; min-width: 200px;\" [formGroup]=\"dateFilter\">\n <mat-label>End Date</mat-label>\n <input matInput [matDatepicker]=\"endPicker\" formControlName=\"endDate\">\n <mat-datepicker-toggle matIconSuffix [for]=\"endPicker\"></mat-datepicker-toggle>\n <mat-datepicker #endPicker></mat-datepicker>\n </mat-form-field>\n </div>\n\n <div style=\"display: flex; gap: 0.5rem;\">\n <span style=\"flex:1\"></span>\n <button\n mat-raised-button color=\"primary\"\n (click)=\"onConnectToChannel()\"\n [disabled]=\"!selectedConnectionChannel.value\"\n >\n Connect\n </button>\n </div>\n </div>\n }\n }\n\n <!-- Send Notification Section -->\n @if ((subscribedNotificationChannels$ | async); as subscribedChannels) {\n @if (subscribedChannels.length > 0) {\n <div\n style=\"padding: 1rem; background: #fff8e1; border-radius: 4px;\"\n [formGroup]=\"notificationForm\"\n >\n <strong>Send Notification</strong>\n\n <!-- Notification Content -->\n <div style=\"display: flex; flex-direction: column; margin-top: 0.5rem;\">\n <mat-form-field style=\"width: 100%;\" appearance=\"outline\">\n <mat-label>Notification Message</mat-label>\n <textarea\n matInput placeholder=\"Type your notification...\"\n formControlName=\"content\"\n rows=\"2\"\n (keydown.enter)=\"onSendNotification(); $event.preventDefault()\"\n ></textarea>\n </mat-form-field>\n <div style=\"display: flex; justify-content: flex-end; margin-top: -0.5rem;\">\n <button\n mat-raised-button color=\"accent\"\n (click)=\"onSendNotification()\"\n [disabled]=\"notificationForm.invalid\"\n >\n Send Notification\n </button>\n </div>\n </div>\n </div>\n }\n }\n\n <!-- Notifications Table - Only shown when connected -->\n @if ((subscribedNotificationChannels$ | async); as subscribedChannels) {\n @if (isChannelConnected(subscribedChannels)) {\n @if ((notificationMessages$ | async); as notifications) {\n <div>\n <div style=\"display: flex; justify-content: space-between; align-items: center;\">\n <strong>Notifications ({{ notifications.length }})</strong>\n </div>\n\n @if (notifications.length > 0) {\n <div style=\"margin-top: 0.5rem; height: 400px; overflow: auto; border: 1px solid #888; border-radius: 4px;\">\n <table mat-table [dataSource]=\"notifications\" style=\"width: 100%;\">\n\n <!-- Date Column -->\n <ng-container matColumnDef=\"date\">\n <th mat-header-cell *matHeaderCellDef style=\"width: 180px;\">Date</th>\n <td mat-cell *matCellDef=\"let notification\">\n {{ formatTimestamp(notification.created) | date }}\n </td>\n </ng-container>\n\n <!-- User Column -->\n <ng-container matColumnDef=\"user\">\n <th mat-header-cell *matHeaderCellDef style=\"width: 150px;\">User</th>\n <td mat-cell *matCellDef=\"let notification\">\n <strong>{{ notification.user_name }}</strong>\n </td>\n </ng-container>\n\n <!-- Message Column -->\n <ng-container matColumnDef=\"message\">\n <th mat-header-cell *matHeaderCellDef>Message</th>\n <td mat-cell *matCellDef=\"let notification\">\n <div class=\"pill\" [style.background-color]=\"notification.content.sessionId.color || '#000000'\">\n {{ notification.content?.message || notification.content }}\n </div>\n </td>\n </ng-container>\n\n <tr mat-header-row *matHeaderRowDef=\"displayedColumns; sticky: true\"></tr>\n <tr mat-row *matRowDef=\"let row; columns: displayedColumns;\"></tr>\n </table>\n </div>\n } @else {\n <div style=\"text-align: center; color: #666; padding: 2rem;\">\n <mat-icon style=\"font-size: 48px; height: 48px; width: 48px; opacity: 0.5;\">notifications_none</mat-icon>\n <p>No notifications yet. Waiting for notifications on this channel...</p>\n </div>\n }\n </div>\n\n <!-- Disconnect Button - Below the table -->\n <div style=\"display: flex; justify-content: right; padding: 1rem;\">\n <button mat-raised-button color=\"warn\" (click)=\"onDisconnectFromChannel()\">\n Disconnect from Channel\n </button>\n </div>\n }\n }\n }\n\n</div>\n", styles: [".colored-message{font-weight:500;padding:4px 8px;border-radius:4px;display:inline-block;max-width:100%;word-wrap:break-word}.colored-message.black-text{color:#000!important}.colored-message.white-text{color:#fff!important;text-shadow:0 0 2px rgba(0,0,0,.5)}td.mat-mdc-cell{padding:8px 12px}.pill{border-radius:1rem;padding:.5rem 1rem;color:#fff}\n"] }]
|
|
10054
|
-
}], propDecorators: { server: [{
|
|
10055
|
-
type: Input
|
|
10056
|
-
}], wsServer: [{
|
|
10057
|
-
type: Input
|
|
10058
|
-
}], jwtToken: [{
|
|
10059
|
-
type: Input
|
|
10060
|
-
}], user: [{
|
|
10061
|
-
type: Input
|
|
10062
|
-
}] } });
|
|
10063
|
-
|
|
10064
|
-
class WsAiMessagingComponent {
|
|
10065
|
-
constructor() { }
|
|
10066
|
-
ngOnInit() {
|
|
10067
|
-
}
|
|
10068
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: WsAiMessagingComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
10069
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: WsAiMessagingComponent, selector: "app-ws-ai-messaging", ngImport: i0, template: "<p>\n ws-ai-messaging coming soon!\n</p>\n", styles: [""] }); }
|
|
10070
|
-
}
|
|
10071
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: WsAiMessagingComponent, decorators: [{
|
|
10072
|
-
type: Component,
|
|
10073
|
-
args: [{ selector: 'app-ws-ai-messaging', standalone: false, template: "<p>\n ws-ai-messaging coming soon!\n</p>\n" }]
|
|
10074
|
-
}], ctorParameters: () => [] });
|
|
10075
|
-
|
|
10076
|
-
class WsChatsComponent {
|
|
10077
|
-
constructor() { }
|
|
10078
|
-
ngOnInit() {
|
|
10079
|
-
}
|
|
10080
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: WsChatsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
10081
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: WsChatsComponent, selector: "app-ws-chats", ngImport: i0, template: "<p>\n ws-chats coming soon!\n</p>\n", styles: [""] }); }
|
|
10082
|
-
}
|
|
10083
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: WsChatsComponent, decorators: [{
|
|
10084
|
-
type: Component,
|
|
10085
|
-
args: [{ selector: 'app-ws-chats', standalone: false, template: "<p>\n ws-chats coming soon!\n</p>\n" }]
|
|
10086
|
-
}], ctorParameters: () => [] });
|
|
10087
|
-
|
|
10088
|
-
class RequestManagerWsDemoComponent {
|
|
10089
|
-
constructor() {
|
|
10090
|
-
this.httpManagerService = inject(HTTPManagerService);
|
|
10091
|
-
this.stateService = inject(StateServiceDemo);
|
|
10092
|
-
this.fb = inject(FormBuilder);
|
|
10093
|
-
this.path = ['ai', 'tests'];
|
|
10094
|
-
this.user$ = this.stateService.user$;
|
|
10095
|
-
this.attempts$ = this.stateService.wsRetryAttempts$;
|
|
10096
|
-
this.nextRetry$ = this.stateService.wsNextRetry$;
|
|
10097
|
-
this.connectionStatus$ = this.stateService.connectionStatus$;
|
|
10098
|
-
this.data$ = this.stateService.data$;
|
|
10099
|
-
this.isPending$ = this.stateService.isPending$;
|
|
10100
|
-
}
|
|
10101
|
-
ngOnInit() {
|
|
10102
|
-
this.stateService.updateConnection(this.server, this.wsServer, this.jwtToken, this.user, this.path);
|
|
10103
|
-
}
|
|
10104
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: RequestManagerWsDemoComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
10105
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: RequestManagerWsDemoComponent, selector: "app-request-manager-ws-demo", inputs: { server: "server", wsServer: "wsServer", jwtToken: "jwtToken", user: "user", path: "path" }, ngImport: i0, template: "<div style=\"margin: 2rem;\">\n\n <h2 style=\"display: flex;\">\n <span style=\"flex:1\">HTTP Request State Manager - Websockets</span>\n @if ((connectionStatus$ | async); as connected) {\n <span>\n WS -\n <span style=\"color: green;\">Connected</span>\n </span>\n } @else {\n <span style=\"color: red;\">Disconnected {{ attempts$ | async }} - {{ nextRetry$ |async }}</span>\n }\n </h2>\n\n <div>\n\n @if ((user$ | async); as userInfo) {\n <div>\n <mat-toolbar>\n <div style=\"display: flex; flex:1\">\n <div style=\"flex:1\">{{ userInfo.name }}</div>\n <div>({{ userInfo.ldap }})</div>\n </div>\n </mat-toolbar>\n </div>\n }\n\n @if ((isPending$ | async)) {\n <div>\n <mat-progress-bar mode=\"indeterminate\"></mat-progress-bar>\n </div>\n }\n\n <mat-tab-group animationDuration=\"0ms\" [selectedIndex]=\"1\">\n\n <mat-tab label=\"WS - Data Control\">\n <!-- DATA CONTROL -->\n <app-ws-data-control\n [server]=\"server\"\n [wsServer]=\"wsServer\"\n [jwtToken]=\"jwtToken\"\n [user]=\"user\"\n [path]=\"path\"\n ></app-ws-data-control>\n\n </mat-tab>\n\n <mat-tab label=\"WS - Messaging\">\n <!-- MESSAGING -->\n <app-ws-messaging\n [server]=\"server\"\n [wsServer]=\"wsServer\"\n [jwtToken]=\"jwtToken\"\n [user]=\"user\"\n [path]=\"path\"\n ></app-ws-messaging>\n\n </mat-tab>\n\n <mat-tab label=\"WS - Notifications\">\n <!-- WS - Notifications -->\n <app-ws-notifications\n [server]=\"server\"\n [wsServer]=\"wsServer\"\n [jwtToken]=\"jwtToken\"\n [user]=\"user\"\n ></app-ws-notifications>\n </mat-tab>\n\n <mat-tab label=\"WS - Chats\" [disabled]=\"true\">\n <!-- WS - Chats -->\n <app-ws-chats></app-ws-chats>\n </mat-tab>\n\n <mat-tab label=\"WS - AI Messaging\" [disabled]=\"true\">\n <!-- WS - AI Messaging -->\n <app-ws-ai-messaging></app-ws-ai-messaging>\n </mat-tab>\n\n </mat-tab-group>\n</div>\n\n</div>\n\n", styles: [""], dependencies: [{ kind: "component", type: i1$2.MatTab, selector: "mat-tab", inputs: ["disabled", "label", "aria-label", "aria-labelledby", "labelClass", "bodyClass"], exportAs: ["matTab"] }, { kind: "component", type: i1$2.MatTabGroup, selector: "mat-tab-group", inputs: ["color", "fitInkBarToContent", "mat-stretch-tabs", "dynamicHeight", "selectedIndex", "headerPosition", "animationDuration", "contentTabIndex", "disablePagination", "disableRipple", "preserveContent", "backgroundColor", "aria-label", "aria-labelledby"], outputs: ["selectedIndexChange", "focusChange", "animationDone", "selectedTabChange"], exportAs: ["matTabGroup"] }, { kind: "component", type: i8$1.MatProgressBar, selector: "mat-progress-bar", inputs: ["color", "value", "bufferValue", "mode"], outputs: ["animationEnd"], exportAs: ["matProgressBar"] }, { kind: "component", type: i3$2.MatToolbar, selector: "mat-toolbar", inputs: ["color"], exportAs: ["matToolbar"] }, { kind: "component", type: WsDataControlComponent, selector: "app-ws-data-control", inputs: ["server", "wsServer", "jwtToken", "user", "path"] }, { kind: "component", type: WsMessagingComponent, selector: "app-ws-messaging", inputs: ["server", "wsServer", "jwtToken", "user", "path"] }, { kind: "component", type: WsNotificationsComponent, selector: "app-ws-notifications", inputs: ["server", "wsServer", "jwtToken", "user"] }, { kind: "component", type: WsAiMessagingComponent, selector: "app-ws-ai-messaging" }, { kind: "component", type: WsChatsComponent, selector: "app-ws-chats" }, { kind: "pipe", type: i1$1.AsyncPipe, name: "async" }] }); }
|
|
10106
|
-
}
|
|
10107
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: RequestManagerWsDemoComponent, decorators: [{
|
|
10108
|
-
type: Component,
|
|
10109
|
-
args: [{ selector: 'app-request-manager-ws-demo', standalone: false, template: "<div style=\"margin: 2rem;\">\n\n <h2 style=\"display: flex;\">\n <span style=\"flex:1\">HTTP Request State Manager - Websockets</span>\n @if ((connectionStatus$ | async); as connected) {\n <span>\n WS -\n <span style=\"color: green;\">Connected</span>\n </span>\n } @else {\n <span style=\"color: red;\">Disconnected {{ attempts$ | async }} - {{ nextRetry$ |async }}</span>\n }\n </h2>\n\n <div>\n\n @if ((user$ | async); as userInfo) {\n <div>\n <mat-toolbar>\n <div style=\"display: flex; flex:1\">\n <div style=\"flex:1\">{{ userInfo.name }}</div>\n <div>({{ userInfo.ldap }})</div>\n </div>\n </mat-toolbar>\n </div>\n }\n\n @if ((isPending$ | async)) {\n <div>\n <mat-progress-bar mode=\"indeterminate\"></mat-progress-bar>\n </div>\n }\n\n <mat-tab-group animationDuration=\"0ms\" [selectedIndex]=\"1\">\n\n <mat-tab label=\"WS - Data Control\">\n <!-- DATA CONTROL -->\n <app-ws-data-control\n [server]=\"server\"\n [wsServer]=\"wsServer\"\n [jwtToken]=\"jwtToken\"\n [user]=\"user\"\n [path]=\"path\"\n ></app-ws-data-control>\n\n </mat-tab>\n\n <mat-tab label=\"WS - Messaging\">\n <!-- MESSAGING -->\n <app-ws-messaging\n [server]=\"server\"\n [wsServer]=\"wsServer\"\n [jwtToken]=\"jwtToken\"\n [user]=\"user\"\n [path]=\"path\"\n ></app-ws-messaging>\n\n </mat-tab>\n\n <mat-tab label=\"WS - Notifications\">\n <!-- WS - Notifications -->\n <app-ws-notifications\n [server]=\"server\"\n [wsServer]=\"wsServer\"\n [jwtToken]=\"jwtToken\"\n [user]=\"user\"\n ></app-ws-notifications>\n </mat-tab>\n\n <mat-tab label=\"WS - Chats\" [disabled]=\"true\">\n <!-- WS - Chats -->\n <app-ws-chats></app-ws-chats>\n </mat-tab>\n\n <mat-tab label=\"WS - AI Messaging\" [disabled]=\"true\">\n <!-- WS - AI Messaging -->\n <app-ws-ai-messaging></app-ws-ai-messaging>\n </mat-tab>\n\n </mat-tab-group>\n</div>\n\n</div>\n\n" }]
|
|
10110
|
-
}], propDecorators: { server: [{
|
|
10111
|
-
type: Input
|
|
10112
|
-
}], wsServer: [{
|
|
10113
|
-
type: Input
|
|
10114
|
-
}], jwtToken: [{
|
|
10115
|
-
type: Input
|
|
10116
|
-
}], user: [{
|
|
10117
|
-
type: Input
|
|
10118
|
-
}], path: [{
|
|
10119
|
-
type: Input
|
|
10120
|
-
}] } });
|
|
10121
|
-
|
|
10122
|
-
class Settings {
|
|
10123
|
-
constructor(enum_1 = [], enum_2 = [], enum_3 = [], enum_4 = []) {
|
|
10124
|
-
this.enum_1 = enum_1;
|
|
10125
|
-
this.enum_2 = enum_2;
|
|
10126
|
-
this.enum_3 = enum_3;
|
|
10127
|
-
this.enum_4 = enum_4;
|
|
10128
|
-
}
|
|
10129
|
-
static adapt(item) {
|
|
10130
|
-
return new Settings(item?.enum_1, item?.enum_2, item?.enum_3, item?.enum_4);
|
|
10131
|
-
}
|
|
10132
|
-
}
|
|
10133
|
-
|
|
10134
|
-
class SettingsStateService extends StoreStateManagerService {
|
|
10135
|
-
constructor() {
|
|
10136
|
-
super(StateStorageOptions.adapt({
|
|
10137
|
-
store: 'sampleStore',
|
|
10138
|
-
options: SettingOptions.adapt({
|
|
10139
|
-
storage: StorageType.SESSION,
|
|
10140
|
-
encrypted: false
|
|
10141
|
-
}),
|
|
10142
|
-
model: Settings.adapt,
|
|
10143
|
-
}));
|
|
10144
|
-
}
|
|
10145
|
-
updateEnum_1(value) {
|
|
10146
|
-
this.updateData(value);
|
|
10147
|
-
}
|
|
10148
|
-
updateEnum_2(value) {
|
|
10149
|
-
this.updateData(value);
|
|
10150
|
-
}
|
|
10151
|
-
getEnum(key) {
|
|
10152
|
-
return this.data$.pipe(map$1(item => item[key]));
|
|
10153
|
-
}
|
|
10154
|
-
getEnum_1() {
|
|
10155
|
-
return this.data$.pipe(map$1(item => item?.enum_1));
|
|
10156
|
-
}
|
|
10157
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SettingsStateService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
10158
|
-
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SettingsStateService }); }
|
|
10159
|
-
}
|
|
10160
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SettingsStateService, decorators: [{
|
|
10161
|
-
type: Injectable
|
|
10162
|
-
}], ctorParameters: () => [] });
|
|
10163
|
-
|
|
10164
|
-
class StoreStateManagerDemoComponent {
|
|
10165
|
-
constructor() {
|
|
10166
|
-
this.settingsStateService = inject(SettingsStateService);
|
|
10167
|
-
this.dataState$ = this.settingsStateService.data$;
|
|
10168
|
-
this.dataEnum$ = this.settingsStateService.getEnum_1();
|
|
10169
|
-
}
|
|
10170
|
-
ngOnInit() {
|
|
10171
|
-
}
|
|
10172
|
-
onUpdateEnum_1() {
|
|
10173
|
-
const rnd = RandomNumber(1000, 9999);
|
|
10174
|
-
this.settingsStateService.updateEnum_1({ enum_1: [{ users: [1, 2, 3, rnd], random: rnd }] });
|
|
10175
|
-
}
|
|
10176
|
-
onUpdateEnum_2() {
|
|
10177
|
-
const rnd = RandomNumber(1000, 9999);
|
|
10178
|
-
this.settingsStateService.updateEnum_2({ enum_2: [{ users: [1, 2, 3, rnd], random: rnd }] });
|
|
10179
|
-
}
|
|
10180
|
-
onGetEnum_1() {
|
|
10181
|
-
this.settingsStateService.getEnum_1().subscribe(data => console.log('DUMP:', data));
|
|
10182
|
-
}
|
|
10183
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: StoreStateManagerDemoComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
10184
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: StoreStateManagerDemoComponent, selector: "app-store-state-manager-demo", providers: [SettingsStateService], ngImport: i0, template: "<div style=\"margin: 2rem;\">\n\n <h2>\n <span>Store State Manager</span>\n <span style=\"margin-left: .5rem;\">\n <mat-icon color=\"accent\">fiber_new</mat-icon>\n </span>\n </h2>\n\n {{ dataState$ | async | json}}\n\n <div style=\"display: flex; gap:.5rem; margin-top: 1rem; margin-bottom: 1rem;\">\n <button mat-stroked-button (click)=\"onUpdateEnum_1()\">Update Start</button>\n <button mat-stroked-button (click)=\"onUpdateEnum_2()\">Update End</button>\n <button mat-stroked-button (click)=\"onGetEnum_1()\">Dump</button>\n </div>\n\n <div>\n <h3 style=\"margin: 0;\">Enum 1</h3>\n {{ dataEnum$ | async | json}}\n </div>\n\n</div>\n", styles: [""], dependencies: [{ kind: "component", type: i3.MatButton, selector: " button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button] ", exportAs: ["matButton"] }, { kind: "component", type: i8.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "pipe", type: i1$1.AsyncPipe, name: "async" }, { kind: "pipe", type: i1$1.JsonPipe, name: "json" }] }); }
|
|
10185
|
-
}
|
|
10186
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: StoreStateManagerDemoComponent, decorators: [{
|
|
10187
|
-
type: Component,
|
|
10188
|
-
args: [{ selector: 'app-store-state-manager-demo', providers: [SettingsStateService], standalone: false, template: "<div style=\"margin: 2rem;\">\n\n <h2>\n <span>Store State Manager</span>\n <span style=\"margin-left: .5rem;\">\n <mat-icon color=\"accent\">fiber_new</mat-icon>\n </span>\n </h2>\n\n {{ dataState$ | async | json}}\n\n <div style=\"display: flex; gap:.5rem; margin-top: 1rem; margin-bottom: 1rem;\">\n <button mat-stroked-button (click)=\"onUpdateEnum_1()\">Update Start</button>\n <button mat-stroked-button (click)=\"onUpdateEnum_2()\">Update End</button>\n <button mat-stroked-button (click)=\"onGetEnum_1()\">Dump</button>\n </div>\n\n <div>\n <h3 style=\"margin: 0;\">Enum 1</h3>\n {{ dataEnum$ | async | json}}\n </div>\n\n</div>\n" }]
|
|
10189
|
-
}] });
|
|
10190
|
-
|
|
10191
|
-
class DatabaseDataDemoComponent {
|
|
10192
|
-
constructor() {
|
|
10193
|
-
this.db = inject(DatabaseManagerService);
|
|
10194
|
-
this.destroy$ = new Subject();
|
|
10195
|
-
this.dataToDisplay = [];
|
|
10196
|
-
this.dataSource = new DatabaseDataSource(this.dataToDisplay);
|
|
10197
|
-
this.displayedColumns = ['id', 'last_name', 'age', 'amount'];
|
|
10198
|
-
this.names = ['Smith', 'Johnson', 'Williams', 'Brown', 'Jones', 'Garcia', 'Miller', 'Davis'];
|
|
10199
|
-
}
|
|
10200
|
-
ngOnDestroy() {
|
|
10201
|
-
this.destroy$.next();
|
|
10202
|
-
this.destroy$.complete();
|
|
10203
|
-
}
|
|
10204
|
-
addData() {
|
|
10205
|
-
const newRecord = {
|
|
10206
|
-
id: Math.max(...this.dataToDisplay.map(r => r.id), 0) + 1,
|
|
10207
|
-
last_name: this.names[Math.floor(Math.random() * this.names.length)],
|
|
10208
|
-
age: Math.floor(Math.random() * 80) + 18,
|
|
10209
|
-
amount: Math.floor(Math.random() * 1000)
|
|
10210
|
-
};
|
|
10211
|
-
this.dataToDisplay = [...this.dataToDisplay, newRecord];
|
|
10212
|
-
this.db.createTableRecord('sample_table', newRecord).subscribe(() => this.refreshData());
|
|
10213
|
-
}
|
|
10214
|
-
removeData() {
|
|
10215
|
-
if (this.dataToDisplay.length > 0) {
|
|
10216
|
-
const removedRecord = this.dataToDisplay[this.dataToDisplay.length - 1];
|
|
10217
|
-
this.dataToDisplay = this.dataToDisplay.slice(0, -1);
|
|
10218
|
-
this.db.deleteTableRecord('sample_table', removedRecord.id).subscribe(() => this.refreshData());
|
|
10219
|
-
}
|
|
10220
|
-
}
|
|
10221
|
-
updateData() {
|
|
10222
|
-
if (this.dataToDisplay.length === 0)
|
|
10223
|
-
return;
|
|
10224
|
-
const randomIndex = Math.floor(Math.random() * this.dataToDisplay.length);
|
|
10225
|
-
const recordToUpdate = this.dataToDisplay[randomIndex];
|
|
10226
|
-
const updatedRecord = {
|
|
10227
|
-
...recordToUpdate,
|
|
10228
|
-
last_name: this.names[Math.floor(Math.random() * this.names.length)],
|
|
10229
|
-
age: Math.floor(Math.random() * 80) + 18,
|
|
10230
|
-
amount: Math.floor(Math.random() * 1000)
|
|
10231
|
-
};
|
|
10232
|
-
this.dataToDisplay = this.dataToDisplay.map((r, index) => index === randomIndex ? updatedRecord : r);
|
|
10233
|
-
this.db.updateTableRecord('sample_table', updatedRecord).subscribe(() => this.refreshData());
|
|
10234
|
-
}
|
|
10235
|
-
clearAllData() {
|
|
10236
|
-
if (this.dataToDisplay.length === 0)
|
|
10237
|
-
return;
|
|
10238
|
-
const idsToDelete = this.dataToDisplay.map(r => r.id);
|
|
10239
|
-
this.dataToDisplay = [];
|
|
10240
|
-
this.db.deleteTableRecords('sample_table', idsToDelete).subscribe(() => this.refreshData());
|
|
10241
|
-
}
|
|
10242
|
-
refreshData() {
|
|
10243
|
-
this.db.getTableRecords('sample_table').subscribe((records) => {
|
|
10244
|
-
this.dataToDisplay = records || [];
|
|
10245
|
-
this.dataSource.setData(this.dataToDisplay);
|
|
10246
|
-
});
|
|
10247
|
-
}
|
|
10248
|
-
ngOnInit() {
|
|
10249
|
-
const tableDef = TableSchemaDef.adapt({
|
|
10250
|
-
table: 'sample_table',
|
|
10251
|
-
schema: '++id, last_name, age, amount'
|
|
10252
|
-
});
|
|
10253
|
-
this.db.createDatabaseTable(tableDef).subscribe(() => {
|
|
10254
|
-
console.log('Database Table Initialized');
|
|
10255
|
-
// hasTable
|
|
10256
|
-
this.db.hasDatabaseTable('sample_table')
|
|
10257
|
-
.subscribe(data => console.log('hasDatabaseTable:', data));
|
|
10258
|
-
// Tables
|
|
10259
|
-
this.db.getDatabaseTables()
|
|
10260
|
-
.subscribe(data => console.log('getDatabaseTables:', data));
|
|
10261
|
-
// getTable
|
|
10262
|
-
this.db.getDatabaseTable('sample_table')
|
|
10263
|
-
.subscribe(data => console.log('getDatabaseTable:', data));
|
|
10264
|
-
// Table Schema
|
|
10265
|
-
this.db.getDatabaseTableSchema('sample_table')
|
|
10266
|
-
.subscribe(data => console.log('getDatabaseTableSchema:', data));
|
|
10267
|
-
// Insert Table Records
|
|
10268
|
-
const records = [
|
|
10269
|
-
{ id: 1, age: 12, last_name: 'Bonifacio', amount: 80 },
|
|
10270
|
-
{ id: 2, age: 15, last_name: 'Johns', amount: 700 },
|
|
10271
|
-
{ id: 3, age: 22, last_name: 'Kims', amount: 234 },
|
|
10272
|
-
{ id: 4, age: 45, last_name: 'Harrys', amount: 1200 },
|
|
10273
|
-
];
|
|
10274
|
-
this.db.createTableRecords('sample_table', records).subscribe(() => {
|
|
10275
|
-
this.refreshData();
|
|
10276
|
-
});
|
|
10277
|
-
});
|
|
10278
|
-
}
|
|
10279
|
-
createTable(tableDef) {
|
|
10280
|
-
return this.db.createDatabaseTable(tableDef);
|
|
10281
|
-
}
|
|
10282
|
-
// CRUD
|
|
10283
|
-
findTableRecords(table, column, value) {
|
|
10284
|
-
return this.db.findTableRecords(table, column, value)
|
|
10285
|
-
.pipe(tap((data) => console.log('findTableRecords:', data)));
|
|
10286
|
-
}
|
|
10287
|
-
findTableRecord(table, column, value) {
|
|
10288
|
-
return this.db.findTableRecord(table, column, value)
|
|
10289
|
-
.pipe(tap((data) => console.log('findTableRecord:', data)));
|
|
10290
|
-
}
|
|
10291
|
-
getTableRecords(table) {
|
|
10292
|
-
return this.db.getTableRecords(table)
|
|
10293
|
-
.pipe(tap((data) => console.log('getTableRecords:', data)));
|
|
10294
|
-
}
|
|
10295
|
-
getTableRecord(table, id) {
|
|
10296
|
-
return this.db.getTableRecord(table, id)
|
|
10297
|
-
.pipe(tap((data) => console.log('getTableRecord:', data)));
|
|
10298
|
-
}
|
|
10299
|
-
createTableRecord(table, record) {
|
|
10300
|
-
return this.db.createTableRecord(table, record)
|
|
10301
|
-
.pipe(tap((data) => console.log('createTableRecord:', data)));
|
|
10302
|
-
}
|
|
10303
|
-
updateTableRecord(table, record) {
|
|
10304
|
-
return this.db.updateTableRecord(table, record)
|
|
10305
|
-
.pipe(tap((data) => console.log('updateTableRecord:', data)));
|
|
10306
|
-
}
|
|
10307
|
-
deleteTableRecord(table, id) {
|
|
10308
|
-
return this.db.deleteTableRecord(table, id)
|
|
10309
|
-
.pipe(tap((data) => console.log('deleteTableRecord:', data)));
|
|
10310
|
-
}
|
|
10311
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: DatabaseDataDemoComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
10312
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: DatabaseDataDemoComponent, selector: "app-database-data-demo", ngImport: i0, template: "<div style=\"padding: 2rem;\">\n\n <h2>\n Database Manager Service Demo\n </h2>\n\n <div style=\"margin-bottom: 1rem; display: flex; gap: 1rem;\">\n <button mat-stroked-button\n color=\"primary\"\n (click)=\"clearAllData()\"\n >\n Clear Data\n </button>\n <button mat-stroked-button\n color=\"primary\"\n (click)=\"updateData()\"\n >\n Update Data\n </button>\n <div style=\"flex:1\"></div>\n <button\n mat-stroked-button\n color=\"warn\"\n [disabled]=\"!dataToDisplay.length\"\n (click)=\"removeData()\"\n >\n Remove Data\n </button>\n <button mat-stroked-button\n color=\"primary\"\n (click)=\"addData()\"\n >\n Add Data\n </button>\n </div>\n\n <div class=\"table-container\">\n <table mat-table [dataSource]=\"dataSource\">\n\n <ng-container matColumnDef=\"id\">\n <th mat-header-cell *matHeaderCellDef>ID</th>\n <td mat-cell *matCellDef=\"let element\">{{element.id}}</td>\n </ng-container>\n\n <ng-container matColumnDef=\"last_name\">\n <th mat-header-cell *matHeaderCellDef>Last Name</th>\n <td mat-cell *matCellDef=\"let element\">{{element.last_name}}</td>\n </ng-container>\n\n <ng-container matColumnDef=\"age\">\n <th mat-header-cell *matHeaderCellDef>Age</th>\n <td mat-cell *matCellDef=\"let element\">{{element.age}}</td>\n </ng-container>\n\n <ng-container matColumnDef=\"amount\">\n <th mat-header-cell *matHeaderCellDef>Amount</th>\n <td mat-cell *matCellDef=\"let element\">{{element.amount | currency}}</td>\n </ng-container>\n\n <tr mat-header-row *matHeaderRowDef=\"displayedColumns\"></tr>\n <tr mat-row *matRowDef=\"let row; columns: displayedColumns;\"></tr>\n </table>\n </div>\n\n</div>\n", styles: [""], dependencies: [{ kind: "component", type: i3.MatButton, selector: " button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button] ", exportAs: ["matButton"] }, { kind: "component", type: i9.MatTable, selector: "mat-table, table[mat-table]", exportAs: ["matTable"] }, { kind: "directive", type: i9.MatHeaderCellDef, selector: "[matHeaderCellDef]" }, { kind: "directive", type: i9.MatHeaderRowDef, selector: "[matHeaderRowDef]", inputs: ["matHeaderRowDef", "matHeaderRowDefSticky"] }, { kind: "directive", type: i9.MatColumnDef, selector: "[matColumnDef]", inputs: ["matColumnDef"] }, { kind: "directive", type: i9.MatCellDef, selector: "[matCellDef]" }, { kind: "directive", type: i9.MatRowDef, selector: "[matRowDef]", inputs: ["matRowDefColumns", "matRowDefWhen"] }, { kind: "directive", type: i9.MatHeaderCell, selector: "mat-header-cell, th[mat-header-cell]" }, { kind: "directive", type: i9.MatCell, selector: "mat-cell, td[mat-cell]" }, { kind: "component", type: i9.MatHeaderRow, selector: "mat-header-row, tr[mat-header-row]", exportAs: ["matHeaderRow"] }, { kind: "component", type: i9.MatRow, selector: "mat-row, tr[mat-row]", exportAs: ["matRow"] }, { kind: "pipe", type: i1$1.CurrencyPipe, name: "currency" }] }); }
|
|
10313
|
-
}
|
|
10314
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: DatabaseDataDemoComponent, decorators: [{
|
|
10315
|
-
type: Component,
|
|
10316
|
-
args: [{ selector: 'app-database-data-demo', standalone: false, template: "<div style=\"padding: 2rem;\">\n\n <h2>\n Database Manager Service Demo\n </h2>\n\n <div style=\"margin-bottom: 1rem; display: flex; gap: 1rem;\">\n <button mat-stroked-button\n color=\"primary\"\n (click)=\"clearAllData()\"\n >\n Clear Data\n </button>\n <button mat-stroked-button\n color=\"primary\"\n (click)=\"updateData()\"\n >\n Update Data\n </button>\n <div style=\"flex:1\"></div>\n <button\n mat-stroked-button\n color=\"warn\"\n [disabled]=\"!dataToDisplay.length\"\n (click)=\"removeData()\"\n >\n Remove Data\n </button>\n <button mat-stroked-button\n color=\"primary\"\n (click)=\"addData()\"\n >\n Add Data\n </button>\n </div>\n\n <div class=\"table-container\">\n <table mat-table [dataSource]=\"dataSource\">\n\n <ng-container matColumnDef=\"id\">\n <th mat-header-cell *matHeaderCellDef>ID</th>\n <td mat-cell *matCellDef=\"let element\">{{element.id}}</td>\n </ng-container>\n\n <ng-container matColumnDef=\"last_name\">\n <th mat-header-cell *matHeaderCellDef>Last Name</th>\n <td mat-cell *matCellDef=\"let element\">{{element.last_name}}</td>\n </ng-container>\n\n <ng-container matColumnDef=\"age\">\n <th mat-header-cell *matHeaderCellDef>Age</th>\n <td mat-cell *matCellDef=\"let element\">{{element.age}}</td>\n </ng-container>\n\n <ng-container matColumnDef=\"amount\">\n <th mat-header-cell *matHeaderCellDef>Amount</th>\n <td mat-cell *matCellDef=\"let element\">{{element.amount | currency}}</td>\n </ng-container>\n\n <tr mat-header-row *matHeaderRowDef=\"displayedColumns\"></tr>\n <tr mat-row *matRowDef=\"let row; columns: displayedColumns;\"></tr>\n </table>\n </div>\n\n</div>\n" }]
|
|
10317
|
-
}], ctorParameters: () => [] });
|
|
10318
|
-
class DatabaseDataSource extends DataSource {
|
|
10319
|
-
constructor(initialData) {
|
|
10320
|
-
super();
|
|
10321
|
-
this._dataStream = new ReplaySubject();
|
|
10322
|
-
this.setData(initialData);
|
|
10323
|
-
}
|
|
10324
|
-
connect() {
|
|
10325
|
-
return this._dataStream;
|
|
10326
|
-
}
|
|
10327
|
-
disconnect() { }
|
|
10328
|
-
setData(data) {
|
|
10329
|
-
this._dataStream.next(data);
|
|
10330
|
-
}
|
|
10331
|
-
}
|
|
10332
|
-
|
|
10333
|
-
class HttpRequestServicesDemoComponent {
|
|
10334
|
-
constructor(configOptions) {
|
|
10335
|
-
this.configOptions = configOptions;
|
|
10336
|
-
this.wsServer = 'ws:';
|
|
10337
|
-
this.jwtToken = '';
|
|
10338
|
-
this.server = 'http:';
|
|
10339
|
-
this.path = ['ai', 'tests'];
|
|
10340
|
-
this.requestTypes = [
|
|
10341
|
-
{ name: "Http Service", value: 'http_service' },
|
|
10342
|
-
// { name: "Http Signals Service", value: 'http_signals_service', new: true },
|
|
10343
|
-
{ name: "Http State Service", value: 'http_state_service' },
|
|
10344
|
-
{ name: "Http State Service - Websockets", value: 'http_state_service_ws', new: false },
|
|
10345
|
-
{ name: "Database Service", value: 'database_service', divider: true, disabled: false },
|
|
10346
|
-
{ name: "Local Storage Service", value: 'local_storage_service' },
|
|
10347
|
-
// { name: "Local Signals Storage Service", value: 'local_storage_signals_service', new: true },
|
|
10348
|
-
{ name: "Store State Manager Service", value: 'store_state_manager', new: true },
|
|
10349
|
-
{ name: "Basic Http Service", value: 'basic_http_service', divider: true },
|
|
10350
|
-
];
|
|
10351
|
-
this.selectedService = this.requestTypes[1].value; //menu selection default
|
|
10352
|
-
}
|
|
10353
|
-
ngOnInit() {
|
|
10354
|
-
if (this.configOptions)
|
|
10355
|
-
this.injectionOptions = this.configOptions;
|
|
10356
|
-
}
|
|
10357
|
-
onSelected(type) {
|
|
10358
|
-
this.selectedService = this.requestTypes[type].value;
|
|
10359
|
-
}
|
|
10360
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: HttpRequestServicesDemoComponent, deps: [{ token: CONFIG_SETTINGS_TOKEN }], target: i0.ɵɵFactoryTarget.Component }); }
|
|
10361
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: HttpRequestServicesDemoComponent, selector: "app-http-request-services-demo", inputs: { wsServer: "wsServer", jwtToken: "jwtToken", server: "server", user: "user", path: "path", adapter: "adapter", mapper: "mapper" }, ngImport: i0, template: "<mat-toolbar style=\"display:flex\">\n <div>Http Request Manager Services</div>\n <div style=\"flex:1\"></div>\n <button mat-stroked-button [matMenuTriggerFor]=\"menu\">Services</button>\n <mat-menu #menu=\"matMenu\">\n @for (type of requestTypes; track type; let i = $index) {\n @if (type?.divider) {\n <div\n style=\"margin-top: .5rem; margin-bottom: .5rem;\"\n >\n <mat-divider></mat-divider>\n </div>\n }\n <button\n mat-menu-item\n (click)=\"onSelected(i)\"\n [disabled]=\"type.disabled\"\n >\n {{ type.name }}\n </button>\n }\n\n </mat-menu>\n</mat-toolbar>\n\n<span>\n @switch (selectedService) {\n @case ('basic_http_service') {\n <p>\n <app-request-manager-basic-demo></app-request-manager-basic-demo>\n </p>\n }\n @case ('http_service') {\n <p>\n <ng-container *ngTemplateOutlet=\"HTTP_OPTIONS\"></ng-container>\n <app-request-manager-demo\n [server]=\"server\"\n [adapter]=\"adapter\"\n [mapper]=\"mapper\"\n ></app-request-manager-demo>\n </p>\n }\n <!-- <p *ngSwitchCase=\"'http_signals_service'\">\n <ng-container *ngTemplateOutlet=\"HTTP_OPTIONS\"></ng-container>\n <app-request-signals-manager-demo></app-request-signals-manager-demo>\n </p> -->\n @case ('http_state_service') {\n <p>\n <ng-container *ngTemplateOutlet=\"HTTP_OPTIONS\"></ng-container>\n <app-request-manager-state-demo\n [server]=\"server\"\n [adapter]=\"adapter\"\n [mapper]=\"mapper\"\n ></app-request-manager-state-demo>\n </p>\n }\n @case ('http_state_service_ws') {\n <p>\n <ng-container *ngTemplateOutlet=\"HTTP_OPTIONS\"></ng-container>\n <app-request-manager-ws-demo\n [server]=\"server\"\n [wsServer]=\"wsServer\"\n [jwtToken]=\"jwtToken\"\n [user]=\"user\"\n [path]=\"path\"\n ></app-request-manager-ws-demo>\n </p>\n }\n @case ('database_service') {\n <p>\n <app-database-data-demo></app-database-data-demo>\n </p>\n }\n @case ('local_storage_service') {\n <p>\n <ng-container *ngTemplateOutlet=\"LOCAL_OPTIONS\"></ng-container>\n <app-local-storage-demo></app-local-storage-demo>\n </p>\n }\n <!-- <p *ngSwitchCase=\"'local_storage_signals_service'\">\n <ng-container *ngTemplateOutlet=\"LOCAL_OPTIONS\"></ng-container>\n <app-local-storage-signals-demo></app-local-storage-signals-demo>\n</p> -->\n@case ('store_state_manager') {\n <p>\n <ng-container *ngTemplateOutlet=\"LOCAL_OPTIONS\"></ng-container>\n <app-store-state-manager-demo></app-store-state-manager-demo>\n </p>\n}\n@default {\n <p>\n Other\n </p>\n}\n}\n</span>\n\n<ng-template #HTTP_OPTIONS>\n @if (injectionOptions?.httpRequestOptions) {\n <div class=\"box\">\n <h3 style=\"font-weight: bold;\">Injection Token Detected - HTTP Options</h3>\n {{ injectionOptions?.httpRequestOptions| json }}\n </div>\n }\n</ng-template>\n\n<ng-template #LOCAL_OPTIONS>\n @if (injectionOptions?.LocalStorageOptions) {\n <ng-container class=\"box\">\n <div class=\"box\">\n <h3 style=\"font-weight: bold;\">Injection Token Detected - LocalStorage Options</h3>\n {{ injectionOptions?.LocalStorageOptions| json }}\n </div>\n </ng-container>\n }\n</ng-template>\n\n\n", styles: [".box{padding:1rem;background-color:#f5f5f5;border:thin gray solid;margin-top:1rem}\n"], dependencies: [{ kind: "directive", type: i1$1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: i3.MatButton, selector: " button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button] ", exportAs: ["matButton"] }, { kind: "component", type: i7.MatMenu, selector: "mat-menu", inputs: ["backdropClass", "aria-label", "aria-labelledby", "aria-describedby", "xPosition", "yPosition", "overlapTrigger", "hasBackdrop", "class", "classList"], outputs: ["closed", "close"], exportAs: ["matMenu"] }, { kind: "component", type: i7.MatMenuItem, selector: "[mat-menu-item]", inputs: ["role", "disabled", "disableRipple"], exportAs: ["matMenuItem"] }, { kind: "directive", type: i7.MatMenuTrigger, selector: "[mat-menu-trigger-for], [matMenuTriggerFor]", inputs: ["mat-menu-trigger-for", "matMenuTriggerFor", "matMenuTriggerData", "matMenuTriggerRestoreFocus"], outputs: ["menuOpened", "onMenuOpen", "menuClosed", "onMenuClose"], exportAs: ["matMenuTrigger"] }, { kind: "component", type: i12.MatDivider, selector: "mat-divider", inputs: ["vertical", "inset"] }, { kind: "component", type: i3$2.MatToolbar, selector: "mat-toolbar", inputs: ["color"], exportAs: ["matToolbar"] }, { kind: "component", type: RequestManagerBasicDemoComponent, selector: "app-request-manager-basic-demo" }, { kind: "component", type: RequestManagerStateDemoComponent, selector: "app-request-manager-state-demo", inputs: ["server", "adapter", "mapper"] }, { kind: "component", type: RequestManagerDemoComponent, selector: "app-request-manager-demo", inputs: ["server", "adapter", "mapper"] }, { kind: "component", type: LocalStorageDemoComponent, selector: "app-local-storage-demo" }, { kind: "component", type: RequestManagerWsDemoComponent, selector: "app-request-manager-ws-demo", inputs: ["server", "wsServer", "jwtToken", "user", "path"] }, { kind: "component", type: StoreStateManagerDemoComponent, selector: "app-store-state-manager-demo" }, { kind: "component", type: DatabaseDataDemoComponent, selector: "app-database-data-demo" }, { kind: "pipe", type: i1$1.JsonPipe, name: "json" }] }); }
|
|
10362
|
-
}
|
|
10363
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: HttpRequestServicesDemoComponent, decorators: [{
|
|
10364
|
-
type: Component,
|
|
10365
|
-
args: [{ selector: 'app-http-request-services-demo', standalone: false, template: "<mat-toolbar style=\"display:flex\">\n <div>Http Request Manager Services</div>\n <div style=\"flex:1\"></div>\n <button mat-stroked-button [matMenuTriggerFor]=\"menu\">Services</button>\n <mat-menu #menu=\"matMenu\">\n @for (type of requestTypes; track type; let i = $index) {\n @if (type?.divider) {\n <div\n style=\"margin-top: .5rem; margin-bottom: .5rem;\"\n >\n <mat-divider></mat-divider>\n </div>\n }\n <button\n mat-menu-item\n (click)=\"onSelected(i)\"\n [disabled]=\"type.disabled\"\n >\n {{ type.name }}\n </button>\n }\n\n </mat-menu>\n</mat-toolbar>\n\n<span>\n @switch (selectedService) {\n @case ('basic_http_service') {\n <p>\n <app-request-manager-basic-demo></app-request-manager-basic-demo>\n </p>\n }\n @case ('http_service') {\n <p>\n <ng-container *ngTemplateOutlet=\"HTTP_OPTIONS\"></ng-container>\n <app-request-manager-demo\n [server]=\"server\"\n [adapter]=\"adapter\"\n [mapper]=\"mapper\"\n ></app-request-manager-demo>\n </p>\n }\n <!-- <p *ngSwitchCase=\"'http_signals_service'\">\n <ng-container *ngTemplateOutlet=\"HTTP_OPTIONS\"></ng-container>\n <app-request-signals-manager-demo></app-request-signals-manager-demo>\n </p> -->\n @case ('http_state_service') {\n <p>\n <ng-container *ngTemplateOutlet=\"HTTP_OPTIONS\"></ng-container>\n <app-request-manager-state-demo\n [server]=\"server\"\n [adapter]=\"adapter\"\n [mapper]=\"mapper\"\n ></app-request-manager-state-demo>\n </p>\n }\n @case ('http_state_service_ws') {\n <p>\n <ng-container *ngTemplateOutlet=\"HTTP_OPTIONS\"></ng-container>\n <app-request-manager-ws-demo\n [server]=\"server\"\n [wsServer]=\"wsServer\"\n [jwtToken]=\"jwtToken\"\n [user]=\"user\"\n [path]=\"path\"\n ></app-request-manager-ws-demo>\n </p>\n }\n @case ('database_service') {\n <p>\n <app-database-data-demo></app-database-data-demo>\n </p>\n }\n @case ('local_storage_service') {\n <p>\n <ng-container *ngTemplateOutlet=\"LOCAL_OPTIONS\"></ng-container>\n <app-local-storage-demo></app-local-storage-demo>\n </p>\n }\n <!-- <p *ngSwitchCase=\"'local_storage_signals_service'\">\n <ng-container *ngTemplateOutlet=\"LOCAL_OPTIONS\"></ng-container>\n <app-local-storage-signals-demo></app-local-storage-signals-demo>\n</p> -->\n@case ('store_state_manager') {\n <p>\n <ng-container *ngTemplateOutlet=\"LOCAL_OPTIONS\"></ng-container>\n <app-store-state-manager-demo></app-store-state-manager-demo>\n </p>\n}\n@default {\n <p>\n Other\n </p>\n}\n}\n</span>\n\n<ng-template #HTTP_OPTIONS>\n @if (injectionOptions?.httpRequestOptions) {\n <div class=\"box\">\n <h3 style=\"font-weight: bold;\">Injection Token Detected - HTTP Options</h3>\n {{ injectionOptions?.httpRequestOptions| json }}\n </div>\n }\n</ng-template>\n\n<ng-template #LOCAL_OPTIONS>\n @if (injectionOptions?.LocalStorageOptions) {\n <ng-container class=\"box\">\n <div class=\"box\">\n <h3 style=\"font-weight: bold;\">Injection Token Detected - LocalStorage Options</h3>\n {{ injectionOptions?.LocalStorageOptions| json }}\n </div>\n </ng-container>\n }\n</ng-template>\n\n\n", styles: [".box{padding:1rem;background-color:#f5f5f5;border:thin gray solid;margin-top:1rem}\n"] }]
|
|
10366
|
-
}], ctorParameters: () => [{ type: ConfigOptions, decorators: [{
|
|
10367
|
-
type: Inject,
|
|
10368
|
-
args: [CONFIG_SETTINGS_TOKEN]
|
|
10369
|
-
}] }], propDecorators: { wsServer: [{
|
|
10370
|
-
type: Input
|
|
10371
|
-
}], jwtToken: [{
|
|
10372
|
-
type: Input
|
|
10373
|
-
}], server: [{
|
|
10374
|
-
type: Input
|
|
10375
|
-
}], user: [{
|
|
10376
|
-
type: Input
|
|
10377
|
-
}], path: [{
|
|
10378
|
-
type: Input
|
|
10379
|
-
}], adapter: [{
|
|
10380
|
-
type: Input
|
|
10381
|
-
}], mapper: [{
|
|
10382
|
-
type: Input
|
|
10383
|
-
}] } });
|
|
10384
|
-
|
|
10385
|
-
class LocalStorageSignalsDemoComponent {
|
|
10386
|
-
// store type is driven by the form control
|
|
10387
|
-
get type() {
|
|
10388
|
-
return (this.typeControl.value) ? +this.typeControl.value : 0;
|
|
10389
|
-
}
|
|
10390
|
-
get isValid() {
|
|
10391
|
-
return this.newStoreForm.valid;
|
|
10392
|
-
}
|
|
10393
|
-
get isValidData() {
|
|
10394
|
-
return this.storageForm.valid;
|
|
10395
|
-
}
|
|
10396
|
-
settingFor(name) {
|
|
10397
|
-
const c = this.localStorageManagerService.setting(name);
|
|
10398
|
-
return c ? c() : null;
|
|
10399
|
-
}
|
|
10400
|
-
constructor(configOptions) {
|
|
10401
|
-
this.configOptions = configOptions;
|
|
10402
|
-
this.fb = inject(FormBuilder);
|
|
10403
|
-
this.utils = inject(UtilsService);
|
|
10404
|
-
this.typeControl = this.fb.control(StorageType.GLOBAL.toString());
|
|
10405
|
-
this.localStorageManagerService = inject(LocalStorageSignalsManagerService);
|
|
10406
|
-
// Use signals directly and computed values for template binding
|
|
10407
|
-
this.settings = computed(() => this.localStorageManagerService.settings());
|
|
10408
|
-
// selected store (signal) and its JSON data for template
|
|
10409
|
-
this.storeSelected = signal(null);
|
|
10410
|
-
this.selectedStoreData = computed(() => {
|
|
10411
|
-
const s = this.storeSelected();
|
|
10412
|
-
if (!s)
|
|
10413
|
-
return '';
|
|
10414
|
-
const computed = this.localStorageManagerService.store(s.name);
|
|
10415
|
-
return computed ? JSON.stringify(computed()) : '';
|
|
10416
|
-
});
|
|
10417
|
-
// filtered settings by selected type
|
|
10418
|
-
this.selectedType = signal(StorageType.GLOBAL);
|
|
10419
|
-
this.filteredSettings = computed(() => {
|
|
10420
|
-
const values = this.settings() || [];
|
|
10421
|
-
return values.filter((item) => item.options.storage === +this.selectedType());
|
|
10422
|
-
});
|
|
10423
|
-
this.storageForm = this.fb.group({
|
|
10424
|
-
store: this.fb.control(null),
|
|
10425
|
-
type: 'local',
|
|
10426
|
-
settingType: 'local',
|
|
10427
|
-
encrypted: false,
|
|
10428
|
-
data: this.fb.control('', Validators.required),
|
|
10429
|
-
});
|
|
10430
|
-
this.newStoreForm = this.fb.group({
|
|
10431
|
-
name: this.fb.control(null, Validators.required),
|
|
10432
|
-
storage: 'local',
|
|
10433
|
-
encrypted: false,
|
|
10434
|
-
data: this.fb.control('', Validators.required),
|
|
10435
|
-
expiresIn: this.fb.control('0')
|
|
10436
|
-
});
|
|
10437
|
-
// no RxJS Observables here; template uses signals/computed directly
|
|
10438
|
-
this.expiresIn = (epoch) => this.utils.expiresIn(epoch);
|
|
10439
|
-
this.isValidJSON = (str) => {
|
|
10440
|
-
try {
|
|
10441
|
-
JSON.parse(str);
|
|
10442
|
-
return true;
|
|
10443
|
-
}
|
|
10444
|
-
catch (e) {
|
|
10445
|
-
return false;
|
|
10446
|
-
}
|
|
10447
|
-
};
|
|
10448
|
-
this.displayedColumns = ['name', 'id', 'encrypted', 'expires', "option"];
|
|
10449
|
-
this.filterData = (values) => {
|
|
10450
|
-
if (!values)
|
|
10451
|
-
return [];
|
|
10452
|
-
return values.filter((item) => item.options && item.options.storage === +this.type);
|
|
10453
|
-
};
|
|
10454
|
-
this.create = false;
|
|
10455
|
-
}
|
|
10456
|
-
ngOnInit() {
|
|
10457
|
-
this.storeProps = this.configOptions?.LocalStorageOptions;
|
|
10458
|
-
this.options = this.storeProps?.options;
|
|
10459
|
-
if (this.options?.storage) {
|
|
10460
|
-
this.typeControl.patchValue(this.options.storage.toString());
|
|
10461
|
-
this.typeControl.disable();
|
|
10462
|
-
}
|
|
10463
|
-
else {
|
|
10464
|
-
this.typeControl.enable();
|
|
10465
|
-
}
|
|
10466
|
-
if (this.options?.expiresIn) {
|
|
10467
|
-
this.newStoreForm.get('expiresIn')?.patchValue(this.options.expiresIn);
|
|
10468
|
-
this.newStoreForm.get('expiresIn')?.disable();
|
|
10469
|
-
}
|
|
10470
|
-
else {
|
|
10471
|
-
this.newStoreForm.get('expiresIn')?.enable();
|
|
10472
|
-
}
|
|
10473
|
-
if (this.options?.encrypted) {
|
|
10474
|
-
this.newStoreForm.get('encrypted')?.patchValue(this.options.encrypted);
|
|
10475
|
-
this.newStoreForm.get('encrypted')?.disable();
|
|
10476
|
-
}
|
|
10477
|
-
else {
|
|
10478
|
-
this.newStoreForm.get('encrypted')?.enable();
|
|
10479
|
-
}
|
|
10480
|
-
// nothing to synchronize - templates read computed signals directly
|
|
10481
|
-
}
|
|
10482
|
-
onCreateStore() {
|
|
10483
|
-
if (!this.isValid)
|
|
10484
|
-
return;
|
|
10485
|
-
const store = this.newStoreForm.value;
|
|
10486
|
-
if (!store.name || store.name === '')
|
|
10487
|
-
return;
|
|
10488
|
-
const options = { storage: this.type, encrypted: store.encrypted, expiresIn: store.expiresIn };
|
|
10489
|
-
this.localStorageManagerService.createStore({
|
|
10490
|
-
name: store.name,
|
|
10491
|
-
data: store.data,
|
|
10492
|
-
options: SettingOptions.adapt(options)
|
|
10493
|
-
});
|
|
10494
|
-
this.newStoreForm.reset();
|
|
10495
|
-
this.create = false;
|
|
10496
|
-
}
|
|
10497
|
-
onUpdateStore(store) {
|
|
10498
|
-
if (!this.storageForm.valid)
|
|
10499
|
-
return;
|
|
10500
|
-
const storeData = this.storageForm.value;
|
|
10501
|
-
const data = JSON.parse(storeData.data || '');
|
|
10502
|
-
const type = (storeData.type === 'local') ? StorageType.GLOBAL : StorageType.SESSION;
|
|
10503
|
-
this.localStorageManagerService.updateStore({
|
|
10504
|
-
name: store.name,
|
|
10505
|
-
data
|
|
10506
|
-
});
|
|
10507
|
-
}
|
|
10508
|
-
onSelectedRow(store) {
|
|
10509
|
-
this.store = store;
|
|
10510
|
-
this.storeSelected.set(store);
|
|
10511
|
-
this.create = false;
|
|
10512
|
-
}
|
|
10513
|
-
onCreate() {
|
|
10514
|
-
this.onCancel();
|
|
10515
|
-
this.create = true;
|
|
10516
|
-
}
|
|
10517
|
-
onDelete(store) {
|
|
10518
|
-
this.localStorageManagerService.deleteStore({
|
|
10519
|
-
name: store.name,
|
|
10520
|
-
});
|
|
10521
|
-
this.onCancel();
|
|
10522
|
-
}
|
|
10523
|
-
onCancel() {
|
|
10524
|
-
this.storeSelected.set(null);
|
|
10525
|
-
this.store = null;
|
|
10526
|
-
this.create = false;
|
|
10527
|
-
}
|
|
10528
|
-
onUpdate(store, data) {
|
|
10529
|
-
this.localStorageManagerService.updateStore({
|
|
10530
|
-
name: store.name,
|
|
10531
|
-
data: JSON.parse(data)
|
|
10532
|
-
});
|
|
10533
|
-
this.onCancel();
|
|
10534
|
-
}
|
|
10535
|
-
onReset() {
|
|
10536
|
-
this.localStorageManagerService.resetStore();
|
|
10537
|
-
}
|
|
10538
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: LocalStorageSignalsDemoComponent, deps: [{ token: CONFIG_SETTINGS_TOKEN }], target: i0.ɵɵFactoryTarget.Component }); }
|
|
10539
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: LocalStorageSignalsDemoComponent, selector: "app-local-storage-signals-demo", ngImport: i0, template: "<div style=\"margin: 2rem;\">\n\n <div style=\"display: flex; gap: 1rem\">\n <h2 style=\"padding-top: .5rem; display: flex;\">\n <div style=\"padding-top: .5rem;\">\n <span>Local Storage Manager</span>\n <span style=\"margin-left: .5rem;\">\n <mat-icon color=\"accent\">fiber_new</mat-icon>\n </span>\n </div>\n <div>\n <button mat-icon-button [matMenuTriggerFor]=\"menu\">\n <mat-icon>info</mat-icon>\n </button>\n </div>\n </h2>\n <span style=\"flex:1\"></span>\n <div style=\"padding-top: .25rem;\">\n <mat-button-toggle-group name=\"storage\" [formControl]=\"typeControl\" (change)=\"onCancel()\">\n <mat-button-toggle value=\"0\">Local Storage</mat-button-toggle>\n <mat-button-toggle value=\"1\">Session Storage</mat-button-toggle>\n </mat-button-toggle-group>\n </div>\n <div style=\"margin-top: .25rem;\">\n <button mat-icon-button (click)=\"onCreate()\">\n <mat-icon>add</mat-icon>\n </button>\n </div>\n </div>\n\n <ng-container *ngIf=\"storeProps?.storageName || storeProps?.storageSettingsName\">\n <div style=\"display: flex; gap: .5rem; flex-direction: column; border: gray solid thin; background-color: whitesmoke; padding: 1rem;\"\n >\n <div style=\"display: flex;\">\n <div *ngIf=\"storeProps?.storageSettingsName\" style=\"flex:1\">\n Database: <b>{{ storeProps?.storageSettingsName }}</b>\n </div>\n <div *ngIf=\"storeProps?.storageName\" style=\"flex:1\">\n Data: <b>{{ storeProps?.storageName }}</b>\n </div>\n </div>\n </div>\n </ng-container>\n\n <div style=\"margin-top: 2rem;\" [formGroup]=\"newStoreForm\" *ngIf=\"create; else LIST\">\n\n <h2>Create Store</h2>\n\n <div style=\"display: flex; gap: 1rem;\">\n <div style=\"flex:1\">\n <mat-form-field appearance=\"outline\">\n <mat-label>Store Name</mat-label>\n <input matInput placeholder=\"sample\" formControlName=\"name\">\n </mat-form-field>\n </div>\n <div>\n <mat-form-field appearance=\"outline\">\n <mat-label>Expires In...</mat-label>\n <mat-select formControlName=\"expiresIn\">\n <mat-option value=\"0\">None</mat-option>\n <mat-option value=\"30s\">30 Seconds</mat-option>\n <mat-option value=\"1mn\">1 Minute</mat-option>\n <mat-option value=\"1hr\">1 Hour</mat-option>\n <mat-option value=\"1d\">1 Day</mat-option>\n </mat-select>\n </mat-form-field>\n </div>\n <div style=\"margin-top: 1rem;\">\n <mat-slide-toggle formControlName=\"encrypted\">Encrypted</mat-slide-toggle>\n </div>\n </div>\n\n <div style=\"display: flex;\">\n <mat-form-field appearance=\"outline\" style=\"flex: 1;\">\n <mat-label>Data (Must be an Object or Array of any)</mat-label>\n <textarea matInput placeholder=\"[]\" formControlName=\"data\" #json></textarea>\n </mat-form-field>\n </div>\n <mat-error *ngIf=\"!isValidJSON(json.value)\">Not Valid Data</mat-error>\n\n <div style=\"display: flex;\">\n <span style=\"flex:1\"></span>\n <button mat-stroked-button (click)=\"onCreateStore()\" [disabled]=\"!(isValid && isValidJSON(json.value))\">\n Save Store\n </button>\n </div>\n\n </div>\n\n <ng-template #LIST>\n\n <div style=\"margin-top: 1rem;\">\n <mat-divider></mat-divider>\n </div>\n{{ settings() | json }}\n <div *ngIf=\"filterData(settings()) as data\">\n <ng-container *ngIf=\"data.length > 0; else NO_DATA\">\n <table mat-table [dataSource]=\"data\">\n <ng-container matColumnDef=\"name\">\n <th mat-header-cell *matHeaderCellDef> Store </th>\n <td mat-cell *matCellDef=\"let element\" style=\"font-weight: bold; text-transform: uppercase;\"> {{element.name}} </td>\n </ng-container>\n\n <ng-container matColumnDef=\"id\">\n <th mat-header-cell *matHeaderCellDef> ID </th>\n <td mat-cell *matCellDef=\"let element\"> {{element.id}} </td>\n </ng-container>\n\n <ng-container matColumnDef=\"encrypted\">\n <th mat-header-cell *matHeaderCellDef style=\"text-align: center;\"> Encrypted </th>\n <td mat-cell *matCellDef=\"let element\" style=\"text-align: center;\">\n <ng-container *ngIf=\"element.options.encrypted; else NO\">YES</ng-container>\n <ng-template #NO><span style=\"color:gray\">NO</span></ng-template>\n </td>\n </ng-container>\n\n <ng-container matColumnDef=\"expires\">\n <th mat-header-cell *matHeaderCellDef style=\"text-align: center;\"> Expires </th>\n <td mat-cell *matCellDef=\"let element\" style=\"text-align: center;\">\n <ng-container *ngIf=\"element.options.expires !== 0; else NO_DATA\">\n {{expiresIn(element.options.expires)}}\n </ng-container>\n <ng-template #NO_DATA>\n \u221E\n </ng-template>\n </td>\n </ng-container>\n\n <ng-container matColumnDef=\"option\">\n <th mat-header-cell *matHeaderCellDef></th>\n <td mat-cell *matCellDef=\"let element\" style=\"padding-right: 0;\">\n <div style=\"display: flex;justify-content: flex-end;\">\n <button mat-icon-button color=\"warn\" (click)=\"onDelete(element); $event.stopPropagation()\">\n <mat-icon>close</mat-icon>\n </button>\n </div>\n </td>\n </ng-container>\n\n <tr mat-header-row *matHeaderRowDef=\"displayedColumns\"></tr>\n <tr mat-row *matRowDef=\"let row; columns: displayedColumns;\" (click)=\"onSelectedRow(row)\"></tr>\n </table>\n </ng-container>\n\n <ng-template #NO_DATA>\n <h3 style=\"margin-top: 1rem;\">No Data</h3>\n </ng-template>\n\n <div *ngIf=\"storeSelected() as selectedStore\" style=\"margin-top: 2rem;\">\n <div style=\"margin-bottom: 1rem;\">\n <mat-divider></mat-divider>\n </div>\n\n <h3>STORE: <span style=\"font-weight: bold;\">{{ selectedStore.name | uppercase }}</span></h3>\n <h3>SETTINGS: {{ localStorageManagerService.setting(selectedStore.name) ? (localStorageManagerService.setting(selectedStore.name)() | json) : '{}' }}</h3>\n <div style=\"display: flex;\">\n <mat-form-field appearance=\"outline\" style=\"flex: 1;\">\n <mat-label>Data (Must be an Object or Array of any)</mat-label>\n <textarea matInput placeholder=\"[]\" #json [value]=\"selectedStoreData()\"></textarea>\n </mat-form-field>\n </div>\n <mat-error *ngIf=\"!isValidJSON(json.value)\">Not Valid Data</mat-error>\n\n <div style=\"display: flex; gap: .5rem;\">\n <span style=\"flex:1\"></span>\n <button mat-stroked-button (click)=\"onCancel()\">Cancel</button>\n <button mat-stroked-button (click)=\"onUpdate(selectedStore, json.value)\" [disabled]=\"!(isValidJSON(json.value))\">Update</button>\n </div>\n </div>\n </div>\n\n </ng-template>\n\n <div style=\"margin-top: 1rem;\">\n <mat-divider></mat-divider>\n </div>\n\n <div style=\"margin-top: 1rem;\">\n <button mat-stroked-button (click)=\"onReset()\">Delete All Stores</button>\n </div>\n\n</div>\n\n\n<mat-menu #menu=\"matMenu\">\n <div style=\"padding: 1rem;\">\n <p>Please note that the LocalStorage (encryption) and management of data is dependant on initializing the APP_ID</p>\n <p>Must provide a value for <b>APP_ID</b> in AppModule->Providers</p>\n <mat-divider></mat-divider>\n <div style=\"font-size: smaller; margin-top: .5rem;\">\n <p>Example: UUID (self.crypto.randomUUID() to generate)</p>\n <div style=\"background-color: whitesmoke; padding: 1rem;\">\n {<br>\n provide: APP_ID,<br>\n useValue: \"056991ac-3537-43ab-b5b9-83edf6554eff\",<br>\n }\n </div>\n </div>\n </div>\n</mat-menu>\n", styles: [".mat-mdc-row .mat-mdc-cell{border-bottom:1px solid transparent;border-top:1px solid transparent;cursor:pointer}.mat-mdc-row:hover .mat-mdc-cell{border-color:currentColor}.demo-row-is-clicked{font-weight:700}.mat-mdc-menu-panel.mat-mdc-menu-panel{max-width:280px!important}\n"], dependencies: [{ kind: "directive", type: i1$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i2$1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2$1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i2$1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "directive", type: i2$1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i2$1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "component", type: i3.MatButton, selector: " button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button] ", exportAs: ["matButton"] }, { kind: "component", type: i3.MatIconButton, selector: "button[mat-icon-button]", exportAs: ["matButton"] }, { kind: "component", type: i4.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i4.MatLabel, selector: "mat-label" }, { kind: "directive", type: i4.MatError, selector: "mat-error, [matError]", inputs: ["id"] }, { kind: "component", type: i5.MatSelect, selector: "mat-select", inputs: ["aria-describedby", "panelClass", "disabled", "disableRipple", "tabIndex", "hideSingleSelectionIndicator", "placeholder", "required", "multiple", "disableOptionCentering", "compareWith", "value", "aria-label", "aria-labelledby", "errorStateMatcher", "typeaheadDebounceInterval", "sortComparator", "id", "panelWidth"], outputs: ["openedChange", "opened", "closed", "selectionChange", "valueChange"], exportAs: ["matSelect"] }, { kind: "component", type: i6.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "component", type: i7.MatMenu, selector: "mat-menu", inputs: ["backdropClass", "aria-label", "aria-labelledby", "aria-describedby", "xPosition", "yPosition", "overlapTrigger", "hasBackdrop", "class", "classList"], outputs: ["closed", "close"], exportAs: ["matMenu"] }, { kind: "directive", type: i7.MatMenuTrigger, selector: "[mat-menu-trigger-for], [matMenuTriggerFor]", inputs: ["mat-menu-trigger-for", "matMenuTriggerFor", "matMenuTriggerData", "matMenuTriggerRestoreFocus"], outputs: ["menuOpened", "onMenuOpen", "menuClosed", "onMenuClose"], exportAs: ["matMenuTrigger"] }, { kind: "component", type: i8.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: i9.MatTable, selector: "mat-table, table[mat-table]", exportAs: ["matTable"] }, { kind: "directive", type: i9.MatHeaderCellDef, selector: "[matHeaderCellDef]" }, { kind: "directive", type: i9.MatHeaderRowDef, selector: "[matHeaderRowDef]", inputs: ["matHeaderRowDef", "matHeaderRowDefSticky"] }, { kind: "directive", type: i9.MatColumnDef, selector: "[matColumnDef]", inputs: ["matColumnDef"] }, { kind: "directive", type: i9.MatCellDef, selector: "[matCellDef]" }, { kind: "directive", type: i9.MatRowDef, selector: "[matRowDef]", inputs: ["matRowDefColumns", "matRowDefWhen"] }, { kind: "directive", type: i9.MatHeaderCell, selector: "mat-header-cell, th[mat-header-cell]" }, { kind: "directive", type: i9.MatCell, selector: "mat-cell, td[mat-cell]" }, { kind: "component", type: i9.MatHeaderRow, selector: "mat-header-row, tr[mat-header-row]", exportAs: ["matHeaderRow"] }, { kind: "component", type: i9.MatRow, selector: "mat-row, tr[mat-row]", exportAs: ["matRow"] }, { kind: "directive", type: i10.MatButtonToggleGroup, selector: "mat-button-toggle-group", inputs: ["appearance", "name", "vertical", "value", "multiple", "disabled", "disabledInteractive", "hideSingleSelectionIndicator", "hideMultipleSelectionIndicator"], outputs: ["valueChange", "change"], exportAs: ["matButtonToggleGroup"] }, { kind: "component", type: i10.MatButtonToggle, selector: "mat-button-toggle", inputs: ["aria-label", "aria-labelledby", "id", "name", "value", "tabIndex", "disableRipple", "appearance", "checked", "disabled", "disabledInteractive"], outputs: ["change"], exportAs: ["matButtonToggle"] }, { kind: "component", type: i11.MatSlideToggle, selector: "mat-slide-toggle", inputs: ["name", "id", "labelPosition", "aria-label", "aria-labelledby", "aria-describedby", "required", "color", "disabled", "disableRipple", "tabIndex", "checked", "hideIcon", "disabledInteractive"], outputs: ["change", "toggleChange"], exportAs: ["matSlideToggle"] }, { kind: "component", type: i12.MatDivider, selector: "mat-divider", inputs: ["vertical", "inset"] }, { kind: "directive", type: i13.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly"], exportAs: ["matInput"] }, { kind: "pipe", type: i1$1.UpperCasePipe, name: "uppercase" }, { kind: "pipe", type: i1$1.JsonPipe, name: "json" }], encapsulation: i0.ViewEncapsulation.None }); }
|
|
10540
|
-
}
|
|
10541
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: LocalStorageSignalsDemoComponent, decorators: [{
|
|
10542
|
-
type: Component,
|
|
10543
|
-
args: [{ selector: 'app-local-storage-signals-demo', encapsulation: ViewEncapsulation.None, standalone: false, template: "<div style=\"margin: 2rem;\">\n\n <div style=\"display: flex; gap: 1rem\">\n <h2 style=\"padding-top: .5rem; display: flex;\">\n <div style=\"padding-top: .5rem;\">\n <span>Local Storage Manager</span>\n <span style=\"margin-left: .5rem;\">\n <mat-icon color=\"accent\">fiber_new</mat-icon>\n </span>\n </div>\n <div>\n <button mat-icon-button [matMenuTriggerFor]=\"menu\">\n <mat-icon>info</mat-icon>\n </button>\n </div>\n </h2>\n <span style=\"flex:1\"></span>\n <div style=\"padding-top: .25rem;\">\n <mat-button-toggle-group name=\"storage\" [formControl]=\"typeControl\" (change)=\"onCancel()\">\n <mat-button-toggle value=\"0\">Local Storage</mat-button-toggle>\n <mat-button-toggle value=\"1\">Session Storage</mat-button-toggle>\n </mat-button-toggle-group>\n </div>\n <div style=\"margin-top: .25rem;\">\n <button mat-icon-button (click)=\"onCreate()\">\n <mat-icon>add</mat-icon>\n </button>\n </div>\n </div>\n\n <ng-container *ngIf=\"storeProps?.storageName || storeProps?.storageSettingsName\">\n <div style=\"display: flex; gap: .5rem; flex-direction: column; border: gray solid thin; background-color: whitesmoke; padding: 1rem;\"\n >\n <div style=\"display: flex;\">\n <div *ngIf=\"storeProps?.storageSettingsName\" style=\"flex:1\">\n Database: <b>{{ storeProps?.storageSettingsName }}</b>\n </div>\n <div *ngIf=\"storeProps?.storageName\" style=\"flex:1\">\n Data: <b>{{ storeProps?.storageName }}</b>\n </div>\n </div>\n </div>\n </ng-container>\n\n <div style=\"margin-top: 2rem;\" [formGroup]=\"newStoreForm\" *ngIf=\"create; else LIST\">\n\n <h2>Create Store</h2>\n\n <div style=\"display: flex; gap: 1rem;\">\n <div style=\"flex:1\">\n <mat-form-field appearance=\"outline\">\n <mat-label>Store Name</mat-label>\n <input matInput placeholder=\"sample\" formControlName=\"name\">\n </mat-form-field>\n </div>\n <div>\n <mat-form-field appearance=\"outline\">\n <mat-label>Expires In...</mat-label>\n <mat-select formControlName=\"expiresIn\">\n <mat-option value=\"0\">None</mat-option>\n <mat-option value=\"30s\">30 Seconds</mat-option>\n <mat-option value=\"1mn\">1 Minute</mat-option>\n <mat-option value=\"1hr\">1 Hour</mat-option>\n <mat-option value=\"1d\">1 Day</mat-option>\n </mat-select>\n </mat-form-field>\n </div>\n <div style=\"margin-top: 1rem;\">\n <mat-slide-toggle formControlName=\"encrypted\">Encrypted</mat-slide-toggle>\n </div>\n </div>\n\n <div style=\"display: flex;\">\n <mat-form-field appearance=\"outline\" style=\"flex: 1;\">\n <mat-label>Data (Must be an Object or Array of any)</mat-label>\n <textarea matInput placeholder=\"[]\" formControlName=\"data\" #json></textarea>\n </mat-form-field>\n </div>\n <mat-error *ngIf=\"!isValidJSON(json.value)\">Not Valid Data</mat-error>\n\n <div style=\"display: flex;\">\n <span style=\"flex:1\"></span>\n <button mat-stroked-button (click)=\"onCreateStore()\" [disabled]=\"!(isValid && isValidJSON(json.value))\">\n Save Store\n </button>\n </div>\n\n </div>\n\n <ng-template #LIST>\n\n <div style=\"margin-top: 1rem;\">\n <mat-divider></mat-divider>\n </div>\n{{ settings() | json }}\n <div *ngIf=\"filterData(settings()) as data\">\n <ng-container *ngIf=\"data.length > 0; else NO_DATA\">\n <table mat-table [dataSource]=\"data\">\n <ng-container matColumnDef=\"name\">\n <th mat-header-cell *matHeaderCellDef> Store </th>\n <td mat-cell *matCellDef=\"let element\" style=\"font-weight: bold; text-transform: uppercase;\"> {{element.name}} </td>\n </ng-container>\n\n <ng-container matColumnDef=\"id\">\n <th mat-header-cell *matHeaderCellDef> ID </th>\n <td mat-cell *matCellDef=\"let element\"> {{element.id}} </td>\n </ng-container>\n\n <ng-container matColumnDef=\"encrypted\">\n <th mat-header-cell *matHeaderCellDef style=\"text-align: center;\"> Encrypted </th>\n <td mat-cell *matCellDef=\"let element\" style=\"text-align: center;\">\n <ng-container *ngIf=\"element.options.encrypted; else NO\">YES</ng-container>\n <ng-template #NO><span style=\"color:gray\">NO</span></ng-template>\n </td>\n </ng-container>\n\n <ng-container matColumnDef=\"expires\">\n <th mat-header-cell *matHeaderCellDef style=\"text-align: center;\"> Expires </th>\n <td mat-cell *matCellDef=\"let element\" style=\"text-align: center;\">\n <ng-container *ngIf=\"element.options.expires !== 0; else NO_DATA\">\n {{expiresIn(element.options.expires)}}\n </ng-container>\n <ng-template #NO_DATA>\n \u221E\n </ng-template>\n </td>\n </ng-container>\n\n <ng-container matColumnDef=\"option\">\n <th mat-header-cell *matHeaderCellDef></th>\n <td mat-cell *matCellDef=\"let element\" style=\"padding-right: 0;\">\n <div style=\"display: flex;justify-content: flex-end;\">\n <button mat-icon-button color=\"warn\" (click)=\"onDelete(element); $event.stopPropagation()\">\n <mat-icon>close</mat-icon>\n </button>\n </div>\n </td>\n </ng-container>\n\n <tr mat-header-row *matHeaderRowDef=\"displayedColumns\"></tr>\n <tr mat-row *matRowDef=\"let row; columns: displayedColumns;\" (click)=\"onSelectedRow(row)\"></tr>\n </table>\n </ng-container>\n\n <ng-template #NO_DATA>\n <h3 style=\"margin-top: 1rem;\">No Data</h3>\n </ng-template>\n\n <div *ngIf=\"storeSelected() as selectedStore\" style=\"margin-top: 2rem;\">\n <div style=\"margin-bottom: 1rem;\">\n <mat-divider></mat-divider>\n </div>\n\n <h3>STORE: <span style=\"font-weight: bold;\">{{ selectedStore.name | uppercase }}</span></h3>\n <h3>SETTINGS: {{ localStorageManagerService.setting(selectedStore.name) ? (localStorageManagerService.setting(selectedStore.name)() | json) : '{}' }}</h3>\n <div style=\"display: flex;\">\n <mat-form-field appearance=\"outline\" style=\"flex: 1;\">\n <mat-label>Data (Must be an Object or Array of any)</mat-label>\n <textarea matInput placeholder=\"[]\" #json [value]=\"selectedStoreData()\"></textarea>\n </mat-form-field>\n </div>\n <mat-error *ngIf=\"!isValidJSON(json.value)\">Not Valid Data</mat-error>\n\n <div style=\"display: flex; gap: .5rem;\">\n <span style=\"flex:1\"></span>\n <button mat-stroked-button (click)=\"onCancel()\">Cancel</button>\n <button mat-stroked-button (click)=\"onUpdate(selectedStore, json.value)\" [disabled]=\"!(isValidJSON(json.value))\">Update</button>\n </div>\n </div>\n </div>\n\n </ng-template>\n\n <div style=\"margin-top: 1rem;\">\n <mat-divider></mat-divider>\n </div>\n\n <div style=\"margin-top: 1rem;\">\n <button mat-stroked-button (click)=\"onReset()\">Delete All Stores</button>\n </div>\n\n</div>\n\n\n<mat-menu #menu=\"matMenu\">\n <div style=\"padding: 1rem;\">\n <p>Please note that the LocalStorage (encryption) and management of data is dependant on initializing the APP_ID</p>\n <p>Must provide a value for <b>APP_ID</b> in AppModule->Providers</p>\n <mat-divider></mat-divider>\n <div style=\"font-size: smaller; margin-top: .5rem;\">\n <p>Example: UUID (self.crypto.randomUUID() to generate)</p>\n <div style=\"background-color: whitesmoke; padding: 1rem;\">\n {<br>\n provide: APP_ID,<br>\n useValue: \"056991ac-3537-43ab-b5b9-83edf6554eff\",<br>\n }\n </div>\n </div>\n </div>\n</mat-menu>\n", styles: [".mat-mdc-row .mat-mdc-cell{border-bottom:1px solid transparent;border-top:1px solid transparent;cursor:pointer}.mat-mdc-row:hover .mat-mdc-cell{border-color:currentColor}.demo-row-is-clicked{font-weight:700}.mat-mdc-menu-panel.mat-mdc-menu-panel{max-width:280px!important}\n"] }]
|
|
10544
|
-
}], ctorParameters: () => [{ type: ConfigOptions, decorators: [{
|
|
10545
|
-
type: Inject,
|
|
10546
|
-
args: [CONFIG_SETTINGS_TOKEN]
|
|
10547
|
-
}] }] });
|
|
10548
|
-
|
|
10549
|
-
class ClientInfo {
|
|
10550
|
-
constructor(domain = '', service = '', id = 0, name = '') {
|
|
10551
|
-
this.domain = domain;
|
|
10552
|
-
this.service = service;
|
|
10553
|
-
this.id = id;
|
|
10554
|
-
this.name = name;
|
|
10555
|
-
}
|
|
10556
|
-
static adapt(item) {
|
|
10557
|
-
return new ClientInfo(item?.domain, item?.service, item?.id, (item?.first_name || item?.last_name) ? `${item?.first_name} ${item?.last_name}` : '');
|
|
10558
|
-
}
|
|
10559
|
-
}
|
|
10560
|
-
|
|
10561
|
-
class ClientInfoMapper {
|
|
10562
|
-
constructor(id = 0, first_name = '', last_name = '', email = '') {
|
|
10563
|
-
this.id = id;
|
|
10564
|
-
this.first_name = first_name;
|
|
10565
|
-
this.last_name = last_name;
|
|
10566
|
-
this.email = email;
|
|
10567
|
-
}
|
|
10568
|
-
static adapt(item) {
|
|
10569
|
-
const first_name = (item?.name) ? item.name.split(' ')[0] : '';
|
|
10570
|
-
const last_name = (item?.name) ? item.name.split(' ')[1] : '';
|
|
10571
|
-
return new ClientInfoMapper(item?.id, first_name, last_name, item?.email);
|
|
10572
|
-
}
|
|
10573
|
-
}
|
|
10574
|
-
|
|
10575
|
-
class AIPrompt {
|
|
10576
|
-
constructor(response = '') {
|
|
10577
|
-
this.response = response;
|
|
10578
|
-
}
|
|
10579
|
-
static adapt(item) {
|
|
10580
|
-
return new AIPrompt(item?.response);
|
|
10581
|
-
}
|
|
10582
|
-
}
|
|
10583
|
-
|
|
10584
|
-
class RequestSignalsManagerDemoComponent {
|
|
10585
|
-
get retry() {
|
|
10586
|
-
return this.requestForm.get('retry')?.value;
|
|
10587
|
-
}
|
|
10588
|
-
get headers() {
|
|
10589
|
-
return this.requestForm.get('headers');
|
|
10590
|
-
}
|
|
10591
|
-
get isValid() {
|
|
10592
|
-
this.requestForm.markAllAsTouched();
|
|
10593
|
-
return this.requestForm.valid;
|
|
10594
|
-
}
|
|
10595
|
-
constructor() {
|
|
10596
|
-
this.displayedColumns = ['id', 'name', 'lastName', 'age'];
|
|
10597
|
-
this.fb = inject(FormBuilder);
|
|
10598
|
-
this.toastMessage = inject(ToastMessageDisplayService);
|
|
10599
|
-
this.httpManagerSignalsService = inject(HTTPManagerSignalsService);
|
|
10600
|
-
// Using signals service: signals are callable in templates (e.g. isPending())
|
|
10601
|
-
this.isPending = this.httpManagerSignalsService.isPending;
|
|
10602
|
-
this.countdown = this.httpManagerSignalsService.countdown;
|
|
10603
|
-
// per-operation plain results and error messages (no rxjs in component)
|
|
10604
|
-
this.GET_result = null;
|
|
10605
|
-
this.POST_result = null;
|
|
10606
|
-
this.PUT_result = null;
|
|
10607
|
-
this.DELETE_result = null;
|
|
10608
|
-
this.STREAM_result = null;
|
|
10609
|
-
this.STREAM_AI_result = null;
|
|
10610
|
-
this.GET_error = '';
|
|
10611
|
-
this.POST_error = '';
|
|
10612
|
-
this.PUT_error = '';
|
|
10613
|
-
this.DELETE_error = '';
|
|
10614
|
-
this.STREAM_error = '';
|
|
10615
|
-
this.STREAM_AI_error = '';
|
|
10616
|
-
this.requestParams = {
|
|
10617
|
-
GET: ApiRequest.adapt(),
|
|
10618
|
-
POST: ApiRequest.adapt(),
|
|
10619
|
-
PUT: ApiRequest.adapt(),
|
|
10620
|
-
DELETE: ApiRequest.adapt(),
|
|
10621
|
-
STREAM: ApiRequest.adapt(),
|
|
10622
|
-
};
|
|
10623
|
-
this.questionControl = this.fb.control("", [Validators.required]);
|
|
10624
|
-
this.downloadRequest = ApiRequest.adapt({
|
|
10625
|
-
server: 'assets/images',
|
|
10626
|
-
path: ['lego.png'],
|
|
10627
|
-
// saveAs: 'john.jpg' // Optional
|
|
10628
|
-
});
|
|
10629
|
-
// downloadRequest = ApiRequest.adapt({
|
|
10630
|
-
// server: 'oidc/ai/file'
|
|
10631
|
-
// })
|
|
10632
|
-
this.sampleClientData = {
|
|
10633
|
-
id: 0,
|
|
10634
|
-
name: "Old School Dates",
|
|
10635
|
-
domain: "osd.com",
|
|
10636
|
-
service: "osd",
|
|
10637
|
-
spiffe: "osd.com/osd",
|
|
10638
|
-
secret: "SMOPECXP-OS4P-USOG-X2II-3XMD1FQDR3IJX",
|
|
10639
|
-
created: 1693003138,
|
|
10640
|
-
modified: 1693003138,
|
|
10641
|
-
icon: "",
|
|
10642
|
-
imageFile: "",
|
|
10643
|
-
email: "wavecoders@gmail.com"
|
|
10644
|
-
};
|
|
10645
|
-
this.requestForm = this.fb.group({
|
|
10646
|
-
path: this.fb.control("ai/"),
|
|
10647
|
-
headers: this.fb.array([]),
|
|
10648
|
-
adapter: [null],
|
|
10649
|
-
mapper: [null],
|
|
10650
|
-
retry: this.fb.group({
|
|
10651
|
-
times: [3],
|
|
10652
|
-
delay: [3],
|
|
10653
|
-
}),
|
|
10654
|
-
polling: [3],
|
|
10655
|
-
});
|
|
10656
|
-
this.AIType = 0;
|
|
10657
|
-
this.sampleAdaptors = [
|
|
10658
|
-
{ label: "ClientInfo Basic", value: ClientInfo.adapt },
|
|
10659
|
-
{ label: "AI Prompt", value: AIPrompt.adapt },
|
|
10660
|
-
];
|
|
10661
|
-
this.sampleMappers = [
|
|
10662
|
-
{ label: "Mapper Basic", value: ClientInfoMapper.adapt },
|
|
10663
|
-
{ label: "AI Prompt", value: AIPrompt.adapt },
|
|
10664
|
-
];
|
|
10665
|
-
this.hasId = (arr) => {
|
|
10666
|
-
if (arr.length === 0)
|
|
10667
|
-
return false;
|
|
10668
|
-
return !isNaN(arr[arr.length - 1]);
|
|
10669
|
-
};
|
|
10670
|
-
this.props = (adapter) => {
|
|
10671
|
-
return (adapter) ? adapter() : null;
|
|
10672
|
-
};
|
|
10673
|
-
// server = `http://sample-endpoint/as/authorization.oauth2`
|
|
10674
|
-
this.arrayObjectsToObjects = (arr) => {
|
|
10675
|
-
return Array.isArray(arr) ? arr.reduce((obj, item) => Object.assign(obj, { [item.key]: item.value }), {}) : {};
|
|
10676
|
-
};
|
|
10677
|
-
}
|
|
10678
|
-
ngOnInit() {
|
|
10679
|
-
// const reqGet2 = ApiRequest.adapt({
|
|
10680
|
-
// server,
|
|
10681
|
-
// path: ['clients'],
|
|
10682
|
-
// headers: { authentication: "Bearer <KEY>" },
|
|
10683
|
-
// adapter: ClientInfo,
|
|
10684
|
-
// dataType: DataType.OBJECT,
|
|
10685
|
-
// // concurrent: false,
|
|
10686
|
-
// // polling: 3, //seconds
|
|
10687
|
-
// })
|
|
10688
|
-
// const req2 = [1024,1025,1026].map(item => {
|
|
10689
|
-
// return this.httpManagerService.getRequest<ClientInfo[]>(reqGet2, [item])
|
|
10690
|
-
// .pipe(
|
|
10691
|
-
// catchError(error => {
|
|
10692
|
-
// return throwError(() => new Error(error.error.message))
|
|
10693
|
-
// })
|
|
10694
|
-
// )
|
|
10695
|
-
// })
|
|
10696
|
-
// forkJoin(req2)
|
|
10697
|
-
// .subscribe(res => console.log(res))
|
|
10698
|
-
}
|
|
10699
|
-
addHeader() {
|
|
10700
|
-
const header = this.fb.group({
|
|
10701
|
-
key: ['', Validators.required],
|
|
10702
|
-
value: ['']
|
|
10703
|
-
});
|
|
10704
|
-
this.headers.push(header);
|
|
10705
|
-
}
|
|
10706
|
-
removeHeader(index) {
|
|
10707
|
-
this.headers.removeAt(index);
|
|
10708
|
-
}
|
|
10709
|
-
compileRequest() {
|
|
10710
|
-
const requestParams = this.requestForm.value;
|
|
10711
|
-
requestParams.headers = this.arrayObjectsToObjects(requestParams.headers || []);
|
|
10712
|
-
const pathReq = (requestParams.path === "") ? [] : (requestParams.path || "").split("/");
|
|
10713
|
-
if (!this.pollingState.checked)
|
|
10714
|
-
requestParams.polling = 0;
|
|
10715
|
-
if (!this.failedState.checked) {
|
|
10716
|
-
requestParams.retry = { times: 0, delay: 0 };
|
|
10717
|
-
}
|
|
10718
|
-
const apiOptions = ApiRequest.adapt(requestParams);
|
|
10719
|
-
apiOptions.path = [];
|
|
10720
|
-
return { apiOptions: apiOptions, path: pathReq };
|
|
10721
|
-
}
|
|
10722
|
-
onGetRequest() {
|
|
10723
|
-
if (!this.isValid)
|
|
10724
|
-
return;
|
|
10725
|
-
const reqParams = this.compileRequest();
|
|
10726
|
-
this.requestParams.GET = reqParams.apiOptions;
|
|
10727
|
-
// reset local state
|
|
10728
|
-
this.GET_result = null;
|
|
10729
|
-
this.GET_error = '';
|
|
10730
|
-
this.httpManagerSignalsService.getRequest(reqParams.apiOptions, reqParams.path)
|
|
10731
|
-
.subscribe({
|
|
10732
|
-
next: (res) => this.GET_result = res,
|
|
10733
|
-
error: (err) => this.errorHandling(err, 'GET')
|
|
10734
|
-
});
|
|
10735
|
-
}
|
|
10736
|
-
onCreateRequest() {
|
|
10737
|
-
if (!this.isValid)
|
|
10738
|
-
return;
|
|
10739
|
-
const reqParams = this.compileRequest();
|
|
10740
|
-
this.requestParams.POST = reqParams.apiOptions;
|
|
10741
|
-
// reset local state
|
|
10742
|
-
this.POST_result = null;
|
|
10743
|
-
this.POST_error = '';
|
|
10744
|
-
this.httpManagerSignalsService.postRequest(this.sampleClientData, reqParams.apiOptions, reqParams.path)
|
|
10745
|
-
.subscribe({
|
|
10746
|
-
next: (res) => this.POST_result = res,
|
|
10747
|
-
error: (err) => this.errorHandling(err, 'POST')
|
|
10748
|
-
});
|
|
10749
|
-
}
|
|
10750
|
-
onUpdateRequest() {
|
|
10751
|
-
if (!this.isValid)
|
|
10752
|
-
return;
|
|
10753
|
-
const reqParams = this.compileRequest();
|
|
10754
|
-
if (!this.hasId(reqParams.path)) {
|
|
10755
|
-
console.log("Missing ID");
|
|
10756
|
-
return;
|
|
10757
|
-
}
|
|
10758
|
-
this.sampleClientData.id = parseInt(reqParams.path[reqParams.path.length - 1]);
|
|
10759
|
-
this.requestParams.PUT = reqParams.apiOptions;
|
|
10760
|
-
// reset local state
|
|
10761
|
-
this.PUT_result = null;
|
|
10762
|
-
this.PUT_error = '';
|
|
10763
|
-
this.httpManagerSignalsService.putRequest(this.sampleClientData, reqParams.apiOptions, reqParams.path)
|
|
10764
|
-
.subscribe({
|
|
10765
|
-
next: (res) => this.PUT_result = res,
|
|
10766
|
-
error: (err) => this.errorHandling(err, 'PUT')
|
|
10767
|
-
});
|
|
10768
|
-
}
|
|
10769
|
-
onDeleteRequest() {
|
|
10770
|
-
if (!this.isValid)
|
|
10771
|
-
return;
|
|
10772
|
-
const reqParams = this.compileRequest();
|
|
10773
|
-
this.requestParams.DELETE = reqParams.apiOptions;
|
|
10774
|
-
if (!this.hasId(reqParams.path)) {
|
|
10775
|
-
console.log("Missing ID");
|
|
10776
|
-
return;
|
|
10777
|
-
}
|
|
10778
|
-
this.sampleClientData.id = parseInt(reqParams.path[reqParams.path.length - 1]);
|
|
10779
|
-
this.requestParams.DELETE = reqParams.apiOptions;
|
|
10780
|
-
// reset local state
|
|
10781
|
-
this.DELETE_result = null;
|
|
10782
|
-
this.DELETE_error = '';
|
|
10783
|
-
this.httpManagerSignalsService.deleteRequest(reqParams.apiOptions, reqParams.path)
|
|
10784
|
-
.subscribe({
|
|
10785
|
-
next: (res) => this.DELETE_result = res,
|
|
10786
|
-
error: (err) => this.errorHandling(err, 'DELETE')
|
|
10787
|
-
});
|
|
10788
|
-
}
|
|
10789
|
-
onStreamPostRequest() {
|
|
10790
|
-
if (!this.isValid)
|
|
10791
|
-
return;
|
|
10792
|
-
const reqParams = this.compileRequest();
|
|
10793
|
-
let payload = {};
|
|
10794
|
-
let apiPath = reqParams.path;
|
|
10795
|
-
let apiOptions = reqParams.apiOptions;
|
|
10796
|
-
let responseMapper = (items) => items.response;
|
|
10797
|
-
if (this.AIType === 0) {
|
|
10798
|
-
// API request
|
|
10799
|
-
payload = { prompt: this.questionControl.value };
|
|
10800
|
-
}
|
|
10801
|
-
else {
|
|
10802
|
-
// Local Ollama request
|
|
10803
|
-
apiOptions.server = "api";
|
|
10804
|
-
apiPath = ["generate"];
|
|
10805
|
-
apiOptions.stream = true;
|
|
10806
|
-
payload = {
|
|
10807
|
-
model: "phi3:latest",
|
|
10808
|
-
prompt: this.questionControl.value,
|
|
10809
|
-
stream: true,
|
|
10810
|
-
};
|
|
10811
|
-
responseMapper = (items) => items.map((word) => word.response).flat().join('');
|
|
10812
|
-
}
|
|
10813
|
-
this.requestParams.STREAM = apiOptions;
|
|
10814
|
-
this.STREAM_AI_result = null;
|
|
10815
|
-
this.STREAM_AI_error = '';
|
|
10816
|
-
this.httpManagerSignalsService.postRequest(payload, apiOptions, apiPath)
|
|
10817
|
-
.subscribe({
|
|
10818
|
-
next: (res) => {
|
|
10819
|
-
try {
|
|
10820
|
-
this.STREAM_AI_result = responseMapper(res);
|
|
10821
|
-
}
|
|
10822
|
-
catch (e) {
|
|
10823
|
-
this.STREAM_AI_result = res;
|
|
10824
|
-
}
|
|
10825
|
-
this.questionControl.reset();
|
|
10826
|
-
},
|
|
10827
|
-
error: (err) => this.errorHandling(err, 'STREAM')
|
|
10828
|
-
});
|
|
10829
|
-
}
|
|
10830
|
-
onStreamRequest() {
|
|
10831
|
-
if (!this.isValid)
|
|
10832
|
-
return;
|
|
10833
|
-
const reqParams = this.compileRequest();
|
|
10834
|
-
reqParams.apiOptions.stream = true;
|
|
10835
|
-
this.requestParams.GET = reqParams.apiOptions;
|
|
10836
|
-
this.STREAM_result = null;
|
|
10837
|
-
this.STREAM_error = '';
|
|
10838
|
-
this.httpManagerSignalsService.getRequest(reqParams.apiOptions, reqParams.path)
|
|
10839
|
-
.subscribe({
|
|
10840
|
-
next: (res) => this.STREAM_result = res,
|
|
10841
|
-
error: (err) => this.errorHandling(err, 'STREAM')
|
|
10842
|
-
});
|
|
10843
|
-
}
|
|
10844
|
-
onDownloadCompleted() {
|
|
10845
|
-
const message = "Download Completed";
|
|
10846
|
-
const display = ToastDisplay.adapt({
|
|
10847
|
-
message,
|
|
10848
|
-
action: 'Ok',
|
|
10849
|
-
color: ToastColors.SUCCESS,
|
|
10850
|
-
icon: 'sentiment_satisfied_alt',
|
|
10851
|
-
});
|
|
10852
|
-
this.toastMessage.toastMessage(display);
|
|
10853
|
-
}
|
|
10854
|
-
onDownloadFailed(err) {
|
|
10855
|
-
const message = "Download Failed";
|
|
10856
|
-
const display = ToastDisplay.adapt({
|
|
10857
|
-
message,
|
|
10858
|
-
action: 'Ok',
|
|
10859
|
-
color: ToastColors.ERROR,
|
|
10860
|
-
icon: 'warning',
|
|
10861
|
-
});
|
|
10862
|
-
this.toastMessage.toastMessage(display);
|
|
10863
|
-
}
|
|
10864
|
-
errorHandling(err, type) {
|
|
10865
|
-
const message = err?.message || String(err);
|
|
10866
|
-
// set local error state
|
|
10867
|
-
if (type === 'GET')
|
|
10868
|
-
this.GET_error = message;
|
|
10869
|
-
if (type === 'POST')
|
|
10870
|
-
this.POST_error = message;
|
|
10871
|
-
if (type === 'PUT')
|
|
10872
|
-
this.PUT_error = message;
|
|
10873
|
-
if (type === 'DELETE')
|
|
10874
|
-
this.DELETE_error = message;
|
|
10875
|
-
if (type === 'STREAM')
|
|
10876
|
-
this.STREAM_error = message;
|
|
10877
|
-
// also set the shared service error signal
|
|
10878
|
-
this.httpManagerSignalsService.error.set(message);
|
|
10879
|
-
return err;
|
|
10880
|
-
}
|
|
10881
|
-
onSelectAIType(type) {
|
|
10882
|
-
this.AIType = type;
|
|
10883
|
-
}
|
|
10884
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: RequestSignalsManagerDemoComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
10885
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: RequestSignalsManagerDemoComponent, selector: "app-request-signals-manager-demo", viewQueries: [{ propertyName: "failedState", first: true, predicate: ["failedState"], descendants: true, static: true }, { propertyName: "pollingState", first: true, predicate: ["pollingState"], descendants: true, static: true }], ngImport: i0, template: "<div style=\"margin: 2rem;\">\n\n <h2>\n <span>HTTP Request Signals Manager</span>\n <span style=\"margin-left: .5rem;\">\n <mat-icon color=\"accent\">fiber_new</mat-icon>\n </span>\n </h2>\n\n <div [formGroup]=\"requestForm\" style=\"margin-top: 2rem;\">\n <div style=\"display: flex; gap: .5rem\">\n <mat-form-field appearance=\"outline\">\n <mat-label>Adapter (Model)</mat-label>\n <mat-select formControlName=\"adapter\" #adapterSelect>\n <mat-option>None</mat-option>\n @for (adapter of sampleAdaptors; track adapter) {\n <mat-option [value]=\"adapter.value\">\n {{adapter.label}}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Mapper (Model)</mat-label>\n <mat-select formControlName=\"mapper\" #mapperSelect>\n <mat-option>None</mat-option>\n @for (mapper of sampleMappers; track mapper) {\n <mat-option [value]=\"mapper.value\">\n {{mapper.label}}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n </div>\n @if (adapterSelect.value || mapperSelect.value) {\n <div style=\"display: flex; margin-bottom: 2rem;\">\n <div style=\"flex:1\" class=\"box\">\n <h3>Adapter (Incoming)</h3>\n @if (adapterSelect.value) {\n <div>\n {{ props(adapterSelect.value) | json }}\n </div>\n } @else {\n No Transformation\n }\n </div>\n <div style=\"flex:1\" class=\"box\">\n <h3>Mapper (Outgoing)</h3>\n @if (mapperSelect.value) {\n <div>\n {{ props(mapperSelect.value) | json }}\n </div>\n } @else {\n No Transformation\n }\n </div>\n </div>\n }\n <div>\n <mat-form-field appearance=\"outline\">\n <mat-label>RestPath (/ delimited)</mat-label>\n <input matInput placeholder=\"clients/list\" formControlName=\"path\">\n </mat-form-field>\n </div>\n <div>\n <div formArrayName=\"headers\">\n @for (task of headers.controls; track task; let i = $index) {\n <div [formGroupName]=\"i\">\n <div style=\"display: flex; gap: .5rem\">\n <mat-form-field appearance=\"outline\">\n <mat-label>Key</mat-label>\n <input matInput placeholder=\"authentication\" formControlName=\"key\">\n </mat-form-field>\n <mat-form-field appearance=\"outline\" style=\"flex:1\">\n <mat-label>Value</mat-label>\n <input matInput placeholder=\"sample\" formControlName=\"value\">\n </mat-form-field>\n <div style=\"margin-top: .5rem;\">\n <button mat-icon-button (click)=\"removeHeader(i)\">\n <mat-icon>close</mat-icon>\n </button>\n </div>\n </div>\n </div>\n }\n </div>\n <button mat-stroked-button (click)=\"addHeader()\" class=\"btn\">Add Header</button>\n </div>\n <div style=\"margin-top: 2rem; display: flex; flex-direction:column; gap: 1rem;\">\n <div>\n <mat-slide-toggle #failedState>Retry on Failed</mat-slide-toggle>\n @if (failedState.checked) {\n <div style=\"display: flex; gap: .5rem; margin-top: 1rem;\" formGroupName=\"retry\">\n <mat-form-field appearance=\"outline\">\n <mat-label>#of Times</mat-label>\n <input matInput placeholder=\"3\" formControlName=\"times\" value=\"3\">\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Delay Until Next</mat-label>\n <input matInput placeholder=\"3\" formControlName=\"delay\" value=\"3\">\n </mat-form-field>\n </div>\n }\n </div>\n <div>\n <mat-slide-toggle #pollingState>Polling</mat-slide-toggle>\n @if (pollingState.checked) {\n <div>\n <mat-form-field appearance=\"outline\" style=\"margin-top: 1rem\">\n <mat-label>#of Seconds</mat-label>\n <input matInput placeholder=\"3\" formControlName=\"polling\" value=\"3\">\n </mat-form-field>\n </div>\n }\n </div>\n </div>\n </div>\n\n <div style=\"margin-bottom: 1rem; margin-top: 2rem;\">\n @if (isPending()) {\n <mat-progress-bar mode=\"indeterminate\"\n ></mat-progress-bar>\n }\n @if (pollingState.checked && !isPending()) {\n <mat-progress-bar mode=\"determinate\"\n [value]=\"countdown()\"\n ></mat-progress-bar>\n }\n </div>\n\n <div style=\"margin-top: 1rem\">\n <mat-divider></mat-divider>\n </div>\n\n <div style=\"margin-top: 2rem\">\n <div style=\"display: flex;\">\n <h2 style=\"flex:1\">GET Request</h2>\n <div>\n <button mat-raised-button (click)=\"onGetRequest()\" class=\"btn\">Request</button>\n </div>\n </div>\n\n @if (GET_error) {\n <div style=\"margin-top: .5rem;\">\n <mat-error>{{ GET_error }}</mat-error>\n </div>\n }\n\n @if (GET_result) {\n <div style=\"margin-top: 1rem;\">\n {{ GET_result | json }}\n </div>\n }\n\n </div>\n\n <div style=\"margin-top: 2rem\">\n <mat-divider></mat-divider>\n </div>\n\n <div style=\"margin-top: 2rem\">\n <div style=\"display: flex;\">\n <h2 style=\"flex:1\">POST Request</h2>\n <div>\n <button mat-raised-button (click)=\"onCreateRequest()\" class=\"btn\">Request</button>\n </div>\n </div>\n\n @if (POST_error) {\n <div style=\"margin-top: .5rem;\">\n <mat-error>{{ POST_error }}</mat-error>\n </div>\n }\n\n @if (POST_result) {\n <div style=\"margin-top: 1rem;\">\n {{ POST_result | json }}\n </div>\n }\n\n </div>\n\n <div style=\"margin-top: 2rem\">\n <mat-divider></mat-divider>\n </div>\n\n <div style=\"margin-top: 2rem\">\n <div style=\"display: flex;\">\n <h2 style=\"flex:1\">PUT Request</h2>\n <div>\n <button mat-raised-button (click)=\"onUpdateRequest()\" class=\"btn\">Request</button>\n </div>\n </div>\n\n @if (PUT_error) {\n <div style=\"margin-top: .5rem;\">\n <mat-error>{{ PUT_error }}</mat-error>\n </div>\n }\n\n <h3>Include Record ID in the RestPath</h3>\n\n @if (PUT_result) {\n <div style=\"margin-top: 1rem;\">\n {{ PUT_result | json }}\n </div>\n }\n\n </div>\n\n <div style=\"margin-top: 2rem\">\n <mat-divider></mat-divider>\n </div>\n\n <div style=\"margin-top: 2rem\">\n <div style=\"display: flex;\">\n <h2 style=\"flex:1\">DELETE Request</h2>\n <div>\n <button mat-raised-button (click)=\"onDeleteRequest()\" class=\"btn\">Request</button>\n </div>\n </div>\n\n <h3>Include Record ID in the RestPath</h3>\n\n @if (DELETE_error) {\n <div style=\"margin-top: .5rem;\">\n <mat-error>{{ DELETE_error }}</mat-error>\n </div>\n }\n\n @if (DELETE_result) {\n <div style=\"margin-top: 1rem;\">\n {{ DELETE_result | json }}\n </div>\n }\n\n </div>\n\n <div style=\"margin-top: 2rem\">\n <mat-divider></mat-divider>\n </div>\n\n <div style=\"margin-top: 2rem\">\n <div style=\"display: flex;\">\n <h2 style=\"flex:1\">Streaming GET Request</h2>\n <div>\n <button mat-raised-button (click)=\"onStreamRequest()\" class=\"btn\">Request</button>\n </div>\n </div>\n\n <!-- <div *ngIf=\"(STREAM_error$ | async) as stream_error\" style=\"margin-top: .5rem;\">\n <mat-error>{{ stream_error }}</mat-error>\n </div> -->\n\n <div style=\"margin-top: 1rem;\">\n @if (STREAM_result; as data) {\n <div class=\"container\">\n <table mat-table [dataSource]=\"data\">\n <ng-container matColumnDef=\"id\">\n <th mat-header-cell *matHeaderCellDef> ID </th>\n <td mat-cell *matCellDef=\"let element\"> {{element.id}} </td>\n </ng-container>\n <ng-container matColumnDef=\"name\">\n <th mat-header-cell *matHeaderCellDef> First Name </th>\n <td mat-cell *matCellDef=\"let element\"> {{element.name}} </td>\n </ng-container>\n <ng-container matColumnDef=\"lastName\">\n <th mat-header-cell *matHeaderCellDef> Last Name </th>\n <td mat-cell *matCellDef=\"let element\"> {{element.lastName}} </td>\n </ng-container>\n <ng-container matColumnDef=\"age\">\n <th mat-header-cell *matHeaderCellDef> Age </th>\n <td mat-cell *matCellDef=\"let element\"> {{element.age}} </td>\n </ng-container>\n <tr mat-header-row *matHeaderRowDef=\"displayedColumns\"></tr>\n <tr mat-row *matRowDef=\"let row; columns: displayedColumns;\"></tr>\n </table>\n </div>\n }\n </div>\n\n</div>\n\n<div style=\"margin-top: 2rem\">\n <mat-divider></mat-divider>\n</div>\n\n<div style=\"margin-top: 2rem\">\n <div style=\"display: flex;\">\n <h2 style=\"flex:1; padding-top: .5rem;\">AI -@if (AIType === 1) {\n <span>STREAMING</span>\n } POST Request</h2>\n <div style=\"display: flex; gap: 1rem;\">\n <button mat-raised-button [matMenuTriggerFor]=\"menu\" style=\"min-width: 120px;\">\n <mat-icon>lan</mat-icon>\n @if (AIType === 0) {\n <span>Server</span>\n } @else {\n Local\n }\n </button>\n <mat-menu #menu=\"matMenu\">\n <button mat-menu-item (click)=\"onSelectAIType(0)\">Server</button>\n <button mat-menu-item (click)=\"onSelectAIType(1)\">Local</button>\n </mat-menu>\n <div>\n <button mat-raised-button (click)=\"onStreamPostRequest()\" class=\"btn\">Ask Me</button>\n </div>\n </div>\n </div>\n\n <div style=\"display: flex;\">\n <mat-form-field appearance=\"outline\" style=\"flex:1\">\n <mat-label>Ask me a Question</mat-label>\n <textarea matInput placeholder=\"Why is the sky blue?\" [formControl]=\"questionControl\"></textarea>\n </mat-form-field>\n </div>\n\n @if (STREAM_AI_error) {\n <div style=\"margin-top: .5rem;\">\n <mat-error>{{ STREAM_AI_error }}</mat-error>\n </div>\n }\n\n @if (AIType === 1) {\n <div style=\"color: red;\">\n You must have Ollama active and the 'phi3:latest' model to use this feature.\n </div>\n } @else {\n <span style=\"color: gray;\">\n Define the RestPath to the API endpoint that will handle the AI request.\n Use: 'ai/chat' for server\n </span>\n }\n\n <div>\n @if (STREAM_AI_result) {\n <div style=\"margin-top: 1rem; font-size: 1.2rem; border-radius: 1rem; border: black 1px solid; padding: 2rem;\">\n <p style=\"margin-bottom: .5rem; white-space:pre-wrap; line-height: 1.6rem;\">{{STREAM_AI_result}}</p>\n </div>\n }\n </div>\n\n</div>\n\n<div style=\"margin-top: 1.5rem; margin-bottom: 1rem; line-height: 1.5rem;\">\n <mat-divider></mat-divider>\n</div>\n\n<div>\n <div style=\"display: flex;\">\n <h2 style=\"flex:1; margin-bottom: 0; padding-top: .5rem; display: flex;\">\n <div>\n Download File\n </div>\n <div style=\"flex:1; margin-left: 1rem;\">\n <mat-slide-toggle #disable>\n @if (disable.checked) {\n <span>\n Enable\n </span>\n } @else {\n Disable\n }\n </mat-slide-toggle>\n </div>\n </h2>\n <div>\n <app-file-downloader\n [disabled]=\"disable.checked\"\n [delayError]=\"3\"\n [apiRequest]=\"downloadRequest\"\n (completed)=\"onDownloadCompleted()\"\n (failed)=\"onDownloadFailed($event)\"\n ></app-file-downloader>\n </div>\n </div>\n</div>\n\n</div>\n", styles: [".btn{min-width:120px}.mat-mdc-row .mat-mdc-cell{border-bottom:1px solid transparent;border-top:1px solid transparent;cursor:pointer}.mat-mdc-row:hover .mat-mdc-cell{border-color:currentColor;background-color:#f5f5f5}.container{height:400px;overflow:auto}.box{padding:10px;border:1px solid #ccc}\n"], dependencies: [{ kind: "directive", type: i2$1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2$1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i2$1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "directive", type: i2$1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i2$1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "directive", type: i2$1.FormGroupName, selector: "[formGroupName]", inputs: ["formGroupName"] }, { kind: "directive", type: i2$1.FormArrayName, selector: "[formArrayName]", inputs: ["formArrayName"] }, { kind: "component", type: i3.MatButton, selector: " button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button] ", exportAs: ["matButton"] }, { kind: "component", type: i3.MatIconButton, selector: "button[mat-icon-button]", exportAs: ["matButton"] }, { kind: "component", type: i4.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i4.MatLabel, selector: "mat-label" }, { kind: "directive", type: i4.MatError, selector: "mat-error, [matError]", inputs: ["id"] }, { kind: "component", type: i5.MatSelect, selector: "mat-select", inputs: ["aria-describedby", "panelClass", "disabled", "disableRipple", "tabIndex", "hideSingleSelectionIndicator", "placeholder", "required", "multiple", "disableOptionCentering", "compareWith", "value", "aria-label", "aria-labelledby", "errorStateMatcher", "typeaheadDebounceInterval", "sortComparator", "id", "panelWidth"], outputs: ["openedChange", "opened", "closed", "selectionChange", "valueChange"], exportAs: ["matSelect"] }, { kind: "component", type: i6.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "component", type: i7.MatMenu, selector: "mat-menu", inputs: ["backdropClass", "aria-label", "aria-labelledby", "aria-describedby", "xPosition", "yPosition", "overlapTrigger", "hasBackdrop", "class", "classList"], outputs: ["closed", "close"], exportAs: ["matMenu"] }, { kind: "component", type: i7.MatMenuItem, selector: "[mat-menu-item]", inputs: ["role", "disabled", "disableRipple"], exportAs: ["matMenuItem"] }, { kind: "directive", type: i7.MatMenuTrigger, selector: "[mat-menu-trigger-for], [matMenuTriggerFor]", inputs: ["mat-menu-trigger-for", "matMenuTriggerFor", "matMenuTriggerData", "matMenuTriggerRestoreFocus"], outputs: ["menuOpened", "onMenuOpen", "menuClosed", "onMenuClose"], exportAs: ["matMenuTrigger"] }, { kind: "component", type: i8.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: i9.MatTable, selector: "mat-table, table[mat-table]", exportAs: ["matTable"] }, { kind: "directive", type: i9.MatHeaderCellDef, selector: "[matHeaderCellDef]" }, { kind: "directive", type: i9.MatHeaderRowDef, selector: "[matHeaderRowDef]", inputs: ["matHeaderRowDef", "matHeaderRowDefSticky"] }, { kind: "directive", type: i9.MatColumnDef, selector: "[matColumnDef]", inputs: ["matColumnDef"] }, { kind: "directive", type: i9.MatCellDef, selector: "[matCellDef]" }, { kind: "directive", type: i9.MatRowDef, selector: "[matRowDef]", inputs: ["matRowDefColumns", "matRowDefWhen"] }, { kind: "directive", type: i9.MatHeaderCell, selector: "mat-header-cell, th[mat-header-cell]" }, { kind: "directive", type: i9.MatCell, selector: "mat-cell, td[mat-cell]" }, { kind: "component", type: i9.MatHeaderRow, selector: "mat-header-row, tr[mat-header-row]", exportAs: ["matHeaderRow"] }, { kind: "component", type: i9.MatRow, selector: "mat-row, tr[mat-row]", exportAs: ["matRow"] }, { kind: "component", type: i8$1.MatProgressBar, selector: "mat-progress-bar", inputs: ["color", "value", "bufferValue", "mode"], outputs: ["animationEnd"], exportAs: ["matProgressBar"] }, { kind: "component", type: i11.MatSlideToggle, selector: "mat-slide-toggle", inputs: ["name", "id", "labelPosition", "aria-label", "aria-labelledby", "aria-describedby", "required", "color", "disabled", "disableRipple", "tabIndex", "checked", "hideIcon", "disabledInteractive"], outputs: ["change", "toggleChange"], exportAs: ["matSlideToggle"] }, { kind: "component", type: i12.MatDivider, selector: "mat-divider", inputs: ["vertical", "inset"] }, { kind: "directive", type: i13.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly"], exportAs: ["matInput"] }, { kind: "component", type: FileDownloaderComponent, selector: "app-file-downloader", inputs: ["delayError", "apiRequest", "displayErrorMessage", "saveFileAs", "labels", "disabled"], outputs: ["completed", "failed"] }, { kind: "pipe", type: i1$1.JsonPipe, name: "json" }] }); }
|
|
10886
|
-
}
|
|
10887
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: RequestSignalsManagerDemoComponent, decorators: [{
|
|
10888
|
-
type: Component,
|
|
10889
|
-
args: [{ selector: 'app-request-signals-manager-demo', standalone: false, template: "<div style=\"margin: 2rem;\">\n\n <h2>\n <span>HTTP Request Signals Manager</span>\n <span style=\"margin-left: .5rem;\">\n <mat-icon color=\"accent\">fiber_new</mat-icon>\n </span>\n </h2>\n\n <div [formGroup]=\"requestForm\" style=\"margin-top: 2rem;\">\n <div style=\"display: flex; gap: .5rem\">\n <mat-form-field appearance=\"outline\">\n <mat-label>Adapter (Model)</mat-label>\n <mat-select formControlName=\"adapter\" #adapterSelect>\n <mat-option>None</mat-option>\n @for (adapter of sampleAdaptors; track adapter) {\n <mat-option [value]=\"adapter.value\">\n {{adapter.label}}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Mapper (Model)</mat-label>\n <mat-select formControlName=\"mapper\" #mapperSelect>\n <mat-option>None</mat-option>\n @for (mapper of sampleMappers; track mapper) {\n <mat-option [value]=\"mapper.value\">\n {{mapper.label}}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n </div>\n @if (adapterSelect.value || mapperSelect.value) {\n <div style=\"display: flex; margin-bottom: 2rem;\">\n <div style=\"flex:1\" class=\"box\">\n <h3>Adapter (Incoming)</h3>\n @if (adapterSelect.value) {\n <div>\n {{ props(adapterSelect.value) | json }}\n </div>\n } @else {\n No Transformation\n }\n </div>\n <div style=\"flex:1\" class=\"box\">\n <h3>Mapper (Outgoing)</h3>\n @if (mapperSelect.value) {\n <div>\n {{ props(mapperSelect.value) | json }}\n </div>\n } @else {\n No Transformation\n }\n </div>\n </div>\n }\n <div>\n <mat-form-field appearance=\"outline\">\n <mat-label>RestPath (/ delimited)</mat-label>\n <input matInput placeholder=\"clients/list\" formControlName=\"path\">\n </mat-form-field>\n </div>\n <div>\n <div formArrayName=\"headers\">\n @for (task of headers.controls; track task; let i = $index) {\n <div [formGroupName]=\"i\">\n <div style=\"display: flex; gap: .5rem\">\n <mat-form-field appearance=\"outline\">\n <mat-label>Key</mat-label>\n <input matInput placeholder=\"authentication\" formControlName=\"key\">\n </mat-form-field>\n <mat-form-field appearance=\"outline\" style=\"flex:1\">\n <mat-label>Value</mat-label>\n <input matInput placeholder=\"sample\" formControlName=\"value\">\n </mat-form-field>\n <div style=\"margin-top: .5rem;\">\n <button mat-icon-button (click)=\"removeHeader(i)\">\n <mat-icon>close</mat-icon>\n </button>\n </div>\n </div>\n </div>\n }\n </div>\n <button mat-stroked-button (click)=\"addHeader()\" class=\"btn\">Add Header</button>\n </div>\n <div style=\"margin-top: 2rem; display: flex; flex-direction:column; gap: 1rem;\">\n <div>\n <mat-slide-toggle #failedState>Retry on Failed</mat-slide-toggle>\n @if (failedState.checked) {\n <div style=\"display: flex; gap: .5rem; margin-top: 1rem;\" formGroupName=\"retry\">\n <mat-form-field appearance=\"outline\">\n <mat-label>#of Times</mat-label>\n <input matInput placeholder=\"3\" formControlName=\"times\" value=\"3\">\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Delay Until Next</mat-label>\n <input matInput placeholder=\"3\" formControlName=\"delay\" value=\"3\">\n </mat-form-field>\n </div>\n }\n </div>\n <div>\n <mat-slide-toggle #pollingState>Polling</mat-slide-toggle>\n @if (pollingState.checked) {\n <div>\n <mat-form-field appearance=\"outline\" style=\"margin-top: 1rem\">\n <mat-label>#of Seconds</mat-label>\n <input matInput placeholder=\"3\" formControlName=\"polling\" value=\"3\">\n </mat-form-field>\n </div>\n }\n </div>\n </div>\n </div>\n\n <div style=\"margin-bottom: 1rem; margin-top: 2rem;\">\n @if (isPending()) {\n <mat-progress-bar mode=\"indeterminate\"\n ></mat-progress-bar>\n }\n @if (pollingState.checked && !isPending()) {\n <mat-progress-bar mode=\"determinate\"\n [value]=\"countdown()\"\n ></mat-progress-bar>\n }\n </div>\n\n <div style=\"margin-top: 1rem\">\n <mat-divider></mat-divider>\n </div>\n\n <div style=\"margin-top: 2rem\">\n <div style=\"display: flex;\">\n <h2 style=\"flex:1\">GET Request</h2>\n <div>\n <button mat-raised-button (click)=\"onGetRequest()\" class=\"btn\">Request</button>\n </div>\n </div>\n\n @if (GET_error) {\n <div style=\"margin-top: .5rem;\">\n <mat-error>{{ GET_error }}</mat-error>\n </div>\n }\n\n @if (GET_result) {\n <div style=\"margin-top: 1rem;\">\n {{ GET_result | json }}\n </div>\n }\n\n </div>\n\n <div style=\"margin-top: 2rem\">\n <mat-divider></mat-divider>\n </div>\n\n <div style=\"margin-top: 2rem\">\n <div style=\"display: flex;\">\n <h2 style=\"flex:1\">POST Request</h2>\n <div>\n <button mat-raised-button (click)=\"onCreateRequest()\" class=\"btn\">Request</button>\n </div>\n </div>\n\n @if (POST_error) {\n <div style=\"margin-top: .5rem;\">\n <mat-error>{{ POST_error }}</mat-error>\n </div>\n }\n\n @if (POST_result) {\n <div style=\"margin-top: 1rem;\">\n {{ POST_result | json }}\n </div>\n }\n\n </div>\n\n <div style=\"margin-top: 2rem\">\n <mat-divider></mat-divider>\n </div>\n\n <div style=\"margin-top: 2rem\">\n <div style=\"display: flex;\">\n <h2 style=\"flex:1\">PUT Request</h2>\n <div>\n <button mat-raised-button (click)=\"onUpdateRequest()\" class=\"btn\">Request</button>\n </div>\n </div>\n\n @if (PUT_error) {\n <div style=\"margin-top: .5rem;\">\n <mat-error>{{ PUT_error }}</mat-error>\n </div>\n }\n\n <h3>Include Record ID in the RestPath</h3>\n\n @if (PUT_result) {\n <div style=\"margin-top: 1rem;\">\n {{ PUT_result | json }}\n </div>\n }\n\n </div>\n\n <div style=\"margin-top: 2rem\">\n <mat-divider></mat-divider>\n </div>\n\n <div style=\"margin-top: 2rem\">\n <div style=\"display: flex;\">\n <h2 style=\"flex:1\">DELETE Request</h2>\n <div>\n <button mat-raised-button (click)=\"onDeleteRequest()\" class=\"btn\">Request</button>\n </div>\n </div>\n\n <h3>Include Record ID in the RestPath</h3>\n\n @if (DELETE_error) {\n <div style=\"margin-top: .5rem;\">\n <mat-error>{{ DELETE_error }}</mat-error>\n </div>\n }\n\n @if (DELETE_result) {\n <div style=\"margin-top: 1rem;\">\n {{ DELETE_result | json }}\n </div>\n }\n\n </div>\n\n <div style=\"margin-top: 2rem\">\n <mat-divider></mat-divider>\n </div>\n\n <div style=\"margin-top: 2rem\">\n <div style=\"display: flex;\">\n <h2 style=\"flex:1\">Streaming GET Request</h2>\n <div>\n <button mat-raised-button (click)=\"onStreamRequest()\" class=\"btn\">Request</button>\n </div>\n </div>\n\n <!-- <div *ngIf=\"(STREAM_error$ | async) as stream_error\" style=\"margin-top: .5rem;\">\n <mat-error>{{ stream_error }}</mat-error>\n </div> -->\n\n <div style=\"margin-top: 1rem;\">\n @if (STREAM_result; as data) {\n <div class=\"container\">\n <table mat-table [dataSource]=\"data\">\n <ng-container matColumnDef=\"id\">\n <th mat-header-cell *matHeaderCellDef> ID </th>\n <td mat-cell *matCellDef=\"let element\"> {{element.id}} </td>\n </ng-container>\n <ng-container matColumnDef=\"name\">\n <th mat-header-cell *matHeaderCellDef> First Name </th>\n <td mat-cell *matCellDef=\"let element\"> {{element.name}} </td>\n </ng-container>\n <ng-container matColumnDef=\"lastName\">\n <th mat-header-cell *matHeaderCellDef> Last Name </th>\n <td mat-cell *matCellDef=\"let element\"> {{element.lastName}} </td>\n </ng-container>\n <ng-container matColumnDef=\"age\">\n <th mat-header-cell *matHeaderCellDef> Age </th>\n <td mat-cell *matCellDef=\"let element\"> {{element.age}} </td>\n </ng-container>\n <tr mat-header-row *matHeaderRowDef=\"displayedColumns\"></tr>\n <tr mat-row *matRowDef=\"let row; columns: displayedColumns;\"></tr>\n </table>\n </div>\n }\n </div>\n\n</div>\n\n<div style=\"margin-top: 2rem\">\n <mat-divider></mat-divider>\n</div>\n\n<div style=\"margin-top: 2rem\">\n <div style=\"display: flex;\">\n <h2 style=\"flex:1; padding-top: .5rem;\">AI -@if (AIType === 1) {\n <span>STREAMING</span>\n } POST Request</h2>\n <div style=\"display: flex; gap: 1rem;\">\n <button mat-raised-button [matMenuTriggerFor]=\"menu\" style=\"min-width: 120px;\">\n <mat-icon>lan</mat-icon>\n @if (AIType === 0) {\n <span>Server</span>\n } @else {\n Local\n }\n </button>\n <mat-menu #menu=\"matMenu\">\n <button mat-menu-item (click)=\"onSelectAIType(0)\">Server</button>\n <button mat-menu-item (click)=\"onSelectAIType(1)\">Local</button>\n </mat-menu>\n <div>\n <button mat-raised-button (click)=\"onStreamPostRequest()\" class=\"btn\">Ask Me</button>\n </div>\n </div>\n </div>\n\n <div style=\"display: flex;\">\n <mat-form-field appearance=\"outline\" style=\"flex:1\">\n <mat-label>Ask me a Question</mat-label>\n <textarea matInput placeholder=\"Why is the sky blue?\" [formControl]=\"questionControl\"></textarea>\n </mat-form-field>\n </div>\n\n @if (STREAM_AI_error) {\n <div style=\"margin-top: .5rem;\">\n <mat-error>{{ STREAM_AI_error }}</mat-error>\n </div>\n }\n\n @if (AIType === 1) {\n <div style=\"color: red;\">\n You must have Ollama active and the 'phi3:latest' model to use this feature.\n </div>\n } @else {\n <span style=\"color: gray;\">\n Define the RestPath to the API endpoint that will handle the AI request.\n Use: 'ai/chat' for server\n </span>\n }\n\n <div>\n @if (STREAM_AI_result) {\n <div style=\"margin-top: 1rem; font-size: 1.2rem; border-radius: 1rem; border: black 1px solid; padding: 2rem;\">\n <p style=\"margin-bottom: .5rem; white-space:pre-wrap; line-height: 1.6rem;\">{{STREAM_AI_result}}</p>\n </div>\n }\n </div>\n\n</div>\n\n<div style=\"margin-top: 1.5rem; margin-bottom: 1rem; line-height: 1.5rem;\">\n <mat-divider></mat-divider>\n</div>\n\n<div>\n <div style=\"display: flex;\">\n <h2 style=\"flex:1; margin-bottom: 0; padding-top: .5rem; display: flex;\">\n <div>\n Download File\n </div>\n <div style=\"flex:1; margin-left: 1rem;\">\n <mat-slide-toggle #disable>\n @if (disable.checked) {\n <span>\n Enable\n </span>\n } @else {\n Disable\n }\n </mat-slide-toggle>\n </div>\n </h2>\n <div>\n <app-file-downloader\n [disabled]=\"disable.checked\"\n [delayError]=\"3\"\n [apiRequest]=\"downloadRequest\"\n (completed)=\"onDownloadCompleted()\"\n (failed)=\"onDownloadFailed($event)\"\n ></app-file-downloader>\n </div>\n </div>\n</div>\n\n</div>\n", styles: [".btn{min-width:120px}.mat-mdc-row .mat-mdc-cell{border-bottom:1px solid transparent;border-top:1px solid transparent;cursor:pointer}.mat-mdc-row:hover .mat-mdc-cell{border-color:currentColor;background-color:#f5f5f5}.container{height:400px;overflow:auto}.box{padding:10px;border:1px solid #ccc}\n"] }]
|
|
10890
|
-
}], ctorParameters: () => [], propDecorators: { failedState: [{
|
|
10891
|
-
type: ViewChild,
|
|
10892
|
-
args: ["failedState", { static: true }]
|
|
10893
|
-
}], pollingState: [{
|
|
10894
|
-
type: ViewChild,
|
|
10895
|
-
args: ["pollingState", { static: true }]
|
|
10896
|
-
}] } });
|
|
10897
|
-
|
|
10898
|
-
// import { MessengerChatModule } from 'src/app/components/messenger-chat/messenger-chat.module';
|
|
10899
|
-
// import { StoreStateManagerModule } from "src/app/beta_components/store-state-manager/store-state-manager.module";
|
|
10900
|
-
class HttpRequestManagerModule {
|
|
10901
|
-
static forRoot(config = ConfigOptions.adapt()) {
|
|
10902
|
-
return {
|
|
10903
|
-
ngModule: HttpRequestManagerModule,
|
|
10904
|
-
providers: [
|
|
10905
|
-
{ provide: CONFIG_SETTINGS_TOKEN, useValue: ConfigOptions.adapt(config) },
|
|
10906
|
-
HTTPManagerService, LocalStorageManagerService //all services that need access to config
|
|
10907
|
-
],
|
|
10908
|
-
};
|
|
10909
|
-
}
|
|
10910
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: HttpRequestManagerModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
|
|
10911
|
-
static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "18.2.14", ngImport: i0, type: HttpRequestManagerModule, declarations: [RequestManagerBasicDemoComponent,
|
|
10912
|
-
HttpRequestServicesDemoComponent,
|
|
10913
|
-
RequestManagerStateDemoComponent,
|
|
10914
|
-
RequestManagerDemoComponent,
|
|
10915
|
-
RequestSignalsManagerDemoComponent,
|
|
10916
|
-
LocalStorageDemoComponent,
|
|
10917
|
-
LocalStorageSignalsDemoComponent,
|
|
10918
|
-
RequestManagerWsDemoComponent,
|
|
10919
|
-
StoreStateManagerDemoComponent,
|
|
10920
|
-
DatabaseDataDemoComponent,
|
|
10921
|
-
WsDataControlComponent,
|
|
10922
|
-
WsMessagingComponent,
|
|
10923
|
-
WsNotificationsComponent,
|
|
10924
|
-
WsAiMessagingComponent,
|
|
10925
|
-
WsChatsComponent], imports: [CommonModule,
|
|
10926
|
-
ToastMessageDisplayModule,
|
|
10927
|
-
FormsModule,
|
|
10928
|
-
ReactiveFormsModule,
|
|
10929
|
-
MatButtonModule,
|
|
10930
|
-
MatTabsModule,
|
|
10931
|
-
MatSelectModule,
|
|
10932
|
-
MatChipsModule,
|
|
10933
|
-
MatMenuModule,
|
|
10934
|
-
MatIconModule,
|
|
10935
|
-
MatTableModule,
|
|
10936
|
-
MatButtonToggleModule,
|
|
10937
|
-
MatAutocompleteModule,
|
|
10938
|
-
MatProgressBarModule,
|
|
10939
|
-
MatProgressSpinnerModule,
|
|
10940
|
-
MatSlideToggleModule,
|
|
10941
|
-
MatDividerModule,
|
|
10942
|
-
MatFormFieldModule,
|
|
10943
|
-
MatInputModule,
|
|
10944
|
-
MatToolbarModule,
|
|
10945
|
-
MatSlideToggleModule, i1.TranslateModule, MatSidenavModule,
|
|
10946
|
-
MatDatepickerModule,
|
|
10947
|
-
MatNativeDateModule,
|
|
10948
|
-
MatCardModule,
|
|
10949
|
-
FileDownloaderModule], exports: [HttpRequestServicesDemoComponent] }); }
|
|
10950
|
-
static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: HttpRequestManagerModule, providers: [
|
|
10951
|
-
{ provide: HTTP_INTERCEPTORS, useClass: WithCredentialsInterceptor, multi: true },
|
|
10952
|
-
{ provide: HTTP_INTERCEPTORS, useClass: RequestHeadersInterceptor, multi: true },
|
|
10953
|
-
{ provide: HTTP_INTERCEPTORS, useClass: ProxyDebuggerInterceptor, multi: true },
|
|
10954
|
-
{ provide: CONFIG_SETTINGS_TOKEN, useValue: ConfigOptions.adapt() },
|
|
10955
|
-
HTTPManagerService, LocalStorageManagerService,
|
|
10956
|
-
ToastMessageDisplayService,
|
|
10957
|
-
MessageDisplayRouterService,
|
|
10958
|
-
SnackbarStrategy
|
|
10959
|
-
], imports: [CommonModule,
|
|
10960
|
-
ToastMessageDisplayModule,
|
|
10961
|
-
FormsModule,
|
|
10962
|
-
ReactiveFormsModule,
|
|
10963
|
-
MatButtonModule,
|
|
10964
|
-
MatTabsModule,
|
|
10965
|
-
MatSelectModule,
|
|
10966
|
-
MatChipsModule,
|
|
10967
|
-
MatMenuModule,
|
|
10968
|
-
MatIconModule,
|
|
10969
|
-
MatTableModule,
|
|
10970
|
-
MatButtonToggleModule,
|
|
10971
|
-
MatAutocompleteModule,
|
|
10972
|
-
MatProgressBarModule,
|
|
10973
|
-
MatProgressSpinnerModule,
|
|
10974
|
-
MatSlideToggleModule,
|
|
10975
|
-
MatDividerModule,
|
|
10976
|
-
MatFormFieldModule,
|
|
10977
|
-
MatInputModule,
|
|
10978
|
-
MatToolbarModule,
|
|
10979
|
-
MatSlideToggleModule,
|
|
10980
|
-
TranslateModule.forRoot(),
|
|
10981
|
-
MatSidenavModule,
|
|
10982
|
-
MatDatepickerModule,
|
|
10983
|
-
MatNativeDateModule,
|
|
10984
|
-
MatCardModule,
|
|
10985
|
-
FileDownloaderModule] }); }
|
|
7457
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: HttpRequestManagerModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
|
|
7458
|
+
static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "18.2.14", ngImport: i0, type: HttpRequestManagerModule, imports: [CommonModule,
|
|
7459
|
+
ToastMessageDisplayModule,
|
|
7460
|
+
FormsModule,
|
|
7461
|
+
ReactiveFormsModule, i1.TranslateModule] }); }
|
|
7462
|
+
static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: HttpRequestManagerModule, providers: [
|
|
7463
|
+
{ provide: HTTP_INTERCEPTORS, useClass: WithCredentialsInterceptor, multi: true },
|
|
7464
|
+
{ provide: HTTP_INTERCEPTORS, useClass: RequestHeadersInterceptor, multi: true },
|
|
7465
|
+
{ provide: HTTP_INTERCEPTORS, useClass: ProxyDebuggerInterceptor, multi: true },
|
|
7466
|
+
{ provide: CONFIG_SETTINGS_TOKEN, useValue: ConfigOptions.adapt() },
|
|
7467
|
+
HTTPManagerService,
|
|
7468
|
+
LocalStorageManagerService,
|
|
7469
|
+
ToastMessageDisplayService,
|
|
7470
|
+
MessageDisplayRouterService,
|
|
7471
|
+
SnackbarStrategy,
|
|
7472
|
+
], imports: [CommonModule,
|
|
7473
|
+
ToastMessageDisplayModule,
|
|
7474
|
+
FormsModule,
|
|
7475
|
+
ReactiveFormsModule,
|
|
7476
|
+
TranslateModule.forRoot()] }); }
|
|
10986
7477
|
}
|
|
10987
7478
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: HttpRequestManagerModule, decorators: [{
|
|
10988
7479
|
type: NgModule,
|
|
@@ -10992,200 +7483,32 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
10992
7483
|
ToastMessageDisplayModule,
|
|
10993
7484
|
FormsModule,
|
|
10994
7485
|
ReactiveFormsModule,
|
|
10995
|
-
MatButtonModule,
|
|
10996
|
-
MatTabsModule,
|
|
10997
|
-
MatSelectModule,
|
|
10998
|
-
MatChipsModule,
|
|
10999
|
-
MatMenuModule,
|
|
11000
|
-
MatIconModule,
|
|
11001
|
-
MatTableModule,
|
|
11002
|
-
MatButtonToggleModule,
|
|
11003
|
-
MatAutocompleteModule,
|
|
11004
|
-
MatProgressBarModule,
|
|
11005
|
-
MatProgressSpinnerModule,
|
|
11006
|
-
MatSlideToggleModule,
|
|
11007
|
-
MatDividerModule,
|
|
11008
|
-
MatFormFieldModule,
|
|
11009
|
-
MatInputModule,
|
|
11010
|
-
MatToolbarModule,
|
|
11011
|
-
MatSlideToggleModule,
|
|
11012
7486
|
TranslateModule.forRoot(),
|
|
11013
|
-
MatSidenavModule,
|
|
11014
|
-
MatDatepickerModule,
|
|
11015
|
-
MatNativeDateModule,
|
|
11016
|
-
MatCardModule,
|
|
11017
|
-
FileDownloaderModule,
|
|
11018
|
-
// MessengerChatModule,
|
|
11019
|
-
// StoreStateManagerModule
|
|
11020
|
-
],
|
|
11021
|
-
declarations: [
|
|
11022
|
-
RequestManagerBasicDemoComponent,
|
|
11023
|
-
HttpRequestServicesDemoComponent,
|
|
11024
|
-
RequestManagerStateDemoComponent,
|
|
11025
|
-
RequestManagerDemoComponent,
|
|
11026
|
-
RequestSignalsManagerDemoComponent,
|
|
11027
|
-
LocalStorageDemoComponent,
|
|
11028
|
-
LocalStorageSignalsDemoComponent,
|
|
11029
|
-
RequestManagerWsDemoComponent,
|
|
11030
|
-
StoreStateManagerDemoComponent,
|
|
11031
|
-
DatabaseDataDemoComponent,
|
|
11032
|
-
WsDataControlComponent,
|
|
11033
|
-
WsMessagingComponent,
|
|
11034
|
-
WsNotificationsComponent,
|
|
11035
|
-
WsAiMessagingComponent,
|
|
11036
|
-
WsChatsComponent,
|
|
11037
|
-
],
|
|
11038
|
-
exports: [
|
|
11039
|
-
HttpRequestServicesDemoComponent,
|
|
11040
7487
|
],
|
|
7488
|
+
declarations: [],
|
|
7489
|
+
exports: [],
|
|
11041
7490
|
providers: [
|
|
11042
7491
|
{ provide: HTTP_INTERCEPTORS, useClass: WithCredentialsInterceptor, multi: true },
|
|
11043
7492
|
{ provide: HTTP_INTERCEPTORS, useClass: RequestHeadersInterceptor, multi: true },
|
|
11044
7493
|
{ provide: HTTP_INTERCEPTORS, useClass: ProxyDebuggerInterceptor, multi: true },
|
|
11045
7494
|
{ provide: CONFIG_SETTINGS_TOKEN, useValue: ConfigOptions.adapt() },
|
|
11046
|
-
HTTPManagerService,
|
|
7495
|
+
HTTPManagerService,
|
|
7496
|
+
LocalStorageManagerService,
|
|
11047
7497
|
ToastMessageDisplayService,
|
|
11048
7498
|
MessageDisplayRouterService,
|
|
11049
|
-
SnackbarStrategy
|
|
7499
|
+
SnackbarStrategy,
|
|
11050
7500
|
],
|
|
11051
7501
|
}]
|
|
11052
7502
|
}] });
|
|
11053
7503
|
|
|
11054
|
-
const defaultCounterState = () => ({
|
|
11055
|
-
count: 0,
|
|
11056
|
-
lastUpdated: new Date().toISOString(),
|
|
11057
|
-
history: []
|
|
11058
|
-
});
|
|
11059
|
-
class StoreStateSignalsDemoComponent {
|
|
11060
|
-
constructor() {
|
|
11061
|
-
// Signals from service
|
|
11062
|
-
this.stateJson = signal('');
|
|
11063
|
-
this.historyLength = signal(0);
|
|
11064
|
-
this.history = signal([]);
|
|
11065
|
-
}
|
|
11066
|
-
ngOnInit() {
|
|
11067
|
-
const options = StateStorageOptions.adapt({
|
|
11068
|
-
store: 'counter-state',
|
|
11069
|
-
model: defaultCounterState,
|
|
11070
|
-
options: {
|
|
11071
|
-
storage: 'global',
|
|
11072
|
-
encrypted: false
|
|
11073
|
-
}
|
|
11074
|
-
});
|
|
11075
|
-
this.stateManager = new StoreStateManagerSignalsService();
|
|
11076
|
-
this.stateManager.init(options);
|
|
11077
|
-
// Update signals when state changes
|
|
11078
|
-
this.updateSignals();
|
|
11079
|
-
}
|
|
11080
|
-
updateSignals() {
|
|
11081
|
-
const state = this.stateManager.data();
|
|
11082
|
-
if (state) {
|
|
11083
|
-
this.stateJson.set(JSON.stringify(state, null, 2));
|
|
11084
|
-
this.historyLength.set(state.history.length);
|
|
11085
|
-
this.history.set([...state.history]);
|
|
11086
|
-
}
|
|
11087
|
-
}
|
|
11088
|
-
increment() {
|
|
11089
|
-
const current = this.stateManager.data();
|
|
11090
|
-
if (current) {
|
|
11091
|
-
this.stateManager.updateState({
|
|
11092
|
-
count: current.count + 1,
|
|
11093
|
-
lastUpdated: new Date().toISOString()
|
|
11094
|
-
});
|
|
11095
|
-
this.updateSignals();
|
|
11096
|
-
}
|
|
11097
|
-
}
|
|
11098
|
-
decrement() {
|
|
11099
|
-
const current = this.stateManager.data();
|
|
11100
|
-
if (current) {
|
|
11101
|
-
this.stateManager.updateState({
|
|
11102
|
-
count: current.count - 1,
|
|
11103
|
-
lastUpdated: new Date().toISOString()
|
|
11104
|
-
});
|
|
11105
|
-
this.updateSignals();
|
|
11106
|
-
}
|
|
11107
|
-
}
|
|
11108
|
-
reset() {
|
|
11109
|
-
this.stateManager.resetState();
|
|
11110
|
-
this.updateSignals();
|
|
11111
|
-
}
|
|
11112
|
-
addToHistory() {
|
|
11113
|
-
const current = this.stateManager.data();
|
|
11114
|
-
if (current) {
|
|
11115
|
-
this.stateManager.updateState({
|
|
11116
|
-
history: [...current.history, current.count],
|
|
11117
|
-
lastUpdated: new Date().toISOString()
|
|
11118
|
-
});
|
|
11119
|
-
this.updateSignals();
|
|
11120
|
-
}
|
|
11121
|
-
}
|
|
11122
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: StoreStateSignalsDemoComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
11123
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: StoreStateSignalsDemoComponent, selector: "app-store-state-signals-demo", ngImport: i0, template: `
|
|
11124
|
-
<div class="demo-container">
|
|
11125
|
-
<h2>Store State Manager Signals Demo</h2>
|
|
11126
|
-
<p>Signal-based state management with localStorage persistence</p>
|
|
11127
|
-
|
|
11128
|
-
<div class="state-display">
|
|
11129
|
-
<h3>Current State (Signal)</h3>
|
|
11130
|
-
<pre>{{ stateJson() }}</pre>
|
|
11131
|
-
</div>
|
|
11132
|
-
|
|
11133
|
-
<div class="actions">
|
|
11134
|
-
<button (click)="increment()">Increment</button>
|
|
11135
|
-
<button (click)="decrement()">Decrement</button>
|
|
11136
|
-
<button (click)="reset()">Reset</button>
|
|
11137
|
-
<button (click)="addToHistory()">Add to History</button>
|
|
11138
|
-
</div>
|
|
11139
|
-
|
|
11140
|
-
<div class="history">
|
|
11141
|
-
<h3>History: {{ historyLength() }} items</h3>
|
|
11142
|
-
<ul>
|
|
11143
|
-
@for (item of history(); track item; let i = $index) {
|
|
11144
|
-
<li>{{ i + 1 }}. {{ item }}</li>
|
|
11145
|
-
}
|
|
11146
|
-
</ul>
|
|
11147
|
-
</div>
|
|
11148
|
-
</div>
|
|
11149
|
-
`, isInline: true, styles: [".demo-container{padding:20px;max-width:600px;margin:0 auto}.state-display{background:#f5f5f5;padding:15px;border-radius:5px;margin:20px 0}pre{background:#fff;padding:10px;border:1px solid #ddd;border-radius:3px}.actions{display:flex;gap:10px;margin:20px 0}button{padding:10px 20px;background:#007bff;color:#fff;border:none;border-radius:4px;cursor:pointer}button:hover{background:#0056b3}.history{margin-top:20px}ul{max-height:200px;overflow-y:auto}\n"] }); }
|
|
11150
|
-
}
|
|
11151
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: StoreStateSignalsDemoComponent, decorators: [{
|
|
11152
|
-
type: Component,
|
|
11153
|
-
args: [{ selector: 'app-store-state-signals-demo', template: `
|
|
11154
|
-
<div class="demo-container">
|
|
11155
|
-
<h2>Store State Manager Signals Demo</h2>
|
|
11156
|
-
<p>Signal-based state management with localStorage persistence</p>
|
|
11157
|
-
|
|
11158
|
-
<div class="state-display">
|
|
11159
|
-
<h3>Current State (Signal)</h3>
|
|
11160
|
-
<pre>{{ stateJson() }}</pre>
|
|
11161
|
-
</div>
|
|
11162
|
-
|
|
11163
|
-
<div class="actions">
|
|
11164
|
-
<button (click)="increment()">Increment</button>
|
|
11165
|
-
<button (click)="decrement()">Decrement</button>
|
|
11166
|
-
<button (click)="reset()">Reset</button>
|
|
11167
|
-
<button (click)="addToHistory()">Add to History</button>
|
|
11168
|
-
</div>
|
|
11169
|
-
|
|
11170
|
-
<div class="history">
|
|
11171
|
-
<h3>History: {{ historyLength() }} items</h3>
|
|
11172
|
-
<ul>
|
|
11173
|
-
@for (item of history(); track item; let i = $index) {
|
|
11174
|
-
<li>{{ i + 1 }}. {{ item }}</li>
|
|
11175
|
-
}
|
|
11176
|
-
</ul>
|
|
11177
|
-
</div>
|
|
11178
|
-
</div>
|
|
11179
|
-
`, styles: [".demo-container{padding:20px;max-width:600px;margin:0 auto}.state-display{background:#f5f5f5;padding:15px;border-radius:5px;margin:20px 0}pre{background:#fff;padding:10px;border:1px solid #ddd;border-radius:3px}.actions{display:flex;gap:10px;margin:20px 0}button{padding:10px 20px;background:#007bff;color:#fff;border:none;border-radius:4px;cursor:pointer}button:hover{background:#0056b3}.history{margin-top:20px}ul{max-height:200px;overflow-y:auto}\n"] }]
|
|
11180
|
-
}] });
|
|
11181
|
-
|
|
11182
7504
|
/*
|
|
11183
7505
|
* Public API Surface of http-request-manager
|
|
11184
7506
|
*/
|
|
7507
|
+
// Core module and services
|
|
11185
7508
|
|
|
11186
7509
|
/**
|
|
11187
7510
|
* Generated bundle index. Do not edit.
|
|
11188
7511
|
*/
|
|
11189
7512
|
|
|
11190
|
-
export { ApiRequest, AppService, AsymmetricalEncryptionService, BatchOptions, BatchRequestResultModel, BatchResult, CONFIG_SETTINGS_TOKEN, ChannelType, ConfigHTTPOptions, ConfigOptions, DataType,
|
|
7513
|
+
export { Action, ApiRequest, AppService, AsymmetricalEncryptionService, BatchOptions, BatchRequestResultModel, BatchResult, CONFIG_SETTINGS_TOKEN, ChannelType, CommunicationMessage, ConfigHTTPOptions, ConfigOptions, DataType, DatabaseManagerService, DatabaseStorage, DbService, DisplayConfig, DisplayRule, ErrorDisplaySettings, GlobalStoreOptions, HTTPManagerService, HTTPManagerSignalsService, HTTPManagerStateService, HeadersService, HttpRequestManagerModule, LocalStorageManagerService, LocalStorageOptions, LocalStorageSignalsManagerService, LoggerService, MessageContent, MessageDisplayRouterService, NotificationMessage, PathQueryService, PublicMessage, Random, RandomHSLColor, RandomHexColor, RandomNumber, RandomNumbers, RandomNumbersUnique, RandomPaletteColor, RandomSignature, RandomStr, RandomVisibleColor, RequestErrorInterceptor, RequestHeadersInterceptor, RequestOptions, RequestService, RequestSignalsService, RetryOptions, SettingOptions, Slide, SnackbarStrategy, StateMessage, StateStorageOptions, StorageData, StorageOption, StorageType, StoreStateManagerService, StoreStateManagerSignalsService, StreamType, SymmetricalEncryptionService, TableSchemaDef, UUID, UUID_STR, UserData, UtilsService, WSOptions, WebSocketMessageService, WithCredentialsInterceptor, calculateBatchProgress, countdown, createChannelName, defaultDisplayRules, delayedRetry, isErrorState, isPendingState, isSuccessState, requestPolling, requestStreaming, streamAI, streamAuto, streamEvents, streamJSON, streamNDJSON };
|
|
11191
7514
|
//# sourceMappingURL=http-request-manager.mjs.map
|