mirror of
https://github.com/jwetzell/showbridge-webui.git
synced 2026-09-11 08:09:27 +00:00
Merge pull request #8 from jwetzell/feat/api-connection
connect to running instance
This commit is contained in:
Generated
-74
@@ -20,7 +20,6 @@
|
|||||||
"ajv": "8.17.1",
|
"ajv": "8.17.1",
|
||||||
"js-yaml": "4.1.1",
|
"js-yaml": "4.1.1",
|
||||||
"lodash-es": "4.17.23",
|
"lodash-es": "4.17.23",
|
||||||
"ngx-timeago": "4.0.0",
|
|
||||||
"rxjs": "7.8.2",
|
"rxjs": "7.8.2",
|
||||||
"tslib": "2.3.0"
|
"tslib": "2.3.0"
|
||||||
},
|
},
|
||||||
@@ -4153,66 +4152,6 @@
|
|||||||
"node": ">=14.0.0"
|
"node": ">=14.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/core": {
|
|
||||||
"version": "1.4.5",
|
|
||||||
"dev": true,
|
|
||||||
"inBundle": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"optional": true,
|
|
||||||
"dependencies": {
|
|
||||||
"@emnapi/wasi-threads": "1.0.4",
|
|
||||||
"tslib": "^2.4.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/runtime": {
|
|
||||||
"version": "1.4.5",
|
|
||||||
"dev": true,
|
|
||||||
"inBundle": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"optional": true,
|
|
||||||
"dependencies": {
|
|
||||||
"tslib": "^2.4.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/wasi-threads": {
|
|
||||||
"version": "1.0.4",
|
|
||||||
"dev": true,
|
|
||||||
"inBundle": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"optional": true,
|
|
||||||
"dependencies": {
|
|
||||||
"tslib": "^2.4.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@napi-rs/wasm-runtime": {
|
|
||||||
"version": "0.2.12",
|
|
||||||
"dev": true,
|
|
||||||
"inBundle": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"optional": true,
|
|
||||||
"dependencies": {
|
|
||||||
"@emnapi/core": "^1.4.3",
|
|
||||||
"@emnapi/runtime": "^1.4.3",
|
|
||||||
"@tybys/wasm-util": "^0.10.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@tybys/wasm-util": {
|
|
||||||
"version": "0.10.0",
|
|
||||||
"dev": true,
|
|
||||||
"inBundle": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"optional": true,
|
|
||||||
"dependencies": {
|
|
||||||
"tslib": "^2.4.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/tslib": {
|
|
||||||
"version": "2.8.0",
|
|
||||||
"dev": true,
|
|
||||||
"inBundle": true,
|
|
||||||
"license": "0BSD",
|
|
||||||
"optional": true
|
|
||||||
},
|
|
||||||
"node_modules/@tailwindcss/oxide-win32-arm64-msvc": {
|
"node_modules/@tailwindcss/oxide-win32-arm64-msvc": {
|
||||||
"version": "4.1.12",
|
"version": "4.1.12",
|
||||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.12.tgz",
|
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.12.tgz",
|
||||||
@@ -7241,19 +7180,6 @@
|
|||||||
"node": ">= 0.6"
|
"node": ">= 0.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/ngx-timeago": {
|
|
||||||
"version": "4.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/ngx-timeago/-/ngx-timeago-4.0.0.tgz",
|
|
||||||
"integrity": "sha512-pTzQyOzefsDGcaM5b4aVtM1gDiP8bteQBTJNyQ/pKR0Hbgue08Zq/SztJK8zZCo08kLi8WiC0DvgWzfQF30C1g==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"tslib": "^2.3.0"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"@angular/core": ">=20.0.0",
|
|
||||||
"rxjs": ">=7.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/node-addon-api": {
|
"node_modules/node-addon-api": {
|
||||||
"version": "6.1.0",
|
"version": "6.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-6.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-6.1.0.tgz",
|
||||||
|
|||||||
@@ -38,7 +38,6 @@
|
|||||||
"ajv": "8.17.1",
|
"ajv": "8.17.1",
|
||||||
"js-yaml": "4.1.1",
|
"js-yaml": "4.1.1",
|
||||||
"lodash-es": "4.17.23",
|
"lodash-es": "4.17.23",
|
||||||
"ngx-timeago": "4.0.0",
|
|
||||||
"rxjs": "7.8.2",
|
"rxjs": "7.8.2",
|
||||||
"tslib": "2.3.0"
|
"tslib": "2.3.0"
|
||||||
},
|
},
|
||||||
|
|||||||
+18
-1
@@ -1,12 +1,29 @@
|
|||||||
<div class="h-screen max-h-screen flex flex-col">
|
<div class="h-screen max-h-screen flex flex-col">
|
||||||
<mat-toolbar color="primary" class="w-full">
|
<mat-toolbar color="primary" class="w-full">
|
||||||
|
@if(eventsService.status() === 'open') {
|
||||||
|
<div matTooltip="Connected" class="h-3 w-3 rounded-full bg-green-400 mr-2"></div>
|
||||||
|
} @else if (eventsService.status() === 'closed') {
|
||||||
|
<div matTooltip="Disconnected" class="h-3 w-3 rounded-full bg-orange-400 mr-2"></div>
|
||||||
|
} @else if (eventsService.status() === 'error') {
|
||||||
|
<div matTooltip="Error" class="h-3 w-3 rounded-full bg-red-400 mr-2"></div>
|
||||||
|
}
|
||||||
<span>showbridge</span>
|
<span>showbridge</span>
|
||||||
|
<div>
|
||||||
|
<label class="ml-4 mr-2">URL:</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
class="pl-1 border-2 border-gray-200 text-white border-solid rounded-sm"
|
||||||
|
[placeholder]="'http://localhost:8000'"
|
||||||
|
[value]="settingsService.baseUrl()"
|
||||||
|
(change)="settingsService.baseUrl.set($event.target.value)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
<span class="flex-auto"></span>
|
<span class="flex-auto"></span>
|
||||||
<button mat-icon-button matTooltip="Download Config" (click)="downloadConfig()">
|
<button mat-icon-button matTooltip="Download Config" (click)="downloadConfig()">
|
||||||
<mat-icon>download</mat-icon>
|
<mat-icon>download</mat-icon>
|
||||||
</button>
|
</button>
|
||||||
@if (configService.pendingConfigIsValid()) {
|
@if (configService.pendingConfigIsValid()) {
|
||||||
<button mat-icon-button matTooltip="Apply Config" (click)="applyConfig()">
|
<button mat-icon-button matTooltip="Apply Config" (click)="applyConfig()" [disabled]="!configService.configIsDirty()">
|
||||||
<mat-icon>save</mat-icon>
|
<mat-icon>save</mat-icon>
|
||||||
</button>
|
</button>
|
||||||
} @else {
|
} @else {
|
||||||
|
|||||||
+10
-8
@@ -1,18 +1,18 @@
|
|||||||
import { Component, effect, inject } from '@angular/core';
|
import { Component, effect, inject } from '@angular/core';
|
||||||
import { MatButtonModule } from '@angular/material/button';
|
import { MatButtonModule } from '@angular/material/button';
|
||||||
import { MatDialog } from '@angular/material/dialog';
|
|
||||||
import { MatIconModule } from '@angular/material/icon';
|
import { MatIconModule } from '@angular/material/icon';
|
||||||
import { MatMenuModule } from '@angular/material/menu';
|
import { MatMenuModule } from '@angular/material/menu';
|
||||||
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
|
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
|
||||||
import { MatSnackBar } from '@angular/material/snack-bar';
|
import { MatSnackBar } from '@angular/material/snack-bar';
|
||||||
import { MatToolbarModule } from '@angular/material/toolbar';
|
import { MatToolbarModule } from '@angular/material/toolbar';
|
||||||
import { TimeagoModule } from 'ngx-timeago';
|
import { RouterOutlet } from '@angular/router';
|
||||||
|
import * as yaml from 'js-yaml';
|
||||||
import { Config } from './models/config.models';
|
import { Config } from './models/config.models';
|
||||||
import { ConfigService } from './services/config.service';
|
import { ConfigService } from './services/config.service';
|
||||||
|
import { EventsService } from './services/events.service';
|
||||||
import { SchemaService } from './services/schema.service';
|
import { SchemaService } from './services/schema.service';
|
||||||
import { ConfigComponent } from './components/config/config.component';
|
import { SettingsService } from './services/settings.service';
|
||||||
import * as yaml from 'js-yaml';
|
import { MatTooltipModule } from '@angular/material/tooltip';
|
||||||
import { RouterOutlet } from '@angular/router';
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-root',
|
selector: 'app-root',
|
||||||
@@ -20,9 +20,9 @@ import { RouterOutlet } from '@angular/router';
|
|||||||
MatToolbarModule,
|
MatToolbarModule,
|
||||||
MatProgressSpinnerModule,
|
MatProgressSpinnerModule,
|
||||||
MatIconModule,
|
MatIconModule,
|
||||||
TimeagoModule,
|
|
||||||
MatMenuModule,
|
MatMenuModule,
|
||||||
MatButtonModule,
|
MatButtonModule,
|
||||||
|
MatTooltipModule,
|
||||||
RouterOutlet
|
RouterOutlet
|
||||||
],
|
],
|
||||||
templateUrl: './app.html',
|
templateUrl: './app.html',
|
||||||
@@ -32,6 +32,8 @@ export class App {
|
|||||||
public schemaService = inject(SchemaService);
|
public schemaService = inject(SchemaService);
|
||||||
public configService = inject(ConfigService);
|
public configService = inject(ConfigService);
|
||||||
|
|
||||||
|
public settingsService = inject(SettingsService);
|
||||||
|
public eventsService = inject(EventsService);
|
||||||
private snackBar = inject(MatSnackBar);
|
private snackBar = inject(MatSnackBar);
|
||||||
constructor() {
|
constructor() {
|
||||||
effect(() => {
|
effect(() => {
|
||||||
@@ -44,8 +46,8 @@ export class App {
|
|||||||
applyConfig() {
|
applyConfig() {
|
||||||
const config = this.configService.currentlyShownConfig();
|
const config = this.configService.currentlyShownConfig();
|
||||||
if (config !== undefined) {
|
if (config !== undefined) {
|
||||||
this.configService.saveConfig(config);
|
this.configService.uploadConfig(config);
|
||||||
this.snackBar.open('Config Saved', 'Dismiss', {
|
this.snackBar.open('Config Uploaded', 'Dismiss', {
|
||||||
duration: 3000,
|
duration: 3000,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,11 +3,12 @@ import { MatButtonModule } from '@angular/material/button';
|
|||||||
import { MatIconModule } from '@angular/material/icon';
|
import { MatIconModule } from '@angular/material/icon';
|
||||||
import { MatMenuModule } from '@angular/material/menu';
|
import { MatMenuModule } from '@angular/material/menu';
|
||||||
import { MatTooltipModule } from '@angular/material/tooltip';
|
import { MatTooltipModule } from '@angular/material/tooltip';
|
||||||
import { Config, ModuleConfiguration, RouteConfiguration } from '../../models/config.models';
|
import { Config, ModuleConfig, RouteConfig } from '../../models/config.models';
|
||||||
import { ConfigService } from '../../services/config.service';
|
import { ConfigService } from '../../services/config.service';
|
||||||
import { SchemaService } from '../../services/schema.service';
|
import { SchemaService } from '../../services/schema.service';
|
||||||
import { ModuleListComponent } from '../module-list/module-list.component';
|
import { ModuleListComponent } from '../module-list/module-list.component';
|
||||||
import { RouteListComponent } from '../route-list.component/route-list.component';
|
import { RouteListComponent } from '../route-list.component/route-list.component';
|
||||||
|
import { EventsService } from '../../services/events.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-config',
|
selector: 'app-config',
|
||||||
@@ -39,7 +40,9 @@ export class ConfigComponent {
|
|||||||
public configService = inject(ConfigService);
|
public configService = inject(ConfigService);
|
||||||
public schemaService = inject(SchemaService);
|
public schemaService = inject(SchemaService);
|
||||||
|
|
||||||
modulesUpdated(modules: ModuleConfiguration[] | undefined) {
|
public eventsService = inject(EventsService);
|
||||||
|
|
||||||
|
modulesUpdated(modules: ModuleConfig[] | undefined) {
|
||||||
if (modules === undefined) {
|
if (modules === undefined) {
|
||||||
console.error('modules is undefined, not updating');
|
console.error('modules is undefined, not updating');
|
||||||
return;
|
return;
|
||||||
@@ -55,7 +58,7 @@ export class ConfigComponent {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
routesUpdated(routes: RouteConfiguration[] | undefined) {
|
routesUpdated(routes: RouteConfig[] | undefined) {
|
||||||
if (routes === undefined) {
|
if (routes === undefined) {
|
||||||
console.error('routes is undefined, not updating');
|
console.error('routes is undefined, not updating');
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { Component, inject, model } from '@angular/core';
|
import { Component, inject, model } from '@angular/core';
|
||||||
import { ModuleComponent } from '../module/module.component';
|
import { ModuleComponent } from '../module/module.component';
|
||||||
import { MatSnackBar } from '@angular/material/snack-bar';
|
import { MatSnackBar } from '@angular/material/snack-bar';
|
||||||
import { ModuleConfiguration } from '../../models/config.models';
|
import { ModuleConfig } from '../../models/config.models';
|
||||||
import { SchemaService } from '../../services/schema.service';
|
import { SchemaService } from '../../services/schema.service';
|
||||||
import { MatButtonModule } from '@angular/material/button';
|
import { MatButtonModule } from '@angular/material/button';
|
||||||
import { MatIconModule } from '@angular/material/icon';
|
import { MatIconModule } from '@angular/material/icon';
|
||||||
@@ -21,12 +21,12 @@ import { MatTooltipModule } from '@angular/material/tooltip';
|
|||||||
styleUrl: './module-list.component.css',
|
styleUrl: './module-list.component.css',
|
||||||
})
|
})
|
||||||
export class ModuleListComponent {
|
export class ModuleListComponent {
|
||||||
modules = model<ModuleConfiguration[]>();
|
modules = model<ModuleConfig[]>();
|
||||||
public schemaService = inject(SchemaService);
|
public schemaService = inject(SchemaService);
|
||||||
|
|
||||||
private snackBar = inject(MatSnackBar);
|
private snackBar = inject(MatSnackBar);
|
||||||
|
|
||||||
moduleUpdated(index: number, module: ModuleConfiguration | undefined) {
|
moduleUpdated(index: number, module: ModuleConfig | undefined) {
|
||||||
if (module === undefined) {
|
if (module === undefined) {
|
||||||
console.error('module is undefined, not updating');
|
console.error('module is undefined, not updating');
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -4,14 +4,20 @@
|
|||||||
isInError() ? 'border-red-500' : 'border-gray-600'
|
isInError() ? 'border-red-500' : 'border-gray-600'
|
||||||
}} border-solid w-full h-full"
|
}} border-solid w-full h-full"
|
||||||
>
|
>
|
||||||
<div class="flex items-center justify-center border-gray-600 border-b-2">
|
<div class="flex items-center justify-center">
|
||||||
<div class="flex-grow ml-1 mr-3 text-white">
|
<div class="flex items-center">
|
||||||
|
<mat-icon [style.color]="inputIndicatorColor()" matTooltip="Input Indicator">keyboard_arrow_down</mat-icon>
|
||||||
|
<mat-icon [style.color]="outputIndicatorColor()" matTooltip="Output Indicator">keyboard_arrow_up</mat-icon>
|
||||||
|
</div>
|
||||||
|
<div class="text-white">
|
||||||
{{ schema().title || module()?.type }}
|
{{ schema().title || module()?.type }}
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center justify-center" (click)="delete.emit()">
|
<div class="flex-grow"></div>
|
||||||
|
<div class="flex items-center justify-center hover:bg-gray-700" (click)="delete.emit()">
|
||||||
<mat-icon class="!text-red-500">close</mat-icon>
|
<mat-icon class="!text-red-500">close</mat-icon>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<hr class="bg-gray-600 h-0.5 border-0">
|
||||||
<div>
|
<div>
|
||||||
<form [formGroup]="formGroup" class="h-full">
|
<form [formGroup]="formGroup" class="h-full">
|
||||||
<div class="m-2">
|
<div class="m-2">
|
||||||
@@ -37,5 +43,20 @@
|
|||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
<div>
|
||||||
|
@if (moduleConfigErrors().length > 0) {
|
||||||
|
<div class="m-2 p-2 border-2 border-red-500">
|
||||||
|
<div class="flex items-center mb-2 text-red-500">
|
||||||
|
<mat-icon>error</mat-icon>
|
||||||
|
<span class="ml-1">Module Errors</span>
|
||||||
|
</div>
|
||||||
|
@for (error of moduleConfigErrors(); track error) {
|
||||||
|
<div class="ml-4 mb-1 text-white">
|
||||||
|
{{ error.error }}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,16 @@
|
|||||||
import { Component, computed, inject, input, model, output } from '@angular/core';
|
import { Component, computed, inject, input, model, output, signal } from '@angular/core';
|
||||||
import { FormControl, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms';
|
import { FormControl, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms';
|
||||||
import { MatFormFieldModule } from '@angular/material/form-field';
|
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||||
import { MatIconModule } from '@angular/material/icon';
|
import { MatIconModule } from '@angular/material/icon';
|
||||||
import { MatMenuModule } from '@angular/material/menu';
|
import { MatMenuModule } from '@angular/material/menu';
|
||||||
import { ModuleConfiguration } from '../../models/config.models';
|
import { ModuleConfig } from '../../models/config.models';
|
||||||
import { SchemaService } from '../../services/schema.service';
|
import { SchemaService } from '../../services/schema.service';
|
||||||
import { ParamsFormComponent } from '../params-form/params-form.component';
|
import { ParamsFormComponent } from '../params-form/params-form.component';
|
||||||
import { JsonPipe } from '@angular/common';
|
import { JsonPipe } from '@angular/common';
|
||||||
import { MatTooltipModule } from '@angular/material/tooltip';
|
import { MatTooltipModule } from '@angular/material/tooltip';
|
||||||
|
import { EventsService } from '../../services/events.service';
|
||||||
|
import { tap, debounceTime } from 'rxjs';
|
||||||
|
import { ConfigService } from '../../services/config.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-module',
|
selector: 'app-module',
|
||||||
@@ -25,7 +28,17 @@ import { MatTooltipModule } from '@angular/material/tooltip';
|
|||||||
})
|
})
|
||||||
export class ModuleComponent {
|
export class ModuleComponent {
|
||||||
path = input<string>('');
|
path = input<string>('');
|
||||||
module = model<ModuleConfiguration>();
|
index = computed(() => {
|
||||||
|
const path = this.path();
|
||||||
|
if (path) {
|
||||||
|
const parts = path.split('/');
|
||||||
|
const lastPart = parts[parts.length - 1];
|
||||||
|
return parseInt(lastPart, 10);
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
});
|
||||||
|
|
||||||
|
module = model<ModuleConfig>();
|
||||||
delete = output<void>();
|
delete = output<void>();
|
||||||
|
|
||||||
params = computed(() => this.module()!.params);
|
params = computed(() => this.module()!.params);
|
||||||
@@ -37,13 +50,26 @@ export class ModuleComponent {
|
|||||||
: undefined;
|
: undefined;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
moduleConfigErrors = computed(() => {
|
||||||
|
const index = this.index();
|
||||||
|
const moduleErrors = this.configService.moduleErrors();
|
||||||
|
if (index !== undefined) {
|
||||||
|
return moduleErrors.filter((error) => error.index === index);
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
});
|
||||||
|
|
||||||
formGroup: FormGroup = new FormGroup({
|
formGroup: FormGroup = new FormGroup({
|
||||||
id: new FormControl('', [Validators.required]),
|
id: new FormControl('', [Validators.required]),
|
||||||
type: new FormControl(''),
|
type: new FormControl(''),
|
||||||
});
|
});
|
||||||
|
|
||||||
private schemaService = inject(SchemaService);
|
inputIndicatorColor = signal<string>('gray');
|
||||||
|
outputIndicatorColor = signal<string>('gray');
|
||||||
|
|
||||||
|
private schemaService = inject(SchemaService);
|
||||||
|
private eventsService = inject(EventsService);
|
||||||
|
private configService = inject(ConfigService);
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.formGroup.patchValue({
|
this.formGroup.patchValue({
|
||||||
id: this.module()?.id,
|
id: this.module()?.id,
|
||||||
@@ -64,6 +90,30 @@ export class ModuleComponent {
|
|||||||
return undefined;
|
return undefined;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
if (this.id() !== undefined) {
|
||||||
|
this.eventsService
|
||||||
|
.getInputEventsForSource(this.id()!)
|
||||||
|
.pipe(
|
||||||
|
tap((inputEvent) => {
|
||||||
|
this.inputIndicatorColor.set(inputEvent.error ? 'red' : 'greenyellow');
|
||||||
|
}),
|
||||||
|
debounceTime(100),
|
||||||
|
)
|
||||||
|
.subscribe((inputEvent) => {
|
||||||
|
this.inputIndicatorColor.set('gray');
|
||||||
|
});
|
||||||
|
this.eventsService
|
||||||
|
.getOutputEventsForDestination(this.id()!)
|
||||||
|
.pipe(
|
||||||
|
tap((outputEvent) => {
|
||||||
|
this.outputIndicatorColor.set(outputEvent.error ? 'red' : 'greenyellow');
|
||||||
|
}),
|
||||||
|
debounceTime(100),
|
||||||
|
)
|
||||||
|
.subscribe((outputEvent) => {
|
||||||
|
this.outputIndicatorColor.set('gray');
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
paramsUpdated(params: any) {
|
paramsUpdated(params: any) {
|
||||||
|
|||||||
@@ -5,15 +5,16 @@
|
|||||||
isInError() ? 'border-red-500' : 'border-gray-600'
|
isInError() ? 'border-red-500' : 'border-gray-600'
|
||||||
}} border-solid w-fit h-full"
|
}} border-solid w-fit h-full"
|
||||||
>
|
>
|
||||||
<div class="flex items-center justify-center border-gray-600 border-b-2">
|
<div class="flex items-center justify-center">
|
||||||
<div class="flex-grow ml-1 mr-3 text-white">
|
<div class="flex-grow ml-1 mr-3 text-white">
|
||||||
{{ schema()?.title || processor()?.type }}
|
{{ schema()?.title || processor()?.type }}
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center justify-center" (click)="delete.emit()">
|
<div class="flex items-center justify-center hover:bg-gray-700" (click)="delete.emit()">
|
||||||
<mat-icon class="!text-red-500">close</mat-icon>
|
<mat-icon class="!text-red-500">close</mat-icon>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@if (hasParams()) {
|
@if (hasParams()) {
|
||||||
|
<hr class="bg-gray-600 h-0.5 border-0">
|
||||||
<div>
|
<div>
|
||||||
<app-params-form
|
<app-params-form
|
||||||
[paramsSchema]="schema()?.properties?.params"
|
[paramsSchema]="schema()?.properties?.params"
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Component, computed, inject, input, model, output, signal } from '@angular/core';
|
import { Component, computed, inject, input, model, output, signal } from '@angular/core';
|
||||||
import { ProcessorConfiguration } from '../../models/config.models';
|
import { ProcessorConfig } from '../../models/config.models';
|
||||||
import { SchemaService } from '../../services/schema.service';
|
import { SchemaService } from '../../services/schema.service';
|
||||||
import { MatIconModule } from '@angular/material/icon';
|
import { MatIconModule } from '@angular/material/icon';
|
||||||
import { ParamsFormComponent } from '../params-form/params-form.component';
|
import { ParamsFormComponent } from '../params-form/params-form.component';
|
||||||
@@ -13,7 +13,7 @@ import { ReactiveFormsModule } from '@angular/forms';
|
|||||||
})
|
})
|
||||||
export class ProcessorComponent {
|
export class ProcessorComponent {
|
||||||
path = input<string>('');
|
path = input<string>('');
|
||||||
processor = model<ProcessorConfiguration>();
|
processor = model<ProcessorConfig>();
|
||||||
delete = output<void>();
|
delete = output<void>();
|
||||||
|
|
||||||
params = computed(() => this.processor()!.params);
|
params = computed(() => this.processor()!.params);
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { Component, inject, input, model } from '@angular/core';
|
import { Component, inject, input, model } from '@angular/core';
|
||||||
import { MatSnackBar } from '@angular/material/snack-bar';
|
import { MatSnackBar } from '@angular/material/snack-bar';
|
||||||
import { ModuleConfiguration, RouteConfiguration } from '../../models/config.models';
|
import { ModuleConfig, RouteConfig } from '../../models/config.models';
|
||||||
import { SchemaService } from '../../services/schema.service';
|
import { SchemaService } from '../../services/schema.service';
|
||||||
import { RouteComponent } from '../route/route.component';
|
import { RouteComponent } from '../route/route.component';
|
||||||
import { MatButtonModule } from '@angular/material/button';
|
import { MatButtonModule } from '@angular/material/button';
|
||||||
@@ -22,7 +22,7 @@ import { MatTooltipModule } from '@angular/material/tooltip';
|
|||||||
})
|
})
|
||||||
export class RouteListComponent {
|
export class RouteListComponent {
|
||||||
|
|
||||||
routes = model<RouteConfiguration[]>();
|
routes = model<RouteConfig[]>();
|
||||||
moduleIds = input<string[]>();
|
moduleIds = input<string[]>();
|
||||||
public schemaService = inject(SchemaService);
|
public schemaService = inject(SchemaService);
|
||||||
|
|
||||||
@@ -41,7 +41,7 @@ export class RouteListComponent {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
routeUpdated(index: number, route: RouteConfiguration | undefined) {
|
routeUpdated(index: number, route: RouteConfig | undefined) {
|
||||||
if (route === undefined) {
|
if (route === undefined) {
|
||||||
console.error('route is undefined, not updating');
|
console.error('route is undefined, not updating');
|
||||||
return;
|
return;
|
||||||
@@ -49,7 +49,6 @@ export class RouteListComponent {
|
|||||||
this.routes.update((routes) => {
|
this.routes.update((routes) => {
|
||||||
if (routes) {
|
if (routes) {
|
||||||
routes[index].input = route.input;
|
routes[index].input = route.input;
|
||||||
routes[index].output = route.output;
|
|
||||||
return [...routes];
|
return [...routes];
|
||||||
}
|
}
|
||||||
return routes;
|
return routes;
|
||||||
|
|||||||
@@ -5,12 +5,16 @@
|
|||||||
isInError() ? 'border-red-500' : 'border-gray-600'
|
isInError() ? 'border-red-500' : 'border-gray-600'
|
||||||
}} border-solid w-full h-full"
|
}} border-solid w-full h-full"
|
||||||
>
|
>
|
||||||
<div class="flex items-center justify-end border-gray-600 border-b-2">
|
<div class="flex items-center justify-end">
|
||||||
|
<div class="flex items-center">
|
||||||
|
<mat-icon [style.color]="indicatorColor()" matTooltip="Route Indicator">chevron_right</mat-icon>
|
||||||
|
</div>
|
||||||
<div class="flex-grow text-left ml-2 text-white">Route {{ index() }}</div>
|
<div class="flex-grow text-left ml-2 text-white">Route {{ index() }}</div>
|
||||||
<div class="flex items-center justify-center hover:bg-gray-700" (click)="delete.emit()">
|
<div class="flex items-center justify-center hover:bg-gray-700" (click)="delete.emit()">
|
||||||
<mat-icon class="!text-red-500">close</mat-icon>
|
<mat-icon class="!text-red-500">close</mat-icon>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<hr class="bg-gray-600 h-0.5 border-0">
|
||||||
<div>
|
<div>
|
||||||
<form [formGroup]="formGroup">
|
<form [formGroup]="formGroup">
|
||||||
<div class="m-2">
|
<div class="m-2">
|
||||||
@@ -69,6 +73,21 @@
|
|||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div>
|
||||||
|
@if (routeConfigErrors().length > 0) {
|
||||||
|
<div class="m-2 p-2 border-2 border-red-500">
|
||||||
|
<div class="flex items-center mb-2 text-red-500">
|
||||||
|
<mat-icon>error</mat-icon>
|
||||||
|
<span class="ml-1">Route Errors</span>
|
||||||
|
</div>
|
||||||
|
@for (error of routeConfigErrors(); track error) {
|
||||||
|
<div class="ml-4 mb-1 text-white">
|
||||||
|
{{ error.error }}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- </div> -->
|
<!-- </div> -->
|
||||||
|
|
||||||
|
|||||||
@@ -1,14 +1,17 @@
|
|||||||
import { Component, computed, inject, input, model, output } from '@angular/core';
|
import { Component, computed, inject, input, model, output, signal } from '@angular/core';
|
||||||
import { FormControl, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms';
|
import { FormControl, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms';
|
||||||
import { MatFormFieldModule } from '@angular/material/form-field';
|
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||||
import { MatIconModule } from '@angular/material/icon';
|
import { MatIconModule } from '@angular/material/icon';
|
||||||
import { MatMenuModule } from '@angular/material/menu';
|
import { MatMenuModule } from '@angular/material/menu';
|
||||||
import { ProcessorConfiguration, RouteConfiguration } from '../../models/config.models';
|
import { ProcessorConfig, RouteConfig } from '../../models/config.models';
|
||||||
import { SchemaService } from '../../services/schema.service';
|
import { SchemaService } from '../../services/schema.service';
|
||||||
import { JsonPipe } from '@angular/common';
|
import { JsonPipe } from '@angular/common';
|
||||||
import { ProcessorComponent } from '../processor/processor.component';
|
import { ProcessorComponent } from '../processor/processor.component';
|
||||||
import { MatSnackBar } from '@angular/material/snack-bar';
|
import { MatSnackBar } from '@angular/material/snack-bar';
|
||||||
import { MatTooltipModule } from '@angular/material/tooltip';
|
import { MatTooltipModule } from '@angular/material/tooltip';
|
||||||
|
import { EventsService } from '../../services/events.service';
|
||||||
|
import { debounceTime, tap } from 'rxjs';
|
||||||
|
import { ConfigService } from '../../services/config.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-route',
|
selector: 'app-route',
|
||||||
@@ -37,7 +40,7 @@ export class RouteComponent {
|
|||||||
return undefined;
|
return undefined;
|
||||||
});
|
});
|
||||||
|
|
||||||
route = model<RouteConfiguration>();
|
route = model<RouteConfig>();
|
||||||
moduleIds = input<string[]>([]);
|
moduleIds = input<string[]>([]);
|
||||||
delete = output<void>();
|
delete = output<void>();
|
||||||
|
|
||||||
@@ -49,12 +52,25 @@ export class RouteComponent {
|
|||||||
return [];
|
return [];
|
||||||
});
|
});
|
||||||
|
|
||||||
|
routeConfigErrors = computed(() => {
|
||||||
|
const index = this.index();
|
||||||
|
const routeErrors = this.configService.routeErrors();
|
||||||
|
if (index !== undefined) {
|
||||||
|
return routeErrors.filter((error) => error.index === index);
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
});
|
||||||
|
|
||||||
formGroup: FormGroup = new FormGroup({
|
formGroup: FormGroup = new FormGroup({
|
||||||
input: new FormControl('', [Validators.required]),
|
input: new FormControl('', [Validators.required]),
|
||||||
});
|
});
|
||||||
|
|
||||||
public schemaService = inject(SchemaService);
|
public schemaService = inject(SchemaService);
|
||||||
private snackBar = inject(MatSnackBar);
|
private snackBar = inject(MatSnackBar);
|
||||||
|
private eventsService = inject(EventsService);
|
||||||
|
private configService = inject(ConfigService);
|
||||||
|
|
||||||
|
indicatorColor = signal<string>('gray');
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.formGroup.patchValue({
|
this.formGroup.patchValue({
|
||||||
@@ -73,6 +89,17 @@ export class RouteComponent {
|
|||||||
return undefined;
|
return undefined;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (this.index() !== undefined) {
|
||||||
|
this.eventsService.getRouteEventsForIndex(this.index()!).pipe(
|
||||||
|
tap((routeEvent)=>{
|
||||||
|
this.indicatorColor.set(routeEvent.error ? 'red' : 'greenyellow');
|
||||||
|
}),
|
||||||
|
debounceTime(100)
|
||||||
|
).subscribe((routeEvent) => {
|
||||||
|
this.indicatorColor.set('gray')
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
isInError(): boolean {
|
isInError(): boolean {
|
||||||
@@ -103,7 +130,7 @@ export class RouteComponent {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
processorUpdated(index: number, processor: ProcessorConfiguration | undefined) {
|
processorUpdated(index: number, processor: ProcessorConfig | undefined) {
|
||||||
if (processor === undefined) {
|
if (processor === undefined) {
|
||||||
console.error('processor is undefined, not updating');
|
console.error('processor is undefined, not updating');
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
export type Config = {
|
export type Config = {
|
||||||
modules: ModuleConfiguration[];
|
api: ApiConfig
|
||||||
routes: RouteConfiguration[];
|
modules: ModuleConfig[];
|
||||||
|
routes: RouteConfig[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ConfigState = {
|
export type ConfigState = {
|
||||||
@@ -9,19 +10,40 @@ export type ConfigState = {
|
|||||||
isCurrent: boolean;
|
isCurrent: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ModuleConfiguration = {
|
export type ApiConfig = {
|
||||||
|
port: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ModuleConfig = {
|
||||||
id?: string;
|
id?: string;
|
||||||
type: string;
|
type: string;
|
||||||
params?: Record<string, any>;
|
params?: Record<string, any>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type RouteConfiguration = {
|
export type RouteConfig = {
|
||||||
input?: string;
|
input?: string;
|
||||||
processors?: ProcessorConfiguration[];
|
processors?: ProcessorConfig[];
|
||||||
output?: string;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ProcessorConfiguration = {
|
export type ProcessorConfig = {
|
||||||
type: string;
|
type: string;
|
||||||
params?: Record<string, any>;
|
params?: Record<string, any>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
export type ModuleError = {
|
||||||
|
index: number;
|
||||||
|
config: ModuleConfig;
|
||||||
|
error: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type RouteError = {
|
||||||
|
index: number;
|
||||||
|
config: RouteConfig;
|
||||||
|
error: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ConfigError = {
|
||||||
|
moduleErrors?: ModuleError[];
|
||||||
|
routeErrors?: RouteError[];
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
import { Config } from "./config.models"
|
||||||
|
|
||||||
|
export type RouterEvent<T, D> = {
|
||||||
|
type: T,
|
||||||
|
data?: D,
|
||||||
|
error?: string,
|
||||||
|
}
|
||||||
|
|
||||||
|
export type RouteEventData = {
|
||||||
|
index: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export type InputEventData = {
|
||||||
|
source: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export type OutputEventData = {
|
||||||
|
destination: string
|
||||||
|
}
|
||||||
@@ -1,7 +1,10 @@
|
|||||||
import { computed, effect, Injectable, signal } from '@angular/core';
|
import { computed, effect, inject, Injectable, signal } from '@angular/core';
|
||||||
import { cloneDeep } from 'lodash-es';
|
import { cloneDeep, isEqual } from 'lodash-es';
|
||||||
import { Config } from '../models/config.models';
|
import { Config, ConfigError, ModuleError, RouteError } from '../models/config.models';
|
||||||
import { SchemaService } from './schema.service';
|
import { SchemaService } from './schema.service';
|
||||||
|
import { HttpClient } from '@angular/common/http';
|
||||||
|
import { SettingsService } from './settings.service';
|
||||||
|
import { EventsService } from './events.service';
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root',
|
providedIn: 'root',
|
||||||
})
|
})
|
||||||
@@ -14,6 +17,22 @@ export class ConfigService {
|
|||||||
return this.schemaService.validate(config);
|
return this.schemaService.validate(config);
|
||||||
});
|
});
|
||||||
currentlyShownConfig = signal<Config | undefined>(undefined);
|
currentlyShownConfig = signal<Config | undefined>(undefined);
|
||||||
|
runningConfig = signal<Config | undefined>(undefined);
|
||||||
|
|
||||||
|
configIsDirty = computed(() => {
|
||||||
|
const currentlyShown = this.currentlyShownConfig();
|
||||||
|
const running = this.runningConfig();
|
||||||
|
if (currentlyShown === undefined || running === undefined) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return !isEqual(currentlyShown, running);
|
||||||
|
});
|
||||||
|
|
||||||
|
moduleErrors = signal<ModuleError[]>([]);
|
||||||
|
routeErrors = signal<RouteError[]>([]);
|
||||||
|
|
||||||
|
private http = inject(HttpClient);
|
||||||
|
private settingsService = inject(SettingsService);
|
||||||
|
|
||||||
constructor(private schemaService: SchemaService) {
|
constructor(private schemaService: SchemaService) {
|
||||||
this.loadConfig();
|
this.loadConfig();
|
||||||
@@ -23,42 +42,69 @@ export class ConfigService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
loadConfig() {
|
loadConfig() {
|
||||||
const configString = localStorage.getItem('config');
|
const configUrl = this.settingsService.configUrl();
|
||||||
if (configString) {
|
if (!configUrl) {
|
||||||
try {
|
console.error('Config URL is not set');
|
||||||
const config = JSON.parse(configString);
|
this.setEmptyConfig();
|
||||||
const valid = this.schemaService.validate(config);
|
return;
|
||||||
if (valid) {
|
}
|
||||||
console.log('Loaded config from local storage', config);
|
this.http.get<Config>(configUrl.toString()).subscribe({
|
||||||
|
next: (config) => {
|
||||||
|
if (this.schemaService.validate(config)) {
|
||||||
this.updateCurrentlyShownConfig(config);
|
this.updateCurrentlyShownConfig(config);
|
||||||
|
this.runningConfig.set(cloneDeep(config));
|
||||||
} else {
|
} else {
|
||||||
console.error('Config in local storage is invalid', config);
|
console.error('Config from server is invalid', config);
|
||||||
this.setEmptyConfig();
|
this.setEmptyConfig();
|
||||||
}
|
}
|
||||||
} catch (e) {
|
},
|
||||||
console.error('Failed to parse config from local storage', e);
|
error: (err) => {
|
||||||
|
console.error('Failed to load config from server', err);
|
||||||
this.setEmptyConfig();
|
this.setEmptyConfig();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
uploadConfig(config: Config) {
|
||||||
|
if (this.schemaService.validate(config)) {
|
||||||
|
this.updateCurrentlyShownConfig(config);
|
||||||
|
const configUrl = this.settingsService.configUrl();
|
||||||
|
if (!configUrl) {
|
||||||
|
console.error('Config URL is not set, cannot upload config');
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
this.http.put<ConfigError>(configUrl.toString(), config).subscribe({
|
||||||
|
next: () => {
|
||||||
|
console.log('Config uploaded successfully');
|
||||||
|
this.moduleErrors.set([]);
|
||||||
|
this.routeErrors.set([]);
|
||||||
|
this.runningConfig.set(cloneDeep(config));
|
||||||
|
},
|
||||||
|
error: (err) => {
|
||||||
|
console.error('Problems occurred while uploading config', err);
|
||||||
|
if (err.error) {
|
||||||
|
const configError: ConfigError = err.error;
|
||||||
|
this.moduleErrors.set(configError.moduleErrors ?? []);
|
||||||
|
this.routeErrors.set(configError.routeErrors ?? []);
|
||||||
|
}
|
||||||
|
this.runningConfig.set(cloneDeep(config));
|
||||||
|
},
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
this.setEmptyConfig();
|
console.error('Uploaded config is invalid', config);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setEmptyConfig() {
|
setEmptyConfig() {
|
||||||
console.log('Setting empty config');
|
console.log('Setting empty config');
|
||||||
this.updateCurrentlyShownConfig({
|
this.updateCurrentlyShownConfig({
|
||||||
|
api: { port: 8080 },
|
||||||
modules: [],
|
modules: [],
|
||||||
routes: [],
|
routes: [],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
saveConfig(config: Config) {
|
|
||||||
localStorage.setItem('config', JSON.stringify(config));
|
|
||||||
console.log('Config saved to local storage', config);
|
|
||||||
}
|
|
||||||
|
|
||||||
updateCurrentlyShownConfig(config: Config) {
|
updateCurrentlyShownConfig(config: Config) {
|
||||||
// NOTE(jwetzell): mark the configStates that match the currently shown as such
|
|
||||||
this.currentlyShownConfig.set(cloneDeep(config));
|
this.currentlyShownConfig.set(cloneDeep(config));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,84 @@
|
|||||||
|
import { effect, inject, Injectable, signal } from '@angular/core';
|
||||||
|
import {
|
||||||
|
InputEventData,
|
||||||
|
OutputEventData,
|
||||||
|
RouteEventData,
|
||||||
|
RouterEvent,
|
||||||
|
} from '../models/events.models';
|
||||||
|
import { filter, Subject } from 'rxjs';
|
||||||
|
import { SettingsService } from './settings.service';
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root',
|
||||||
|
})
|
||||||
|
export class EventsService {
|
||||||
|
status = signal<string>('closed');
|
||||||
|
socket?: WebSocket;
|
||||||
|
private routeEvents$ = new Subject<RouterEvent<'route', RouteEventData>>();
|
||||||
|
private inputEvents$ = new Subject<RouterEvent<'input', InputEventData>>();
|
||||||
|
private outputEvents$ = new Subject<RouterEvent<'output', OutputEventData>>();
|
||||||
|
private settingsService = inject(SettingsService);
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
effect(() => {
|
||||||
|
console.log('Websocket URL changed:', this.settingsService.wsUrl());
|
||||||
|
this.reload();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
reload() {
|
||||||
|
try {
|
||||||
|
this.socket = new WebSocket(this.settingsService.wsUrl());
|
||||||
|
this.socket.onopen = () => {
|
||||||
|
this.status.set('open');
|
||||||
|
};
|
||||||
|
|
||||||
|
this.socket.onclose = () => {
|
||||||
|
this.status.set('closed');
|
||||||
|
};
|
||||||
|
|
||||||
|
this.socket.onerror = (error) => {
|
||||||
|
this.status.set('error');
|
||||||
|
};
|
||||||
|
|
||||||
|
this.socket.onmessage = (event) => {
|
||||||
|
const messageObj = JSON.parse(event.data);
|
||||||
|
switch (messageObj.type) {
|
||||||
|
case 'route':
|
||||||
|
this.routeEvents$.next(messageObj);
|
||||||
|
break;
|
||||||
|
case 'input':
|
||||||
|
this.inputEvents$.next(messageObj);
|
||||||
|
break;
|
||||||
|
case 'output':
|
||||||
|
this.outputEvents$.next(messageObj);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
console.warn('Unknown event type:', messageObj.type);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to connect to WebSocket:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getRouteEventsForIndex(index: number) {
|
||||||
|
return this.routeEvents$.pipe(filter((event) => event.data?.index === index));
|
||||||
|
}
|
||||||
|
|
||||||
|
getInputEventsForSource(source: string) {
|
||||||
|
return this.inputEvents$.pipe(filter((event) => event.data?.source === source));
|
||||||
|
}
|
||||||
|
|
||||||
|
getOutputEventsForDestination(destination: string) {
|
||||||
|
return this.outputEvents$.pipe(filter((event) => event.data?.destination === destination));
|
||||||
|
}
|
||||||
|
|
||||||
|
sendEvent<T, D>(event: RouterEvent<T, D>) {
|
||||||
|
if (this.socket && this.socket.readyState === WebSocket.OPEN) {
|
||||||
|
console.log('sending event:', event);
|
||||||
|
this.socket.send(JSON.stringify(event));
|
||||||
|
} else {
|
||||||
|
console.warn('WebSocket is not open. Cannot send event:', event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,19 +11,18 @@ import {
|
|||||||
import { Ajv2020, JSONSchemaType } from 'ajv/dist/2020';
|
import { Ajv2020, JSONSchemaType } from 'ajv/dist/2020';
|
||||||
import { SomeJSONSchema } from 'ajv/dist/types/json-schema';
|
import { SomeJSONSchema } from 'ajv/dist/types/json-schema';
|
||||||
import { sortBy } from 'lodash-es';
|
import { sortBy } from 'lodash-es';
|
||||||
import { Config, ModuleConfiguration, ProcessorConfiguration } from '../models/config.models';
|
import { Config, ModuleConfig, ProcessorConfig, RouteConfig } from '../models/config.models';
|
||||||
import { ObjectInfo, ParamsFormInfo } from '../models/form.model';
|
import { ObjectInfo, ParamsFormInfo } from '../models/form.model';
|
||||||
import { SettingsService } from './settings.service';
|
import { SettingsService } from './settings.service';
|
||||||
import { RouteConfiguration } from '../models/config.models';
|
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root',
|
providedIn: 'root',
|
||||||
})
|
})
|
||||||
export class SchemaService {
|
export class SchemaService {
|
||||||
configSchema = signal<JSONSchemaType<Config> | undefined>(undefined);
|
configSchema = signal<JSONSchemaType<Config> | undefined>(undefined);
|
||||||
modulesSchema = signal<JSONSchemaType<ModuleConfiguration[]> | undefined>(undefined);
|
modulesSchema = signal<JSONSchemaType<ModuleConfig[]> | undefined>(undefined);
|
||||||
routesSchema = signal<JSONSchemaType<RouteConfiguration[]> | undefined>(undefined);
|
routesSchema = signal<JSONSchemaType<RouteConfig[]> | undefined>(undefined);
|
||||||
processorsSchema = signal<JSONSchemaType<ProcessorConfiguration[]> | undefined>(undefined);
|
processorsSchema = signal<JSONSchemaType<ProcessorConfig[]> | undefined>(undefined);
|
||||||
|
|
||||||
schemasLoaded = computed(() => {
|
schemasLoaded = computed(() => {
|
||||||
return (
|
return (
|
||||||
@@ -90,7 +89,7 @@ export class SchemaService {
|
|||||||
loadModulesSchema() {
|
loadModulesSchema() {
|
||||||
this.http
|
this.http
|
||||||
.get<
|
.get<
|
||||||
JSONSchemaType<ModuleConfiguration[]>
|
JSONSchemaType<ModuleConfig[]>
|
||||||
>(this.settingsService.modulesSchemaUrl().toString())
|
>(this.settingsService.modulesSchemaUrl().toString())
|
||||||
.subscribe((schema) => {
|
.subscribe((schema) => {
|
||||||
this.setModulesSchema(schema);
|
this.setModulesSchema(schema);
|
||||||
@@ -99,7 +98,7 @@ export class SchemaService {
|
|||||||
|
|
||||||
loadRoutesSchema() {
|
loadRoutesSchema() {
|
||||||
this.http
|
this.http
|
||||||
.get<JSONSchemaType<RouteConfiguration[]>>(this.settingsService.routesSchemaUrl().toString())
|
.get<JSONSchemaType<RouteConfig[]>>(this.settingsService.routesSchemaUrl().toString())
|
||||||
.subscribe((schema) => {
|
.subscribe((schema) => {
|
||||||
this.setRoutesSchema(schema);
|
this.setRoutesSchema(schema);
|
||||||
});
|
});
|
||||||
@@ -108,7 +107,7 @@ export class SchemaService {
|
|||||||
loadProcessorsSchema() {
|
loadProcessorsSchema() {
|
||||||
this.http
|
this.http
|
||||||
.get<
|
.get<
|
||||||
JSONSchemaType<ProcessorConfiguration[]>
|
JSONSchemaType<ProcessorConfig[]>
|
||||||
>(this.settingsService.processorsSchemaUrl().toString())
|
>(this.settingsService.processorsSchemaUrl().toString())
|
||||||
.subscribe((schema) => {
|
.subscribe((schema) => {
|
||||||
this.setProcessorsSchema(schema);
|
this.setProcessorsSchema(schema);
|
||||||
@@ -142,13 +141,13 @@ export class SchemaService {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
getSkeletonForRoute(): RouteConfiguration {
|
getSkeletonForRoute(): RouteConfig {
|
||||||
const template: RouteConfiguration = {};
|
const template: RouteConfig = {};
|
||||||
return template;
|
return template;
|
||||||
}
|
}
|
||||||
|
|
||||||
getSkeletonForProcessor(processorType: string): ProcessorConfiguration {
|
getSkeletonForProcessor(processorType: string): ProcessorConfig {
|
||||||
const template: ProcessorConfiguration = {
|
const template: ProcessorConfig = {
|
||||||
type: processorType,
|
type: processorType,
|
||||||
};
|
};
|
||||||
const itemInfo = this.processorTypes.find((itemInfo) => itemInfo.type === processorType);
|
const itemInfo = this.processorTypes.find((itemInfo) => itemInfo.type === processorType);
|
||||||
@@ -160,8 +159,8 @@ export class SchemaService {
|
|||||||
return template;
|
return template;
|
||||||
}
|
}
|
||||||
|
|
||||||
getSkeletonForModule(moduleType: string): ModuleConfiguration {
|
getSkeletonForModule(moduleType: string): ModuleConfig {
|
||||||
const template: ModuleConfiguration = {
|
const template: ModuleConfig = {
|
||||||
type: moduleType,
|
type: moduleType,
|
||||||
};
|
};
|
||||||
const itemInfo = this.moduleTypes.find((itemInfo) => itemInfo.type === moduleType);
|
const itemInfo = this.moduleTypes.find((itemInfo) => itemInfo.type === moduleType);
|
||||||
@@ -198,17 +197,17 @@ export class SchemaService {
|
|||||||
this.configSchema.set(schema);
|
this.configSchema.set(schema);
|
||||||
}
|
}
|
||||||
|
|
||||||
setModulesSchema(schema: JSONSchemaType<ModuleConfiguration[]>) {
|
setModulesSchema(schema: JSONSchemaType<ModuleConfig[]>) {
|
||||||
this.modulesSchema.set(schema);
|
this.modulesSchema.set(schema);
|
||||||
this.moduleTypes = [];
|
this.moduleTypes = [];
|
||||||
this.populateModuleTypes();
|
this.populateModuleTypes();
|
||||||
}
|
}
|
||||||
|
|
||||||
setRoutesSchema(schema: JSONSchemaType<RouteConfiguration[]>) {
|
setRoutesSchema(schema: JSONSchemaType<RouteConfig[]>) {
|
||||||
this.routesSchema.set(schema);
|
this.routesSchema.set(schema);
|
||||||
}
|
}
|
||||||
|
|
||||||
setProcessorsSchema(schema: JSONSchemaType<ProcessorConfiguration[]>) {
|
setProcessorsSchema(schema: JSONSchemaType<ProcessorConfig[]>) {
|
||||||
this.processorsSchema.set(schema);
|
this.processorsSchema.set(schema);
|
||||||
this.processorTypes = [];
|
this.processorTypes = [];
|
||||||
this.populateProcessorTypes();
|
this.populateProcessorTypes();
|
||||||
|
|||||||
@@ -5,11 +5,17 @@ import { computed, Injectable, signal } from '@angular/core';
|
|||||||
})
|
})
|
||||||
export class SettingsService {
|
export class SettingsService {
|
||||||
public isDummySite: boolean = false;
|
public isDummySite: boolean = false;
|
||||||
public configSchemaPath = signal('/config.schema.json');
|
public configPath = signal('/api/v1/config');
|
||||||
public modulesSchemaPath = signal('/modules.schema.json');
|
public configSchemaPath = signal('/schema/config');
|
||||||
public routesSchemaPath = signal('/routes.schema.json');
|
public modulesSchemaPath = signal('/schema/modules');
|
||||||
public processorsSchemaPath = signal('/processors.schema.json');
|
public routesSchemaPath = signal('/schema/routes');
|
||||||
public baseUrl = signal(window.location.href);
|
public processorsSchemaPath = signal('/schema/processors');
|
||||||
|
public wsPath = signal('/ws');
|
||||||
|
public baseUrl = signal('http://localhost:8080');
|
||||||
|
|
||||||
|
public configUrl = computed(() => {
|
||||||
|
return new URL(this.configPath(), this.baseUrl());
|
||||||
|
});
|
||||||
|
|
||||||
public configSchemaUrl = computed(() => {
|
public configSchemaUrl = computed(() => {
|
||||||
return new URL(this.configSchemaPath(), this.baseUrl());
|
return new URL(this.configSchemaPath(), this.baseUrl());
|
||||||
@@ -27,6 +33,13 @@ export class SettingsService {
|
|||||||
return new URL(this.processorsSchemaPath(), this.baseUrl());
|
return new URL(this.processorsSchemaPath(), this.baseUrl());
|
||||||
});
|
});
|
||||||
|
|
||||||
|
public wsUrl = computed(() => {
|
||||||
|
const url = new URL(this.baseUrl());
|
||||||
|
url.protocol = url.protocol === 'https:' ? 'wss:' : 'ws:';
|
||||||
|
url.pathname = this.wsPath();
|
||||||
|
return url;
|
||||||
|
});
|
||||||
|
|
||||||
constructor() {}
|
constructor() {}
|
||||||
|
|
||||||
updateBaseUrl(baseUrl: string) {
|
updateBaseUrl(baseUrl: string) {
|
||||||
|
|||||||
Reference in New Issue
Block a user